diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 29fef9e46..5ba2b140a 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -2052,6 +2052,9 @@ LastSwiftMigration = 0800; ProvisioningStyle = Automatic; SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; com.apple.DataProtection = { enabled = 1; }; @@ -2061,6 +2064,9 @@ com.apple.InterAppAudio = { enabled = 0; }; + com.apple.Keychain = { + enabled = 1; + }; com.apple.Push = { enabled = 1; }; diff --git a/Signal/Signal.entitlements b/Signal/Signal.entitlements index dfec421ea..79da5ce67 100644 --- a/Signal/Signal.entitlements +++ b/Signal/Signal.entitlements @@ -4,6 +4,8 @@ aps-environment development + com.apple.developer.default-data-protection + NSFileProtectionComplete com.apple.developer.icloud-container-identifiers iCloud.$(CFBundleIdentifier) @@ -16,5 +18,13 @@ iCloud.$(CFBundleIdentifier) + com.apple.security.application-groups + + group.org.whispersystems.signal.group + + keychain-access-groups + + $(AppIdentifierPrefix)org.whispersystems.signal + diff --git a/Signal/src/Profiles/OWSProfileManager.m b/Signal/src/Profiles/OWSProfileManager.m index cc0737759..ba6fe9109 100644 --- a/Signal/src/Profiles/OWSProfileManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -1389,9 +1389,17 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; static NSString *profileAvatarsDirPath = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - NSString *documentsPath = - [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; - profileAvatarsDirPath = [documentsPath stringByAppendingPathComponent:@"ProfileAvatars"]; + NSString *documentDirPath = [OWSFileSystem appDocumentDirectoryPath]; + NSString *sharedDataDirPath = [OWSFileSystem appSharedDataDirectoryPath]; + + NSString *oldProfileAvatarsDirPath = [documentDirPath stringByAppendingPathComponent:@"ProfileAvatars"]; + NSString *newProfileAvatarsDirPath = [sharedDataDirPath stringByAppendingPathComponent:@"ProfileAvatars"]; + + [OWSFileSystem moveAppFilePath:oldProfileAvatarsDirPath + sharedDataFilePath:newProfileAvatarsDirPath + exceptionName:@"ProfileManagerCouldNotMigrateProfileDirectory"]; + + profileAvatarsDirPath = newProfileAvatarsDirPath; BOOL isDirectory; BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:profileAvatarsDirPath isDirectory:&isDirectory]; diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m index 5c6a74003..a40b62ee5 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m @@ -185,9 +185,17 @@ NS_ASSUME_NONNULL_BEGIN static NSString *attachmentsFolder = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - NSString *documentsPath = - [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; - attachmentsFolder = [documentsPath stringByAppendingPathComponent:@"Attachments"]; + NSString *documentDirPath = [OWSFileSystem appDocumentDirectoryPath]; + NSString *sharedDataDirPath = [OWSFileSystem appSharedDataDirectoryPath]; + + NSString *oldAttachmentsDirPath = [documentDirPath stringByAppendingPathComponent:@"Attachments"]; + NSString *newAttachmentsDirPath = [sharedDataDirPath stringByAppendingPathComponent:@"Attachments"]; + + [OWSFileSystem moveAppFilePath:oldAttachmentsDirPath + sharedDataFilePath:newAttachmentsDirPath + exceptionName:@"CouldNotMigrateAttachmentsDirectory"]; + + attachmentsFolder = newAttachmentsDirPath; BOOL isDirectory; BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:attachmentsFolder isDirectory:&isDirectory]; diff --git a/SignalServiceKit/src/Storage/TSStorageManager.h b/SignalServiceKit/src/Storage/TSStorageManager.h index b8e5f0007..0dbd4ad4a 100644 --- a/SignalServiceKit/src/Storage/TSStorageManager.h +++ b/SignalServiceKit/src/Storage/TSStorageManager.h @@ -63,10 +63,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, readonly) YapDatabaseConnection *dbReadConnection; @property (nullable, nonatomic, readonly) YapDatabaseConnection *dbReadWriteConnection; -#pragma mark - Utilities - -- (void)protectFolderAtPath:(NSString *)path; - @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/TSStorageManager.m b/SignalServiceKit/src/Storage/TSStorageManager.m index fb9fe3c65..dbe9d99b1 100644 --- a/SignalServiceKit/src/Storage/TSStorageManager.m +++ b/SignalServiceKit/src/Storage/TSStorageManager.m @@ -24,13 +24,18 @@ NS_ASSUME_NONNULL_BEGIN -NSString *const TSStorageManagerExceptionNameDatabasePasswordInaccessible = @"TSStorageManagerExceptionNameDatabasePasswordInaccessible"; -NSString *const TSStorageManagerExceptionNameDatabasePasswordInaccessibleWhileBackgrounded = - @"TSStorageManagerExceptionNameDatabasePasswordInaccessibleWhileBackgrounded"; -NSString *const TSStorageManagerExceptionNameDatabasePasswordUnwritable = @"TSStorageManagerExceptionNameDatabasePasswordUnwritable"; -NSString *const TSStorageManagerExceptionNameNoDatabase = @"TSStorageManagerExceptionNameNoDatabase"; +NSString *const TSStorageManagerExceptionName_DatabasePasswordInaccessible + = @"TSStorageManagerExceptionName_DatabasePasswordInaccessible"; +NSString *const TSStorageManagerExceptionName_DatabasePasswordInaccessibleWhileBackgrounded + = @"TSStorageManagerExceptionName_DatabasePasswordInaccessibleWhileBackgrounded"; +NSString *const TSStorageManagerExceptionName_DatabasePasswordUnwritable + = @"TSStorageManagerExceptionName_DatabasePasswordUnwritable"; +NSString *const TSStorageManagerExceptionName_NoDatabase = @"TSStorageManagerExceptionName_NoDatabase"; +NSString *const TSStorageManagerExceptionName_CouldNotMoveDatabaseFile + = @"TSStorageManagerExceptionName_CouldNotMoveDatabaseFile"; +NSString *const TSStorageManagerExceptionName_CouldNotCreateDatabaseDirectory + = @"TSStorageManagerExceptionName_CouldNotCreateDatabaseDirectory"; -static const NSString *const databaseName = @"Signal.sqlite"; static NSString *keychainService = @"TSKeyChainService"; static NSString *keychainDBPassAccount = @"TSDatabasePass"; @@ -231,7 +236,7 @@ void setDatabaseInitialized() // Sleep to give analytics events time to be delivered. [NSThread sleepForTimeInterval:15.0f]; - [NSException raise:TSStorageManagerExceptionNameNoDatabase format:@"Failed to initialize database."]; + [NSException raise:TSStorageManagerExceptionName_NoDatabase format:@"Failed to initialize database."]; } OWSSingletonAssert(); @@ -253,6 +258,7 @@ void setDatabaseInitialized() options.cipherKeyBlock = ^{ return databasePassword; }; + options.enableMultiProcessSupport = YES; #ifdef DEBUG _database = [[OWSDatabase alloc] initWithPath:[self dbPath] @@ -350,9 +356,14 @@ void setDatabaseInitialized() } - (void)protectSignalFiles { - [OWSFileSystem protectFolderAtPath:[self dbPath]]; - [OWSFileSystem protectFolderAtPath:[[self dbPath] stringByAppendingString:@"-shm"]]; - [OWSFileSystem protectFolderAtPath:[[self dbPath] stringByAppendingString:@"-wal"]]; + // The old database location was in the Document directory, + // so protect the database files individually. + [OWSFileSystem protectFolderAtPath:[self oldDatabaseFilePath]]; + [OWSFileSystem protectFolderAtPath:[self oldDatabaseFilePath_SHM]]; + [OWSFileSystem protectFolderAtPath:[self oldDatabaseFilePath_WAL]]; + + // Protect the entire new database directory. + [OWSFileSystem protectFolderAtPath:[self newDatabaseDirPath]]; } - (nullable YapDatabaseConnection *)newDatabaseConnection @@ -364,33 +375,118 @@ void setDatabaseInitialized() return FALSE; } -- (BOOL)dbExists { - return [[NSFileManager defaultManager] fileExistsAtPath:[self dbPath]]; +- (NSString *)oldDatabaseDirPath +{ + return [OWSFileSystem appDocumentDirectoryPath]; } -- (NSString *)dbPath { - NSString *databasePath; +- (NSString *)newDatabaseDirPath +{ + NSString *databaseDirPath = [[OWSFileSystem appSharedDataDirectoryPath] stringByAppendingPathComponent:@"database"]; NSFileManager *fileManager = [NSFileManager defaultManager]; -#if TARGET_OS_IPHONE - NSURL *fileURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; - NSString *path = [fileURL path]; - databasePath = [path stringByAppendingPathComponent:databaseName]; -#elif TARGET_OS_MAC + if (![fileManager fileExistsAtPath:databaseDirPath]) { + NSError *_Nullable error; + BOOL success = [fileManager createDirectoryAtPath:databaseDirPath + withIntermediateDirectories:NO + attributes:nil + error:&error]; + if (!success || error) { + NSString *errorDescription = + [NSString stringWithFormat:@"%@ Could not create new database directory: %@, error: %@", + self.logTag, + databaseDirPath, + error]; + OWSFail(@"%@", errorDescription); + [NSException raise:TSStorageManagerExceptionName_CouldNotCreateDatabaseDirectory + format:@"%@", errorDescription]; + } + } + return databaseDirPath; +} - NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; - NSArray *urlPaths = [fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask]; +- (NSString *)databaseFilename +{ + return @"Signal.sqlite"; +} - NSURL *appDirectory = [[urlPaths objectAtIndex:0] URLByAppendingPathComponent:bundleID isDirectory:YES]; +- (NSString *)databaseFilename_SHM +{ + return [self.databaseFilename stringByAppendingString:@"-shm"]; +} - if (![fileManager fileExistsAtPath:[appDirectory path]]) { - [fileManager createDirectoryAtURL:appDirectory withIntermediateDirectories:NO attributes:nil error:nil]; +- (NSString *)databaseFilename_WAL +{ + return [self.databaseFilename stringByAppendingString:@"-wal"]; +} + +- (NSString *)oldDatabaseFilePath +{ + return [self.oldDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename]; +} + +- (NSString *)oldDatabaseFilePath_SHM +{ + return [self.oldDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_SHM]; +} + +- (NSString *)oldDatabaseFilePath_WAL +{ + return [self.oldDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_WAL]; +} + +- (NSString *)newDatabaseFilePath +{ + return [self.newDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename]; +} + +- (NSString *)newDatabaseFilePath_SHM +{ + return [self.newDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_SHM]; +} + +- (NSString *)newDatabaseFilePath_WAL +{ + return [self.newDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_WAL]; +} + +- (NSString *)dbPath +{ + [OWSFileSystem moveAppFilePath:self.oldDatabaseFilePath + sharedDataFilePath:self.newDatabaseFilePath + exceptionName:TSStorageManagerExceptionName_CouldNotMoveDatabaseFile]; + [OWSFileSystem moveAppFilePath:self.oldDatabaseFilePath_SHM + sharedDataFilePath:self.newDatabaseFilePath_SHM + exceptionName:TSStorageManagerExceptionName_CouldNotMoveDatabaseFile]; + [OWSFileSystem moveAppFilePath:self.oldDatabaseFilePath_WAL + sharedDataFilePath:self.newDatabaseFilePath_WAL + exceptionName:TSStorageManagerExceptionName_CouldNotMoveDatabaseFile]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL hasAllNewFiles = ([fileManager fileExistsAtPath:self.newDatabaseFilePath] && + [fileManager fileExistsAtPath:self.newDatabaseFilePath_SHM] && + [fileManager fileExistsAtPath:self.newDatabaseFilePath_WAL]); + BOOL hasAnyNewFiles = ([fileManager fileExistsAtPath:self.newDatabaseFilePath] || + [fileManager fileExistsAtPath:self.newDatabaseFilePath_SHM] || + [fileManager fileExistsAtPath:self.newDatabaseFilePath_WAL]); + if (!hasAllNewFiles && !hasAnyNewFiles) { + for (NSString *filePath in @[ + self.newDatabaseFilePath, + self.newDatabaseFilePath_SHM, + self.newDatabaseFilePath_WAL, + self.newDatabaseFilePath, + self.newDatabaseFilePath_SHM, + self.newDatabaseFilePath_WAL, + ]) { + DDLogInfo(@"%@ Database file %@ exists %d", self.logTag, filePath, [fileManager fileExistsAtPath:filePath]); + } + OWSFail(@"%@ Incomplete set of database files.", self.logTag); } - databasePath = [appDirectory.filePathURL.absoluteString stringByAppendingPathComponent:databaseName]; -#endif + DDLogError(@"databasePath: %@", self.newDatabaseFilePath); + [DDLog flushLog]; - return databasePath; + return self.newDatabaseFilePath; } + (BOOL)isDatabasePasswordAccessible @@ -421,7 +517,7 @@ void setDatabaseInitialized() // 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 + [NSException raise:TSStorageManagerExceptionName_DatabasePasswordInaccessibleWhileBackgrounded format:@"%@", errorDescription]; } @@ -483,7 +579,7 @@ void setDatabaseInitialized() // Sleep to give analytics events time to be delivered. [NSThread sleepForTimeInterval:15.0f]; - [NSException raise:TSStorageManagerExceptionNameDatabasePasswordUnwritable + [NSException raise:TSStorageManagerExceptionName_DatabasePasswordUnwritable format:@"Setting DB password failed with error: %@", keySetError]; } else { DDLogWarn(@"Succesfully set new DB password."); diff --git a/SignalServiceKit/src/Util/OWSFileSystem.h b/SignalServiceKit/src/Util/OWSFileSystem.h index 3eae177bb..b9abd6a5d 100644 --- a/SignalServiceKit/src/Util/OWSFileSystem.h +++ b/SignalServiceKit/src/Util/OWSFileSystem.h @@ -10,6 +10,14 @@ NS_ASSUME_NONNULL_BEGIN + (void)protectFolderAtPath:(NSString *)path; ++ (NSString *)appDocumentDirectoryPath; + ++ (NSString *)appSharedDataDirectoryPath; + ++ (void)moveAppFilePath:(NSString *)oldFilePath + sharedDataFilePath:(NSString *)newFilePath + exceptionName:(NSString *)exceptionName; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/OWSFileSystem.m b/SignalServiceKit/src/Util/OWSFileSystem.m index 6e69eb0e2..1d77559ad 100644 --- a/SignalServiceKit/src/Util/OWSFileSystem.m +++ b/SignalServiceKit/src/Util/OWSFileSystem.m @@ -28,6 +28,56 @@ NS_ASSUME_NONNULL_BEGIN } } ++ (NSString *)appDocumentDirectoryPath +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *documentDirectoryURL = + [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; + return [documentDirectoryURL path]; +} + ++ (NSString *)appSharedDataDirectoryPath +{ + NSURL *groupContainerDirectoryURL = [[NSFileManager defaultManager] + containerURLForSecurityApplicationGroupIdentifier:@"group.org.whispersystems.signal.group"]; + return [groupContainerDirectoryURL path]; +} + ++ (void)moveAppFilePath:(NSString *)oldFilePath + sharedDataFilePath:(NSString *)newFilePath + exceptionName:(NSString *)exceptionName +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:oldFilePath]) { + return; + } + if ([fileManager fileExistsAtPath:newFilePath]) { + DDLogError(@""); + return; + } + + NSDate *startDate = [NSDate new]; + + NSError *_Nullable error; + BOOL success = [fileManager moveItemAtPath:oldFilePath toPath:newFilePath error:&error]; + if (!success || error) { + NSString *errorDescription = + [NSString stringWithFormat:@"%@ Could not move database file from %@ to %@, error: %@", + self.logTag, + oldFilePath, + newFilePath, + error]; + OWSFail(@"%@", errorDescription); + [NSException raise:exceptionName format:@"%@", errorDescription]; + } + + DDLogInfo(@"%@ Moving file or directory from %@ to %@ in: %f", + self.logTag, + oldFilePath, + newFilePath, + fabs([startDate timeIntervalSinceNow])); +} + @end NS_ASSUME_NONNULL_END