Merge pull request #223 from loki-project/shared-sender-keys

SSK Protocol Changes
This commit is contained in:
Niels Andriesse 2020-07-09 12:30:12 +10:00 committed by GitHub
commit e25f4aec38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 200 additions and 85 deletions

View file

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

2
Pods

@ -1 +1 @@
Subproject commit 49611da3fc5eac88efd8d192e9ca7e2a84813a7e
Subproject commit 158e066d79806ef0e466370da5c303dedb90c654

View file

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

View file

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

View file

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

View file

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

View file

@ -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<String>, transaction: YapDatabaseReadWriteTransaction) -> Promise<TSGroupThread> {
@ -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<String> = 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<String> = 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<String> = []
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<String> = []
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<String> = []
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

View file

@ -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..<Int(SharedSenderKeysImplementation.ivSize)]
let ciphertext = ivAndCiphertext[Int(SharedSenderKeysImplementation.ivSize)...]
let gcm = GCM(iv: iv.bytes, tagLength: Int(SharedSenderKeysImplementation.gcmTagSize), mode: .combined)
guard let messageKey = ratchet.messageKeys.last else { throw RatchetingError.messageKeyMissing(targetKeyIndex: keyIndex, groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) }
guard let messageKey = ratchet.messageKeys.last else {
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))
}

View file

@ -26,7 +26,7 @@ public extension Storage {
read { transaction in
transaction.enumerateRows(inCollection: collection) { key, object, _, _ in
guard let publicKey = key as? String, let ratchet = object as? ClosedGroupRatchet else { return }
let senderKey = ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: publicKey)
let senderKey = ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: Data(hex: publicKey))
result.insert(senderKey)
}
}

View file

@ -74,8 +74,8 @@ public final class SyncMessagesProtocol : NSObject {
let group = thread.groupModel
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(group.groupId)
let name = group.groupName!
let members = group.groupMemberIds
let admins = group.groupAdminIds
let members = group.groupMemberIds.map { Data(hex: $0) }
let admins = group.groupAdminIds.map { Data(hex: $0) }
guard let groupPrivateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey) else {
print("[Loki] Couldn't get private key for SSK based closed group.")
return AnyPromise.from(Promise<Void>(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() {

View file

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

View file

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