Add app group, share keychain. Take a first pass at file migration to shared data directory.

This commit is contained in:
Matthew Chen 2017-11-28 13:03:38 -05:00
parent 1ccf5132c1
commit cd11ec5698
8 changed files with 221 additions and 39 deletions

View File

@ -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;
};

View File

@ -4,6 +4,8 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.$(CFBundleIdentifier)</string>
@ -16,5 +18,13 @@
<array>
<string>iCloud.$(CFBundleIdentifier)</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.whispersystems.signal.group</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)org.whispersystems.signal</string>
</array>
</dict>
</plist>

View File

@ -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];

View File

@ -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];

View File

@ -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

View File

@ -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.");

View File

@ -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

View File

@ -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