From c907721a184ee68f3a79fc6622e5d32670dcbff6 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 10 Oct 2018 08:55:20 -0400 Subject: [PATCH] Rotate profile key if blocklist intersects profile whitelist. --- Signal.xcodeproj/project.pbxproj | 1 - .../ViewControllers/DebugUI/DebugUIMessages.m | 12 +- .../ViewControllers/DebugUI/DebugUIStress.m | 2 +- .../ViewControllers/NewGroupViewController.m | 2 +- Signal/test/util/SearcherTest.swift | 4 +- SignalMessaging/environment/AppSetup.m | 6 +- .../OWS111UDAttributesMigration.swift | 6 - SignalMessaging/profiles/OWSProfileManager.h | 4 +- SignalMessaging/profiles/OWSProfileManager.m | 204 ++++++++++++++++-- SignalMessaging/profiles/OWSUserProfile.h | 4 + SignalServiceKit/src/Messages/TSGroupModel.h | 2 + SignalServiceKit/src/Messages/TSGroupModel.m | 2 + 12 files changed, 212 insertions(+), 37 deletions(-) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 483e2523f..1edd42b87 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -1627,7 +1627,6 @@ 4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */, 34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */, 34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */, - 4C858A552130CBEC001B45D3 /* OWS110SortIdMigration.swift */, 349EA07B2162AEA700F7B17F /* OWS111UDAttributesMigration.swift */, 346129931FD1E30000532771 /* OWSDatabaseMigration.h */, 346129941FD1E30000532771 /* OWSDatabaseMigration.m */, diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index 938b2c36f..d269fd043 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -239,9 +239,9 @@ NS_ASSUME_NONNULL_BEGIN itemWithTitle:@"Request Bogus group info" actionBlock:^{ OWSLogInfo(@"Requesting bogus group info for thread: %@", thread); - OWSSyncGroupsRequestMessage *syncGroupsRequestMessage = - [[OWSSyncGroupsRequestMessage alloc] initWithThread:thread - groupId:[Randomness generateRandomBytes:16]]; + OWSSyncGroupsRequestMessage *syncGroupsRequestMessage = [[OWSSyncGroupsRequestMessage alloc] + initWithThread:thread + groupId:[Randomness generateRandomBytes:kGroupIdLength]]; [SSKEnvironment.shared.messageSender enqueueMessage:syncGroupsRequestMessage success:^{ OWSLogWarn(@"Successfully sent Request Group Info message."); @@ -3816,7 +3816,7 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac recipientId, [TSAccountManager localNumber], ] mutableCopy]; - NSData *groupId = [Randomness generateRandomBytes:16]; + NSData *groupId = [Randomness generateRandomBytes:kGroupIdLength]; TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId]; @@ -4309,7 +4309,7 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac recipientId, [TSAccountManager localNumber], ] mutableCopy]; - NSData *groupId = [Randomness generateRandomBytes:16]; + NSData *groupId = [Randomness generateRandomBytes:kGroupIdLength]; TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId]; @@ -4348,7 +4348,7 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac recipientId, [TSAccountManager localNumber], ] mutableCopy]; - NSData *groupId = [Randomness generateRandomBytes:16]; + NSData *groupId = [Randomness generateRandomBytes:kGroupIdLength]; TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId]; diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIStress.m b/Signal/src/ViewControllers/DebugUI/DebugUIStress.m index 49d4700a5..c47cd87a2 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIStress.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIStress.m @@ -506,7 +506,7 @@ NS_ASSUME_NONNULL_BEGIN [[TSGroupModel alloc] initWithTitle:[groupThread.groupModel.groupName stringByAppendingString:@" Copy"] memberIds:groupThread.groupModel.groupMemberIds image:groupThread.groupModel.groupImage - groupId:[Randomness generateRandomBytes:16]]; + groupId:[Randomness generateRandomBytes:kGroupIdLength]]; thread = [TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction]; }]; OWSAssertDebug(thread); diff --git a/Signal/src/ViewControllers/NewGroupViewController.m b/Signal/src/ViewControllers/NewGroupViewController.m index 30f3e9184..33bda1c69 100644 --- a/Signal/src/ViewControllers/NewGroupViewController.m +++ b/Signal/src/ViewControllers/NewGroupViewController.m @@ -86,7 +86,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)commonInit { - _groupId = [Randomness generateRandomBytes:16]; + _groupId = [Randomness generateRandomBytes:kGroupIdLength]; _messageSender = SSKEnvironment.shared.messageSender; _contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self]; diff --git a/Signal/test/util/SearcherTest.swift b/Signal/test/util/SearcherTest.swift index 5c2334246..c2bcf8914 100644 --- a/Signal/test/util/SearcherTest.swift +++ b/Signal/test/util/SearcherTest.swift @@ -80,11 +80,11 @@ class ConversationSearcherTest: SignalBaseTest { SSKEnvironment.shared.contactsManager = ConversationSearcherContactsManager() self.dbConnection.readWrite { transaction in - let bookModel = TSGroupModel(title: "Book Club", memberIds: [aliceRecipientId, bobRecipientId], image: nil, groupId: Randomness.generateRandomBytes(16)) + let bookModel = TSGroupModel(title: "Book Club", memberIds: [aliceRecipientId, bobRecipientId], image: nil, groupId: Randomness.generateRandomBytes(kGroupIdLength)) let bookClubGroupThread = TSGroupThread.getOrCreateThread(with: bookModel, transaction: transaction) self.bookClubThread = ThreadViewModel(thread: bookClubGroupThread, transaction: transaction) - let snackModel = TSGroupModel(title: "Snack Club", memberIds: [aliceRecipientId], image: nil, groupId: Randomness.generateRandomBytes(16)) + let snackModel = TSGroupModel(title: "Snack Club", memberIds: [aliceRecipientId], image: nil, groupId: Randomness.generateRandomBytes(kGroupIdLength)) let snackClubGroupThread = TSGroupThread.getOrCreateThread(with: snackModel, transaction: transaction) self.snackClubThread = ThreadViewModel(thread: snackClubGroupThread, transaction: transaction) diff --git a/SignalMessaging/environment/AppSetup.m b/SignalMessaging/environment/AppSetup.m index 225be5094..23a04b611 100644 --- a/SignalMessaging/environment/AppSetup.m +++ b/SignalMessaging/environment/AppSetup.m @@ -53,11 +53,7 @@ NS_ASSUME_NONNULL_BEGIN OWSContactsManager *contactsManager = [[OWSContactsManager alloc] initWithPrimaryStorage:primaryStorage]; ContactsUpdater *contactsUpdater = [ContactsUpdater new]; OWSMessageSender *messageSender = [[OWSMessageSender alloc] initWithPrimaryStorage:primaryStorage]; - - OWSProfileManager *profileManager = [[OWSProfileManager alloc] initWithPrimaryStorage:primaryStorage - messageSender:messageSender - networkManager:networkManager]; - + OWSProfileManager *profileManager = [[OWSProfileManager alloc] initWithPrimaryStorage:primaryStorage]; OWSMessageManager *messageManager = [[OWSMessageManager alloc] initWithPrimaryStorage:primaryStorage]; OWSBlockingManager *blockingManager = [[OWSBlockingManager alloc] initWithPrimaryStorage:primaryStorage]; OWSIdentityManager *identityManager = [[OWSIdentityManager alloc] initWithPrimaryStorage:primaryStorage]; diff --git a/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift b/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift index 98f35617a..3c5180db7 100644 --- a/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift +++ b/SignalMessaging/environment/migrations/OWS111UDAttributesMigration.swift @@ -8,12 +8,6 @@ import SignalServiceKit @objc public class OWS111UDAttributesMigration: OWSDatabaseMigration { - // MARK: - Singletons - - private var networkManager: TSNetworkManager { - return SSKEnvironment.shared.networkManager - } - // MARK: - // increment a similar constant for each migration. diff --git a/SignalMessaging/profiles/OWSProfileManager.h b/SignalMessaging/profiles/OWSProfileManager.h index 9cfa1ddd7..533b36b38 100644 --- a/SignalMessaging/profiles/OWSProfileManager.h +++ b/SignalMessaging/profiles/OWSProfileManager.h @@ -22,9 +22,7 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter; - (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage - messageSender:(OWSMessageSender *)messageSender - networkManager:(TSNetworkManager *)networkManager; +- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage; + (instancetype)sharedManager; diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 64470bc6b..2ab8adb80 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -11,9 +11,11 @@ #import #import #import +#import #import #import #import +#import #import #import #import @@ -27,6 +29,7 @@ #import #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -42,10 +45,7 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; @interface OWSProfileManager () -@property (nonatomic, readonly) OWSMessageSender *messageSender; @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) TSNetworkManager *networkManager; -@property (nonatomic, readonly) OWSIdentityManager *identityManager; // This property can be accessed on any thread, while synchronized on self. @property (atomic, readonly) OWSUserProfile *localUserProfile; @@ -72,8 +72,6 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; } - (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage - messageSender:(OWSMessageSender *)messageSender - networkManager:(TSNetworkManager *)networkManager { self = [super init]; @@ -83,18 +81,18 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; OWSAssertIsOnMainThread(); OWSAssertDebug(primaryStorage); - OWSAssertDebug(messageSender); - OWSAssertDebug(messageSender); - _messageSender = messageSender; _dbConnection = primaryStorage.newDatabaseConnection; - _networkManager = networkManager; _profileAvatarImageCache = [NSCache new]; _currentAvatarDownloads = [NSMutableSet new]; OWSSingletonAssert(); + [AppReadiness runNowOrWhenAppIsReady:^{ + [self rotateLocalProfileKeyIfNecessarySync]; + }]; + return self; } @@ -109,6 +107,10 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; selector:@selector(applicationDidBecomeActive:) name:OWSApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(blockListDidChange:) + name:kNSNotificationName_BlockListDidChange + object:nil]; } #pragma mark - Dependencies @@ -124,7 +126,21 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; - (OWSIdentityManager *)identityManager { - return [OWSIdentityManager sharedManager]; + return SSKEnvironment.shared.identityManager; +} + +- (OWSMessageSender *)messageSender { + OWSAssertDebug(SSKEnvironment.shared.messageSender); + + return SSKEnvironment.shared.messageSender; +} + +- (TSNetworkManager *)networkManager { + return SSKEnvironment.shared.networkManager; +} + +- (OWSBlockingManager *)blockingManager { + return SSKEnvironment.shared.blockingManager; } #pragma mark - User Profile Accessor @@ -504,6 +520,154 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; [ProfileFetcherJob runWithRecipientId:localNumber ignoreThrottling:YES]; } +#pragma mark - Profile Key Rotation + +- (nullable NSString *)groupKeyForGroupId:(NSData *)groupId { + NSString *groupIdKey = [groupId hexadecimalString]; + return groupIdKey; +} + +- (nullable NSData *)groupIdForGroupKey:(NSString *)groupKey { + NSMutableData *groupId = [NSMutableData new]; + + for (NSUInteger i = 0; i + 2 <= groupKey.length; i += 2) { + NSString *_Nullable byteString = [groupKey substringWithRange:NSMakeRange(i, 2)]; + if (!byteString) { + OWSFailDebug(@"Couldn't slice group key."); + return nil; + } + unsigned byteValue; + if (![[NSScanner scannerWithString:byteString] scanHexInt:&byteValue]) { + OWSFailDebug(@"Couldn't parse hex byte: %@.", byteString); + return nil; + } + if (byteValue > 0xff) { + OWSFailDebug(@"Invalid hex byte: %@ (%d).", byteString, byteValue); + return nil; + } + uint8_t byte = (uint8_t)(0xff & byteValue); + [groupId appendBytes:&byte length:1]; + } + if (groupId.length != kGroupIdLength) { + OWSFailDebug(@"Parsed group id has unexpected length: %@ (%lu)", + groupId.hexadecimalString, + (unsigned long)groupId.length); + return nil; + } + return [groupId copy]; +} + +- (void)rotateLocalProfileKeyIfNecessarySync { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self rotateLocalProfileKeyIfNecessarySync]; + }); +} + +- (void)rotateLocalProfileKeyIfNecessaryAsync { + OWSAssertDebug(AppReadiness.isAppReady); + + NSMutableSet *whitelistedRecipientIds = [NSMutableSet new]; + NSMutableSet *whitelistedGroupIds = [NSMutableSet new]; + [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { + [whitelistedRecipientIds + addObjectsFromArray:[transaction allKeysInCollection:kOWSProfileManager_UserWhitelistCollection]]; + + NSArray *whitelistedGroupKeys = + [transaction allKeysInCollection:kOWSProfileManager_GroupWhitelistCollection]; + for (NSString *groupKey in whitelistedGroupKeys) { + NSData *_Nullable groupId = [self groupIdForGroupKey:groupKey]; + if (!groupId) { + continue; + } + + [whitelistedGroupIds addObject:groupId]; + + TSGroupThread *_Nullable thread = [TSGroupThread threadWithGroupId:groupId transaction:transaction]; + if (!thread) { + OWSLogInfo(@"Could not find whitelisted thread: %@", groupId.hexadecimalString); + continue; + } + + [whitelistedRecipientIds addObjectsFromArray:thread.recipientIdentifiers]; + } + }]; + + [whitelistedRecipientIds removeObject:[TSAccountManager localNumber]]; + + NSSet *blockedRecipientIds = [NSSet setWithArray:self.blockingManager.blockedPhoneNumbers]; + NSSet *blockedGroupIds = [NSSet setWithArray:self.blockingManager.blockedGroupIds]; + + // Find the users and groups which are both a) blocked b) may have our current profile key. + NSMutableSet *intersectingRecipientIds = [blockedRecipientIds mutableCopy]; + [intersectingRecipientIds intersectSet:whitelistedRecipientIds]; + NSMutableSet *intersectingGroupIds = [blockedGroupIds mutableCopy]; + [intersectingGroupIds intersectSet:whitelistedGroupIds]; + + BOOL isProfileKeySharedWithBlocked = (intersectingRecipientIds.count > 0 || intersectingGroupIds.count > 0); + if (!isProfileKeySharedWithBlocked) { + // No need to rotate the profile key. + return; + } + + // Rotate the profile key + + // Make copies of the current local profile state. + OWSUserProfile *localUserProfile = self.localUserProfile; + NSString *_Nullable oldProfileName = localUserProfile.profileName; + NSString *_Nullable oldAvatarFileName = localUserProfile.avatarFileName; + NSData *_Nullable oldAvatarData = [self profileAvatarDataForRecipientId:self.tsAccountManager.localNumber]; + + // Rotate the stored profile key. + // + // This will always succeed. + [self.localUserProfile + updateWithProfileKey:[OWSAES256Key generateRandomKey] + dbConnection:self.dbConnection + completion:^{ + // Remove blocked users and groups from profile whitelist. + // + // This will always succeed. + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [transaction removeObjectsForKeys:intersectingRecipientIds.allObjects + inCollection:kOWSProfileManager_UserWhitelistCollection]; + for (NSData *groupId in intersectingGroupIds) { + NSString *groupIdKey = [self groupKeyForGroupId:groupId]; + [transaction removeObjectForKey:groupIdKey + inCollection:kOWSProfileManager_GroupWhitelistCollection]; + } + }]; + + // Try to re-upload our profile name and avatar, if any. + // + // This may fail. + if (oldProfileName.length > 0) { + [self updateServiceWithProfileName:oldProfileName + success:^{ + OWSLogInfo(@"Update to profile name after profile key rotation succeeded."); + } + failure:^{ + OWSLogInfo(@"Update to profile name after profile key rotation failed."); + }]; + } + + if (oldAvatarData.length > 0 && oldAvatarFileName.length > 0) { + [self uploadAvatarToService:oldAvatarData + success:^(NSString *_Nullable avatarUrlPath) { + OWSLogInfo(@"Update to profile avatar after profile key rotation succeeded."); + // We need to update the local profile with the avatar state since + // it is cleared during the "avatar update" process. + [self.localUserProfile updateWithAvatarUrlPath:avatarUrlPath + avatarFileName:oldAvatarFileName + dbConnection:self.dbConnection + completion:nil]; + } + failure:^{ + OWSLogInfo(@"Update to profile avatar after profile key rotation failed."); + }]; + } + }]; +} + #pragma mark - Profile Whitelist - (void)clearProfileWhitelist @@ -581,6 +745,10 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; { OWSAssertDebug(recipientId.length > 0); + if ([self.blockingManager isRecipientIdBlocked:recipientId]) { + return NO; + } + __block BOOL result = NO; [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { NSNumber *_Nullable oldValue = @@ -594,7 +762,7 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; { OWSAssertDebug(groupId.length > 0); - NSString *groupIdKey = [groupId hexadecimalString]; + NSString *groupIdKey = [self groupKeyForGroupId:groupId]; __block BOOL didChange = NO; [self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { @@ -645,7 +813,11 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; { OWSAssertDebug(groupId.length > 0); - NSString *groupIdKey = [groupId hexadecimalString]; + if ([self.blockingManager isGroupIdBlocked:groupId]) { + return NO; + } + + NSString *groupIdKey = [self groupKeyForGroupId:groupId]; __block BOOL result = NO; [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { @@ -1173,6 +1345,14 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; // TODO: Sync if necessary. } +- (void)blockListDidChange:(NSNotification *)notification { + OWSAssertIsOnMainThread(); + + [AppReadiness runNowOrWhenAppIsReady:^{ + [self rotateLocalProfileKeyIfNecessarySync]; + }]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/profiles/OWSUserProfile.h b/SignalMessaging/profiles/OWSUserProfile.h index 4453a5dd0..e0e298180 100644 --- a/SignalMessaging/profiles/OWSUserProfile.h +++ b/SignalMessaging/profiles/OWSUserProfile.h @@ -61,6 +61,10 @@ extern NSString *const kLocalProfileUniqueId; dbConnection:(YapDatabaseConnection *)dbConnection completion:(nullable OWSUserProfileCompletion)completion; +- (void)updateWithProfileKey:(OWSAES256Key *)profileKey + dbConnection:(YapDatabaseConnection *)dbConnection + completion:(nullable OWSUserProfileCompletion)completion; + - (void)clearWithProfileKey:(OWSAES256Key *)profileKey dbConnection:(YapDatabaseConnection *)dbConnection completion:(nullable OWSUserProfileCompletion)completion; diff --git a/SignalServiceKit/src/Messages/TSGroupModel.h b/SignalServiceKit/src/Messages/TSGroupModel.h index f9f7c48fb..287209687 100644 --- a/SignalServiceKit/src/Messages/TSGroupModel.h +++ b/SignalServiceKit/src/Messages/TSGroupModel.h @@ -7,6 +7,8 @@ NS_ASSUME_NONNULL_BEGIN +extern const NSUInteger kGroupIdLength; + @interface TSGroupModel : TSYapDatabaseObject @property (nonatomic) NSArray *groupMemberIds; diff --git a/SignalServiceKit/src/Messages/TSGroupModel.m b/SignalServiceKit/src/Messages/TSGroupModel.m index 1665c8133..377bd23f1 100644 --- a/SignalServiceKit/src/Messages/TSGroupModel.m +++ b/SignalServiceKit/src/Messages/TSGroupModel.m @@ -8,6 +8,8 @@ NS_ASSUME_NONNULL_BEGIN +const NSUInteger kGroupIdLength = 16; + @interface TSGroupModel () @property (nullable, nonatomic) NSString *groupName;