Add support for key specs.

pull/1/head
Matthew Chen 8 years ago
parent c5079ed3d7
commit 5d422e03d1

@ -23,7 +23,6 @@
#import <SignalMessaging/Environment.h> #import <SignalMessaging/Environment.h>
#import <SignalMessaging/OWSContactsManager.h> #import <SignalMessaging/OWSContactsManager.h>
#import <SignalMessaging/OWSContactsSyncing.h> #import <SignalMessaging/OWSContactsSyncing.h>
#import <SignalMessaging/OWSDatabaseConverter.h>
#import <SignalMessaging/OWSMath.h> #import <SignalMessaging/OWSMath.h>
#import <SignalMessaging/OWSPreferences.h> #import <SignalMessaging/OWSPreferences.h>
#import <SignalMessaging/OWSProfileManager.h> #import <SignalMessaging/OWSProfileManager.h>
@ -45,6 +44,7 @@
#import <SignalServiceKit/TSSocketManager.h> #import <SignalServiceKit/TSSocketManager.h>
#import <SignalServiceKit/TSStorageManager+Calling.h> #import <SignalServiceKit/TSStorageManager+Calling.h>
#import <SignalServiceKit/TextSecureKitEnv.h> #import <SignalServiceKit/TextSecureKitEnv.h>
#import <YapDatabase/YapDatabaseCryptoUtils.h>
@import WebRTC; @import WebRTC;
@import Intents; @import Intents;
@ -221,7 +221,9 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
return; return;
} }
[OWSDatabaseConverter convertDatabaseIfNecessary]; NSError *_Nullable error = [self convertDatabaseIfNecessary];
// TODO: Handle this error.
OWSAssert(!error);
[NSUserDefaults migrateToSharedUserDefaults]; [NSUserDefaults migrateToSharedUserDefaults];
@ -230,6 +232,33 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[TSAttachmentStream migrateToSharedData]; [TSAttachmentStream migrateToSharedData];
} }
- (nullable NSError *)convertDatabaseIfNecessary
{
NSString *databaseFilePath = [TSStorageManager legacyDatabaseFilePath];
NSError *error;
NSData *_Nullable databasePassword = [OWSStorage tryToLoadDatabasePassword:&error];
if (!databasePassword || error) {
return (error
?: OWSErrorWithCodeDescription(
OWSErrorCodeDatabaseConversionFatalError, @"Failed to load database password"));
}
YapDatabaseSaltBlock saltBlock = ^(NSData *saltData) {
DDLogVerbose(@"%@ saltData: %@", self.logTag, saltData.hexadecimalString);
[OWSStorage storeDatabaseSalt:saltData];
};
YapDatabaseKeySpecBlock keySpecBlock = ^(NSData *keySpecData) {
DDLogVerbose(@"%@ keySpecData: %@", self.logTag, keySpecData.hexadecimalString);
[OWSStorage storeDatabaseKeySpec:keySpecData];
};
return [YapDatabaseCryptoUtils convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword
saltBlock:saltBlock
keySpecBlock:keySpecBlock];
}
- (void)startupLogging - (void)startupLogging
{ {
DDLogInfo(@"iOS Version: %@", [UIDevice currentDevice].systemVersion); DDLogInfo(@"iOS Version: %@", [UIDevice currentDevice].systemVersion);

@ -3,12 +3,12 @@
// //
#import "OWSDatabaseConverterTest.h" #import "OWSDatabaseConverterTest.h"
#import "OWSDatabaseConverter.h"
#import <Curve25519Kit/Randomness.h> #import <Curve25519Kit/Randomness.h>
#import <SignalServiceKit/NSData+hexString.h> #import <SignalServiceKit/NSData+hexString.h>
#import <SignalServiceKit/OWSStorage.h> #import <SignalServiceKit/OWSStorage.h>
#import <SignalServiceKit/YapDatabaseConnection+OWS.h> #import <SignalServiceKit/YapDatabaseConnection+OWS.h>
#import <YapDatabase/YapDatabase.h> #import <YapDatabase/YapDatabase.h>
#import <YapDatabase/YapDatabaseCryptoUtils.h>
#import <YapDatabase/YapDatabasePrivate.h> #import <YapDatabase/YapDatabasePrivate.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -21,9 +21,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - #pragma mark -
@interface OWSDatabaseConverter (OWSDatabaseConverterTest) @interface YapDatabaseCryptoUtils (OWSDatabaseConverterTest)
+ (BOOL)doesDatabaseNeedToBeConverted:(NSString *)databaseFilePath;
+ (NSData *)readFirstNBytesOfDatabaseFile:(NSString *)filePath byteCount:(NSUInteger)byteCount; + (NSData *)readFirstNBytesOfDatabaseFile:(NSString *)filePath byteCount:(NSUInteger)byteCount;
@ -230,7 +228,7 @@ NS_ASSUME_NONNULL_BEGIN
NSString *temporaryDirectory = NSTemporaryDirectory(); NSString *temporaryDirectory = NSTemporaryDirectory();
NSString *filename = [[NSUUID UUID].UUIDString stringByAppendingString:@".sqlite"]; NSString *filename = [[NSUUID UUID].UUIDString stringByAppendingString:@".sqlite"];
NSString *databaseFilePath = [temporaryDirectory stringByAppendingPathComponent:filename]; NSString *databaseFilePath = [temporaryDirectory stringByAppendingPathComponent:filename];
DDLogInfo(@"%@ databaseFilePath: %@", self.logTag, databaseFilePath); DDLogInfo(@"%@ databaseFilePath: %@", self.logTag, databaseFilePath);
[DDLog flushLog]; [DDLog flushLog];
@ -270,7 +268,7 @@ NS_ASSUME_NONNULL_BEGIN
{ {
NSData *databasePassword = [self randomDatabasePassword]; NSData *databasePassword = [self randomDatabasePassword];
NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:databasePassword]; NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:databasePassword];
XCTAssertTrue([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertTrue([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
} }
- (void)testDoesDatabaseNeedToBeConverted_ConvertedWithoutKeyspec - (void)testDoesDatabaseNeedToBeConverted_ConvertedWithoutKeyspec
@ -280,7 +278,7 @@ NS_ASSUME_NONNULL_BEGIN
NSData *_Nullable databaseKeySpec = nil; NSData *_Nullable databaseKeySpec = nil;
NSString *_Nullable databaseFilePath = NSString *_Nullable databaseFilePath =
[self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec]; [self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec];
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertFalse([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
} }
- (void)testDoesDatabaseNeedToBeConverted_ConvertedWithKeyspec - (void)testDoesDatabaseNeedToBeConverted_ConvertedWithKeyspec
@ -290,7 +288,7 @@ NS_ASSUME_NONNULL_BEGIN
NSData *databaseKeySpec = [self randomDatabaseKeySpec]; NSData *databaseKeySpec = [self randomDatabaseKeySpec];
NSString *_Nullable databaseFilePath = NSString *_Nullable databaseFilePath =
[self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec]; [self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec];
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertFalse([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
} }
// Verifies that legacy users with non-converted databases can convert. // Verifies that legacy users with non-converted databases can convert.
@ -298,31 +296,31 @@ NS_ASSUME_NONNULL_BEGIN
{ {
NSData *databasePassword = [self randomDatabasePassword]; NSData *databasePassword = [self randomDatabasePassword];
NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:databasePassword]; NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:databasePassword];
XCTAssertTrue([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertTrue([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
__block NSData *_Nullable databaseSalt = nil; __block NSData *_Nullable databaseSalt = nil;
OWSDatabaseSaltBlock saltBlock = ^(NSData *saltData) { YapDatabaseSaltBlock saltBlock = ^(NSData *saltData) {
OWSAssert(!databaseSalt); OWSAssert(!databaseSalt);
OWSAssert(saltData); OWSAssert(saltData);
databaseSalt = saltData; databaseSalt = saltData;
}; };
__block NSData *_Nullable databaseKeySpec = nil; __block NSData *_Nullable databaseKeySpec = nil;
OWSDatabaseSaltBlock keySpecBlock = ^(NSData *keySpecData) { YapDatabaseKeySpecBlock keySpecBlock = ^(NSData *keySpecData) {
OWSAssert(!databaseKeySpec); OWSAssert(!databaseKeySpec);
OWSAssert(keySpecData); OWSAssert(keySpecData);
databaseKeySpec = keySpecData; databaseKeySpec = keySpecData;
}; };
NSError *_Nullable error = [OWSDatabaseConverter convertDatabaseIfNecessary:databaseFilePath NSError *_Nullable error = [YapDatabaseCryptoUtils convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword databasePassword:databasePassword
saltBlock:saltBlock saltBlock:saltBlock
keySpecBlock:keySpecBlock]; keySpecBlock:keySpecBlock];
if (error) { if (error) {
DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error); DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error);
} }
XCTAssertNil(error); XCTAssertNil(error);
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertFalse([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
XCTAssertNotNil(databaseSalt); XCTAssertNotNil(databaseSalt);
XCTAssertEqual(databaseSalt.length, kSQLCipherSaltLength); XCTAssertEqual(databaseSalt.length, kSQLCipherSaltLength);
XCTAssertNotNil(databaseKeySpec); XCTAssertNotNil(databaseKeySpec);
@ -340,31 +338,31 @@ NS_ASSUME_NONNULL_BEGIN
{ {
NSData *databasePassword = [self randomDatabasePassword]; NSData *databasePassword = [self randomDatabasePassword];
NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:databasePassword]; NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:databasePassword];
XCTAssertTrue([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertTrue([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
__block NSData *_Nullable databaseSalt = nil; __block NSData *_Nullable databaseSalt = nil;
OWSDatabaseSaltBlock saltBlock = ^(NSData *saltData) { YapDatabaseSaltBlock saltBlock = ^(NSData *saltData) {
OWSAssert(!databaseSalt); OWSAssert(!databaseSalt);
OWSAssert(saltData); OWSAssert(saltData);
databaseSalt = saltData; databaseSalt = saltData;
}; };
__block NSData *_Nullable databaseKeySpec = nil; __block NSData *_Nullable databaseKeySpec = nil;
OWSDatabaseSaltBlock keySpecBlock = ^(NSData *keySpecData) { YapDatabaseKeySpecBlock keySpecBlock = ^(NSData *keySpecData) {
OWSAssert(!databaseKeySpec); OWSAssert(!databaseKeySpec);
OWSAssert(keySpecData); OWSAssert(keySpecData);
databaseKeySpec = keySpecData; databaseKeySpec = keySpecData;
}; };
NSError *_Nullable error = [OWSDatabaseConverter convertDatabaseIfNecessary:databaseFilePath NSError *_Nullable error = [YapDatabaseCryptoUtils convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword databasePassword:databasePassword
saltBlock:saltBlock saltBlock:saltBlock
keySpecBlock:keySpecBlock]; keySpecBlock:keySpecBlock];
if (error) { if (error) {
DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error); DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error);
} }
XCTAssertNil(error); XCTAssertNil(error);
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertFalse([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
XCTAssertNotNil(databaseSalt); XCTAssertNotNil(databaseSalt);
XCTAssertEqual(databaseSalt.length, kSQLCipherSaltLength); XCTAssertEqual(databaseSalt.length, kSQLCipherSaltLength);
XCTAssertNotNil(databaseKeySpec); XCTAssertNotNil(databaseKeySpec);
@ -385,28 +383,28 @@ NS_ASSUME_NONNULL_BEGIN
NSData *_Nullable databaseKeySpec = nil; NSData *_Nullable databaseKeySpec = nil;
NSString *_Nullable databaseFilePath = NSString *_Nullable databaseFilePath =
[self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec]; [self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec];
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertFalse([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
OWSDatabaseSaltBlock saltBlock = ^(NSData *saltData) { YapDatabaseSaltBlock saltBlock = ^(NSData *saltData) {
OWSAssert(saltData); OWSAssert(saltData);
XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__); XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__);
}; };
OWSDatabaseSaltBlock keySpecBlock = ^(NSData *keySpecData) { YapDatabaseKeySpecBlock keySpecBlock = ^(NSData *keySpecData) {
OWSAssert(keySpecData); OWSAssert(keySpecData);
XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__); XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__);
}; };
NSError *_Nullable error = [OWSDatabaseConverter convertDatabaseIfNecessary:databaseFilePath NSError *_Nullable error = [YapDatabaseCryptoUtils convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword databasePassword:databasePassword
saltBlock:saltBlock saltBlock:saltBlock
keySpecBlock:keySpecBlock]; keySpecBlock:keySpecBlock];
if (error) { if (error) {
DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error); DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error);
} }
XCTAssertNil(error); XCTAssertNil(error);
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertFalse([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
BOOL isValid = [self verifyTestDatabase:databaseFilePath BOOL isValid = [self verifyTestDatabase:databaseFilePath
databasePassword:databasePassword databasePassword:databasePassword
@ -423,28 +421,28 @@ NS_ASSUME_NONNULL_BEGIN
NSData *databaseKeySpec = [self randomDatabaseKeySpec]; NSData *databaseKeySpec = [self randomDatabaseKeySpec];
NSString *_Nullable databaseFilePath = NSString *_Nullable databaseFilePath =
[self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec]; [self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec];
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertFalse([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
OWSDatabaseSaltBlock saltBlock = ^(NSData *saltData) { YapDatabaseSaltBlock saltBlock = ^(NSData *saltData) {
OWSAssert(saltData); OWSAssert(saltData);
XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__); XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__);
}; };
OWSDatabaseSaltBlock keySpecBlock = ^(NSData *keySpecData) { YapDatabaseKeySpecBlock keySpecBlock = ^(NSData *keySpecData) {
OWSAssert(keySpecData); OWSAssert(keySpecData);
XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__); XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__);
}; };
NSError *_Nullable error = [OWSDatabaseConverter convertDatabaseIfNecessary:databaseFilePath NSError *_Nullable error = [YapDatabaseCryptoUtils convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword databasePassword:databasePassword
saltBlock:saltBlock saltBlock:saltBlock
keySpecBlock:keySpecBlock]; keySpecBlock:keySpecBlock];
if (error) { if (error) {
DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error); DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error);
} }
XCTAssertNil(error); XCTAssertNil(error);
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]); XCTAssertFalse([YapDatabaseCryptoUtils doesDatabaseNeedToBeConverted:databaseFilePath]);
BOOL isValid = [self verifyTestDatabase:databaseFilePath BOOL isValid = [self verifyTestDatabase:databaseFilePath
databasePassword:databasePassword databasePassword:databasePassword
@ -459,35 +457,35 @@ NS_ASSUME_NONNULL_BEGIN
sqlite3 *db; sqlite3 *db;
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
const int ROWSTOINSERT = 3; const int ROWSTOINSERT = 3;
NSString *databaseFilePath = [self createTempDatabaseFilePath]; NSString *databaseFilePath = [self createTempDatabaseFilePath];
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]); OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
NSData *keyData = [self randomDatabasePassword]; NSData *keyData = [self randomDatabasePassword];
/* Step 1. Create a new encrypted database. */ /* Step 1. Create a new encrypted database. */
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE; int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL); int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]); rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL); rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL); rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL); rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
for(int row = 0; row < ROWSTOINSERT; row++) { for(int row = 0; row < ROWSTOINSERT; row++) {
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT); rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
@ -498,78 +496,78 @@ NS_ASSUME_NONNULL_BEGIN
} }
rc = sqlite3_finalize(stmt); rc = sqlite3_finalize(stmt);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
NSString *salt = [self executeSingleStringQuery:@"PRAGMA cipher_salt;" NSString *salt = [self executeSingleStringQuery:@"PRAGMA cipher_salt;"
db:db]; db:db];
rc = sqlite3_close(db); rc = sqlite3_close(db);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
[self logHeaderOfDatabaseFile:databaseFilePath [self logHeaderOfDatabaseFile:databaseFilePath
label:@"Unconverted header"]; label:@"Unconverted header"];
/* Step 2. Rewrite header */ /* Step 2. Rewrite header */
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL); rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]); rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA user_version = 2;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA user_version = 2;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
int log, ckpt; int log, ckpt;
rc = sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_FULL, &log, &ckpt); rc = sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_FULL, &log, &ckpt);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
DDLogInfo(@"log = %d, ckpt = %d", log, ckpt); DDLogInfo(@"log = %d, ckpt = %d", log, ckpt);
rc = sqlite3_close(db); rc = sqlite3_close(db);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
[self logHeaderOfDatabaseFile:databaseFilePath [self logHeaderOfDatabaseFile:databaseFilePath
label:@"Converted header"]; label:@"Converted header"];
/* Step 3. Open the database and query it */ /* Step 3. Open the database and query it */
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL); rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]); rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt]; NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
DDLogInfo(@"salt pragma = %@", saltPragma); DDLogInfo(@"salt pragma = %@", saltPragma);
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL); rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]); XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]);
XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]); XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]);
rc = sqlite3_close(db); rc = sqlite3_close(db);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
} }
@ -578,18 +576,18 @@ NS_ASSUME_NONNULL_BEGIN
db:(sqlite3 *)db db:(sqlite3 *)db
{ {
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(db, sql.UTF8String, -1, &stmt, NULL); int rc = sqlite3_prepare_v2(db, sql.UTF8String, -1, &stmt, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_step(stmt); rc = sqlite3_step(stmt);
XCTAssertTrue(rc = SQLITE_ROW); XCTAssertTrue(rc = SQLITE_ROW);
int result = sqlite3_column_int(stmt, 0); int result = sqlite3_column_int(stmt, 0);
rc = sqlite3_finalize(stmt); rc = sqlite3_finalize(stmt);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
return result; return result;
} }
@ -597,16 +595,16 @@ NS_ASSUME_NONNULL_BEGIN
db:(sqlite3 *)db db:(sqlite3 *)db
{ {
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(db, sql.UTF8String, -1, &stmt, NULL); int rc = sqlite3_prepare_v2(db, sql.UTF8String, -1, &stmt, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_step(stmt); rc = sqlite3_step(stmt);
XCTAssertTrue(rc = SQLITE_ROW); XCTAssertTrue(rc = SQLITE_ROW);
NSString *result = [NSString stringWithFormat:@"%s", sqlite3_column_text(stmt, 0)]; NSString *result = [NSString stringWithFormat:@"%s", sqlite3_column_text(stmt, 0)];
rc = sqlite3_finalize(stmt); rc = sqlite3_finalize(stmt);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
return result; return result;
} }
@ -616,45 +614,45 @@ NS_ASSUME_NONNULL_BEGIN
sqlite3 *db; sqlite3 *db;
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
const int ROWSTOINSERT = 3; const int ROWSTOINSERT = 3;
NSString *databaseFilePath = [self createTempDatabaseFilePath]; NSString *databaseFilePath = [self createTempDatabaseFilePath];
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]); OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
NSData *keyData = [self randomDatabasePassword]; NSData *keyData = [self randomDatabasePassword];
NSData *databaseSalt = [self randomDatabaseSalt]; NSData *databaseSalt = [self randomDatabaseSalt];
NSString *salt = databaseSalt.hexadecimalString; NSString *salt = databaseSalt.hexadecimalString;
/* Step 1. Create a new encrypted database. */ /* Step 1. Create a new encrypted database. */
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE; int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL); int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]); rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt]; NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
DDLogInfo(@"salt pragma = %@", saltPragma); DDLogInfo(@"salt pragma = %@", saltPragma);
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL); rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL); rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL); rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL); rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
for(int row = 0; row < ROWSTOINSERT; row++) { for(int row = 0; row < ROWSTOINSERT; row++) {
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT); rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
@ -665,40 +663,40 @@ NS_ASSUME_NONNULL_BEGIN
} }
rc = sqlite3_finalize(stmt); rc = sqlite3_finalize(stmt);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_close(db); rc = sqlite3_close(db);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
/* Step 2. Open the database and query it */ /* Step 2. Open the database and query it */
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL); rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]); rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
// NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt]; // NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
// DDLogInfo(@"salt pragma = %@", saltPragma); // DDLogInfo(@"salt pragma = %@", saltPragma);
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL); rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]); XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]);
XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]); XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]);
rc = sqlite3_close(db); rc = sqlite3_close(db);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
} }
@ -710,40 +708,40 @@ NS_ASSUME_NONNULL_BEGIN
sqlite3 *db; sqlite3 *db;
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
const int ROWSTOINSERT = 3; const int ROWSTOINSERT = 3;
NSString *databaseFilePath = [self createTempDatabaseFilePath]; NSString *databaseFilePath = [self createTempDatabaseFilePath];
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]); OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
NSData *keyData = [self randomDatabasePassword]; NSData *keyData = [self randomDatabasePassword];
NSData *databaseSalt = [self randomDatabaseSalt]; NSData *databaseSalt = [self randomDatabaseSalt];
NSString *salt = databaseSalt.hexadecimalString; NSString *salt = databaseSalt.hexadecimalString;
/* Step 1. Create a new encrypted database. */ /* Step 1. Create a new encrypted database. */
int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE; int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL); int rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]); rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt]; NSString *saltPragma = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", salt];
DDLogInfo(@"salt pragma = %@", saltPragma); DDLogInfo(@"salt pragma = %@", saltPragma);
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL); rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
{ {
int status = sqlite3_exec(db, "PRAGMA auto_vacuum = FULL; VACUUM;", NULL, NULL, NULL); int status = sqlite3_exec(db, "PRAGMA auto_vacuum = FULL; VACUUM;", NULL, NULL, NULL);
XCTAssertEqual(status, SQLITE_OK); XCTAssertEqual(status, SQLITE_OK);
// Set synchronous to normal for THIS sqlite instance. // Set synchronous to normal for THIS sqlite instance.
// //
// This does NOT affect normal connections. // This does NOT affect normal connections.
@ -757,7 +755,7 @@ NS_ASSUME_NONNULL_BEGIN
// (This sqlite db is also used to perform checkpoints. // (This sqlite db is also used to perform checkpoints.
// But a normal value won't affect these operations, // But a normal value won't affect these operations,
// as they will perform sync operations whether the connection is normal or full.) // as they will perform sync operations whether the connection is normal or full.)
status = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL); status = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
XCTAssertEqual(status, SQLITE_OK); XCTAssertEqual(status, SQLITE_OK);
@ -765,32 +763,32 @@ NS_ASSUME_NONNULL_BEGIN
// //
// We only need to do set this pragma for THIS connection, // We only need to do set this pragma for THIS connection,
// because it is the only connection that performs checkpoints. // because it is the only connection that performs checkpoints.
NSString *pragma_journal_size_limit = NSString *pragma_journal_size_limit =
[NSString stringWithFormat:@"PRAGMA journal_size_limit = %d;", 0]; [NSString stringWithFormat:@"PRAGMA journal_size_limit = %d;", 0];
status = sqlite3_exec(db, [pragma_journal_size_limit UTF8String], NULL, NULL, NULL); status = sqlite3_exec(db, [pragma_journal_size_limit UTF8String], NULL, NULL, NULL);
XCTAssertEqual(status, SQLITE_OK); XCTAssertEqual(status, SQLITE_OK);
// Disable autocheckpointing. // Disable autocheckpointing.
// //
// YapDatabase has its own optimized checkpointing algorithm built-in. // YapDatabase has its own optimized checkpointing algorithm built-in.
// It knows the state of every active connection for the database, // 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. // 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); status = sqlite3_wal_autocheckpoint(db, 0);
XCTAssertEqual(status, SQLITE_OK); XCTAssertEqual(status, SQLITE_OK);
} }
rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL); rc = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS t1 (a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT);", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL); rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL); rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES (?);", -1, &stmt, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
for(int row = 0; row < ROWSTOINSERT; row++) { for(int row = 0; row < ROWSTOINSERT; row++) {
rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT); rc = sqlite3_bind_text(stmt, 1, [[NSString stringWithFormat:@"%d", (int) arc4random()] UTF8String], -1, SQLITE_TRANSIENT);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
@ -801,38 +799,38 @@ NS_ASSUME_NONNULL_BEGIN
} }
rc = sqlite3_finalize(stmt); rc = sqlite3_finalize(stmt);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); rc = sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_close(db); rc = sqlite3_close(db);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
/* Step 2. Open the database and query it */ /* Step 2. Open the database and query it */
rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL); rc = sqlite3_open_v2([databaseFilePath UTF8String], &db, openFlags, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]); rc = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL); rc = sqlite3_exec(db, [saltPragma UTF8String], NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA cipher_plaintext_header_size = 32;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL); rc = sqlite3_exec(db, "PRAGMA journal_size_limit = 1048576;", NULL, NULL, NULL);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]); XCTAssertEqual(2, [self executeSingleIntQuery:@"SELECT count(*) FROM sqlite_master;" db:db]);
XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]); XCTAssertEqual(ROWSTOINSERT, [self executeSingleIntQuery:@"SELECT count(*) FROM t1;" db:db]);
rc = sqlite3_close(db); rc = sqlite3_close(db);
XCTAssertTrue(rc == SQLITE_OK); XCTAssertTrue(rc == SQLITE_OK);
} }
@ -843,7 +841,8 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(databaseFilePath.length > 0); OWSAssert(databaseFilePath.length > 0);
OWSAssert(label.length > 0); OWSAssert(label.length > 0);
NSData *headerData = [OWSDatabaseConverter readFirstNBytesOfDatabaseFile:databaseFilePath byteCount:kSqliteHeaderLength]; NSData *headerData =
[YapDatabaseCryptoUtils readFirstNBytesOfDatabaseFile:databaseFilePath byteCount:kSqliteHeaderLength];
OWSAssert(headerData); OWSAssert(headerData);
NSMutableString *output = [NSMutableString new]; NSMutableString *output = [NSMutableString new];
[output appendFormat:@"Hex: %@, ", headerData.hexadecimalString]; [output appendFormat:@"Hex: %@, ", headerData.hexadecimalString];

@ -2,33 +2,4 @@
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// //
NS_ASSUME_NONNULL_BEGIN
extern const NSUInteger kSqliteHeaderLength;
extern const NSUInteger kSQLCipherSaltLength;
extern const NSUInteger kSQLCipherDerivedKeyLength;
extern const NSUInteger kSQLCipherKeySpecLength;
typedef void (^OWSDatabaseSaltBlock)(NSData *saltData);
typedef void (^OWSDatabaseKeySpecBlock)(NSData *keySpecData);
// Used to convert YapDatabase/SQLCipher databases whose header is encrypted
// to databases whose first 32 bytes are unencrypted so that iOS can determine
// that this is a SQLite database using WAL and therefore not terminate the app
// when it is suspended.
@interface OWSDatabaseConverter : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (nullable NSError *)convertDatabaseIfNecessary;
+ (nullable NSError *)convertDatabaseIfNecessary:(NSString *)databaseFilePath
databasePassword:(NSData *)databasePassword
saltBlock:(OWSDatabaseSaltBlock)saltBlock
keySpecBlock:(OWSDatabaseKeySpecBlock)keySpecBlock;
+ (nullable NSData *)deriveDatabaseKeyForPassword:(NSData *)passwordData saltData:(NSData *)saltData;
+ (nullable NSData *)databaseKeySpecForPassword:(NSData *)passwordData saltData:(NSData *)saltData;
@end
NS_ASSUME_NONNULL_END

@ -2,426 +2,4 @@
// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// //
#import "OWSDatabaseConverter.h"
#import "sqlite3.h"
#import <CommonCrypto/CommonCrypto.h>
#import <SignalServiceKit/NSData+hexString.h>
#import <SignalServiceKit/OWSError.h>
#import <SignalServiceKit/OWSFileSystem.h>
#import <SignalServiceKit/TSStorageManager.h>
#import <YapDatabase/YapDatabase.h>
NS_ASSUME_NONNULL_BEGIN
const NSUInteger kSqliteHeaderLength = 32;
const NSUInteger kSQLCipherSaltLength = 16;
const NSUInteger kSQLCipherDerivedKeyLength = 32;
const NSUInteger kSQLCipherKeySpecLength = 48;
@interface OWSStorage (OWSDatabaseConverter)
+ (YapDatabaseDeserializer)logOnFailureDeserializer;
@end
#pragma mark -
@implementation OWSDatabaseConverter
+ (NSData *)readFirstNBytesOfDatabaseFile:(NSString *)filePath byteCount:(NSUInteger)byteCount
{
OWSAssert(filePath.length > 0);
@autoreleasepool {
NSError *error;
// We use NSDataReadingMappedAlways instead of NSDataReadingMappedIfSafe because
// we know the database will always exist for the duration of this instance of NSData.
NSData *_Nullable data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]
options:NSDataReadingMappedAlways
error:&error];
if (!data || error) {
DDLogError(@"%@ Couldn't read database file header.", self.logTag);
// TODO: Make a convenience method (on a category of NSException?) that
// flushes DDLog before raising a terminal exception.
[NSException raise:@"Couldn't read database file header" format:@""];
}
// Pull this constant out so that we can use it in our YapDatabase fork.
NSData *_Nullable headerData = [data subdataWithRange:NSMakeRange(0, byteCount)];
if (!headerData || headerData.length != byteCount) {
[NSException raise:@"Database file header has unexpected length"
format:@"Database file header has unexpected length: %zd", headerData.length];
}
return [headerData copy];
}
}
+ (BOOL)doesDatabaseNeedToBeConverted:(NSString *)databaseFilePath
{
OWSAssert(databaseFilePath.length > 0);
if (![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]) {
DDLogVerbose(@"%@ database file not found.", self.logTag);
return nil;
}
NSData *headerData = [self readFirstNBytesOfDatabaseFile:databaseFilePath byteCount:kSqliteHeaderLength];
OWSAssert(headerData);
NSString *kUnencryptedHeader = @"SQLite format 3\0";
NSData *unencryptedHeaderData = [kUnencryptedHeader dataUsingEncoding:NSUTF8StringEncoding];
BOOL isUnencrypted = [unencryptedHeaderData
isEqualToData:[headerData subdataWithRange:NSMakeRange(0, unencryptedHeaderData.length)]];
if (isUnencrypted) {
DDLogVerbose(@"%@ doesDatabaseNeedToBeConverted; legacy database header already decrypted.", self.logTag);
return NO;
}
return YES;
}
+ (nullable NSError *)convertDatabaseIfNecessary
{
NSString *databaseFilePath = [TSStorageManager legacyDatabaseFilePath];
NSError *error;
NSData *_Nullable databasePassword = [OWSStorage tryToLoadDatabasePassword:&error];
if (!databasePassword || error) {
return (error
?: OWSErrorWithCodeDescription(
OWSErrorCodeDatabaseConversionFatalError, @"Failed to load database password"));
}
OWSDatabaseSaltBlock saltBlock = ^(NSData *saltData) {
[OWSStorage storeDatabaseSalt:saltData];
};
OWSDatabaseKeySpecBlock keySpecBlock = ^(NSData *keySpecData) {
[OWSStorage storeDatabaseKeySpec:keySpecData];
};
return [self convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword
saltBlock:saltBlock
keySpecBlock:keySpecBlock];
}
// TODO upon failure show user error UI
// TODO upon failure anything we need to do "back out" partial migration
+ (nullable NSError *)convertDatabaseIfNecessary:(NSString *)databaseFilePath
databasePassword:(NSData *)databasePassword
saltBlock:(OWSDatabaseSaltBlock)saltBlock
keySpecBlock:(OWSDatabaseKeySpecBlock)keySpecBlock
{
if (![self doesDatabaseNeedToBeConverted:databaseFilePath]) {
return nil;
}
return [self convertDatabase:databaseFilePath
databasePassword:databasePassword
saltBlock:saltBlock
keySpecBlock:keySpecBlock];
}
+ (nullable NSError *)convertDatabase:(NSString *)databaseFilePath
databasePassword:(NSData *)databasePassword
saltBlock:(OWSDatabaseSaltBlock)saltBlock
keySpecBlock:(OWSDatabaseKeySpecBlock)keySpecBlock
{
OWSAssert(databaseFilePath.length > 0);
OWSAssert(databasePassword.length > 0);
OWSAssert(saltBlock);
OWSAssert(keySpecBlock);
DDLogVerbose(@"%@ databasePassword: %@", self.logTag, databasePassword.hexadecimalString);
NSData *saltData;
{
NSData *headerData = [self readFirstNBytesOfDatabaseFile:databaseFilePath byteCount:kSqliteHeaderLength];
OWSAssert(headerData);
OWSAssert(headerData.length >= kSQLCipherSaltLength);
saltData = [headerData subdataWithRange:NSMakeRange(0, kSQLCipherSaltLength)];
DDLogVerbose(@"%@ saltData: %@", self.logTag, saltData.hexadecimalString);
// Make sure we successfully persist the salt (persumably in the keychain) before
// proceeding with the database conversion or we could leave the app in an
// unrecoverable state.
saltBlock(saltData);
}
{
NSData *_Nullable keySpecData = [self databaseKeySpecForPassword:databasePassword saltData:saltData];
if (!keySpecData || keySpecData.length != kSQLCipherKeySpecLength) {
DDLogError(@"Error deriving key spec");
return OWSErrorWithCodeDescription(OWSErrorCodeDatabaseConversionFatalError, @"Invalid key spec");
}
DDLogVerbose(@"%@ keySpecData: %@", self.logTag, keySpecData.hexadecimalString);
OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
// Make sure we successfully persist the key spec (persumably in the keychain) before
// proceeding with the database conversion or we could leave the app in an
// unrecoverable state.
keySpecBlock(keySpecData);
}
// -----------------------------------------------------------
//
// This block was derived from [Yapdatabase openDatabase].
sqlite3 *db;
int status;
{
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE;
status = sqlite3_open_v2([databaseFilePath UTF8String], &db, flags, NULL);
if (status != SQLITE_OK) {
// There are a few reasons why the database might not open.
// One possibility is if the database file has become corrupt.
// Sometimes the open function returns a db to allow us to query it for the error message.
// The openConfigCreate block will close it for us.
if (db) {
DDLogError(@"Error opening database: %d %s", status, sqlite3_errmsg(db));
} else {
DDLogError(@"Error opening database: %d", status);
}
return OWSErrorWithCodeDescription(OWSErrorCodeDatabaseConversionFatalError, @"Failed to open database");
}
}
// -----------------------------------------------------------
//
// This block was derived from [Yapdatabase configureEncryptionForDatabase].
{
NSData *keyData = databasePassword;
status = sqlite3_key(db, [keyData bytes], (int)[keyData length]);
if (status != SQLITE_OK) {
DDLogError(@"Error setting SQLCipher key: %d %s", status, sqlite3_errmsg(db));
return OWSErrorWithCodeDescription(
OWSErrorCodeDatabaseConversionFatalError, @"Failed to set SQLCipher key");
}
}
// -----------------------------------------------------------
//
// This block was derived from [Yapdatabase configureDatabase].
{
NSError *_Nullable error = [self executeSql:@"PRAGMA journal_mode = WAL;"
db:db
label:@"PRAGMA journal_mode = WAL"];
if (error) {
return error;
}
// TODO verify we need to do this.
// 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.)
error = [self executeSql:@"PRAGMA synchronous = NORMAL;"
db:db
label:@"PRAGMA synchronous = NORMAL"];
// Any error isn't critical, so we can continue.
// Set journal_size_imit.
//
// We only need to do set this pragma for THIS connection,
// because it is the only connection that performs checkpoints.
NSInteger defaultPragmaJournalSizeLimit = 0;
NSString *pragma_journal_size_limit =
[NSString stringWithFormat:@"PRAGMA journal_size_limit = %ld;", (long)defaultPragmaJournalSizeLimit];
error = [self executeSql:pragma_journal_size_limit
db:db
label:@"PRAGMA journal_size_limit"];
// Any error 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.
sqlite3_wal_autocheckpoint(db, 0);
// END DB setup copied from YapDatabase
// BEGIN SQLCipher migration
}
#ifdef DEBUG
// We can obtain the database salt in two ways: by reading the first 16 bytes of the encrypted
// header OR by using "PRAGMA cipher_salt". In DEBUG builds, we verify that these two values
// match.
{
NSString *_Nullable saltString =
[self executeSingleStringQuery:@"PRAGMA cipher_salt;" db:db label:@"extracting database salt"];
DDLogVerbose(@"%@ saltString: %@", self.logTag, saltString);
OWSAssert([saltData.hexadecimalString isEqualToString:saltString]);
}
#endif
// -----------------------------------------------------------
//
// SQLCipher migration
{
NSString *setPlainTextHeaderPragma =
[NSString stringWithFormat:@"PRAGMA cipher_plaintext_header_size = %zd;", kSqliteHeaderLength];
NSError *_Nullable error = [self executeSql:setPlainTextHeaderPragma
db:db
label:setPlainTextHeaderPragma];
if (error) {
return error;
}
// Modify the first page, so that SQLCipher will overwrite, respecting our new cipher_plaintext_header_size
NSString *tableName = [NSString stringWithFormat:@"signal-migration-%@", [NSUUID new].UUIDString];
NSString *modificationSQL =
[NSString stringWithFormat:@"CREATE TABLE \"%@\"(a integer); INSERT INTO \"%@\"(a) VALUES (1);",
tableName,
tableName];
error = [self executeSql:modificationSQL
db:db
label:modificationSQL];
if (error) {
return error;
}
// Force a checkpoint so that the plaintext is written to the actual DB file, not just living in the WAL.
int log, ckpt;
status = sqlite3_wal_checkpoint_v2(db, NULL, SQLITE_CHECKPOINT_FULL, &log, &ckpt);
if (status != SQLITE_OK) {
DDLogError(@"%@ Error forcing checkpoint. status: %d, log: %d, ckpt: %d, error: %s", self.logTag, status, log, ckpt, sqlite3_errmsg(db));
return OWSErrorWithCodeDescription(
OWSErrorCodeDatabaseConversionFatalError, @"Error forcing checkpoint.");
}
sqlite3_close(db);
}
return nil;
}
+ (nullable NSError *)executeSql:(NSString *)sql
db:(sqlite3 *)db
label:(NSString *)label
{
OWSAssert(db);
OWSAssert(sql.length > 0);
DDLogVerbose(@"%@ %@", self.logTag, sql);
int status = sqlite3_exec(db, [sql UTF8String], NULL, NULL, NULL);
if (status != SQLITE_OK) {
DDLogError(@"Error %@: status: %d, error: %s",
label,
status,
sqlite3_errmsg(db));
return OWSErrorWithCodeDescription(
OWSErrorCodeDatabaseConversionFatalError, [NSString stringWithFormat:@"Failed to set %@", label]);
}
return nil;
}
+ (nullable NSString *)executeSingleStringQuery:(NSString *)sql db:(sqlite3 *)db label:(NSString *)label
{
sqlite3_stmt *statement;
int status = sqlite3_prepare_v2(db, sql.UTF8String, -1, &statement, NULL);
if (status != SQLITE_OK) {
DDLogError(@"%@ Error %@: %d, error: %s", self.logTag, label, status, sqlite3_errmsg(db));
return nil;
}
status = sqlite3_step(statement);
if (status != SQLITE_ROW) {
DDLogError(@"%@ Missing %@: %d, error: %s", self.logTag, label, status, sqlite3_errmsg(db));
return nil;
}
const unsigned char *valueBytes = sqlite3_column_text(statement, 0);
int valueLength = sqlite3_column_bytes(statement, 0);
OWSAssert(valueLength == kSqliteHeaderLength);
OWSAssert(valueBytes != NULL);
NSString *result =
[[NSString alloc] initWithBytes:valueBytes length:(NSUInteger)valueLength encoding:NSUTF8StringEncoding];
sqlite3_finalize(statement);
statement = NULL;
return result;
}
+ (nullable NSData *)deriveDatabaseKeyForPassword:(NSData *)passwordData saltData:(NSData *)saltData
{
OWSAssert(passwordData.length > 0);
OWSAssert(saltData.length == kSQLCipherSaltLength);
unsigned char *derivedKeyBytes = malloc((size_t)kSQLCipherDerivedKeyLength);
OWSAssert(derivedKeyBytes);
// See: PBKDF2_ITER.
const unsigned int workfactor = 64000;
int result = CCKeyDerivationPBKDF(kCCPBKDF2,
passwordData.bytes,
(size_t)passwordData.length,
saltData.bytes,
(size_t)saltData.length,
kCCPRFHmacAlgSHA1,
workfactor,
derivedKeyBytes,
kSQLCipherDerivedKeyLength);
if (result != kCCSuccess) {
DDLogError(@"Error deriving key: %d", result);
return nil;
}
NSData *_Nullable derivedKeyData = [NSData dataWithBytes:derivedKeyBytes length:kSQLCipherDerivedKeyLength];
if (!derivedKeyData || derivedKeyData.length != kSQLCipherDerivedKeyLength) {
DDLogError(@"Invalid derived key: %d", result);
return nil;
}
DDLogVerbose(@"%@ derivedKeyData: %@", self.logTag, derivedKeyData.hexadecimalString);
return derivedKeyData;
}
+ (nullable NSData *)databaseKeySpecForPassword:(NSData *)passwordData saltData:(NSData *)saltData
{
OWSAssert(passwordData.length > 0);
OWSAssert(saltData.length == kSQLCipherSaltLength);
NSData *_Nullable derivedKeyData = [self deriveDatabaseKeyForPassword:passwordData saltData:saltData];
if (!derivedKeyData || derivedKeyData.length != kSQLCipherDerivedKeyLength) {
DDLogError(@"Error deriving key");
return nil;
}
DDLogVerbose(@"%@ derivedKeyData: %@", self.logTag, derivedKeyData.hexadecimalString);
NSMutableData *keySpecData = [NSMutableData new];
[keySpecData appendData:derivedKeyData];
[keySpecData appendData:saltData];
DDLogVerbose(@"%@ keySpecData: %@", self.logTag, keySpecData.hexadecimalString);
OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
return keySpecData;
}
@end
NS_ASSUME_NONNULL_END

@ -13,6 +13,7 @@
#import <Curve25519Kit/Randomness.h> #import <Curve25519Kit/Randomness.h>
#import <SAMKeychain/SAMKeychain.h> #import <SAMKeychain/SAMKeychain.h>
#import <YapDatabase/YapDatabase.h> #import <YapDatabase/YapDatabase.h>
#import <YapDatabase/YapDatabaseCryptoUtils.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -30,11 +31,6 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
static NSString *keychainDBSalt = @"OWSDatabaseSalt"; static NSString *keychainDBSalt = @"OWSDatabaseSalt";
static NSString *keychainDBKeySpec = @"OWSDatabaseKeySpec"; static NSString *keychainDBKeySpec = @"OWSDatabaseKeySpec";
// TODO: Move these constants to YapDatabase.
const NSUInteger kSqliteHeaderLength = 32;
const NSUInteger kSQLCipherSaltLength = 16;
const NSUInteger kSQLCipherKeySpecLength = 48;
const NSUInteger kDatabasePasswordLength = 30; const NSUInteger kDatabasePasswordLength = 30;
typedef NSData *_Nullable (^LoadDatabaseMetadataBlock)(NSError **_Nullable); typedef NSData *_Nullable (^LoadDatabaseMetadataBlock)(NSError **_Nullable);
@ -385,9 +381,13 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
- (BOOL)tryToLoadDatabase - (BOOL)tryToLoadDatabase
{ {
// We determine the database password / key spec first, since a side effect of // We determine the database password, salt and key spec first, since a side effect of
// this can be deleting any existing database file (if we're recovering // this can be deleting any existing database file (if we're recovering
// from a corrupt keychain). // from a corrupt keychain).
NSData *databasePassword = [self databasePassword];
OWSAssert(databasePassword.length > 0);
NSData *databaseSalt = [self databaseSalt];
OWSAssert(databaseSalt.length > 0);
NSData *databaseKeySpec = [self databaseKeySpec]; NSData *databaseKeySpec = [self databaseKeySpec];
OWSAssert(databaseKeySpec.length == kSQLCipherKeySpecLength); OWSAssert(databaseKeySpec.length == kSQLCipherKeySpecLength);
@ -555,6 +555,32 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
+ (nullable NSData *)tryToLoadDatabaseKeySpec:(NSError **)errorHandle + (nullable NSData *)tryToLoadDatabaseKeySpec:(NSError **)errorHandle
{ {
return [self tryToLoadKeyChainValue:keychainDBKeySpec errorHandle:errorHandle]; return [self tryToLoadKeyChainValue:keychainDBKeySpec errorHandle:errorHandle];
// NSData *_Nullable keySpecData = [self tryToLoadKeyChainValue:keychainDBKeySpec errorHandle:errorHandle];
//
// if (!keySpecData) {
// DDLogInfo(@"%@ Trying to derive database key spec.", self.logTag);
// NSData *_Nullable passwordData = [self tryToLoadDatabasePassword:errorHandle];
// if (passwordData && !*errorHandle) {
// NSData *_Nullable saltData = [self tryToLoadDatabaseSalt:errorHandle];
// if (saltData && !*errorHandle) {
// OWSAssert(passwordData.length > 0);
// OWSAssert(saltData.length == kSQLCipherSaltLength);
//
// keySpecData = [YapDatabaseCryptoUtils databaseKeySpecForPassword:passwordData saltData:saltData];
// OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
//
// if (keySpecData) {
// DDLogInfo(@"%@ database key spec derived.", self.logTag);
// [self storeDatabaseKeySpec:keySpecData];
// }
// }
// }
// }
//
// OWSAssert(keySpecData);
//
// return keySpecData;
} }
- (NSData *)databasePassword - (NSData *)databasePassword
@ -563,7 +589,15 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
return [OWSStorage tryToLoadDatabasePassword:errorHandle]; return [OWSStorage tryToLoadDatabasePassword:errorHandle];
} }
createDataBlock:^{ createDataBlock:^{
return [self createAndSetNewDatabasePassword]; NSData *passwordData = [self createAndSetNewDatabasePassword];
NSData *saltData = [self createAndSetNewDatabaseSalt];
NSData *keySpecData = [self createAndSetNewDatabaseKeySpec];
OWSAssert(passwordData.length > 0);
OWSAssert(saltData.length == kSQLCipherSaltLength);
OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
return passwordData;
} }
label:@"Database password"]; label:@"Database password"];
} }
@ -574,7 +608,15 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
return [OWSStorage tryToLoadDatabaseSalt:errorHandle]; return [OWSStorage tryToLoadDatabaseSalt:errorHandle];
} }
createDataBlock:^{ createDataBlock:^{
return [self createAndSetNewDatabaseSalt]; NSData *passwordData = [self createAndSetNewDatabasePassword];
NSData *saltData = [self createAndSetNewDatabaseSalt];
NSData *keySpecData = [self createAndSetNewDatabaseKeySpec];
OWSAssert(passwordData.length > 0);
OWSAssert(saltData.length == kSQLCipherSaltLength);
OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
return saltData;
} }
label:@"Database salt"]; label:@"Database salt"];
} }
@ -585,7 +627,17 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
return [OWSStorage tryToLoadDatabaseKeySpec:errorHandle]; return [OWSStorage tryToLoadDatabaseKeySpec:errorHandle];
} }
createDataBlock:^{ createDataBlock:^{
return [self createAndSetNewDatabaseKeySpec]; OWSFail(@"%@ It should never be necessary to generate a random key spec.", self.logTag);
NSData *passwordData = [self createAndSetNewDatabasePassword];
NSData *saltData = [self createAndSetNewDatabaseSalt];
NSData *keySpecData = [self createAndSetNewDatabaseKeySpec];
OWSAssert(passwordData.length > 0);
OWSAssert(saltData.length == kSQLCipherSaltLength);
OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
return keySpecData;
} }
label:@"Database key spec"]; label:@"Database key spec"];
} }
@ -659,7 +711,7 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
- (NSData *)createAndSetNewDatabaseSalt - (NSData *)createAndSetNewDatabaseSalt
{ {
NSData *saltData = [Randomness generateRandomBytes:kSQLCipherSaltLength]; NSData *saltData = [Randomness generateRandomBytes:(int)kSQLCipherSaltLength];
[OWSStorage storeDatabaseSalt:saltData]; [OWSStorage storeDatabaseSalt:saltData];
@ -673,7 +725,7 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
NSData *databaseSalt = [self databaseSalt]; NSData *databaseSalt = [self databaseSalt];
OWSAssert(databaseSalt.length == kSQLCipherSaltLength); OWSAssert(databaseSalt.length == kSQLCipherSaltLength);
NSData *keySpecData = [OWSDatabaseConverter databaseKeySpecForPassword:databasePassword saltData:databaseSalt]; NSData *keySpecData = [YapDatabaseCryptoUtils databaseKeySpecForPassword:databasePassword saltData:databaseSalt];
OWSAssert(keySpecData.length == kSQLCipherKeySpecLength); OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
[OWSStorage storeDatabaseKeySpec:keySpecData]; [OWSStorage storeDatabaseKeySpec:keySpecData];
@ -699,6 +751,7 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
{ {
[SAMKeychain deletePasswordForService:keychainService account:keychainDBPassAccount]; [SAMKeychain deletePasswordForService:keychainService account:keychainDBPassAccount];
[SAMKeychain deletePasswordForService:keychainService account:keychainDBSalt]; [SAMKeychain deletePasswordForService:keychainService account:keychainDBSalt];
[SAMKeychain deletePasswordForService:keychainService account:keychainDBKeySpec];
} }
- (unsigned long long)databaseFileSize - (unsigned long long)databaseFileSize

Loading…
Cancel
Save