diff --git a/src/Storage/TSStorageManager.h b/src/Storage/TSStorageManager.h index 21195cd21..46d0727fe 100644 --- a/src/Storage/TSStorageManager.h +++ b/src/Storage/TSStorageManager.h @@ -1,9 +1,5 @@ // -// TSStorageManager.h -// TextSecureKit -// -// Created by Frederic Jacobs on 27/10/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "TSStorageKeys.h" @@ -21,9 +17,17 @@ extern NSString *const TSUIDatabaseConnectionDidUpdateNotification; @interface TSStorageManager : NSObject + (instancetype)sharedManager; + +/** + * Returns NO if: + * + * - Keychain is locked because device has just been restarted. + * - Password could not be retrieved because of a keychain error. + */ ++ (BOOL)isDatabasePasswordAccessible; + - (void)setupDatabase; - (void)deleteThreadsAndMessages; -- (BOOL)databasePasswordAccessible; - (void)resetSignalStorage; - (YapDatabase *)database; diff --git a/src/Storage/TSStorageManager.m b/src/Storage/TSStorageManager.m index 82bc6729a..49bba31ed 100644 --- a/src/Storage/TSStorageManager.m +++ b/src/Storage/TSStorageManager.m @@ -1,6 +1,4 @@ // -// TSStorageManager.m -// // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // @@ -23,6 +21,8 @@ NSString *const TSUIDatabaseConnectionDidUpdateNotification = @"TSUIDatabaseConnectionDidUpdateNotification"; NSString *const TSStorageManagerExceptionNameDatabasePasswordInaccessible = @"TSStorageManagerExceptionNameDatabasePasswordInaccessible"; +NSString *const TSStorageManagerExceptionNameDatabasePasswordInaccessibleWhileBackgrounded = + @"TSStorageManagerExceptionNameDatabasePasswordInaccessibleWhileBackgrounded"; NSString *const TSStorageManagerExceptionNameDatabasePasswordUnwritable = @"TSStorageManagerExceptionNameDatabasePasswordUnwritable"; NSString *const TSStorageManagerExceptionNameNoDatabase = @"TSStorageManagerExceptionNameNoDatabase"; @@ -270,7 +270,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; return databasePath; } -- (BOOL)databasePasswordAccessible ++ (BOOL)isDatabasePasswordAccessible { [SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]; NSError *error; @@ -287,6 +287,17 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; return NO; } +- (void)backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:(NSString *)errorDescription +{ + OWSAssert([UIApplication sharedApplication].applicationState == UIApplicationStateBackground); + + // Presumably this happened in response to a push notification. It's possible that the keychain is corrupted + // but it could also just be that the user hasn't yet unlocked their device since our password is + // kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly + [NSException raise:TSStorageManagerExceptionNameDatabasePasswordInaccessibleWhileBackgrounded + format:@"%@", errorDescription]; +} + - (NSData *)databasePassword { [SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]; @@ -296,7 +307,19 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; [SAMKeychain passwordForService:keychainService account:keychainDBPassAccount error:&keyFetchError]; if (keyFetchError) { - // Either this is a new install so there's no existing password to retrieve + UIApplicationState applicationState = [UIApplication sharedApplication].applicationState; + NSString *errorDescription = [NSString stringWithFormat:@"Database password inaccessible. No unlock since device restart? Error: %@ ApplicationState: %d", keyFetchError, (int)applicationState]; + DDLogError(@"%@ %@", self.tag, errorDescription); + [DDLog flushLog]; + + if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { + // TODO: Rather than crash here, we should detect the situation earlier + // and exit gracefully - (in the app delegate?). See the ` + // This is a last ditch effort to avoid blowing away the user's database. + [self backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:errorDescription]; + } + + // 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.