diff --git a/Signal/test/util/OWSDatabaseConverterTest.m b/Signal/test/util/OWSDatabaseConverterTest.m index ff51059f5..02a930323 100644 --- a/Signal/test/util/OWSDatabaseConverterTest.m +++ b/Signal/test/util/OWSDatabaseConverterTest.m @@ -46,6 +46,10 @@ NS_ASSUME_NONNULL_BEGIN }; options.enableMultiProcessSupport = YES; + OWSAssert(options.cipherDefaultkdfIterNumber == 0); + OWSAssert(options.kdfIterNumber == 0); + OWSAssert(options.cipherPageSize == 0); + YapDatabase *database = [[YapDatabase alloc] initWithPath:databaseFilePath serializer:nil deserializer:[OWSStorage logOnFailureDeserializer] diff --git a/SignalMessaging/utils/OWSDatabaseConverter.m b/SignalMessaging/utils/OWSDatabaseConverter.m index 7b31503c5..bcc13cf85 100644 --- a/SignalMessaging/utils/OWSDatabaseConverter.m +++ b/SignalMessaging/utils/OWSDatabaseConverter.m @@ -3,15 +3,22 @@ // #import "OWSDatabaseConverter.h" +#import "sqlite3.h" #import #import NS_ASSUME_NONNULL_BEGIN +NSString *const OWSOWSDatabaseConverterErrorDomain = @"OWSOWSDatabaseConverterErrorDomain"; +const int kCouldNotOpenDatabase = 1; +const int kCouldNotLoadDatabasePassword = 2; + @implementation OWSDatabaseConverter + (BOOL)doesDatabaseNeedToBeConverted:(NSString *)databaseFilePath { + OWSAssert(databaseFilePath.length > 0); + if (![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]) { DDLogVerbose(@"%@ Skipping database conversion; no legacy database found.", self.logTag); return NO; @@ -59,11 +66,22 @@ NS_ASSUME_NONNULL_BEGIN return; } - [self convertDatabase]; + [self convertDatabase:(NSString *)databaseFilePath]; } -+ (void)convertDatabase ++ (nullable NSError *)convertDatabase:(NSString *)databaseFilePath { + OWSAssert(databaseFilePath.length > 0); + + NSError *error; + NSData *_Nullable databasePassword = [OWSStorage tryToLoadDatabasePassword:&error]; + if (!databasePassword || error) { + return (error + ?: [NSError errorWithDomain:OWSOWSDatabaseConverterErrorDomain + code:kCouldNotLoadDatabasePassword + userInfo:nil]); + } + // TODO: // Hello Matthew, @@ -129,98 +147,69 @@ NS_ASSUME_NONNULL_BEGIN // // We use SQLITE_OPEN_NOMUTEX to use the multi-thread threading mode, // // as we will be serializing access to the connection externally. // - // int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE; - // - // int status = sqlite3_open_v2([databasePath 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) { - // YDBLogError(@"Error opening database: %d %s", status, sqlite3_errmsg(db)); - // } - // else { - // YDBLogError(@"Error opening database: %d", status); - // } - // - // return NO; - // } - // // Add a busy handler if we are in multiprocess mode - // if (options.enableMultiProcessSupport) { - // sqlite3_busy_handler(db, connectionBusyHandler, (__bridge void *)(self)); - // } + + + // ----------------------------------------------------------- + // + // This block was derived from [Yapdatabase openDatabase]. + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_PRIVATECACHE; + sqlite3 *db; + int 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 [NSError errorWithDomain:OWSOWSDatabaseConverterErrorDomain code:kCouldNotOpenDatabase userInfo:nil]; + } + + // ----------------------------------------------------------- // - // return YES; + // This block was derived from [Yapdatabase configureEncryptionForDatabase]. + NSData *keyData = databasePassword; + + // //Setting the encrypted database page size + // if (options.cipherPageSize > 0) { + // char *errorMsg; + // NSString *pragmaCommand = [NSString stringWithFormat:@"PRAGMA cipher_page_size = %lu", (unsigned + // long)options.cipherPageSize]; + // if + // (sqlite3_exec(sqlite, + // [pragmaCommand + // UTF8String], NULL, + // NULL, + // &errorMsg) != SQLITE_OK) + // { + // YDBLogError(@"failed + // to set + // database + // cipher_page_size: + // %s", + // errorMsg); + // return NO; + // } // } // - // - // - //#ifdef SQLITE_HAS_CODEC - // /** - // * Configures database encryption via SQLCipher. - // **/ - // - (BOOL)configureEncryptionForDatabase:(sqlite3 *)sqlite + // int status = sqlite3_key(sqlite, [keyData bytes], (int)[keyData length]); + // if (status != SQLITE_OK) // { - // if (options.cipherKeyBlock) - // { - // NSData *keyData = options.cipherKeyBlock(); - // - // if (keyData == nil) - // { - // NSAssert(NO, @"YapDatabaseOptions.cipherKeyBlock cannot return nil!"); - // return NO; - // } - // - // //Setting the PBKDF2 default iteration number (this will have effect next time database is opened) - // if (options.cipherDefaultkdfIterNumber > 0) { - // char *errorMsg; - // NSString *pragmaCommand = [NSString stringWithFormat:@"PRAGMA cipher_default_kdf_iter = %lu", - // (unsigned long)options.cipherDefaultkdfIterNumber]; if (sqlite3_exec(sqlite, [pragmaCommand - // UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK) - // { - // YDBLogError(@"failed to set database cipher_default_kdf_iter: %s", errorMsg); - // return NO; - // } - // } - // - // //Setting the PBKDF2 iteration number - // if (options.kdfIterNumber > 0) { - // char *errorMsg; - // NSString *pragmaCommand = [NSString stringWithFormat:@"PRAGMA kdf_iter = %lu", (unsigned - // long)options.kdfIterNumber]; if (sqlite3_exec(sqlite, [pragmaCommand UTF8String], NULL, NULL, - // &errorMsg) != SQLITE_OK) - // { - // YDBLogError(@"failed to set database kdf_iter: %s", errorMsg); - // return NO; - // } - // } - // - // //Setting the encrypted database page size - // if (options.cipherPageSize > 0) { - // char *errorMsg; - // NSString *pragmaCommand = [NSString stringWithFormat:@"PRAGMA cipher_page_size = %lu", (unsigned - // long)options.cipherPageSize]; if (sqlite3_exec(sqlite, [pragmaCommand UTF8String], NULL, NULL, - // &errorMsg) != SQLITE_OK) - // { - // YDBLogError(@"failed to set database cipher_page_size: %s", errorMsg); - // return NO; - // } - // } - // - // int status = sqlite3_key(sqlite, [keyData bytes], (int)[keyData length]); - // if (status != SQLITE_OK) - // { - // YDBLogError(@"Error setting SQLCipher key: %d %s", status, sqlite3_errmsg(sqlite)); - // return NO; - // } - // } - // - // return YES; + // YDBLogError(@"Error setting SQLCipher key: %d %s", status, sqlite3_errmsg(sqlite)); + // return NO; // } - //#endif + //} + // + // return YES; + // } + // #endif + + return nil; } @end diff --git a/SignalServiceKit/src/Storage/OWSStorage.h b/SignalServiceKit/src/Storage/OWSStorage.h index 067b6a625..26f985d1d 100644 --- a/SignalServiceKit/src/Storage/OWSStorage.h +++ b/SignalServiceKit/src/Storage/OWSStorage.h @@ -55,6 +55,8 @@ extern NSString *const StorageIsReadyNotification; */ + (BOOL)isDatabasePasswordAccessible; ++ (nullable NSData *)tryToLoadDatabasePassword:(NSError **)errorHandle; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/OWSStorage.m b/SignalServiceKit/src/Storage/OWSStorage.m index f1e0db456..35321cd8d 100644 --- a/SignalServiceKit/src/Storage/OWSStorage.m +++ b/SignalServiceKit/src/Storage/OWSStorage.m @@ -510,13 +510,21 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; return NO; } -- (NSData *)databasePassword ++ (nullable NSData *)tryToLoadDatabasePassword:(NSError **)errorHandle { + OWSAssert(errorHandle); + [SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]; + NSData *_Nullable passwordData = + [SAMKeychain passwordDataForService:keychainService account:keychainDBPassAccount error:errorHandle]; + return passwordData; +} + +- (NSData *)databasePassword +{ NSError *keyFetchError; - NSString *dbPassword = - [SAMKeychain passwordForService:keychainService account:keychainDBPassAccount error:&keyFetchError]; + NSData *_Nullable passwordData = [OWSStorage tryToLoadDatabasePassword:&keyFetchError]; if (keyFetchError) { NSString *errorDescription = @@ -554,15 +562,16 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; // Try to reset app by deleting database. [OWSStorage resetAllStorage]; - dbPassword = [self createAndSetNewDatabasePassword]; + passwordData = [self createAndSetNewDatabasePassword]; } - return [dbPassword dataUsingEncoding:NSUTF8StringEncoding]; + return passwordData; } -- (NSString *)createAndSetNewDatabasePassword +- (NSData *)createAndSetNewDatabasePassword { - NSString *password = [[Randomness generateRandomBytes:30] base64EncodedString]; + NSData *password = + [[[Randomness generateRandomBytes:30] base64EncodedString] dataUsingEncoding:NSUTF8StringEncoding]; [OWSStorage storeDatabasePassword:password]; @@ -602,12 +611,14 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; return fileSize; } -+ (void)storeDatabasePassword:(NSString *)password ++ (void)storeDatabasePassword:(NSData *)passwordData { NSError *error; [SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]; - BOOL success = - [SAMKeychain setPassword:password forService:keychainService account:keychainDBPassAccount error:&error]; + BOOL success = [SAMKeychain setPasswordData:passwordData + forService:keychainService + account:keychainDBPassAccount + error:&error]; if (!success || error) { OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotStoreDatabasePassword]);