diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 8657cf54d..73b1af86c 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -1215,7 +1215,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; thread:message.thread attempts:OWSMessageSenderRetryAttempts success:^{ - DDLogInfo(@"Succesfully sent sync transcript."); + DDLogInfo(@"Successfully sent sync transcript."); } failure:^(NSError *error) { // FIXME: We don't yet honor the isRetryable flag here, since sendSyncTranscriptForMessage diff --git a/SignalServiceKit/src/Storage/OWSStorage.m b/SignalServiceKit/src/Storage/OWSStorage.m index 555a0da46..756ef7a7e 100644 --- a/SignalServiceKit/src/Storage/OWSStorage.m +++ b/SignalServiceKit/src/Storage/OWSStorage.m @@ -407,6 +407,11 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void); OWSAssert(options.pragmaJournalSizeLimit == 0); OWSAssert(options.pragmaMMapSize == 0); + // Sanity checking elsewhere asserts we should only regenerate key specs when + // there is no existing database, so rather than lazily generate in the cipherKeySpecBlock + // we must ensure the keyspec exists before we create the database. + [self ensureDatabaseKeySpecExists]; + OWSDatabase *database = [[OWSDatabase alloc] initWithPath:[self databaseFilePath] serializer:nil deserializer:[[self class] logOnFailureDeserializer] @@ -564,20 +569,21 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void); [SAMKeychain deletePasswordForService:keychainService account:keychainDBLegacyPassphrase]; } -- (NSData *)databaseKeySpec +- (void)ensureDatabaseKeySpecExists { NSError *error; - NSData *_Nullable data = [[self class] tryToLoadDatabaseCipherKeySpec:&error]; + NSData *_Nullable keySpec = [[self class] tryToLoadDatabaseCipherKeySpec:&error]; - if (error) { + if (error || (keySpec.length != kSQLCipherKeySpecLength)) { // Because we use kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, // the keychain will be inaccessible after device restart until // device is unlocked for the first time. If the app receives // a push notification, we won't be able to access the keychain to // process that notification, so we should just terminate by throwing // an uncaught exception. - NSString *errorDescription = - [NSString stringWithFormat:@"CipherKeySpec inaccessible. No unlock since device restart? Error: %@", error]; + NSString *errorDescription = [NSString + stringWithFormat:@"CipherKeySpec inaccessible. New install or no unlock since device restart? Error: %@", + error]; if (CurrentAppContext().isMainApp) { UIApplicationState applicationState = CurrentAppContext().mainApplicationState; errorDescription = @@ -600,9 +606,8 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void); // At this point, either this is a new install so there's no existing password to retrieve // or the keychain has become corrupt. Either way, we want to get back to a // "known good state" and behave like a new install. - - BOOL shouldHaveDatabaseMetadata = [NSFileManager.defaultManager fileExistsAtPath:[self databaseFilePath]]; - if (shouldHaveDatabaseMetadata) { + BOOL doesDBExist = [NSFileManager.defaultManager fileExistsAtPath:[self databaseFilePath]]; + if (doesDBExist) { OWSFail(@"%@ Could not load database metadata", self.logTag); OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabaseSecondAttempt]); } @@ -610,11 +615,27 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void); // Try to reset app by deleting database. [OWSStorage resetAllStorage]; - data = [Randomness generateRandomBytes:(int)kSQLCipherKeySpecLength]; - [[self class] storeDatabaseCipherKeySpec:data]; + keySpec = [Randomness generateRandomBytes:(int)kSQLCipherKeySpecLength]; + [[self class] storeDatabaseCipherKeySpec:keySpec]; } +} - return data; +- (NSData *)databaseKeySpec +{ + NSError *error; + NSData *_Nullable keySpec = [[self class] tryToLoadDatabaseCipherKeySpec:&error]; + + if (error) { + DDLogError(@"%@ failed to fetch databaseKeySpec with error: %@", self.logTag, error); + [self raiseKeySpecInaccessibleExceptionWithErrorDescription:@"CipherKeySpec inaccessible"]; + } + + if (keySpec.length != kSQLCipherKeySpecLength) { + DDLogError(@"%@ keyspec had length: %lu", self.logTag, (unsigned long)keySpec.length); + [self raiseKeySpecInaccessibleExceptionWithErrorDescription:@"CipherKeySpec invalid"]; + } + + return keySpec; } - (void)raiseKeySpecInaccessibleExceptionWithErrorDescription:(NSString *)errorDescription @@ -667,7 +688,7 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void); OWSRaiseException( OWSStorageExceptionName_DatabasePasswordUnwritable, @"Setting keychain value failed with error: %@", error); } else { - DDLogWarn(@"Succesfully set new keychain value."); + DDLogWarn(@"Successfully set new keychain value."); } }