From 1fda8e424931d8a01097f704fced299abd5ea7a1 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 8 Feb 2021 11:33:47 +1100 Subject: [PATCH] Add encryption key pair request message --- Session/Closed Groups/EditClosedGroupVC.swift | 2 +- .../ClosedGroupControlMessage.swift | 30 ++++++++++++--- .../Protos/Generated/SNProto.swift | 3 ++ .../Protos/Generated/SessionProtos.pb.swift | 6 +++ .../Protos/SessionProtos.proto | 15 ++++---- .../MessageReceiver+Handling.swift | 38 +++++++++++++++++-- .../Sending & Receiving/MessageReceiver.swift | 13 ++++++- .../MessageSender+ClosedGroups.swift | 17 ++++++++- 8 files changed, 104 insertions(+), 20 deletions(-) diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index ec89fa71b..dffd336e6 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -273,7 +273,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega if !members.contains(getUserHexEncodedPublicKey()) { try MessageSender.leave(groupPublicKey, using: transaction) } else { - try MessageSender.update(groupPublicKey, with: members, name: name, transaction: transaction) + try MessageSender.v2_update(groupPublicKey, with: members, name: name, transaction: transaction) } } catch { DispatchQueue.main.async { diff --git a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift index 4d4723047..05c259c77 100644 --- a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift @@ -23,11 +23,15 @@ public final class ClosedGroupControlMessage : ControlMessage { case new(publicKey: Data, name: String, encryptionKeyPair: ECKeyPair, members: [Data], admins: [Data]) /// - Note: Deprecated in favor of more explicit group updates. case update(name: String, members: [Data]) - case encryptionKeyPair([KeyPairWrapper]) // The new encryption key pair encrypted for each member individually + /// An encryption key pair encrypted for each member individually. + /// + /// - Note: `publicKey` is only set when an encryption key pair is sent in a one-to-one context (i.e. not in a group). + case encryptionKeyPair(publicKey: Data?, wrappers: [KeyPairWrapper]) case nameChange(name: String) case membersAdded(members: [Data]) case membersRemoved(members: [Data]) case memberLeft + case encryptionKeyPairRequest public var description: String { switch self { @@ -38,6 +42,7 @@ public final class ClosedGroupControlMessage : ControlMessage { case .membersAdded: return "membersAdded" case .membersRemoved: return "membersRemoved" case .memberLeft: return "memberLeft" + case .encryptionKeyPairRequest: return "encryptionKeyPairRequest" } } } @@ -103,6 +108,7 @@ public final class ClosedGroupControlMessage : ControlMessage { case .membersAdded(let members): return !members.isEmpty case .membersRemoved(let members): return !members.isEmpty case .memberLeft: return true + case .encryptionKeyPairRequest: return true } } @@ -123,8 +129,9 @@ public final class ClosedGroupControlMessage : ControlMessage { let members = coder.decodeObject(forKey: "members") as? [Data] else { return nil } self.kind = .update(name: name, members: members) case "encryptionKeyPair": + let publicKey = coder.decodeObject(forKey: "publicKey") as? Data guard let wrappers = coder.decodeObject(forKey: "wrappers") as? [KeyPairWrapper] else { return nil } - self.kind = .encryptionKeyPair(wrappers) + self.kind = .encryptionKeyPair(publicKey: publicKey, wrappers: wrappers) case "nameChange": guard let name = coder.decodeObject(forKey: "name") as? String else { return nil } self.kind = .nameChange(name: name) @@ -136,6 +143,8 @@ public final class ClosedGroupControlMessage : ControlMessage { self.kind = .membersRemoved(members: members) case "memberLeft": self.kind = .memberLeft + case "encryptionKeyPairRequest": + self.kind = .encryptionKeyPairRequest default: return nil } } @@ -155,8 +164,9 @@ public final class ClosedGroupControlMessage : ControlMessage { coder.encode("update", forKey: "kind") coder.encode(name, forKey: "name") coder.encode(members, forKey: "members") - case .encryptionKeyPair(let wrappers): + case .encryptionKeyPair(let publicKey, let wrappers): coder.encode("encryptionKeyPair", forKey: "kind") + coder.encode(publicKey, forKey: "publicKey") coder.encode(wrappers, forKey: "wrappers") case .nameChange(let name): coder.encode("nameChange", forKey: "kind") @@ -169,6 +179,8 @@ public final class ClosedGroupControlMessage : ControlMessage { coder.encode(members, forKey: "members") case .memberLeft: coder.encode("memberLeft", forKey: "kind") + case .encryptionKeyPairRequest: + coder.encode("encryptionKeyPairRequest", forKey: "kind") } } @@ -192,8 +204,9 @@ public final class ClosedGroupControlMessage : ControlMessage { guard let name = closedGroupControlMessageProto.name else { return nil } kind = .update(name: name, members: closedGroupControlMessageProto.members) case .encryptionKeyPair: + let publicKey = closedGroupControlMessageProto.publicKey let wrappers = closedGroupControlMessageProto.wrappers.compactMap { KeyPairWrapper.fromProto($0) } - kind = .encryptionKeyPair(wrappers) + kind = .encryptionKeyPair(publicKey: publicKey, wrappers: wrappers) case .nameChange: guard let name = closedGroupControlMessageProto.name else { return nil } kind = .nameChange(name: name) @@ -203,6 +216,8 @@ public final class ClosedGroupControlMessage : ControlMessage { kind = .membersRemoved(members: closedGroupControlMessageProto.members) case .memberLeft: kind = .memberLeft + case .encryptionKeyPairRequest: + kind = .encryptionKeyPairRequest } return ClosedGroupControlMessage(kind: kind) } @@ -232,8 +247,11 @@ public final class ClosedGroupControlMessage : ControlMessage { closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .update) closedGroupControlMessage.setName(name) closedGroupControlMessage.setMembers(members) - case .encryptionKeyPair(let wrappers): + case .encryptionKeyPair(let publicKey, let wrappers): closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .encryptionKeyPair) + if let publicKey = publicKey { + closedGroupControlMessage.setPublicKey(publicKey) + } closedGroupControlMessage.setWrappers(wrappers.compactMap { $0.toProto() }) case .nameChange(let name): closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .nameChange) @@ -246,6 +264,8 @@ public final class ClosedGroupControlMessage : ControlMessage { closedGroupControlMessage.setMembers(members) case .memberLeft: closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .memberLeft) + case .encryptionKeyPairRequest: + closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .encryptionKeyPairRequest) } let contentProto = SNProtoContent.builder() let dataMessageProto = SNProtoDataMessage.builder() diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 2a6a55a44..5d79bfbbb 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -2410,6 +2410,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM case membersAdded = 5 case membersRemoved = 6 case memberLeft = 7 + case encryptionKeyPairRequest = 8 } private class func SNProtoDataMessageClosedGroupControlMessageTypeWrap(_ value: SessionProtos_DataMessage.ClosedGroupControlMessage.TypeEnum) -> SNProtoDataMessageClosedGroupControlMessageType { @@ -2421,6 +2422,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM case .membersAdded: return .membersAdded case .membersRemoved: return .membersRemoved case .memberLeft: return .memberLeft + case .encryptionKeyPairRequest: return .encryptionKeyPairRequest } } @@ -2433,6 +2435,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM case .membersAdded: return .membersAdded case .membersRemoved: return .membersRemoved case .memberLeft: return .memberLeft + case .encryptionKeyPairRequest: return .encryptionKeyPairRequest } } diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index c0e0553b8..c266767ee 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -1152,6 +1152,9 @@ struct SessionProtos_DataMessage { case membersRemoved // = 6 case memberLeft // = 7 + /// wrappers + case encryptionKeyPairRequest // = 8 + init() { self = .new } @@ -1165,6 +1168,7 @@ struct SessionProtos_DataMessage { case 5: self = .membersAdded case 6: self = .membersRemoved case 7: self = .memberLeft + case 8: self = .encryptionKeyPairRequest default: return nil } } @@ -1178,6 +1182,7 @@ struct SessionProtos_DataMessage { case .membersAdded: return 5 case .membersRemoved: return 6 case .memberLeft: return 7 + case .encryptionKeyPairRequest: return 8 } } @@ -3131,6 +3136,7 @@ extension SessionProtos_DataMessage.ClosedGroupControlMessage.TypeEnum: SwiftPro 5: .same(proto: "MEMBERS_ADDED"), 6: .same(proto: "MEMBERS_REMOVED"), 7: .same(proto: "MEMBER_LEFT"), + 8: .same(proto: "ENCRYPTION_KEY_PAIR_REQUEST"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 9662b06bb..fc9e8f2c1 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -169,13 +169,14 @@ message DataMessage { message ClosedGroupControlMessage { enum Type { - NEW = 1; // publicKey, name, encryptionKeyPair, members, admins - UPDATE = 2; // name, members - ENCRYPTION_KEY_PAIR = 3; // wrappers - NAME_CHANGE = 4; // name - MEMBERS_ADDED = 5; // members - MEMBERS_REMOVED = 6; // members - MEMBER_LEFT = 7; + NEW = 1; // publicKey, name, encryptionKeyPair, members, admins + UPDATE = 2; // name, members + ENCRYPTION_KEY_PAIR = 3; // publicKey, wrappers + NAME_CHANGE = 4; // name + MEMBERS_ADDED = 5; // members + MEMBERS_REMOVED = 6; // members + MEMBER_LEFT = 7; + ENCRYPTION_KEY_PAIR_REQUEST = 8; } message KeyPairWrapper { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index cfbb6b3b6..166d1b2da 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -253,6 +253,7 @@ extension MessageReceiver { case .membersAdded: handleClosedGroupMembersAdded(message, using: transaction) case .membersRemoved: handleClosedGroupMembersRemoved(message, using: transaction) case .memberLeft: handleClosedGroupMemberLeft(message, using: transaction) + case .encryptionKeyPairRequest: handleClosedGroupEncryptionKeyPairRequest(message, using: transaction) } } @@ -294,7 +295,8 @@ extension MessageReceiver { private static func handleClosedGroupEncryptionKeyPair(_ message: ClosedGroupControlMessage, using transaction: Any) { // Prepare - guard case let .encryptionKeyPair(wrappers) = message.kind, let groupPublicKey = message.groupPublicKey else { return } + guard case let .encryptionKeyPair(explicitGroupPublicKey, wrappers) = message.kind, + let groupPublicKey = explicitGroupPublicKey?.toHexString() ?? message.groupPublicKey else { return } let transaction = transaction as! YapDatabaseReadWriteTransaction let userPublicKey = getUserHexEncodedPublicKey() guard let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { @@ -305,8 +307,8 @@ extension MessageReceiver { guard let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { return SNLog("Ignoring closed group encryption key pair for nonexistent group.") } - guard thread.groupModel.groupAdminIds.contains(message.sender!) else { - return SNLog("Ignoring closed group encryption key pair from non-admin.") + guard thread.groupModel.groupMemberIds.contains(message.sender!) else { + return SNLog("Ignoring closed group encryption key pair from non-member.") } // Find our wrapper and decrypt it if possible guard let wrapper = wrappers.first(where: { $0.publicKey == userPublicKey }), let encryptedKeyPair = wrapper.encryptedKeyPair else { return } @@ -329,7 +331,11 @@ extension MessageReceiver { } catch { return SNLog("Couldn't parse closed group encryption key pair.") } - // Store it + // Store it if needed + let closedGroupEncryptionKeyPairs = Storage.shared.getClosedGroupEncryptionKeyPairs(for: groupPublicKey) + guard !closedGroupEncryptionKeyPairs.contains(keyPair) else { + return SNLog("Ignoring duplicate closed group encryption key pair.") + } Storage.shared.addClosedGroupEncryptionKeyPair(keyPair, for: groupPublicKey, using: transaction) SNLog("Received a new closed group encryption key pair.") } @@ -450,6 +456,30 @@ extension MessageReceiver { } } + private static func handleClosedGroupEncryptionKeyPairRequest(_ message: ClosedGroupControlMessage, using transaction: Any) { + guard case .encryptionKeyPairRequest = message.kind else { return } + let transaction = transaction as! YapDatabaseReadWriteTransaction + guard let groupPublicKey = message.groupPublicKey else { return } + performIfValid(for: message, using: transaction) { groupID, _, group in + let publicKey = message.sender! + // Guard against self-sends + guard publicKey != getUserHexEncodedPublicKey() else { + return SNLog("Ignoring invalid closed group update.") + } + // Get the latest encryption key pair + guard let encryptionKeyPair = Storage.shared.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { return } + // Send it + guard let proto = try? SNProtoKeyPair.builder(publicKey: encryptionKeyPair.publicKey, + privateKey: encryptionKeyPair.privateKey).build(), let plaintext = try? proto.serializedData() else { return } + let thread = TSContactThread.getOrCreateThread(withContactId: publicKey, transaction: transaction) + guard let ciphertext = try? MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey) else { return } + SNLog("Responding to closed group encryption key pair request from: \(publicKey).") + let wrapper = ClosedGroupControlMessage.KeyPairWrapper(publicKey: publicKey, encryptedKeyPair: ciphertext) + let closedGroupControlMessage = ClosedGroupControlMessage(kind: .encryptionKeyPair(publicKey: Data(hex: groupPublicKey), wrappers: [ wrapper ])) + MessageSender.send(closedGroupControlMessage, in: thread, using: transaction) + } + } + private static func performIfValid(for message: ClosedGroupControlMessage, using transaction: Any, _ update: (Data, TSGroupThread, TSGroupModel) -> Void) { // Prepare let transaction = transaction as! YapDatabaseReadWriteTransaction diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index e6b61cd11..ddcce7150 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -22,7 +22,7 @@ public enum MessageReceiver { public var isRetryable: Bool { switch self { - case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, .noData, .senderBlocked, .selfSend, .decryptionFailed: return false + case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, .noData, .senderBlocked, .selfSend: return false default: return true } } @@ -88,8 +88,17 @@ public enum MessageReceiver { } } } - try decrypt() groupPublicKey = envelope.source + do { + try decrypt() + } catch { + do { + try MessageSender.requestEncryptionKeyPair(for: groupPublicKey!, using: transaction as! YapDatabaseReadWriteTransaction) + } catch { + // Do nothing + } + throw error // Throw the * decryption * error and not the error generated by requestEncryptionKeyPair (if it generated one) + } default: throw Error.unknownEnvelopeType } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift index 57386e521..03f6b14fb 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift @@ -69,7 +69,7 @@ extension MessageSender { let ciphertext = try MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey) return ClosedGroupControlMessage.KeyPairWrapper(publicKey: publicKey, encryptedKeyPair: ciphertext) } - let closedGroupControlMessage = ClosedGroupControlMessage(kind: .encryptionKeyPair(wrappers)) + let closedGroupControlMessage = ClosedGroupControlMessage(kind: .encryptionKeyPair(publicKey: nil, wrappers: wrappers)) let _ = MessageSender.sendNonDurably(closedGroupControlMessage, in: thread, using: transaction).done { // FIXME: It'd be great if we could make this a durable operation // Store it * after * having sent out the message to the group SNMessagingKitConfiguration.shared.storage.write { transaction in @@ -234,6 +234,21 @@ extension MessageSender { infoMessage.save(with: transaction) } + public static func requestEncryptionKeyPair(for groupPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) throws { + // Get the group, check preconditions & prepare + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) + let threadID = TSGroupThread.threadId(fromGroupId: groupID) + guard let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { + SNLog("Can't request encryption key pair for nonexistent closed group.") + throw Error.noThread + } + let group = thread.groupModel + guard group.groupMemberIds.contains(getUserHexEncodedPublicKey()) else { return } + // Send the request to the group + let closedGroupControlMessage = ClosedGroupControlMessage(kind: .encryptionKeyPairRequest) + MessageSender.send(closedGroupControlMessage, in: thread, using: transaction) + } + // MARK: - Deprecated