diff --git a/SignalServiceKit/protobuf/SignalService.proto b/SignalServiceKit/protobuf/SignalService.proto index 3676e152a..ba737af42 100644 --- a/SignalServiceKit/protobuf/SignalService.proto +++ b/SignalServiceKit/protobuf/SignalService.proto @@ -243,9 +243,9 @@ message DataMessage { message ClosedGroupUpdate { // Loki enum Type { - NEW = 0; // groupPublicKey, name, groupPrivateKey, chainKeys, members, admins - INFO = 1; // groupPublicKey, name, chainKeys, members, admins - CHAIN_KEY = 2; // groupPublicKey, chainKeys + NEW = 0; // groupPublicKey, name, groupPrivateKey, senderKeys, members, admins + INFO = 1; // groupPublicKey, name, senderKeys, members, admins + SENDER_KEY = 2; // groupPublicKey, senderKeys } message SenderKey { diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift new file mode 100644 index 000000000..26ee6a8c3 --- /dev/null +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupSenderKey.swift @@ -0,0 +1,44 @@ + +internal final class ClosedGroupSenderKey : NSObject, NSCoding { + internal let chainKey: Data + internal let keyIndex: UInt + + // MARK: Initialization + init(chainKey: Data, keyIndex: UInt) { + self.chainKey = chainKey + self.keyIndex = keyIndex + } + + // MARK: Coding + public init?(coder: NSCoder) { + guard let chainKey = coder.decodeObject(forKey: "chainKey") as? Data, + let keyIndex = coder.decodeObject(forKey: "keyIndex") as? UInt else { return nil } + self.chainKey = chainKey + self.keyIndex = UInt(keyIndex) + super.init() + } + + public func encode(with coder: NSCoder) { + coder.encode(chainKey, forKey: "chainKey") + coder.encode(keyIndex, forKey: "keyIndex") + } + + // MARK: Proto Conversion + internal func toProto() throws -> SSKProtoDataMessageClosedGroupUpdateSenderKey { + return try SSKProtoDataMessageClosedGroupUpdateSenderKey.builder(chainKey: chainKey, keyIndex: UInt32(keyIndex)).build() + } + + // MARK: Equality + override public func isEqual(_ other: Any?) -> Bool { + guard let other = other as? ClosedGroupSenderKey else { return false } + return chainKey == other.chainKey && keyIndex == other.keyIndex + } + + // MARK: Hashing + override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:) + return chainKey.hashValue ^ keyIndex.hashValue + } + + // MARK: Description + override public var description: String { return "[ chainKey : \(chainKey), keyIndex : \(keyIndex) ]" } +} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift index bc74baa52..f4e00d3aa 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift @@ -9,21 +9,11 @@ internal final class ClosedGroupUpdateMessage : TSOutgoingMessage { @objc internal override func shouldBeSaved() -> Bool { return false } @objc internal override func shouldSyncTranscript() -> Bool { return false } - // MARK: Sender Key - internal struct SenderKey { - internal let chainKey: Data - internal let keyIndex: UInt - - internal func toProto() throws -> SSKProtoDataMessageClosedGroupUpdateSenderKey { - return try SSKProtoDataMessageClosedGroupUpdateSenderKey.builder(chainKey: chainKey, keyIndex: UInt32(keyIndex)).build() - } - } - // MARK: Kind internal enum Kind { - case new(groupPublicKey: Data, name: String, groupPrivateKey: Data, senderKeys: [SenderKey], members: [String], admins: [String]) - case info(groupPublicKey: Data, name: String, senderKeys: [SenderKey], members: [String], admins: [String]) - case chainKey(groupPublicKey: Data, senderKey: SenderKey) + 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 senderKey(groupPublicKey: Data, senderKey: ClosedGroupSenderKey) } // MARK: Initialization @@ -34,12 +24,63 @@ internal final class ClosedGroupUpdateMessage : TSOutgoingMessage { groupMetaMessage: .unspecified, quotedMessage: nil, contactShare: nil, linkPreview: nil) } - required init?(coder: NSCoder) { + required init(dictionary: [String:Any]) throws { preconditionFailure("Use init(thread:kind:) instead.") } - required init(dictionary: [String:Any]) throws { - preconditionFailure("Use init(thread:kind:) instead.") + // MARK: Coding + internal required init?(coder: NSCoder) { + guard let thread = coder.decodeObject(forKey: "thread") as? TSThread, + let timestamp = coder.decodeObject(forKey: "timestamp") as? UInt64, + let groupPublicKey = coder.decodeObject(forKey: "groupPublicKey") as? Data, + let senderKeys = coder.decodeObject(forKey: "senderKeys") as? [ClosedGroupSenderKey], + let rawKind = coder.decodeObject(forKey: "kind") as? String else { return nil } + switch rawKind { + 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 } + 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 } + self.kind = .info(groupPublicKey: groupPublicKey, name: name, senderKeys: senderKeys, members: members, admins: admins) + case "senderKey": + guard let senderKey = senderKeys.first else { return nil } + self.kind = .senderKey(groupPublicKey: groupPublicKey, senderKey: senderKey) + default: return nil + } + super.init(outgoingMessageWithTimestamp: timestamp, in: thread, messageBody: "", + attachmentIds: NSMutableArray(), expiresInSeconds: 0, expireStartedAt: 0, isVoiceMessage: false, + groupMetaMessage: .unspecified, quotedMessage: nil, contactShare: nil, linkPreview: nil) + } + + internal override func encode(with coder: NSCoder) { + coder.encode(thread, forKey: "thread") + coder.encode(timestamp, forKey: "timestamp") + switch kind { + case .new(let groupPublicKey, let name, let groupPrivateKey, let senderKeys, let members, let admins): + coder.encode("new", forKey: "kind") + coder.encode(groupPublicKey, forKey: "groupPublicKey") + coder.encode(name, forKey: "name") + coder.encode(groupPrivateKey, forKey: "groupPrivateKey") + coder.encode(senderKeys, forKey: "senderKeys") + coder.encode(members, forKey: "members") + coder.encode(admins, forKey: "admins") + case .info(let groupPublicKey, let name, let senderKeys, let members, let admins): + coder.encode("info", forKey: "kind") + coder.encode(groupPublicKey, forKey: "groupPublicKey") + coder.encode(name, forKey: "name") + coder.encode(senderKeys, forKey: "senderKeys") + coder.encode(members, forKey: "members") + coder.encode(admins, forKey: "admins") + case .senderKey(let groupPublicKey, let senderKey): + coder.encode("senderKey", forKey: "kind") + coder.encode(groupPublicKey, forKey: "groupPublicKey") + coder.encode([ senderKey ], forKey: "senderKeys") + } } // MARK: Building @@ -61,7 +102,7 @@ internal final class ClosedGroupUpdateMessage : TSOutgoingMessage { closedGroupUpdate.setSenderKeys(try senderKeys.map { try $0.toProto() }) closedGroupUpdate.setMembers(members) closedGroupUpdate.setAdmins(admins) - case .chainKey(let groupPublicKey, let senderKey): + case .senderKey(let groupPublicKey, let senderKey): closedGroupUpdate = SSKProtoDataMessageClosedGroupUpdate.builder(groupPublicKey: groupPublicKey, type: .chainKey) 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 077e222cc..5be674145 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift @@ -15,7 +15,6 @@ public final class ClosedGroupsProtocol : NSObject { // TODO: // • Always reset all ratchets if someone leaves or is kicked? - // • Closed group update message deserialization // • Multi device // • ClosedGroupsProtocol // • SyncMessagesProtocol @@ -48,7 +47,7 @@ public final class ClosedGroupsProtocol : NSObject { // the user can only pick from existing contacts) establishSessionsIfNeeded(with: members, using: transaction) // Send a closed group update message to all members involved using established channels - let senderKeys = ratchets.map { ClosedGroupUpdateMessage.SenderKey(chainKey: Data(hex: $0.chainKey), keyIndex: $0.keyIndex) } + let senderKeys = ratchets.map { ClosedGroupSenderKey(chainKey: Data(hex: $0.chainKey), keyIndex: $0.keyIndex) } for member in members { let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) @@ -89,13 +88,13 @@ public final class ClosedGroupsProtocol : NSObject { } // Send a closed group update message to the existing members with the new members' ratchets (this message is // aimed at the group) - let senderKeys = ratchets.map { ClosedGroupUpdateMessage.SenderKey(chainKey: Data(hex: $0.chainKey), keyIndex: $0.keyIndex) } + let senderKeys = ratchets.map { ClosedGroupSenderKey(chainKey: Data(hex: $0.chainKey), keyIndex: $0.keyIndex) } let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: senderKeys, members: members, admins: admins) let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // Send closed group update messages to the new members using established channels let allSenderKeys = Storage.getAllClosedGroupRatchets(for: groupPublicKey).map { - ClosedGroupUpdateMessage.SenderKey(chainKey: Data(hex: $0.chainKey), keyIndex: $0.keyIndex) + ClosedGroupSenderKey(chainKey: Data(hex: $0.chainKey), keyIndex: $0.keyIndex) } for member in newMembers { let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: allSenderKeys, members: members, admins: admins) @@ -144,7 +143,7 @@ public final class ClosedGroupsProtocol : NSObject { } // Send a closed group update message to all members (minus the ones that were removed) with everyone's new // ratchets using established channels - let senderKeys = ratchets.map { ClosedGroupUpdateMessage.SenderKey(chainKey: Data(hex: $0.chainKey), keyIndex: $0.keyIndex) } + let senderKeys = ratchets.map { ClosedGroupSenderKey(chainKey: Data(hex: $0.chainKey), keyIndex: $0.keyIndex) } for member in members { let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) @@ -277,12 +276,12 @@ public final class ClosedGroupsProtocol : NSObject { Storage.removeAllClosedGroupRatchets(for: groupPublicKey, using: transaction) let userPublicKey = getUserHexEncodedPublicKey() let newRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) - let newSenderKey = ClosedGroupUpdateMessage.SenderKey(chainKey: Data(hex: newRatchet.chainKey), keyIndex: newRatchet.keyIndex) + let newSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: newRatchet.chainKey), keyIndex: newRatchet.keyIndex) Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, ratchet: newRatchet, using: transaction) for member in members { let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.chainKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: newSenderKey) + let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: newSenderKey) let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction)