| 
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -29,6 +29,14 @@ static NSString *keychainService = @"TSKeyChainService";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static NSString *keychainDBPassAccount = @"TSDatabasePass";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static NSString *keychainDBSalt = @"OWSDatabaseSalt";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// TODO: Move this to YapDatabase.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const NSUInteger kSqliteHeaderLength = 32;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const NSUInteger kSQLCipherSaltLength = 16;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const NSUInteger kDatabasePasswordLength = 30;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				typedef NSData *_Nullable (^LoadDatabaseMetadataBlock)(NSError **_Nullable);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#pragma mark -
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				@protocol OWSDatabaseConnectionDelegate <NSObject>
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -379,6 +387,9 @@ static NSString *keychainDBSalt = @"OWSDatabaseSalt";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // this can be deleting any existing database file (if we're recovering
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // from a corrupt keychain).
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSData *databasePassword = [self databasePassword];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(databasePassword.length > 0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSData *databaseSalt = [self databaseSalt];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(databaseSalt.length > 0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    YapDatabaseOptions *options = [[YapDatabaseOptions alloc] init];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    options.corruptAction = YapDatabaseCorruptAction_Fail;
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -386,6 +397,10 @@ static NSString *keychainDBSalt = @"OWSDatabaseSalt";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return databasePassword;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    options.enableMultiProcessSupport = YES;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    options.cipherSaltBlock = ^{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return databaseSalt;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    options.cipherUnencryptedHeaderLength = kSqliteHeaderLength;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // If any of these asserts fails, we need to verify and update
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // OWSDatabaseConverter which assumes the values of these options.
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -542,13 +557,45 @@ static NSString *keychainDBSalt = @"OWSDatabaseSalt";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (NSData *)databasePassword
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSError *keyFetchError;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSData *_Nullable passwordData = [OWSStorage tryToLoadDatabasePassword:&keyFetchError];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return [self loadMetadataOrClearDatabase:^(NSError **_Nullable errorHandle) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return [OWSStorage tryToLoadDatabasePassword:errorHandle];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        createDataBlock:^{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return [self createAndSetNewDatabasePassword];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        label:@"Database password"];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (NSData *)databaseSalt
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return [self loadMetadataOrClearDatabase:^(NSError **_Nullable errorHandle) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return [OWSStorage tryToLoadDatabaseSalt:errorHandle];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        createDataBlock:^{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return [self createAndSetNewDatabaseSalt];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        label:@"Database salt"];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (NSData *)loadMetadataOrClearDatabase:(LoadDatabaseMetadataBlock)loadDataBlock
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                        createDataBlock:(CreateDatabaseMetadataBlock)createDataBlock
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                  label:(NSString *)label
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(loadDataBlock);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(createDataBlock);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSError *error;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSData *_Nullable data = loadDataBlock(&error);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (keyFetchError) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // 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:@"Database password inaccessible. No unlock since device restart? Error: %@",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                      keyFetchError];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [NSString stringWithFormat:@"%@ inaccessible. No unlock since device restart? Error: %@", label, error];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (CurrentAppContext().isMainApp) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            UIApplicationState applicationState = CurrentAppContext().mainApplicationState;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            errorDescription =
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -566,37 +613,46 @@ static NSString *keychainDBSalt = @"OWSDatabaseSalt";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            [self backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                      @"Password inaccessible; not main app."];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                      [NSString stringWithFormat:@"%@ inaccessible; not main app.", label]];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // 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 shouldHavePassword = [NSFileManager.defaultManager fileExistsAtPath:[self databaseFilePath]];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (shouldHavePassword) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        BOOL shouldHaveDatabaseMetadata = [NSFileManager.defaultManager fileExistsAtPath:[self databaseFilePath]];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (shouldHaveDatabaseMetadata) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            OWSProdCritical([OWSAnalyticsEvents storageErrorCouldNotLoadDatabaseSecondAttempt]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Try to reset app by deleting database.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [OWSStorage resetAllStorage];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        passwordData = [self createAndSetNewDatabasePassword];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        data = createDataBlock();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return passwordData;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return data;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (NSData *)createAndSetNewDatabasePassword
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSData *password =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        [[[Randomness generateRandomBytes:30] base64EncodedString] dataUsingEncoding:NSUTF8StringEncoding];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSData *password = [[[Randomness generateRandomBytes:kDatabasePasswordLength] base64EncodedString]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        dataUsingEncoding:NSUTF8StringEncoding];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    [OWSStorage storeDatabasePassword:password];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return password;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (NSData *)createAndSetNewDatabaseSalt
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    NSData *saltData = [Randomness generateRandomBytes:kSQLCipherSaltLength];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    [OWSStorage storeDatabaseSalt:saltData];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    return saltData;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				- (void)backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:(NSString *)errorDescription
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(CurrentAppContext().isMainApp && CurrentAppContext().isInBackground);
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -661,6 +717,8 @@ static NSString *keychainDBSalt = @"OWSDatabaseSalt";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				+ (void)storeDatabaseSalt:(NSData *)saltData
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    OWSAssert(saltData.length == kSQLCipherSaltLength);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    [self storeKeyChainValue:saltData keychainKey:keychainDBSalt];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
						
					 | 
				
			
			 | 
			 | 
			
				
 
 |