diff --git a/SignalMessaging/profiles/OWSProfileManager.h b/SignalMessaging/profiles/OWSProfileManager.h index d52ba1a78..afe8b2faf 100644 --- a/SignalMessaging/profiles/OWSProfileManager.h +++ b/SignalMessaging/profiles/OWSProfileManager.h @@ -74,6 +74,7 @@ extern const NSUInteger kOWSProfileManager_MaxAvatarDiameter; - (nullable OWSAES256Key *)profileKeyForRecipientId:(NSString *)recipientId; +- (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID avoidWriteTransaction:(BOOL)avoidWriteTransaction; - (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID; - (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID transaction:(YapDatabaseReadWriteTransaction *)transaction; diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index b1776bc6e..58017d509 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -1025,6 +1025,11 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); } - (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID +{ + return [self profileNameForRecipientWithID:recipientID avoidingWriteTransaction:NO]; +} + +- (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID avoidingWriteTransaction:(BOOL)avoidWriteTransaction { if ([self.tsAccountManager.localNumber isEqualToString:recipientID]) { return self.localUserProfile.profileName; @@ -1047,18 +1052,22 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); } } - __block NSString *result; + if (!avoidWriteTransaction) { + __block NSString *result; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - result = [self profileNameForRecipientWithID:recipientID transaction:transaction]; - } error:nil]; + [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + result = [self profileNameForRecipientWithID:recipientID transaction:transaction]; + } error:nil]; - NSString *shortID = [recipientID substringWithRange:NSMakeRange(recipientID.length - 8, 8)]; - NSString *suffix = [NSString stringWithFormat:@" (...%@)", shortID]; - if ([result hasSuffix:suffix]) { - return [result substringToIndex:result.length - suffix.length]; + NSString *shortID = [recipientID substringWithRange:NSMakeRange(recipientID.length - 8, 8)]; + NSString *suffix = [NSString stringWithFormat:@" (...%@)", shortID]; + if ([result hasSuffix:suffix]) { + return [result substringToIndex:result.length - suffix.length]; + } else { + return result; + } } else { - return result; + return recipientID; } } diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift index 4959d68ec..8d6275da6 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift @@ -136,6 +136,15 @@ public final class ClosedGroupsProtocol : NSObject { } else { // Establish sessions if needed establishSessionsIfNeeded(with: [String](members), using: transaction) + // Send closed group update messages to any new members using established channels + for member in newMembers { + let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) + thread.save(with: transaction) + let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, + groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [], members: membersAsData, admins: adminsAsData) + let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) + messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + } // Send out the user's new ratchet to all members (minus the removed ones) using established channels let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) @@ -150,7 +159,7 @@ public final class ClosedGroupsProtocol : NSObject { } } } - } else { + } else if !newMembers.isEmpty { seal.fulfill(()) // Generate ratchets for any new members newSenderKeys = newMembers.map { publicKey in @@ -162,17 +171,24 @@ public final class ClosedGroupsProtocol : NSObject { members: membersAsData, admins: adminsAsData) let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) - } - // Establish sessions if needed - establishSessionsIfNeeded(with: [String](newMembers), using: transaction) - // Send closed group update messages to the new members using established channels - var allSenderKeys = Storage.getAllClosedGroupSenderKeys(for: groupPublicKey) - allSenderKeys.formUnion(newSenderKeys) - for member in newMembers { - let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) - thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, - groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: membersAsData, admins: adminsAsData) + // Establish sessions if needed + establishSessionsIfNeeded(with: [String](newMembers), using: transaction) + // Send closed group update messages to the new members using established channels + var allSenderKeys = Storage.getAllClosedGroupSenderKeys(for: groupPublicKey) + allSenderKeys.formUnion(newSenderKeys) + for member in newMembers { + let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) + thread.save(with: transaction) + let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, + groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: membersAsData, admins: adminsAsData) + let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) + messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + } + } else { + seal.fulfill(()) + let allSenderKeys = Storage.getAllClosedGroupSenderKeys(for: groupPublicKey) + let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, + senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: membersAsData, admins: adminsAsData) let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) } @@ -265,7 +281,7 @@ public final class ClosedGroupsProtocol : NSObject { let missingSenderKeys = Set(members).subtracting(senderKeys.map { $0.publicKey.toHexString() }) let userPublicKey = getUserHexEncodedPublicKey() if missingSenderKeys.contains(userPublicKey) { - establishSessionsIfNeeded(with: [String](missingSenderKeys), using: transaction) + establishSessionsIfNeeded(with: [String](members), using: transaction) let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) for member in members { @@ -283,9 +299,15 @@ public final class ClosedGroupsProtocol : NSObject { // Create the group let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) let group = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) - let thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction) - thread.usesSharedSenderKeys = true - thread.save(with: transaction) + let thread: TSGroupThread + if let t = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) { + thread = t + thread.setGroupModel(group, with: transaction) + } else { + thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction) + thread.usesSharedSenderKeys = true + thread.save(with: transaction) + } SSKEnvironment.shared.profileManager.addThread(toProfileWhitelist: thread) // Add the group to the user's set of public keys to poll for Storage.setClosedGroupPrivateKey(groupPrivateKey.toHexString(), for: groupPublicKey, using: transaction) @@ -389,7 +411,9 @@ public final class ClosedGroupsProtocol : NSObject { // Respond to the request print("[Loki] Responding to sender key request from: \(senderPublicKey).") SessionManagementProtocol.sendSessionRequestIfNeeded(to: senderPublicKey, using: transaction) // This internally takes care of multi device - let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) + guard let userRatchet = Storage.getClosedGroupRatchet(for: groupPublicKey, senderPublicKey: userPublicKey) else { + return print("[Loki] Missing own ratchet.") + } let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) let thread = TSContactThread.getOrCreateThread(withContactId: senderPublicKey, transaction: transaction) thread.save(with: transaction) @@ -402,23 +426,9 @@ public final class ClosedGroupsProtocol : NSObject { private static func handleSenderKeyMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, from senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { // Prepare let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() - let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) - guard let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else { - return print("[Loki] Ignoring closed group sender key for nonexistent group.") - } - let group = thread.groupModel guard let senderKey = closedGroupUpdate.senderKeys.first else { return print("[Loki] Ignoring invalid closed group sender key.") } - // Check that the requesting user is a member of the group - var membersAndLinkedDevices: Set = Set(group.groupMemberIds) - for member in group.groupMemberIds { - let deviceLinks = OWSPrimaryStorage.shared().getDeviceLinks(for: member, in: transaction) - membersAndLinkedDevices.formUnion(deviceLinks.flatMap { [ $0.master.publicKey, $0.slave.publicKey ] }) - } - guard membersAndLinkedDevices.contains(senderPublicKey) else { - return print("[Loki] Ignoring closed group sender key from non-member.") - } guard senderKey.publicKey.toHexString() == senderPublicKey else { return print("[Loki] Ignoring invalid closed group sender key.") } diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift index 170158c6f..59e9d8cb5 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift @@ -177,7 +177,12 @@ public final class SharedSenderKeysImplementation : NSObject { throw RatchetingError.messageKeyMissing(targetKeyIndex: keyIndex, groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) } let aes = try AES(key: Data(hex: messageKey).bytes, blockMode: gcm, padding: .noPadding) - return Data(try aes.decrypt(ciphertext.bytes)) + do { + return Data(try aes.decrypt(ciphertext.bytes)) + } catch { + ClosedGroupsProtocol.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) + throw error + } } @objc public func isClosedGroup(_ publicKey: String) -> Bool { diff --git a/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift b/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift index 2ca72032f..f4866b5be 100644 --- a/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift +++ b/SignalServiceKit/src/Loki/Utilities/DisplayNameUtilities.swift @@ -13,6 +13,15 @@ public final class UserDisplayNameUtilities : NSObject { } // MARK: Sessions + @objc(getPrivateChatDisplayNameAvoidWriteTransaction:) + public static func getPrivateChatDisplayNameAvoidingWriteTransaction(for publicKey: String) -> String? { + if publicKey == userPublicKey { + return userDisplayName + } else { + return SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: publicKey, avoidingWriteTransaction: true) + } + } + @objc public static func getPrivateChatDisplayName(for publicKey: String) -> String? { if publicKey == userPublicKey { return userDisplayName diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index bf73525ba..146e71eda 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -667,7 +667,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; BOOL isSyncMessage = [message isKindOfClass:[OWSOutgoingSyncMessage class]]; if (thread == nil && !isSyncMessage) { - OWSFailDebug(@"Missing thread for non-sync message."); // The thread has been deleted since the message was enqueued. NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageSendNoValidRecipients, diff --git a/SignalServiceKit/src/Messages/TSGroupModel.m b/SignalServiceKit/src/Messages/TSGroupModel.m index c2e233665..6157d55b4 100644 --- a/SignalServiceKit/src/Messages/TSGroupModel.m +++ b/SignalServiceKit/src/Messages/TSGroupModel.m @@ -133,7 +133,7 @@ const int32_t kGroupIdLength = 16; if ([membersWhoLeft count] > 0) { NSArray *oldMembersNames = [[membersWhoLeft allObjects] map:^NSString*(NSString* item) { - return [LKUserDisplayNameUtilities getPrivateChatDisplayNameFor:item]; + return [LKUserDisplayNameUtilities getPrivateChatDisplayNameAvoidWriteTransaction:item]; }]; updatedGroupInfoString = [updatedGroupInfoString stringByAppendingString:[NSString diff --git a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h index b19d1af21..01694420a 100644 --- a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h +++ b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h @@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN - (OWSAES256Key *)localProfileKey; - (nullable NSString *)localProfileName; +- (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID avoidingWriteTransaction:(BOOL)avoidWriteTransaction; - (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID; - (nullable NSString *)profileNameForRecipientWithID:(NSString *)recipientID transaction:(YapDatabaseReadWriteTransaction *)transaction; - (nullable NSString *)profilePictureURL;