Add encryption key pair request message

pull/349/head
Niels Andriesse 3 years ago
parent 0cbdc8be96
commit 1fda8e4249

@ -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 {

@ -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()

@ -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
}
}

@ -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"),
]
}

@ -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 {

@ -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

@ -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
}
}

@ -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

Loading…
Cancel
Save