diff --git a/Podfile.lock b/Podfile.lock index 71ee4350b..e1dfcbbe0 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -42,13 +42,13 @@ PODS: - PureLayout (3.1.6) - Reachability (3.2) - SAMKeychain (1.5.3) - - SessionAxolotlKit (1.0.5): + - SessionAxolotlKit (1.0.6): - CocoaLumberjack - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.2) - SessionHKDFKit (~> 0.0.5) - SwiftProtobuf (~> 1.5.0) - - SessionAxolotlKit/Tests (1.0.5): + - SessionAxolotlKit/Tests (1.0.6): - CocoaLumberjack - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.2) @@ -72,18 +72,18 @@ PODS: - SessionHKDFKit/Tests (0.0.5): - CocoaLumberjack - SessionCoreKit - - SessionMetadataKit (1.0.5): + - SessionMetadataKit (1.0.6): - CocoaLumberjack - CryptoSwift (~> 1.3) - - SessionAxolotlKit (~> 1.0.5) + - SessionAxolotlKit (~> 1.0.6) - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.2) - SessionHKDFKit (~> 0.0.5) - SwiftProtobuf (~> 1.5.0) - - SessionMetadataKit/Tests (1.0.5): + - SessionMetadataKit/Tests (1.0.6): - CocoaLumberjack - CryptoSwift (~> 1.3) - - SessionAxolotlKit (~> 1.0.5) + - SessionAxolotlKit (~> 1.0.6) - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.2) - SessionHKDFKit (~> 0.0.5) @@ -98,10 +98,10 @@ PODS: - PromiseKit (~> 6.0) - Reachability - SAMKeychain - - SessionAxolotlKit (~> 1.0.5) + - SessionAxolotlKit (~> 1.0.6) - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.3) - - SessionMetadataKit (~> 1.0.5) + - SessionMetadataKit (~> 1.0.6) - Starscream - SwiftProtobuf (~> 1.5.0) - YapDatabase/SQLCipher @@ -115,10 +115,10 @@ PODS: - PromiseKit (~> 6.0) - Reachability - SAMKeychain - - SessionAxolotlKit (~> 1.0.5) + - SessionAxolotlKit (~> 1.0.6) - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.3) - - SessionMetadataKit (~> 1.0.5) + - SessionMetadataKit (~> 1.0.6) - Starscream - SwiftProtobuf (~> 1.5.0) - YapDatabase/SQLCipher @@ -277,7 +277,7 @@ CHECKOUT OPTIONS: :commit: b72c2d1e6132501db906de2cffa8ded7803c54f4 :git: https://github.com/signalapp/Mantle SessionAxolotlKit: - :commit: 27b63f4ecb890c38a78750e12d1569033074915b + :commit: 663f58f4da7bf4d159e366352c2bb7715049671b :git: https://github.com/loki-project/session-ios-protocol-kit.git SessionCoreKit: :commit: 0d66c90657b62cb66ecd2767c57408a951650f23 @@ -289,7 +289,7 @@ CHECKOUT OPTIONS: :commit: 0dcf8cf8a7995ef8663146f7063e6c1d7f5a3274 :git: https://github.com/nielsandriesse/session-ios-hkdf-kit.git SessionMetadataKit: - :commit: bddba9ef2050af9a223f2698528776b6b80dc221 + :commit: 935300f1de6c3e6b77fd6f7ad69b7ce3d7ee9ab5 :git: https://github.com/loki-project/session-ios-metadata-kit Starscream: :commit: b09ea163c3cb305152c65b299cb024610f52e735 @@ -312,12 +312,12 @@ SPEC CHECKSUMS: PureLayout: bd3c4ec3a3819ad387c99ebb72c6b129c3ed4d2d Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - SessionAxolotlKit: e6c39822a8ad68217966f02ce135291023938144 + SessionAxolotlKit: f65b7402b63549597e23bc6c8f92c5e2da182ae4 SessionCoreKit: 778a3f6e3da788b43497734166646025b6392e88 SessionCurve25519Kit: 9bb9afe199e4bc23578a4b15932ad2c57bd047b1 SessionHKDFKit: b0f4e669411703ab925aba07491c5611564d1419 - SessionMetadataKit: f5d9cc78092f84f3940822506c14a381e79af9ff - SessionServiceKit: 0a50bd8482a0787a6f7134b4c0854a0a091535e8 + SessionMetadataKit: 1e5dbd59f6229d9238557751bfbc57c0f4b1dd6b + SessionServiceKit: 903f4a01384ad4f827e035e693cd87605c223724 SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072 SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9 Starscream: 8aaf1a7feb805c816d0e7d3190ef23856f6665b9 diff --git a/Pods b/Pods index 49611da3f..158e066d7 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 49611da3fc5eac88efd8d192e9ca7e2a84813a7e +Subproject commit 158e066d79806ef0e466370da5c303dedb90c654 diff --git a/SessionServiceKit.podspec b/SessionServiceKit.podspec index a75ba3a44..ef5f272f0 100644 --- a/SessionServiceKit.podspec +++ b/SessionServiceKit.podspec @@ -42,7 +42,7 @@ A Swift/Objective-C library for communicating with the Session messaging service s.dependency 'CocoaLumberjack' s.dependency 'CryptoSwift', '~> 1.3' s.dependency 'AFNetworking' - s.dependency 'SessionAxolotlKit', '~> 1.0.5' + s.dependency 'SessionAxolotlKit', '~> 1.0.6' s.dependency 'Mantle' s.dependency 'YapDatabase/SQLCipher' s.dependency 'Starscream' @@ -52,7 +52,7 @@ A Swift/Objective-C library for communicating with the Session messaging service s.dependency 'Reachability' s.dependency 'SwiftProtobuf', '~> 1.5.0' s.dependency 'SessionCoreKit', '~> 1.0.0' - s.dependency 'SessionMetadataKit', '~> 1.0.5' + s.dependency 'SessionMetadataKit', '~> 1.0.6' s.dependency 'PromiseKit', '~> 6.0' s.test_spec 'Tests' do |test_spec| diff --git a/SignalServiceKit/protobuf/SignalService.proto b/SignalServiceKit/protobuf/SignalService.proto index 64a1505b0..de4cb009e 100644 --- a/SignalServiceKit/protobuf/SignalService.proto +++ b/SignalServiceKit/protobuf/SignalService.proto @@ -243,9 +243,11 @@ message DataMessage { message ClosedGroupUpdate { // Loki enum Type { - NEW = 0; // groupPublicKey, name, groupPrivateKey, senderKeys, members, admins - INFO = 1; // groupPublicKey, name, senderKeys, members, admins - SENDER_KEY = 2; // groupPublicKey, senderKeys + NEW = 0; // groupPublicKey, name, groupPrivateKey, senderKeys, members, admins + INFO = 1; // groupPublicKey, name, senderKeys, members, admins + SENDER_KEY_REQUEST = 2; // groupPublicKey + SENDER_KEY = 3; // groupPublicKey, senderKeys + } message SenderKey { @@ -254,7 +256,7 @@ message DataMessage { // @required optional uint32 keyIndex = 2; // @required - optional string publicKey = 3; + optional bytes publicKey = 3; } optional string name = 1; @@ -262,8 +264,8 @@ message DataMessage { optional bytes groupPublicKey = 2; optional bytes groupPrivateKey = 3; repeated SenderKey senderKeys = 4; - repeated string members = 5; - repeated string admins = 6; + repeated bytes members = 5; + repeated bytes admins = 6; // @required optional Type type = 7; } diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift index 9c6c16d27..e29fda01e 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift @@ -2,10 +2,10 @@ internal final class ClosedGroupSenderKey : NSObject, NSCoding { internal let chainKey: Data internal let keyIndex: UInt - internal let publicKey: String + internal let publicKey: Data // MARK: Initialization - init(chainKey: Data, keyIndex: UInt, publicKey: String) { + init(chainKey: Data, keyIndex: UInt, publicKey: Data) { self.chainKey = chainKey self.keyIndex = keyIndex self.publicKey = publicKey @@ -15,7 +15,7 @@ internal final class ClosedGroupSenderKey : NSObject, NSCoding { public init?(coder: NSCoder) { guard let chainKey = coder.decodeObject(forKey: "chainKey") as? Data, let keyIndex = coder.decodeObject(forKey: "keyIndex") as? UInt, - let publicKey = coder.decodeObject(forKey: "publicKey") as? String else { return nil } + let publicKey = coder.decodeObject(forKey: "publicKey") as? Data else { return nil } self.chainKey = chainKey self.keyIndex = UInt(keyIndex) self.publicKey = publicKey @@ -45,5 +45,7 @@ internal final class ClosedGroupSenderKey : NSObject, NSCoding { } // MARK: Description - override public var description: String { return "[ chainKey : \(chainKey), keyIndex : \(keyIndex), publicKey: \(publicKey) ]" } + override public var description: String { + return "[ chainKey : \(chainKey), keyIndex : \(keyIndex), publicKey: \(publicKey.toHexString()) ]" + } } diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift index 61867f103..a4488ca53 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift @@ -11,8 +11,9 @@ internal final class ClosedGroupUpdateMessage : TSOutgoingMessage { // MARK: Kind internal enum Kind { - case new(groupPublicKey: Data, name: String, groupPrivateKey: Data, senderKeys: [ClosedGroupSenderKey], members: [String], admins: [String]) - case info(groupPublicKey: Data, name: String, senderKeys: [ClosedGroupSenderKey], members: [String], admins: [String]) + case new(groupPublicKey: Data, name: String, groupPrivateKey: Data, senderKeys: [ClosedGroupSenderKey], members: [Data], admins: [Data]) + case info(groupPublicKey: Data, name: String, senderKeys: [ClosedGroupSenderKey], members: [Data], admins: [Data]) + case senderKeyRequest(groupPublicKey: Data) case senderKey(groupPublicKey: Data, senderKey: ClosedGroupSenderKey) } @@ -39,14 +40,17 @@ internal final class ClosedGroupUpdateMessage : TSOutgoingMessage { case "new": guard let name = coder.decodeObject(forKey: "name") as? String, let groupPrivateKey = coder.decodeObject(forKey: "groupPrivateKey") as? Data, - let members = coder.decodeObject(forKey: "members") as? [String], - let admins = coder.decodeObject(forKey: "admins") as? [String] else { return nil } + let members = coder.decodeObject(forKey: "members") as? [Data], + let admins = coder.decodeObject(forKey: "admins") as? [Data] else { return nil } self.kind = .new(groupPublicKey: groupPublicKey, name: name, groupPrivateKey: groupPrivateKey, senderKeys: senderKeys, members: members, admins: admins) case "info": guard let name = coder.decodeObject(forKey: "name") as? String, - let members = coder.decodeObject(forKey: "members") as? [String], - let admins = coder.decodeObject(forKey: "admins") as? [String] else { return nil } + let members = coder.decodeObject(forKey: "members") as? [Data], + let admins = coder.decodeObject(forKey: "admins") as? [Data] else { return nil } self.kind = .info(groupPublicKey: groupPublicKey, name: name, senderKeys: senderKeys, members: members, admins: admins) + case "senderKeyRequest": + guard let name = coder.decodeObject(forKey: "name") as? String else { return nil } + self.kind = .senderKeyRequest(groupPublicKey: groupPublicKey) case "senderKey": guard let senderKey = senderKeys.first else { return nil } self.kind = .senderKey(groupPublicKey: groupPublicKey, senderKey: senderKey) @@ -76,6 +80,8 @@ internal final class ClosedGroupUpdateMessage : TSOutgoingMessage { coder.encode(senderKeys, forKey: "senderKeys") coder.encode(members, forKey: "members") coder.encode(admins, forKey: "admins") + case .senderKeyRequest(let groupPublicKey): + coder.encode(groupPublicKey, forKey: "groupPublicKey") case .senderKey(let groupPublicKey, let senderKey): coder.encode("senderKey", forKey: "kind") coder.encode(groupPublicKey, forKey: "groupPublicKey") @@ -102,6 +108,8 @@ internal final class ClosedGroupUpdateMessage : TSOutgoingMessage { closedGroupUpdate.setSenderKeys(try senderKeys.map { try $0.toProto() }) closedGroupUpdate.setMembers(members) closedGroupUpdate.setAdmins(admins) + case .senderKeyRequest(let groupPublicKey): + closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .senderKeyRequest) case .senderKey(let groupPublicKey, let senderKey): closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .senderKey) closedGroupUpdate.setSenderKeys([ try senderKey.toProto() ]) diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift index 1588ab137..5bc658d6d 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift @@ -14,6 +14,8 @@ import PromiseKit public final class ClosedGroupsProtocol : NSObject { public static let isSharedSenderKeysEnabled = false + // MARK: - Sending + /// - Note: It's recommended to batch fetch the device links for the given set of members before invoking this, to avoid the message sending pipeline /// making a request for each member. public static func createClosedGroup(name: String, members: Set, transaction: YapDatabaseReadWriteTransaction) -> Promise { @@ -27,6 +29,7 @@ public final class ClosedGroupsProtocol : NSObject { // Ensure the current user's master device is the one that's included in the member list members.remove(userPublicKey) members.insert(UserDefaults.standard[.masterHexEncodedPublicKey] ?? userPublicKey) + let membersAsData = members.map { Data(hex: $0) } // Create ratchets for all members (and their linked devices) var membersAndLinkedDevices: Set = members for member in members { @@ -35,10 +38,11 @@ public final class ClosedGroupsProtocol : NSObject { } let senderKeys: [ClosedGroupSenderKey] = membersAndLinkedDevices.map { publicKey in let ratchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: publicKey, using: transaction) - return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: publicKey) + return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: Data(hex: publicKey)) } // Create the group let admins = [ UserDefaults.standard[.masterHexEncodedPublicKey] ?? userPublicKey ] + let adminsAsData = admins.map { Data(hex: $0) } let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) let group = TSGroupModel(title: name, memberIds: [String](members), image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) let thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction) @@ -53,7 +57,7 @@ public final class ClosedGroupsProtocol : NSObject { let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, - groupPrivateKey: groupKeyPair.privateKey, senderKeys: senderKeys, members: [String](members), admins: admins) + groupPrivateKey: groupKeyPair.privateKey, senderKeys: senderKeys, members: membersAsData, admins: adminsAsData) let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) promises.append(SSKEnvironment.shared.messageSender.sendPromise(message: closedGroupUpdateMessage)) } @@ -76,12 +80,14 @@ public final class ClosedGroupsProtocol : NSObject { let group = thread.groupModel let name = group.groupName! let admins = group.groupAdminIds + let adminsAsData = admins.map { Data(hex: $0) } guard let groupPrivateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey) else { return print("[Loki] Can't get private key for closed group.") } // Add the members to the member list var members = group.groupMemberIds members.append(contentsOf: newMembers) + let membersAsData = members.map { Data(hex: $0) } // Generate ratchets for the new members (and their linked devices) var newMembersAndLinkedDevices: Set = newMembers for member in newMembers { @@ -90,11 +96,11 @@ public final class ClosedGroupsProtocol : NSObject { } let senderKeys: [ClosedGroupSenderKey] = newMembersAndLinkedDevices.map { publicKey in let ratchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: publicKey, using: transaction) - return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: publicKey) + return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: Data(hex: publicKey)) } // Send a closed group update message to the existing members with the new members' ratchets (this message is aimed at the group) let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: senderKeys, - members: members, admins: admins) + members: membersAsData, admins: adminsAsData) let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // Establish sessions if needed @@ -106,7 +112,7 @@ public final class ClosedGroupsProtocol : NSObject { 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: members, admins: admins) + groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: membersAsData, admins: adminsAsData) let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) } @@ -138,6 +144,7 @@ public final class ClosedGroupsProtocol : NSObject { let group = thread.groupModel let name = group.groupName! let admins = group.groupAdminIds + let adminsAsData = admins.map { Data(hex: $0) } // Remove the members from the member list var members = group.groupMemberIds let indexes = membersToRemove.compactMap { members.firstIndex(of: $0) } @@ -145,9 +152,10 @@ public final class ClosedGroupsProtocol : NSObject { return print("[Loki] Can't remove users from group.") } indexes.forEach { members.remove(at: $0) } + let membersAsData = members.map { Data(hex: $0) } // Send the update to the group (don't include new ratchets as everyone should generate new ratchets individually) let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: [], - members: members, admins: admins) + members: membersAsData, admins: adminsAsData) let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // Delete all ratchets (it's important that this happens after sending out the update) @@ -162,7 +170,7 @@ public final class ClosedGroupsProtocol : NSObject { // Send out the user's new ratchet to all members (minus the removed ones) and their linked devices using established channels let userPublicKey = getUserHexEncodedPublicKey() let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) - let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: userPublicKey) + let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) for member in members { // This internally takes care of multi device let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) @@ -180,6 +188,20 @@ public final class ClosedGroupsProtocol : NSObject { infoMessage.save(with: transaction) } + public static func requestSenderKey(for groupPublicKey: String, senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { + // Establish session if needed + SessionManagementProtocol.establishSessionIfNeeded(with: senderPublicKey, using: transaction) + // Send the request + let thread = TSContactThread.getOrCreateThread(withContactId: senderPublicKey, transaction: transaction) + thread.save(with: transaction) + let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKeyRequest(groupPublicKey: Data(hex: groupPublicKey)) + let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) + let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue + messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + } + + // MARK: - Receiving + @objc(handleSharedSenderKeysUpdateIfNeeded:from:transaction:) public static func handleSharedSenderKeysUpdateIfNeeded(_ dataMessage: SSKProtoDataMessage, from publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { // Note that `publicKey` is either the public key of the group or the public key of the @@ -188,6 +210,7 @@ public final class ClosedGroupsProtocol : NSObject { switch closedGroupUpdate.type { case .new: handleNewGroupMessage(closedGroupUpdate, using: transaction) case .info: handleInfoMessage(closedGroupUpdate, from: publicKey, using: transaction) + case .senderKeyRequest: handleSenderKeyRequestMessage(closedGroupUpdate, from: publicKey, using: transaction) case .senderKey: handleSenderKeyMessage(closedGroupUpdate, from: publicKey, using: transaction) } } @@ -198,12 +221,12 @@ public final class ClosedGroupsProtocol : NSObject { let name = closedGroupUpdate.name let groupPrivateKey = closedGroupUpdate.groupPrivateKey! let senderKeys = closedGroupUpdate.senderKeys - let members = closedGroupUpdate.members - let admins = closedGroupUpdate.admins + let members = closedGroupUpdate.members.map { $0.toHexString() } + let admins = closedGroupUpdate.admins.map { $0.toHexString() } // Persist the ratchets senderKeys.forEach { senderKey in let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: []) - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderKey.publicKey, ratchet: ratchet, using: transaction) + Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderKey.publicKey.toHexString(), ratchet: ratchet, using: transaction) } // Create the group let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) @@ -230,18 +253,27 @@ public final class ClosedGroupsProtocol : NSObject { let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() let name = closedGroupUpdate.name let senderKeys = closedGroupUpdate.senderKeys - let members = closedGroupUpdate.members - let admins = closedGroupUpdate.admins + let members = closedGroupUpdate.members.map { $0.toHexString() } + let admins = closedGroupUpdate.admins.map { $0.toHexString() } // Get the group let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) guard let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else { return print("[Loki] Ignoring closed group update for nonexistent group.") } let group = thread.groupModel + // Check that the sender is a member of the group (before the update) + var membersAndLinkedDevices: Set = [] + for member in group.groupMemberIds { + let deviceLinks = OWSPrimaryStorage.shared().getDeviceLinks(for: member, in: transaction) + membersAndLinkedDevices.formUnion(deviceLinks.flatMap { [ $0.master.hexEncodedPublicKey, $0.slave.hexEncodedPublicKey ] }) + } + guard membersAndLinkedDevices.contains(senderPublicKey) else { + return print("[Loki] Ignoring closed group info message from non-member.") + } // Store the ratchets for any new members (it's important that this happens before the code below) senderKeys.forEach { senderKey in let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: []) - Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderKey.publicKey, ratchet: ratchet, using: transaction) + Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderKey.publicKey.toHexString(), ratchet: ratchet, using: transaction) } // Delete all ratchets and either: // • Send out the user's new ratchet using established channels if other members of the group left or were removed @@ -256,7 +288,7 @@ public final class ClosedGroupsProtocol : NSObject { } else { establishSessionsIfNeeded(with: members, using: transaction) // This internally takes care of multi device let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) - let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: userPublicKey) + let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) for member in members { let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) @@ -269,22 +301,72 @@ public final class ClosedGroupsProtocol : NSObject { // Update the group let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) thread.setGroupModel(newGroupModel, with: transaction) - // Notify the user - let infoMessageType: TSInfoMessageType = wasUserRemoved ? .typeGroupQuit : .typeGroupUpdate - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: infoMessageType) - infoMessage.save(with: transaction) + // Notify the user if needed (don't notify them if the message just contained linked device sender keys) + if Set(members) != Set(oldMembers) || Set(admins) != Set(group.groupAdminIds) || name != group.groupName { + let infoMessageType: TSInfoMessageType = wasUserRemoved ? .typeGroupQuit : .typeGroupUpdate + let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: infoMessageType) + infoMessage.save(with: transaction) + } + } + + private static func handleSenderKeyRequestMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, from senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { + // Prepare + let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue + let userPublicKey = getUserHexEncodedPublicKey() + let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) + guard let groupThread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else { + return print("[Loki] Ignoring closed group update for nonexistent group.") + } + let group = groupThread.groupModel + // Check that the requesting user is a member of the group + var membersAndLinkedDevices: Set = [] + for member in group.groupMemberIds { + let deviceLinks = OWSPrimaryStorage.shared().getDeviceLinks(for: member, in: transaction) + membersAndLinkedDevices.formUnion(deviceLinks.flatMap { [ $0.master.hexEncodedPublicKey, $0.slave.hexEncodedPublicKey ] }) + } + guard membersAndLinkedDevices.contains(senderPublicKey) else { + return print("[Loki] Ignoring closed group sender key request from non-member.") + } + // Respond to the request + SessionManagementProtocol.establishSessionIfNeeded(with: senderPublicKey, using: transaction) // This internally takes care of multi device + 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)) + let thread = TSContactThread.getOrCreateThread(withContactId: senderPublicKey, transaction: transaction) + thread.save(with: transaction) + let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) + let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) + messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device } /// Invoked upon receiving a sender key from another user. 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 update for nonexistent group.") + } + let group = thread.groupModel guard let senderKey = closedGroupUpdate.senderKeys.first else { return print("[Loki] Ignoring invalid closed group update.") } + // Check that the requesting user is a member of the group + var membersAndLinkedDevices: Set = [] + for member in group.groupMemberIds { + let deviceLinks = OWSPrimaryStorage.shared().getDeviceLinks(for: member, in: transaction) + membersAndLinkedDevices.formUnion(deviceLinks.flatMap { [ $0.master.hexEncodedPublicKey, $0.slave.hexEncodedPublicKey ] }) + } + guard membersAndLinkedDevices.contains(senderPublicKey) else { + return print("[Loki] Ignoring closed group sender key from non-member.") + } + // Store the sender key let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: []) Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: ratchet, using: transaction) } + // MARK: - General + @objc(establishSessionsIfNeededWithClosedGroupMembers:transaction:) public static func establishSessionsIfNeeded(with closedGroupMembers: [String], using transaction: YapDatabaseReadWriteTransaction) { closedGroupMembers.forEach { publicKey in diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift index 75026cdf5..9f988f976 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift @@ -149,11 +149,23 @@ public final class SharedSenderKeysImplementation : NSObject, SharedSenderKeysPr } public func decrypt(_ ivAndCiphertext: Data, for groupPublicKey: String, senderPublicKey: String, keyIndex: UInt, using transaction: YapDatabaseReadWriteTransaction) throws -> Data { - let ratchet = try stepRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, until: keyIndex, using: transaction) + let ratchet: ClosedGroupRatchet + do { + ratchet = try stepRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, until: keyIndex, using: transaction) + } catch { + // FIXME: It'd be cleaner to handle this in OWSMessageDecrypter (where all the other decryption errors are handled), but this was a lot more + // convenient because there's an easy way to get the sender public key from here. + if case RatchetingError.loadingFailed(_, _) = error { + ClosedGroupsProtocol.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) + } + throw error + } let iv = ivAndCiphertext[0..(error: SyncMessagesProtocolError.privateKeyMissing)) @@ -87,7 +87,7 @@ public final class SyncMessagesProtocol : NSObject { let linkedDevices = deviceLinks.flatMap { [ $0.master.hexEncodedPublicKey, $0.slave.hexEncodedPublicKey ] }.filter { $0 != userPublicKey } let senderKeys: [ClosedGroupSenderKey] = linkedDevices.map { publicKey in let ratchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: publicKey, using: transaction) - return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: publicKey) + return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: Data(hex: publicKey)) } // Send a closed group update message to the existing members with the linked devices' ratchets (this message is aimed at the group) func sendMessageToGroup() { diff --git a/SignalServiceKit/src/Protos/Generated/SSKProto.swift b/SignalServiceKit/src/Protos/Generated/SSKProto.swift index ce4ea9408..688e8ef74 100644 --- a/SignalServiceKit/src/Protos/Generated/SSKProto.swift +++ b/SignalServiceKit/src/Protos/Generated/SSKProto.swift @@ -3293,7 +3293,7 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { // MARK: - SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder - @objc public class func builder(chainKey: Data, keyIndex: UInt32, publicKey: String) -> SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder { + @objc public class func builder(chainKey: Data, keyIndex: UInt32, publicKey: Data) -> SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder { return SSKProtoDataMessageClosedGroupUpdateSenderKeyBuilder(chainKey: chainKey, keyIndex: keyIndex, publicKey: publicKey) } @@ -3309,7 +3309,7 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { @objc fileprivate override init() {} - @objc fileprivate init(chainKey: Data, keyIndex: UInt32, publicKey: String) { + @objc fileprivate init(chainKey: Data, keyIndex: UInt32, publicKey: Data) { super.init() setChainKey(chainKey) @@ -3325,7 +3325,7 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { proto.keyIndex = valueParam } - @objc public func setPublicKey(_ valueParam: String) { + @objc public func setPublicKey(_ valueParam: Data) { proto.publicKey = valueParam } @@ -3344,12 +3344,12 @@ extension SSKProtoDataMessageLokiProfile.SSKProtoDataMessageLokiProfileBuilder { @objc public let keyIndex: UInt32 - @objc public let publicKey: String + @objc public let publicKey: Data private init(proto: SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey, chainKey: Data, keyIndex: UInt32, - publicKey: String) { + publicKey: Data) { self.proto = proto self.chainKey = chainKey self.keyIndex = keyIndex @@ -3423,13 +3423,15 @@ extension SSKProtoDataMessageClosedGroupUpdateSenderKey.SSKProtoDataMessageClose @objc public enum SSKProtoDataMessageClosedGroupUpdateType: Int32 { case new = 0 case info = 1 - case senderKey = 2 + case senderKeyRequest = 2 + case senderKey = 3 } private class func SSKProtoDataMessageClosedGroupUpdateTypeWrap(_ value: SignalServiceProtos_DataMessage.ClosedGroupUpdate.TypeEnum) -> SSKProtoDataMessageClosedGroupUpdateType { switch value { case .new: return .new case .info: return .info + case .senderKeyRequest: return .senderKeyRequest case .senderKey: return .senderKey } } @@ -3438,6 +3440,7 @@ extension SSKProtoDataMessageClosedGroupUpdateSenderKey.SSKProtoDataMessageClose switch value { case .new: return .new case .info: return .info + case .senderKeyRequest: return .senderKeyRequest case .senderKey: return .senderKey } } @@ -3498,23 +3501,23 @@ extension SSKProtoDataMessageClosedGroupUpdateSenderKey.SSKProtoDataMessageClose proto.senderKeys = wrappedItems.map { $0.proto } } - @objc public func addMembers(_ valueParam: String) { + @objc public func addMembers(_ valueParam: Data) { var items = proto.members items.append(valueParam) proto.members = items } - @objc public func setMembers(_ wrappedItems: [String]) { + @objc public func setMembers(_ wrappedItems: [Data]) { proto.members = wrappedItems } - @objc public func addAdmins(_ valueParam: String) { + @objc public func addAdmins(_ valueParam: Data) { var items = proto.admins items.append(valueParam) proto.admins = items } - @objc public func setAdmins(_ wrappedItems: [String]) { + @objc public func setAdmins(_ wrappedItems: [Data]) { proto.admins = wrappedItems } @@ -3559,11 +3562,11 @@ extension SSKProtoDataMessageClosedGroupUpdateSenderKey.SSKProtoDataMessageClose return proto.hasGroupPrivateKey } - @objc public var members: [String] { + @objc public var members: [Data] { return proto.members } - @objc public var admins: [String] { + @objc public var admins: [Data] { return proto.admins } diff --git a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift index adaac0663..1ce1b34a1 100644 --- a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift +++ b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift @@ -1527,9 +1527,9 @@ struct SignalServiceProtos_DataMessage { var senderKeys: [SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey] = [] - var members: [String] = [] + var members: [Data] = [] - var admins: [String] = [] + var admins: [Data] = [] /// @required var type: SignalServiceProtos_DataMessage.ClosedGroupUpdate.TypeEnum { @@ -1552,8 +1552,11 @@ struct SignalServiceProtos_DataMessage { /// groupPublicKey, name, senderKeys, members, admins case info // = 1 + /// groupPublicKey + case senderKeyRequest // = 2 + /// groupPublicKey, senderKeys - case senderKey // = 2 + case senderKey // = 3 init() { self = .new @@ -1563,7 +1566,8 @@ struct SignalServiceProtos_DataMessage { switch rawValue { case 0: self = .new case 1: self = .info - case 2: self = .senderKey + case 2: self = .senderKeyRequest + case 3: self = .senderKey default: return nil } } @@ -1572,7 +1576,8 @@ struct SignalServiceProtos_DataMessage { switch self { case .new: return 0 case .info: return 1 - case .senderKey: return 2 + case .senderKeyRequest: return 2 + case .senderKey: return 3 } } @@ -1604,8 +1609,8 @@ struct SignalServiceProtos_DataMessage { mutating func clearKeyIndex() {self._keyIndex = nil} /// @required - var publicKey: String { - get {return _publicKey ?? String()} + var publicKey: Data { + get {return _publicKey ?? SwiftProtobuf.Internal.emptyData} set {_publicKey = newValue} } /// Returns true if `publicKey` has been explicitly set. @@ -1619,7 +1624,7 @@ struct SignalServiceProtos_DataMessage { fileprivate var _chainKey: Data? = nil fileprivate var _keyIndex: UInt32? = nil - fileprivate var _publicKey: String? = nil + fileprivate var _publicKey: Data? = nil } init() {} @@ -4044,8 +4049,8 @@ extension SignalServiceProtos_DataMessage.ClosedGroupUpdate: SwiftProtobuf.Messa case 2: try decoder.decodeSingularBytesField(value: &self._groupPublicKey) case 3: try decoder.decodeSingularBytesField(value: &self._groupPrivateKey) case 4: try decoder.decodeRepeatedMessageField(value: &self.senderKeys) - case 5: try decoder.decodeRepeatedStringField(value: &self.members) - case 6: try decoder.decodeRepeatedStringField(value: &self.admins) + case 5: try decoder.decodeRepeatedBytesField(value: &self.members) + case 6: try decoder.decodeRepeatedBytesField(value: &self.admins) case 7: try decoder.decodeSingularEnumField(value: &self._type) default: break } @@ -4066,10 +4071,10 @@ extension SignalServiceProtos_DataMessage.ClosedGroupUpdate: SwiftProtobuf.Messa try visitor.visitRepeatedMessageField(value: self.senderKeys, fieldNumber: 4) } if !self.members.isEmpty { - try visitor.visitRepeatedStringField(value: self.members, fieldNumber: 5) + try visitor.visitRepeatedBytesField(value: self.members, fieldNumber: 5) } if !self.admins.isEmpty { - try visitor.visitRepeatedStringField(value: self.admins, fieldNumber: 6) + try visitor.visitRepeatedBytesField(value: self.admins, fieldNumber: 6) } if let v = self._type { try visitor.visitSingularEnumField(value: v, fieldNumber: 7) @@ -4094,7 +4099,8 @@ extension SignalServiceProtos_DataMessage.ClosedGroupUpdate.TypeEnum: SwiftProto static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 0: .same(proto: "NEW"), 1: .same(proto: "INFO"), - 2: .same(proto: "SENDER_KEY"), + 2: .same(proto: "SENDER_KEY_REQUEST"), + 3: .same(proto: "SENDER_KEY"), ] } @@ -4111,7 +4117,7 @@ extension SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey: SwiftProt switch fieldNumber { case 1: try decoder.decodeSingularBytesField(value: &self._chainKey) case 2: try decoder.decodeSingularUInt32Field(value: &self._keyIndex) - case 3: try decoder.decodeSingularStringField(value: &self._publicKey) + case 3: try decoder.decodeSingularBytesField(value: &self._publicKey) default: break } } @@ -4125,7 +4131,7 @@ extension SignalServiceProtos_DataMessage.ClosedGroupUpdate.SenderKey: SwiftProt try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) } if let v = self._publicKey { - try visitor.visitSingularStringField(value: v, fieldNumber: 3) + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) } try unknownFields.traverse(visitor: &visitor) }