|
|
|
@ -9,6 +9,7 @@
|
|
|
|
|
#import <SignalServiceKit/YapDatabaseConnection+OWS.h>
|
|
|
|
|
#import <YapDatabase/YapDatabase.h>
|
|
|
|
|
#import <YapDatabase/YapDatabasePrivate.h>
|
|
|
|
|
#import <SignalServiceKit/NSData+hexString.h>
|
|
|
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
@ -24,6 +25,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
+ (BOOL)doesDatabaseNeedToBeConverted:(NSString *)databaseFilePath;
|
|
|
|
|
|
|
|
|
|
+ (NSData *)readFirstNBytesOfDatabaseFile:(NSString *)filePath byteCount:(NSUInteger)byteCount;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
@ -133,6 +136,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)createTestDatabase:(NSString *)databaseFilePath databasePassword:(NSData *)databasePassword
|
|
|
|
|
databaseSalt:(NSData *_Nullable)databaseSalt
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(databaseFilePath.length > 0);
|
|
|
|
|
OWSAssert(databasePassword.length > 0);
|
|
|
|
@ -141,8 +145,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
[self openYapDatabase:databaseFilePath
|
|
|
|
|
databasePassword:databasePassword
|
|
|
|
|
databaseSalt:nil
|
|
|
|
|
databaseSalt:databaseSalt
|
|
|
|
|
databaseBlock:^(YapDatabase *database) {
|
|
|
|
|
[self logHeaderOfDatabaseFile:databaseFilePath
|
|
|
|
|
label:@"mid-creation"];
|
|
|
|
|
|
|
|
|
|
YapDatabaseConnection *dbConnection = database.newConnection;
|
|
|
|
|
[dbConnection setObject:@(YES) forKey:@"test_key_name" inCollection:@"test_collection_name"];
|
|
|
|
|
[dbConnection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:nil];
|
|
|
|
@ -187,18 +194,31 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
databaseSalt:nil];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString *)createTempDatabaseFilePath
|
|
|
|
|
{
|
|
|
|
|
NSString *temporaryDirectory = NSTemporaryDirectory();
|
|
|
|
|
NSString *filename = [[NSUUID UUID].UUIDString stringByAppendingString:@".sqlite"];
|
|
|
|
|
NSString *databaseFilePath = [temporaryDirectory stringByAppendingPathComponent:filename];
|
|
|
|
|
|
|
|
|
|
DDLogInfo(@"%@ databaseFilePath: %@", self.logTag, databaseFilePath);
|
|
|
|
|
[DDLog flushLog];
|
|
|
|
|
|
|
|
|
|
return databaseFilePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If databaseSalt is nil, creates a non-converted database.
|
|
|
|
|
// Otherwise creates a pre-converted database.
|
|
|
|
|
- (nullable NSString *)createDatabase:(NSData *)databasePassword
|
|
|
|
|
databaseSalt:(NSData *_Nullable)databaseSalt
|
|
|
|
|
{
|
|
|
|
|
NSString *temporaryDirectory = NSTemporaryDirectory();
|
|
|
|
|
NSString *filename = [[NSUUID UUID].UUIDString stringByAppendingString:@".sqlite"];
|
|
|
|
|
NSString *databaseFilePath = [temporaryDirectory stringByAppendingPathComponent:filename];
|
|
|
|
|
NSString *databaseFilePath = [self createTempDatabaseFilePath];
|
|
|
|
|
|
|
|
|
|
DDLogInfo(@"%@ databaseFilePath: %@", self.logTag, databaseFilePath);
|
|
|
|
|
[self createTestDatabase:databaseFilePath databasePassword:databasePassword databaseSalt:databaseSalt];
|
|
|
|
|
|
|
|
|
|
[self logHeaderOfDatabaseFile:databaseFilePath
|
|
|
|
|
label:@"created"];
|
|
|
|
|
|
|
|
|
|
[self createTestDatabase:databaseFilePath databasePassword:databasePassword];
|
|
|
|
|
[DDLog flushLog];
|
|
|
|
|
|
|
|
|
|
BOOL isValid = [self verifyTestDatabase:databaseFilePath databasePassword:databasePassword databaseSalt:databaseSalt];
|
|
|
|
|
OWSAssert(isValid);
|
|
|
|
@ -217,7 +237,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
- (void)testDoesDatabaseNeedToBeConverted_Converted
|
|
|
|
|
{
|
|
|
|
|
// TODO: When we can create converted databases.
|
|
|
|
|
NSData *databasePassword = [self randomDatabasePassword];
|
|
|
|
|
NSData *databaseSalt = [self randomDatabaseSalt];
|
|
|
|
|
NSString *_Nullable databaseFilePath = [self createDatabase:databasePassword
|
|
|
|
|
databaseSalt:databaseSalt];
|
|
|
|
|
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verifies that legacy users with non-converted databases can convert.
|
|
|
|
@ -276,6 +300,507 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
XCTAssertTrue(isValid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testConversionWithoutYapDatabase
|
|
|
|
|
{
|
|
|
|
|
sqlite3 *db;
|
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
|
const int ROWSTOINSERT = 3;
|
|
|
|
|
|
|
|
|
|
NSString *databaseFilePath = [self createTempDatabaseFilePath];
|
|
|
|
|
|
|
|
|
|
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
|
|
|
|
|
|
|
|
|
|
NSData *keyData = [self randomDatabasePassword];
|
|
|
|
|
|
|
|
|
|
/* Step 1. Create a new encrypted database. */
|
|
|
|
|
|
|
|
|
|
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
for(int row = 0; row < ROWSTOINSERT; row++) {
|
|
|
|
|
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_step(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_DONE);
|
|
|
|
|
rc = sqlite3_reset(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
}
|
|
|
|
|
rc = sqlite3_finalize(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
NSString *salt = [self executeSingleStringQuery:@"PRAGMA cipher_salt;"
|
|
|
|
|
db:db];
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_close(db);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
[self logHeaderOfDatabaseFile:databaseFilePath
|
|
|
|
|
label:@"Unconverted header"];
|
|
|
|
|
|
|
|
|
|
/* Step 2. Rewrite header */
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA user_version = 2;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
int log, ckpt;
|
|
|
|
|
rc = sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_FULL, &log, &ckpt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
DDLogInfo(@"log = %d, ckpt = %d", log, ckpt);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_close(db);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
[self logHeaderOfDatabaseFile:databaseFilePath
|
|
|
|
|
label:@"Converted header"];
|
|
|
|
|
|
|
|
|
|
/* Step 3. Open the database and query it */
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
|
|
|
|
DDLogInfo(@"salt pragma = %@", saltPragma);
|
|
|
|
|
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]);
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_close(db);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (int)executeSingleIntQuery:(NSString *)sql
|
|
|
|
|
db:(sqlite3 *)db
|
|
|
|
|
{
|
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_prepare_v2(db, sql.UTF8String, -1, &stmt, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_step(stmt);
|
|
|
|
|
XCTAssertTrue(rc = SQLITE_ROW);
|
|
|
|
|
|
|
|
|
|
int result = sqlite3_column_int(stmt, 0);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_finalize(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString *)executeSingleStringQuery:(NSString *)sql
|
|
|
|
|
db:(sqlite3 *)db
|
|
|
|
|
{
|
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_prepare_v2(db, sql.UTF8String, -1, &stmt, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_step(stmt);
|
|
|
|
|
XCTAssertTrue(rc = SQLITE_ROW);
|
|
|
|
|
NSString *result = [NSString stringWithFormat:@"%s", sqlite3_column_text(stmt, 0)];
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_finalize(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testNewUserWithoutYapDatabase
|
|
|
|
|
{
|
|
|
|
|
sqlite3 *db;
|
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
|
const int ROWSTOINSERT = 3;
|
|
|
|
|
|
|
|
|
|
NSString *databaseFilePath = [self createTempDatabaseFilePath];
|
|
|
|
|
|
|
|
|
|
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
|
|
|
|
|
|
|
|
|
|
NSData *keyData = [self randomDatabasePassword];
|
|
|
|
|
NSData *databaseSalt = [self randomDatabaseSalt];
|
|
|
|
|
NSString *salt = databaseSalt.hexadecimalString;
|
|
|
|
|
|
|
|
|
|
/* Step 1. Create a new encrypted database. */
|
|
|
|
|
|
|
|
|
|
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
|
|
|
|
DDLogInfo(@"salt pragma = %@", saltPragma);
|
|
|
|
|
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
for(int row = 0; row < ROWSTOINSERT; row++) {
|
|
|
|
|
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_step(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_DONE);
|
|
|
|
|
rc = sqlite3_reset(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
}
|
|
|
|
|
rc = sqlite3_finalize(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_close(db);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
/* Step 2. Open the database and query it */
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
// NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
|
|
|
|
// DDLogInfo(@"salt pragma = %@", saltPragma);
|
|
|
|
|
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]);
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_close(db);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testCreatePreconvertedDatabaseWithoutYapDatabase
|
|
|
|
|
{
|
|
|
|
|
sqlite3 *db;
|
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
|
const int ROWSTOINSERT = 3;
|
|
|
|
|
|
|
|
|
|
NSString *databaseFilePath = [self createTempDatabaseFilePath];
|
|
|
|
|
|
|
|
|
|
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
|
|
|
|
|
|
|
|
|
|
NSData *keyData = [self randomDatabasePassword];
|
|
|
|
|
NSData *databaseSalt = [self randomDatabaseSalt];
|
|
|
|
|
NSString *salt = databaseSalt.hexadecimalString;
|
|
|
|
|
|
|
|
|
|
/* Step 1. Create a new encrypted database. */
|
|
|
|
|
|
|
|
|
|
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
|
|
|
|
DDLogInfo(@"salt pragma = %@", saltPragma);
|
|
|
|
|
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
for(int row = 0; row < ROWSTOINSERT; row++) {
|
|
|
|
|
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_step(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_DONE);
|
|
|
|
|
rc = sqlite3_reset(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
}
|
|
|
|
|
rc = sqlite3_finalize(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_close(db);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testNewUserLikeYapDatabase
|
|
|
|
|
{
|
|
|
|
|
sqlite3 *db;
|
|
|
|
|
sqlite3_stmt *stmt;
|
|
|
|
|
const int ROWSTOINSERT = 3;
|
|
|
|
|
|
|
|
|
|
NSString *databaseFilePath = [self createTempDatabaseFilePath];
|
|
|
|
|
|
|
|
|
|
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
|
|
|
|
|
|
|
|
|
|
NSData *keyData = [self randomDatabasePassword];
|
|
|
|
|
NSData *databaseSalt = [self randomDatabaseSalt];
|
|
|
|
|
NSString *salt = databaseSalt.hexadecimalString;
|
|
|
|
|
|
|
|
|
|
/* Step 1. Create a new encrypted database. */
|
|
|
|
|
|
|
|
|
|
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
|
|
|
|
|
|
|
|
|
|
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
|
|
|
|
DDLogInfo(@"salt pragma = %@", saltPragma);
|
|
|
|
|
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
int status = sqlite3_exec(db, "PRAGMA auto_vacuum = FULL; VACUUM;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertEqual(status, SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
// Set synchronous to normal for THIS sqlite instance.
|
|
|
|
|
//
|
|
|
|
|
// This does NOT affect normal connections.
|
|
|
|
|
// That is, this does NOT affect YapDatabaseConnection instances.
|
|
|
|
|
// The sqlite connections of normal YapDatabaseConnection instances will follow the set pragmaSynchronous value.
|
|
|
|
|
//
|
|
|
|
|
// The reason we hardcode normal for this sqlite instance is because
|
|
|
|
|
// it's only used to write the initial snapshot value.
|
|
|
|
|
// And this doesn't need to be durable, as it is initialized to zero everytime.
|
|
|
|
|
//
|
|
|
|
|
// (This sqlite db is also used to perform checkpoints.
|
|
|
|
|
// But a normal value won't affect these operations,
|
|
|
|
|
// as they will perform sync operations whether the connection is normal or full.)
|
|
|
|
|
|
|
|
|
|
status = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertEqual(status, SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
// Set journal_size_imit.
|
|
|
|
|
//
|
|
|
|
|
// We only need to do set this pragma for THIS connection,
|
|
|
|
|
// because it is the only connection that performs checkpoints.
|
|
|
|
|
|
|
|
|
|
NSString *pragma_journal_size_limit =
|
|
|
|
|
[NSString stringWithFormat:@"PRAGMA journal_size_limit = %d;", 0];
|
|
|
|
|
|
|
|
|
|
status = sqlite3_exec(db, [pragma_journal_size_limit UTF8String], NULL, NULL, NULL);
|
|
|
|
|
XCTAssertEqual(status, SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// // Set mmap_size (if needed).
|
|
|
|
|
// //
|
|
|
|
|
// // This configures memory mapped I/O.
|
|
|
|
|
//
|
|
|
|
|
// if (options.pragmaMMapSize > 0)
|
|
|
|
|
// {
|
|
|
|
|
// NSString *pragma_mmap_size =
|
|
|
|
|
// [NSString stringWithFormat:@"PRAGMA mmap_size = %ld;", (long)options.pragmaMMapSize];
|
|
|
|
|
//
|
|
|
|
|
// status = sqlite3_exec(db, [pragma_mmap_size UTF8String], NULL, NULL, NULL);
|
|
|
|
|
// if (status != SQLITE_OK)
|
|
|
|
|
// {
|
|
|
|
|
// YDBLogError(@"Error setting PRAGMA mmap_size: %d %s", status, sqlite3_errmsg(db));
|
|
|
|
|
// // This isn't critical, so we can continue.
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// Disable autocheckpointing.
|
|
|
|
|
//
|
|
|
|
|
// YapDatabase has its own optimized checkpointing algorithm built-in.
|
|
|
|
|
// It knows the state of every active connection for the database,
|
|
|
|
|
// so it can invoke the checkpoint methods at the precise time in which a checkpoint can be most effective.
|
|
|
|
|
|
|
|
|
|
status = sqlite3_wal_autocheckpoint(db, 0);
|
|
|
|
|
XCTAssertEqual(status, SQLITE_OK);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PRAGMA auto_vacuum = FULL; VACUUM;
|
|
|
|
|
// PRAGMA synchronous = NORMAL;
|
|
|
|
|
// PRAGMA journal_size_limit = 0;
|
|
|
|
|
// sqlite3_wal_autocheckpoint(db, 0);
|
|
|
|
|
// yap_vfs_shim_name = [NSString stringWithFormat:@"yap_vfs_shim_%@", [[NSUUID UUID] UUIDString]];
|
|
|
|
|
// yap_vfs_shim_register([yap_vfs_shim_name UTF8String], NULL, &yap_vfs_shim);
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
for(int row = 0; row < ROWSTOINSERT; row++) {
|
|
|
|
|
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_step(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_DONE);
|
|
|
|
|
rc = sqlite3_reset(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
}
|
|
|
|
|
rc = sqlite3_finalize(stmt);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_close(db);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
/* Step 2. Open the database and query it */
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
// NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
|
|
|
|
|
// DDLogInfo(@"salt pragma = %@", saltPragma);
|
|
|
|
|
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]);
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]);
|
|
|
|
|
|
|
|
|
|
rc = sqlite3_close(db);
|
|
|
|
|
XCTAssertTrue(rc == SQLITE_OK);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)logHeaderOfDatabaseFile:(NSString *)databaseFilePath
|
|
|
|
|
label:(NSString *)label
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(databaseFilePath.length > 0);
|
|
|
|
|
OWSAssert(label.length > 0);
|
|
|
|
|
|
|
|
|
|
NSData *headerData = [OWSDatabaseConverter readFirstNBytesOfDatabaseFile:databaseFilePath byteCount:kSqliteHeaderLength];
|
|
|
|
|
OWSAssert(headerData);
|
|
|
|
|
NSMutableString *output = [NSMutableString new];
|
|
|
|
|
[output appendFormat:@"Hex: %@, ", headerData.hexadecimalString];
|
|
|
|
|
[output appendString:@"Ascii: "];
|
|
|
|
|
NSMutableCharacterSet *characterSet = [NSMutableCharacterSet new];
|
|
|
|
|
[characterSet formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
|
|
|
[characterSet formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
|
|
|
|
|
[characterSet formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
|
|
|
|
|
[characterSet formUnionWithCharacterSet:[NSCharacterSet symbolCharacterSet]];
|
|
|
|
|
|
|
|
|
|
const unsigned char *bytes = (const unsigned char *) headerData.bytes;
|
|
|
|
|
for (NSUInteger i=0; i < headerData.length; i++) {
|
|
|
|
|
unsigned char byte = bytes[i];
|
|
|
|
|
if ([characterSet characterIsMember:(unichar)byte]) {
|
|
|
|
|
[output appendFormat:@"%C", (unichar)byte];
|
|
|
|
|
} else {
|
|
|
|
|
[output appendString:@"_"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
DDLogInfo(@"%@: %@", label, output);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_END
|
|
|
|
|
|
|
|
|
|