From 832e70f58803a9deca14078e6f5eb25df8dc6c60 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 30 Jun 2020 16:05:35 +1000 Subject: [PATCH] Debug --- Podfile.lock | 30 +-- Pods | 2 +- SessionServiceKit.podspec | 4 +- Signal/src/AppDelegate.m | 16 +- Signal/src/Loki/View Controllers/HomeVC.swift | 1 + .../src/Loki/View Controllers/LandingVC.swift | 2 +- .../View Controllers/NewClosedGroupVC.swift | 2 +- .../ConversationViewController.m | 2 +- .../HomeView/HomeViewController.m | 4 +- SignalServiceKit/protobuf/SignalService.proto | 9 - SignalServiceKit/src/Loki/API/SnodeAPI.swift | 2 +- .../Closed Groups/ClosedGroupRatchet.swift | 44 ++++ .../ClosedGroupUpdateMessage.swift | 4 + .../Closed Groups/ClosedGroupsProtocol.swift | 214 ++---------------- .../SharedSenderKeysImplementation.swift | 171 ++++++++++++++ .../Closed Groups/Storage+ClosedGroups.swift | 28 ++- .../Messages/Interactions/TSOutgoingMessage.m | 6 +- .../src/Messages/OWSMessageDecrypter.m | 21 +- .../src/Messages/OWSMessageHandler.m | 2 + .../src/Messages/OWSMessageManager.m | 33 +-- .../src/Messages/OWSMessageSender.m | 132 +++++------ 21 files changed, 360 insertions(+), 369 deletions(-) create mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift create mode 100644 SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift diff --git a/Podfile.lock b/Podfile.lock index ac7067012..4aeeeb19a 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.2): + - SessionAxolotlKit (1.0.4): - CocoaLumberjack - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.2) - SessionHKDFKit (~> 0.0.5) - SwiftProtobuf (~> 1.5.0) - - SessionAxolotlKit/Tests (1.0.2): + - SessionAxolotlKit/Tests (1.0.4): - CocoaLumberjack - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.2) @@ -72,18 +72,18 @@ PODS: - SessionHKDFKit/Tests (0.0.5): - CocoaLumberjack - SessionCoreKit - - SessionMetadataKit (1.0.3): + - SessionMetadataKit (1.0.4): - CocoaLumberjack - CryptoSwift (~> 1.3) - - SessionAxolotlKit (~> 1.0.2) + - SessionAxolotlKit (~> 1.0.4) - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.2) - SessionHKDFKit (~> 0.0.5) - SwiftProtobuf (~> 1.5.0) - - SessionMetadataKit/Tests (1.0.3): + - SessionMetadataKit/Tests (1.0.4): - CocoaLumberjack - CryptoSwift (~> 1.3) - - SessionAxolotlKit (~> 1.0.2) + - SessionAxolotlKit (~> 1.0.4) - 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.2) + - SessionAxolotlKit (~> 1.0.4) - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.3) - - SessionMetadataKit (~> 1.0.3) + - SessionMetadataKit (~> 1.0.4) - Starscream - SwiftProtobuf (~> 1.5.0) - YapDatabase/SQLCipher @@ -115,10 +115,10 @@ PODS: - PromiseKit (~> 6.0) - Reachability - SAMKeychain - - SessionAxolotlKit (~> 1.0.2) + - SessionAxolotlKit (~> 1.0.4) - SessionCoreKit (~> 1.0.0) - SessionCurve25519Kit (~> 2.1.3) - - SessionMetadataKit (~> 1.0.3) + - SessionMetadataKit (~> 1.0.4) - Starscream - SwiftProtobuf (~> 1.5.0) - YapDatabase/SQLCipher @@ -277,7 +277,7 @@ CHECKOUT OPTIONS: :commit: b72c2d1e6132501db906de2cffa8ded7803c54f4 :git: https://github.com/signalapp/Mantle SessionAxolotlKit: - :commit: 0338147cd5faefbb17e0bbf43cd008615ef64fe2 + :commit: e267e0c404d2a6126d889242d551c98fb8945158 :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: e23212e8494157d7a4daabbd4842be59117d9420 + :commit: fbdd35c99a147ea34bd2143ae30e1fd4407c346c :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: 88f72573df989042510d8a1737bd0c0057dda3da + SessionAxolotlKit: 3723011fe66a1e80af2f0e80e7b08460199a658a SessionCoreKit: 778a3f6e3da788b43497734166646025b6392e88 SessionCurve25519Kit: 9bb9afe199e4bc23578a4b15932ad2c57bd047b1 SessionHKDFKit: b0f4e669411703ab925aba07491c5611564d1419 - SessionMetadataKit: 581eb0da986e5a1752d07bfa89cf54bbe1fe3bca - SessionServiceKit: 344dff85e344fd3177d7b0b7aff4647a9d5e9efc + SessionMetadataKit: 2b0e500e6c7f8c425c596781e4307282fdc54bef + SessionServiceKit: 151860f2bd0decc7d735ab94d538d381e98a9be5 SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072 SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9 Starscream: 8aaf1a7feb805c816d0e7d3190ef23856f6665b9 diff --git a/Pods b/Pods index 8e09671ad..341156442 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 8e09671add980ffbb06ba27e1bbb2aa558b5b4cf +Subproject commit 341156442f131ceef3741497db79d67014728946 diff --git a/SessionServiceKit.podspec b/SessionServiceKit.podspec index ea9d11c0f..3d9f6fbb2 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.2' + s.dependency 'SessionAxolotlKit', '~> 1.0.4' 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.3' + s.dependency 'SessionMetadataKit', '~> 1.0.4' s.dependency 'PromiseKit', '~> 6.0' s.test_spec 'Tests' do |test_spec| diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index bfaa94427..0a4e9b5b9 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -176,8 +176,8 @@ static NSTimeInterval launchStartedAt; [DDLog flushLog]; // Loki: Stop pollers - [self stopPollerIfNeeded]; - [self stopOpenGroupPollersIfNeeded]; + [self stopPoller]; + [self stopOpenGroupPollers]; } - (void)applicationWillEnterForeground:(UIApplication *)application @@ -197,8 +197,8 @@ static NSTimeInterval launchStartedAt; [DDLog flushLog]; // Loki: Stop pollers - [self stopPollerIfNeeded]; - [self stopOpenGroupPollersIfNeeded]; + [self stopPoller]; + [self stopOpenGroupPollers]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { @@ -1387,7 +1387,7 @@ static NSTimeInterval launchStartedAt; [self.poller startIfNeeded]; } -- (void)stopPoller { [self.lokiPoller stop]; } +- (void)stopPoller { [self.poller stop]; } - (void)startClosedGroupPollerIfNeeded { @@ -1413,10 +1413,8 @@ static NSTimeInterval launchStartedAt; [SSKEnvironment.shared.messageSenderJobQueue clearAllJobs]; [SSKEnvironment.shared.identityManager clearIdentityKey]; [LKSnodeAPI clearSnodePool]; - [self stopPollerIfNeeded]; - [self stopOpenGroupPollersIfNeeded]; - [self.lokiNewsFeedPoller stop]; - [self.lokiMessengerUpdatesFeedPoller stop]; + [self stopPoller]; + [self stopOpenGroupPollers]; [LKPublicChatManager.shared stopPollers]; bool wasUnlinked = [NSUserDefaults.standardUserDefaults boolForKey:@"wasUnlinked"]; [SignalApp resetAppData:^{ diff --git a/Signal/src/Loki/View Controllers/HomeVC.swift b/Signal/src/Loki/View Controllers/HomeVC.swift index 41be5de7d..7ce0e771f 100644 --- a/Signal/src/Loki/View Controllers/HomeVC.swift +++ b/Signal/src/Loki/View Controllers/HomeVC.swift @@ -149,6 +149,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol if OWSIdentityManager.shared().identityKeyPair() != nil { let appDelegate = UIApplication.shared.delegate as! AppDelegate appDelegate.startPollerIfNeeded() + appDelegate.startClosedGroupPollerIfNeeded() appDelegate.startOpenGroupPollersIfNeeded() } // Populate onion request path countries cache diff --git a/Signal/src/Loki/View Controllers/LandingVC.swift b/Signal/src/Loki/View Controllers/LandingVC.swift index 2f2606e45..f02699cbc 100644 --- a/Signal/src/Loki/View Controllers/LandingVC.swift +++ b/Signal/src/Loki/View Controllers/LandingVC.swift @@ -162,7 +162,7 @@ final class LandingVC : BaseVC, LinkDeviceVCDelegate, DeviceLinkingModalDelegate func handleDeviceLinkingModalDismissed() { let appDelegate = UIApplication.shared.delegate as! AppDelegate - appDelegate.stopPollerIfNeeded() + appDelegate.stopPoller() TSAccountManager.sharedInstance().resetForReregistration() } diff --git a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift index 15868a02a..73e66a7e9 100644 --- a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift +++ b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift @@ -169,7 +169,7 @@ final class NewClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelegat return showError(title: NSLocalizedString("A closed group cannot have more than 20 members", comment: "")) } let selectedContacts = self.selectedContacts - ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in + ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in let _ = FileServerAPI.getDeviceLinks(associatedWith: selectedContacts).ensure2 { var thread: TSGroupThread! try! Storage.writeSync { transaction in diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 03d476e31..6c86ff223 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -1262,7 +1262,7 @@ typedef enum : NSUInteger { - (void)restoreSession { dispatch_async(dispatch_get_main_queue(), ^{ [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [LKSessionManagementProtocol startSessionResetInThread:self.thread using:transaction]; + [LKSessionManagementProtocol startSessionResetInThread:self.thread transaction:transaction]; } error:nil]; }); } diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index 6ea800faf..e597e2164 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -681,8 +681,8 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { [SSKEnvironment.shared.identityManager clearIdentityKey]; [LKSnodeAPI clearSnodePool]; AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate; - [appDelegate stopPollerIfNeeded]; - [appDelegate stopOpenGroupPollersIfNeeded]; + [appDelegate stopPoller]; + [appDelegate stopOpenGroupPollers]; [SSKEnvironment.shared.tsAccountManager resetForReregistration]; UIViewController *rootViewController = [[OnboardingController new] initialViewController]; OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:rootViewController]; diff --git a/SignalServiceKit/protobuf/SignalService.proto b/SignalServiceKit/protobuf/SignalService.proto index 9fa40a1c3..3353f0f94 100644 --- a/SignalServiceKit/protobuf/SignalService.proto +++ b/SignalServiceKit/protobuf/SignalService.proto @@ -132,15 +132,6 @@ message CallMessage { optional bytes profileKey = 6; } -message ClosedGroupCiphertext { - // @required - optional bytes ciphertext = 1; - // @required - optional string senderPublicKey = 2; - // @required - optional uint32 keyIndex = 3; -} - message DataMessage { enum Flags { END_SESSION = 1; diff --git a/SignalServiceKit/src/Loki/API/SnodeAPI.swift b/SignalServiceKit/src/Loki/API/SnodeAPI.swift index 2dcc3686f..6d25db9bb 100644 --- a/SignalServiceKit/src/Loki/API/SnodeAPI.swift +++ b/SignalServiceKit/src/Loki/API/SnodeAPI.swift @@ -169,7 +169,7 @@ public final class SnodeAPI : NSObject { try! Storage.writeSync { transaction in Storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey, using: transaction) } - let lastHash = Storage.getLastMessageHash(for: snode, associatedWith: publicKey) + let lastHash = Storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? "" let parameters = [ "pubKey" : publicKey, "lastHash" : lastHash ] return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) } diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift new file mode 100644 index 000000000..07fbf3ede --- /dev/null +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupRatchet.swift @@ -0,0 +1,44 @@ + +public final class ClosedGroupRatchet : NSObject, NSCoding { + public let chainKey: String + public let keyIndex: UInt + public let messageKeys: [String] + + // MARK: Initialization + public init(chainKey: String, keyIndex: UInt, messageKeys: [String]) { + self.chainKey = chainKey + self.keyIndex = keyIndex + self.messageKeys = messageKeys + } + + // MARK: Coding + public init?(coder: NSCoder) { + guard let chainKey = coder.decodeObject(forKey: "chainKey") as? String, + let keyIndex = coder.decodeObject(forKey: "keyIndex") as? UInt, + let messageKeys = coder.decodeObject(forKey: "messageKeys") as? [String] else { return nil } + self.chainKey = chainKey + self.keyIndex = UInt(keyIndex) + self.messageKeys = messageKeys + super.init() + } + + public func encode(with coder: NSCoder) { + coder.encode(chainKey, forKey: "chainKey") + coder.encode(keyIndex, forKey: "keyIndex") + coder.encode(messageKeys, forKey: "messageKeys") + } + + // MARK: Equality + override public func isEqual(_ other: Any?) -> Bool { + guard let other = other as? ClosedGroupRatchet else { return false } + return chainKey == other.chainKey && keyIndex == other.keyIndex && messageKeys == other.messageKeys + } + + // MARK: Hashing + override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:) + return chainKey.hashValue ^ keyIndex.hashValue ^ messageKeys.hashValue + } + + // MARK: Description + override public var description: String { return "[ chainKey : \(chainKey), keyIndex : \(keyIndex), messageKeys : \(messageKeys.prettifiedDescription) ]" } +} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift index 834d7be94..ad9fb664a 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupUpdateMessage.swift @@ -3,6 +3,10 @@ internal final class ClosedGroupUpdateMessage : TSOutgoingMessage { private let kind: Kind + @objc internal var isGroupCreationMessage: Bool { + if case .new = kind { return true } else { return false } + } + // MARK: Settings @objc internal override var ttl: UInt32 { return UInt32(TTLUtilities.getTTL(for: .closedGroupUpdate)) } diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift index 32219065a..0893a9b52 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift @@ -1,4 +1,3 @@ -import CryptoSwift import PromiseKit // A few notes about making changes in this file: @@ -13,57 +12,6 @@ import PromiseKit /// See [the documentation](https://github.com/loki-project/session-protocol-docs/wiki/Medium-Size-Groups) for more information. @objc(LKClosedGroupsProtocol) public final class ClosedGroupsProtocol : NSObject { - private static let gcmTagSize: UInt = 16 - private static let ivSize: UInt = 12 - - // A quick overview of how shared sender key based closed groups work: - // - // • When a user creates the group, they generate a key pair for the group along with a ratchet for - // every member of the group. They bundle this together with some other group info such as the group - // name in a `ClosedGroupUpdateMessage` and send that using established channels to every member of - // the group. Note that because a user can only pick from their existing contacts when selecting - // the group members they don't need to establish sessions before being able to send the - // `ClosedGroupUpdateMessage`. Another way to optimize the performance of the group creation process - // is to batch fetch the device links of all members involved ahead of time, rather than letting - // the sending pipeline do it separately for every user the `ClosedGroupUpdateMessage` is sent to. - // • After the group is created, every user polls for the public key associated with the group. - // • Upon receiving a `ClosedGroupUpdateMessage` of type `.new`, a user sends session requests to all - // other members of the group they don't yet have a session with for reasons outlined below. - // • When a user sends a message they step their ratchet and use the resulting message key to encrypt - // the message. - // • When another user receives that message, they step the ratchet associated with the sender and - // use the resulting message key to decrypt the message. - // • When a user leaves the group, new ratchets must be generated for all members to ensure that the - // user that left can't decrypt messages going forward. To this end every user deletes all ratchets - // associated with the group in question upon receiving a group update message that indicates that - // a user left. They then generate a new ratchet for themselves and send it out to all members of - // the group (again fetching device links ahead of time). The user should already have established - // sessions with all other members at this point because of the behavior outlined a few points above. - // • When a user adds a new member to the group, they generate a ratchet for that new member and - // send that bundled in a `ClosedGroupUpdateMessage` to the group. They send a - // `ClosedGroupUpdateMessage` with the newly generated ratchet but also the existing ratchets of - // every other member of the group to the user that joined. - // • When a user kicks a member from the group, they re-generate ratchets for everyone and send - // those out to all members (minus the member that was just kicked) in a - // `ClosedGroupUpdateMessage` using established channels. - - public struct Ratchet { - public let chainKey: String - public let keyIndex: UInt - public let messageKeys: [String] - } - - public enum RatchetingError : LocalizedError { - case loadingFailed(groupPublicKey: String, senderPublicKey: String) - case messageKeyMissing(targetKeyIndex: UInt, groupPublicKey: String, senderPublicKey: String) - - public var errorDescription: String? { - switch self { - case .loadingFailed(let groupPublicKey, let senderPublicKey): return "Couldn't get ratchet for closed group with public key: \(groupPublicKey), sender public key: \(senderPublicKey)." - case .messageKeyMissing(let targetKeyIndex, let groupPublicKey, let senderPublicKey): return "Couldn't find message key for old key index: \(targetKeyIndex), public key: \(groupPublicKey), sender public key: \(senderPublicKey)." - } - } - } /// - 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. @@ -72,13 +20,15 @@ public final class ClosedGroupsProtocol : NSObject { let userPublicKey = getUserHexEncodedPublicKey() // Generate a key pair for the group let groupKeyPair = Curve25519.generateKeyPair() - let groupPublicKey = groupKeyPair.publicKey.toHexString() + let groupPublicKey = groupKeyPair.hexEncodedPublicKey // Ensure the current user's master device is included in the member list membersAsSet.remove(userPublicKey) membersAsSet.insert(UserDefaults.standard[.masterHexEncodedPublicKey] ?? userPublicKey) // Create ratchets for all users involved let members = [String](membersAsSet) - let ratchets = members.map { generateRatchet(for: groupPublicKey, senderPublicKey: $0, transaction: transaction) } + let ratchets = members.map { + SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: $0, using: transaction) + } // Create the group let admins = [ UserDefaults.standard[.masterHexEncodedPublicKey] ?? userPublicKey ] let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) @@ -89,12 +39,16 @@ public final class ClosedGroupsProtocol : NSObject { SSKEnvironment.shared.profileManager.addThread(toProfileWhitelist: thread) // Send a closed group update message to all members involved let chainKeys = ratchets.map { Data(hex: $0.chainKey) } - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: groupKeyPair.publicKey, name: name, groupPrivateKey: groupKeyPair.privateKey, chainKeys: chainKeys, members: members, admins: admins) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + for member in members { + 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, chainKeys: chainKeys, members: members, admins: admins) + let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) + let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue + messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + } // Store the group's key pair - Storage.addClosedGroupKeyPair(groupKeyPair) + Storage.setClosedGroupPrivateKey(groupKeyPair.privateKey.toHexString(), for: groupPublicKey, using: transaction) // Notify the user let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate) infoMessage.save(with: transaction) @@ -104,144 +58,13 @@ public final class ClosedGroupsProtocol : NSObject { return thread } - private static func generateRatchet(for groupPublicKey: String, senderPublicKey: String, transaction: YapDatabaseReadWriteTransaction) -> Ratchet { - let rootChainKey = Data.getSecureRandomData(ofSize: 32)!.toHexString() - let ratchet = Ratchet(chainKey: rootChainKey, keyIndex: 0, messageKeys: []) - Storage.setClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: ratchet, transaction: transaction) - return ratchet - } - - private static func step(_ ratchet: Ratchet) throws -> Ratchet { - let nextMessageKey = try HMAC(key: Data(hex: ratchet.chainKey).bytes, variant: .sha256).authenticate([ UInt8(1) ]) - let nextChainKey = try HMAC(key: Data(hex: ratchet.chainKey).bytes, variant: .sha256).authenticate([ UInt8(2) ]) - let nextKeyIndex = ratchet.keyIndex + 1 - return Ratchet(chainKey: nextChainKey.toHexString(), keyIndex: nextKeyIndex, messageKeys: ratchet.messageKeys + [ nextMessageKey.toHexString() ]) - } - - /// - Note: Sync. Don't call from the main thread. - private static func stepRatchetOnce(for groupPublicKey: String, senderPublicKey: String, transaction: YapDatabaseReadWriteTransaction) throws -> Ratchet { - #if DEBUG - assert(!Thread.isMainThread) - #endif - guard let ratchet = Storage.getClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) else { - let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) - print("[Loki] \(error.errorDescription!)") - throw error - } - do { - let result = try step(ratchet) - Storage.setClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, transaction: transaction) - return result - } catch { - print("[Loki] Couldn't step ratchet due to error: \(error).") - throw error - } - } - - private static func stepRatchetOnceAsync(for groupPublicKey: String, senderPublicKey: String) -> Promise { - let (promise, seal) = Promise.pending() - SnodeAPI.workQueue.async { - try! Storage.writeSync { transaction in - do { - let result = try stepRatchetOnce(for: groupPublicKey, senderPublicKey: senderPublicKey, transaction: transaction) - seal.fulfill(result) - } catch { - seal.reject(error) - } - } - } - return promise - } - - /// - Note: Sync. Don't call from the main thread. - private static func stepRatchet(for groupPublicKey: String, senderPublicKey: String, until targetKeyIndex: UInt, transaction: YapDatabaseReadWriteTransaction) throws -> Ratchet { - #if DEBUG - assert(!Thread.isMainThread) - #endif - guard let ratchet = Storage.getClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) else { - let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) - print("[Loki] \(error.errorDescription!)") - throw error - } - if targetKeyIndex < ratchet.keyIndex { - // There's no need to advance the ratchet if this is invoked for an old key index - guard ratchet.messageKeys.count > targetKeyIndex else { - let error = RatchetingError.messageKeyMissing(targetKeyIndex: targetKeyIndex, groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) - print("[Loki] \(error.errorDescription!)") - throw error - } - return ratchet - } else { - var currentKeyIndex = ratchet.keyIndex - var result = ratchet - while currentKeyIndex < targetKeyIndex { - do { - result = try step(result) - } catch { - print("[Loki] Couldn't step ratchet due to error: \(error).") - throw error - } - } - Storage.setClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, transaction: transaction) - return result - } - } - - private static func stepRatchetAsync(for groupPublicKey: String, senderPublicKey: String, until targetKeyIndex: UInt) -> Promise { - let (promise, seal) = Promise.pending() - SnodeAPI.workQueue.async { - try! Storage.writeSync { transaction in - do { - let result = try stepRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, until: targetKeyIndex, transaction: transaction) - seal.fulfill(result) - } catch { - seal.reject(error) - } - } - } - return promise - } - - @objc(encryptPlaintext:forGroupWithPublicKey:senderPublicKey:) - static func objc_encrypt(_ plaintext: Data, for groupPublicKey: String, senderPublicKey: String) -> [Any]? { - guard let (ivAndCiphertext, keyIndex) = try? encrypt(plaintext, for: groupPublicKey, senderPublicKey: senderPublicKey).wait() else { return nil } - return [ ivAndCiphertext, NSNumber(value: keyIndex) ] - } - - public static func encrypt(_ plaintext: Data, for groupPublicKey: String, senderPublicKey: String) -> Promise<(ivAndCiphertext: Data, keyIndex: UInt)> { - return stepRatchetOnceAsync(for: groupPublicKey, senderPublicKey: senderPublicKey).map2 { ratchet in - let iv = Data.getSecureRandomData(ofSize: ivSize)! - let gcm = GCM(iv: iv.bytes, tagLength: Int(gcmTagSize), mode: .combined) - let messageKey = ratchet.messageKeys.last! - let aes = try AES(key: messageKey.bytes, blockMode: gcm, padding: .noPadding) - let ciphertext = try aes.encrypt(plaintext.bytes) - return (ivAndCiphertext: iv + Data(bytes: ciphertext), ratchet.keyIndex) - } - } - - @objc(decryptCiphertext:forGroupWithPublicKey:senderPublicKey:keyIndex:) - static func objc_decrypt(_ ivAndCiphertext: Data, for groupPublicKey: String, senderPublicKey: String, keyIndex: UInt) -> Data? { - return try? decrypt(ivAndCiphertext, for: groupPublicKey, senderPublicKey: senderPublicKey, keyIndex: keyIndex).wait() - } - - public static func decrypt(_ ivAndCiphertext: Data, for groupPublicKey: String, senderPublicKey: String, keyIndex: UInt) -> Promise { - return stepRatchetAsync(for: groupPublicKey, senderPublicKey: senderPublicKey, until: keyIndex).map2 { ratchet in - let iv = ivAndCiphertext[0.. Bool { guard let closedGroupUpdate = dataMessage.closedGroupUpdate else { return false } switch closedGroupUpdate.type { case .new: // Unwrap the message - let groupPublicKey = closedGroupUpdate.groupPublicKey + let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() let name = closedGroupUpdate.name let groupPrivateKey = closedGroupUpdate.groupPrivateKey! let chainKeys = closedGroupUpdate.chainKeys @@ -249,19 +72,18 @@ public final class ClosedGroupsProtocol : NSObject { let admins = closedGroupUpdate.admins // Persist the ratchets zip(members, chainKeys).forEach { (member, chainKey) in - let ratchet = Ratchet(chainKey: chainKey.toHexString(), keyIndex: 0, messageKeys: []) - Storage.setClosedGroupRatchet(groupPublicKey: groupPublicKey.toHexString(), senderPublicKey: member, ratchet: ratchet, transaction: transaction) + let ratchet = ClosedGroupRatchet(chainKey: chainKey.toHexString(), keyIndex: 0, messageKeys: []) + Storage.setClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: member, ratchet: ratchet, using: transaction) } // Create the group - let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey.toHexString()) + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) let group = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) let thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction) thread.usesSharedSenderKeys = true thread.save(with: transaction) SSKEnvironment.shared.profileManager.addThread(toProfileWhitelist: thread) // Add the group to the user's set of public keys to poll for - let groupKeyPair = ECKeyPair(publicKey: groupPublicKey, privateKey: groupPrivateKey)! - Storage.addClosedGroupKeyPair(groupKeyPair) + Storage.setClosedGroupPrivateKey(groupPrivateKey.toHexString(), for: groupPublicKey, using: transaction) // Notify the user let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate) infoMessage.save(with: transaction) diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift new file mode 100644 index 000000000..cad72d498 --- /dev/null +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/SharedSenderKeysImplementation.swift @@ -0,0 +1,171 @@ +import CryptoSwift +import PromiseKit +import SessionMetadataKit + +@objc(LKSharedSenderKeysImplementation) +public final class SharedSenderKeysImplementation : NSObject, SharedSenderKeysProtocol { + private static let gcmTagSize: UInt = 16 + private static let ivSize: UInt = 12 + + // MARK: Documentation + // A quick overview of how shared sender key based closed groups work: + // + // • When a user creates the group, they generate a key pair for the group along with a ratchet for + // every member of the group. They bundle this together with some other group info such as the group + // name in a `ClosedGroupUpdateMessage` and send that using established channels to every member of + // the group. Note that because a user can only pick from their existing contacts when selecting + // the group members they don't need to establish sessions before being able to send the + // `ClosedGroupUpdateMessage`. Another way to optimize the performance of the group creation process + // is to batch fetch the device links of all members involved ahead of time, rather than letting + // the sending pipeline do it separately for every user the `ClosedGroupUpdateMessage` is sent to. + // • After the group is created, every user polls for the public key associated with the group. + // • Upon receiving a `ClosedGroupUpdateMessage` of type `.new`, a user sends session requests to all + // other members of the group they don't yet have a session with for reasons outlined below. + // • When a user sends a message they step their ratchet and use the resulting message key to encrypt + // the message. + // • When another user receives that message, they step the ratchet associated with the sender and + // use the resulting message key to decrypt the message. + // • When a user leaves the group, new ratchets must be generated for all members to ensure that the + // user that left can't decrypt messages going forward. To this end every user deletes all ratchets + // associated with the group in question upon receiving a group update message that indicates that + // a user left. They then generate a new ratchet for themselves and send it out to all members of + // the group (again fetching device links ahead of time). The user should already have established + // sessions with all other members at this point because of the behavior outlined a few points above. + // • When a user adds a new member to the group, they generate a ratchet for that new member and + // send that bundled in a `ClosedGroupUpdateMessage` to the group. They send a + // `ClosedGroupUpdateMessage` with the newly generated ratchet but also the existing ratchets of + // every other member of the group to the user that joined. + // • When a user kicks a member from the group, they re-generate ratchets for everyone and send + // those out to all members (minus the member that was just kicked) in a + // `ClosedGroupUpdateMessage` using established channels. + + // MARK: Ratcheting Error + public enum RatchetingError : LocalizedError { + case loadingFailed(groupPublicKey: String, senderPublicKey: String) + case messageKeyMissing(targetKeyIndex: UInt, groupPublicKey: String, senderPublicKey: String) + + public var errorDescription: String? { + switch self { + case .loadingFailed(let groupPublicKey, let senderPublicKey): return "Couldn't get ratchet for closed group with public key: \(groupPublicKey), sender public key: \(senderPublicKey)." + case .messageKeyMissing(let targetKeyIndex, let groupPublicKey, let senderPublicKey): return "Couldn't find message key for old key index: \(targetKeyIndex), public key: \(groupPublicKey), sender public key: \(senderPublicKey)." + } + } + } + + // MARK: Initialization + @objc public static let shared = SharedSenderKeysImplementation() + + private override init() { } + + // MARK: Private/Internal API + internal func generateRatchet(for groupPublicKey: String, senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) -> ClosedGroupRatchet { + let rootChainKey = Data.getSecureRandomData(ofSize: 32)!.toHexString() + let ratchet = ClosedGroupRatchet(chainKey: rootChainKey, keyIndex: 0, messageKeys: []) + Storage.setClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: ratchet, using: transaction) + return ratchet + } + + private func step(_ ratchet: ClosedGroupRatchet) throws -> ClosedGroupRatchet { + let nextMessageKey = try HMAC(key: Data(hex: ratchet.chainKey).bytes, variant: .sha256).authenticate([ UInt8(1) ]) + let nextChainKey = try HMAC(key: Data(hex: ratchet.chainKey).bytes, variant: .sha256).authenticate([ UInt8(2) ]) + let nextKeyIndex = ratchet.keyIndex + 1 + return ClosedGroupRatchet(chainKey: nextChainKey.toHexString(), keyIndex: nextKeyIndex, messageKeys: ratchet.messageKeys + [ nextMessageKey.toHexString() ]) + } + + /// - Note: Sync. Don't call from the main thread. + private func stepRatchetOnce(for groupPublicKey: String, senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) throws -> ClosedGroupRatchet { + #if DEBUG + assert(!Thread.isMainThread) + #endif + guard let ratchet = Storage.getClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) else { + let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) + print("[Loki] \(error.errorDescription!)") + throw error + } + do { + let result = try step(ratchet) + Storage.setClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, using: transaction) + return result + } catch { + print("[Loki] Couldn't step ratchet due to error: \(error).") + throw error + } + } + + /// - Note: Sync. Don't call from the main thread. + private func stepRatchet(for groupPublicKey: String, senderPublicKey: String, until targetKeyIndex: UInt, using transaction: YapDatabaseReadWriteTransaction) throws -> ClosedGroupRatchet { + #if DEBUG + assert(!Thread.isMainThread) + #endif + guard let ratchet = Storage.getClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) else { + let error = RatchetingError.loadingFailed(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) + print("[Loki] \(error.errorDescription!)") + throw error + } + if targetKeyIndex < ratchet.keyIndex { + // There's no need to advance the ratchet if this is invoked for an old key index + guard ratchet.messageKeys.count > targetKeyIndex else { + let error = RatchetingError.messageKeyMissing(targetKeyIndex: targetKeyIndex, groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey) + print("[Loki] \(error.errorDescription!)") + throw error + } + return ratchet + } else { + var currentKeyIndex = ratchet.keyIndex + var result = ratchet + while currentKeyIndex < targetKeyIndex { + do { + result = try step(result) + currentKeyIndex = result.keyIndex + } catch { + print("[Loki] Couldn't step ratchet due to error: \(error).") + throw error + } + } + Storage.setClosedGroupRatchet(groupPublicKey: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: result, using: transaction) + return result + } + } + + @objc(encrypt:forGroupWithPublicKey:senderPublicKey:protocolContext:error:) + public func encrypt(_ plaintext: Data, forGroupWithPublicKey groupPublicKey: String, senderPublicKey: String, protocolContext: Any) throws -> [Any] { + let transaction = protocolContext as! YapDatabaseReadWriteTransaction + let (ivAndCiphertext, keyIndex) = try encrypt(plaintext, for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) + return [ ivAndCiphertext, NSNumber(value: keyIndex) ] + } + + public func encrypt(_ plaintext: Data, for groupPublicKey: String, senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) throws -> (ivAndCiphertext: Data, keyIndex: UInt) { + let ratchet = try stepRatchetOnce(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction) + let iv = Data.getSecureRandomData(ofSize: SharedSenderKeysImplementation.ivSize)! + let gcm = GCM(iv: iv.bytes, tagLength: Int(SharedSenderKeysImplementation.gcmTagSize), mode: .combined) + let messageKey = ratchet.messageKeys.last! + let aes = try AES(key: Data(hex: messageKey).bytes, blockMode: gcm, padding: .noPadding) + let ciphertext = try aes.encrypt(plaintext.bytes) + return (ivAndCiphertext: iv + Data(bytes: ciphertext), ratchet.keyIndex) + } + + @objc(decrypt:forGroupWithPublicKey:senderPublicKey:keyIndex:protocolContext:error:) + public func decrypt(_ ivAndCiphertext: Data, forGroupWithPublicKey groupPublicKey: String, senderPublicKey: String, keyIndex: UInt, protocolContext: Any) throws -> Data { + let transaction = protocolContext as! YapDatabaseReadWriteTransaction + return try decrypt(ivAndCiphertext, for: groupPublicKey, senderPublicKey: senderPublicKey, keyIndex: keyIndex, using: transaction) + } + + 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 iv = ivAndCiphertext[0.. Bool { + return Storage.getUserClosedGroupPublicKeys().contains(publicKey) + } + + public func getKeyPair(forGroupWithPublicKey groupPublicKey: String) -> ECKeyPair { + let privateKey = Storage.getClosedGroupPrivateKey(for: groupPublicKey)! + return ECKeyPair(publicKey: Data(hex: groupPublicKey.removing05PrefixIfNeeded()), privateKey: Data(hex: privateKey))! + } +} diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/Storage+ClosedGroups.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/Storage+ClosedGroups.swift index f7c1a82ed..bc9e3ab37 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/Storage+ClosedGroups.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/Storage+ClosedGroups.swift @@ -4,16 +4,16 @@ internal extension Storage { // MARK: Ratchets internal static let closedGroupRatchetCollection = "LokiClosedGroupRatchetCollection" - internal static func getClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String) -> ClosedGroupsProtocol.Ratchet? { + internal static func getClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String) -> ClosedGroupRatchet? { let key = "\(groupPublicKey).\(senderPublicKey)" - var result: ClosedGroupsProtocol.Ratchet? + var result: ClosedGroupRatchet? read { transaction in - result = transaction.object(forKey: key, inCollection: closedGroupRatchetCollection) as? ClosedGroupsProtocol.Ratchet + result = transaction.object(forKey: key, inCollection: closedGroupRatchetCollection) as? ClosedGroupRatchet } return result } - internal static func setClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String, ratchet: ClosedGroupsProtocol.Ratchet, transaction: YapDatabaseReadWriteTransaction) { + internal static func setClosedGroupRatchet(groupPublicKey: String, senderPublicKey: String, ratchet: ClosedGroupRatchet, using transaction: YapDatabaseReadWriteTransaction) { let key = "\(groupPublicKey).\(senderPublicKey)" transaction.setObject(ratchet, forKey: key, inCollection: closedGroupRatchetCollection) } @@ -21,29 +21,27 @@ internal extension Storage { @objc internal extension Storage { - // MARK: Key Pairs - internal static let closedGroupKeyPairCollection = "LokiClosedGroupKeyPairCollection" + // MARK: Private Keys + internal static let closedGroupPrivateKeyCollection = "LokiClosedGroupPrivateKeyCollection" internal static func getUserClosedGroupPublicKeys() -> Set { var result: Set = [] read { transaction in - result = Set(transaction.allKeys(inCollection: closedGroupKeyPairCollection)) + result = Set(transaction.allKeys(inCollection: closedGroupPrivateKeyCollection)) } return result } - @objc(getKeyPairForClosedGroupWithPublicKey:) - internal static func getClosedGroupKeyPair(for publicKey: String) -> ECKeyPair? { - var result: ECKeyPair? + @objc(getPrivateKeyForClosedGroupWithPublicKey:) + internal static func getClosedGroupPrivateKey(for publicKey: String) -> String? { + var result: String? read { transaction in - result = transaction.object(forKey: publicKey, inCollection: closedGroupKeyPairCollection) as? ECKeyPair + result = transaction.object(forKey: publicKey, inCollection: closedGroupPrivateKeyCollection) as? String } return result } - internal static func addClosedGroupKeyPair(_ keyPair: ECKeyPair) { - try! writeSync { transaction in - transaction.setObject(keyPair, forKey: keyPair.hexEncodedPublicKey, inCollection: closedGroupKeyPairCollection) - } + internal static func setClosedGroupPrivateKey(_ privateKey: String, for publicKey: String, using transaction: YapDatabaseReadWriteTransaction) { + transaction.setObject(privateKey, forKey: publicKey, inCollection: closedGroupPrivateKeyCollection) } } diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 3422bc08f..b21048258 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -1117,7 +1117,7 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt NSError *error; SSKProtoDataMessage *_Nullable dataProto = [builder buildAndReturnError:&error]; - if (error || dataProto == nil) { + if (error != nil || dataProto == nil) { OWSFailDebug(@"Couldn't build protobuf due to error: %@.", error); return nil; } @@ -1127,7 +1127,7 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt - (nullable id)prepareCustomContentBuilder:(SignalRecipient *)recipient { SSKProtoDataMessage *_Nullable dataMessage = [self buildDataMessage:recipient.recipientId]; - if (!dataMessage) { + if (dataMessage == nil) { OWSFailDebug(@"Couldn't build protobuf."); return nil; } @@ -1144,7 +1144,7 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt NSError *error; NSData *_Nullable contentData = [contentBuilder buildSerializedDataAndReturnError:&error]; - if (error || !contentData) { + if (error != nil || contentData == nil) { OWSFailDebug(@"Couldn't serialize protobuf due to error: %@.", error); return nil; } diff --git a/SignalServiceKit/src/Messages/OWSMessageDecrypter.m b/SignalServiceKit/src/Messages/OWSMessageDecrypter.m index db4dc9b6d..f897a055b 100644 --- a/SignalServiceKit/src/Messages/OWSMessageDecrypter.m +++ b/SignalServiceKit/src/Messages/OWSMessageDecrypter.m @@ -489,10 +489,11 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes NSError *cipherError; SMKSecretSessionCipher *_Nullable cipher = [[SMKSecretSessionCipher alloc] initWithSessionResetImplementation:self.sessionResetImplementation - sessionStore:self.primaryStorage - preKeyStore:self.primaryStorage - signedPreKeyStore:self.primaryStorage - identityStore:self.identityManager + sessionStore:self.primaryStorage + preKeyStore:self.primaryStorage + signedPreKeyStore:self.primaryStorage + identityStore:self.identityManager + sharedSenderKeysImplementation:LKSharedSenderKeysImplementation.shared error:&cipherError]; if (cipherError || !cipher) { @@ -503,17 +504,20 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes ECKeyPair *keyPair = nil; // Loki: SMKSecretSessionCipher will fall back on the user's key pair if this is nil if (envelope.type == SSKProtoEnvelopeTypeClosedGroupCiphertext) { - keyPair = [LKStorage getKeyPairForClosedGroupWithPublicKey:envelope.source]; + NSString *groupPrivateKey = [LKStorage getPrivateKeyForClosedGroupWithPublicKey:envelope.source]; + if (groupPrivateKey != nil) { + keyPair = [[ECKeyPair alloc] initWithPublicKey:[NSData dataFromHexString:[envelope.source removing05PrefixIfNeeded]] privateKey:[NSData dataFromHexString:groupPrivateKey]]; + } } NSError *decryptError; SMKDecryptResult *_Nullable decryptResult = [cipher throwswrapped_decryptMessageWithCertificateValidator:certificateValidator + senderPublicKey:envelope.source cipherTextData:encryptedData timestamp:serverTimestamp localRecipientId:localRecipientId localDeviceId:localDeviceId - keyPair:keyPair protocolContext:transaction error:&decryptError]; @@ -585,7 +589,6 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes return; } - OWSFailDebug(@"%@", underlyingError); failureBlock(underlyingError); return; } @@ -688,8 +691,8 @@ NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDes envelope:(SSKProtoEnvelope *)envelope transaction:(YapDatabaseReadWriteTransaction *)transaction { - NSString *hexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source; - TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction]; + NSString *masterPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source; + TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:masterPublicKey transaction:transaction]; [SSKEnvironment.shared.notificationsManager notifyUserForErrorMessage:errorMessage thread:contactThread transaction:transaction]; diff --git a/SignalServiceKit/src/Messages/OWSMessageHandler.m b/SignalServiceKit/src/Messages/OWSMessageHandler.m index 7f0209498..b17f2bdf8 100644 --- a/SignalServiceKit/src/Messages/OWSMessageHandler.m +++ b/SignalServiceKit/src/Messages/OWSMessageHandler.m @@ -38,6 +38,8 @@ NSString *envelopeAddress(SSKProtoEnvelope *envelope) return @"UnidentifiedSender"; case SSKProtoEnvelopeTypeFriendRequest: return @"LokiFriendRequest"; + case SSKProtoEnvelopeTypeClosedGroupCiphertext: + return @"ClosedGroupCiphertext"; default: // Shouldn't happen OWSProdFail([OWSAnalyticsEvents messageManagerErrorEnvelopeTypeOther]); diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index c01d411e1..27b6c75f5 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -273,6 +273,7 @@ NS_ASSUME_NONNULL_BEGIN case SSKProtoEnvelopeTypeFriendRequest: case SSKProtoEnvelopeTypeCiphertext: case SSKProtoEnvelopeTypePrekeyBundle: + case SSKProtoEnvelopeTypeClosedGroupCiphertext: case SSKProtoEnvelopeTypeUnidentifiedSender: if (!plaintextData) { OWSFailDebug(@"missing decrypted data for envelope: %@", [self descriptionForEnvelope:envelope]); @@ -424,27 +425,9 @@ NS_ASSUME_NONNULL_BEGIN return; } - // Loki: Decrypt closed group message if applicable - NSData *sharedSenderKeysPlaintext = nil; - if ([[LKStorage getUserClosedGroupPublicKeys] containsObject:envelope.source]) { + if (envelope.content != nil) { NSError *error; - SSKProtoClosedGroupCiphertext *_Nullable closedGroupCiphertextProto = [SSKProtoClosedGroupCiphertext parseData:plaintextData error:&error]; - if (error != nil || closedGroupCiphertextProto == nil) { - OWSFailDebug(@"Couldn't parse proto due to error: %@.", error); - return; - } - NSString *senderPublicKey = closedGroupCiphertextProto.senderPublicKey; - uint32_t keyIndex = closedGroupCiphertextProto.keyIndex; - sharedSenderKeysPlaintext = [LKClosedGroupsProtocol decryptCiphertext:closedGroupCiphertextProto.ciphertext forGroupWithPublicKey:envelope.source senderPublicKey:senderPublicKey keyIndex:keyIndex]; - if (sharedSenderKeysPlaintext == nil) { - OWSFailDebug(@"Couldn't parse proto due to error: %@.", error); - return; - } - } - - if (envelope.content != nil || sharedSenderKeysPlaintext != nil) { - NSError *error; - SSKProtoContent *_Nullable contentProto = [SSKProtoContent parseData:(sharedSenderKeysPlaintext ?: plaintextData) error:&error]; + SSKProtoContent *_Nullable contentProto = [SSKProtoContent parseData:plaintextData error:&error]; if (error != nil || contentProto == nil) { OWSFailDebug(@"Couldn't parse proto due to error: %@.", error); return; @@ -564,7 +547,7 @@ NS_ASSUME_NONNULL_BEGIN } } - BOOL usesSharedSenderKeys = [LKClosedGroupsProtocol handleSharedSenderKeysUpdateIfNeeded:dataMessage transaction:transaction]; + [LKClosedGroupsProtocol handleSharedSenderKeysUpdateIfNeeded:dataMessage transaction:transaction]; if (dataMessage.group) { TSGroupThread *_Nullable groupThread = @@ -1033,7 +1016,7 @@ NS_ASSUME_NONNULL_BEGIN // Loki: Handle closed groups sync message [LKSyncMessagesProtocol handleClosedGroupSyncMessageIfNeeded:syncMessage wrappedIn:envelope transaction:transaction]; } else if (syncMessage.openGroups != nil) { - // Loki: Handle open groups sync message + // Loki: Handle open group sync message [LKSyncMessagesProtocol handleOpenGroupSyncMessageIfNeeded:syncMessage wrappedIn:envelope transaction:transaction]; } else { OWSLogWarn(@"Ignoring unsupported sync message."); @@ -1359,10 +1342,8 @@ NS_ASSUME_NONNULL_BEGIN BOOL wasCurrentUserRemovedFromGroup = [removedMemberIds containsObject:userMasterPublicKey]; if (!wasCurrentUserRemovedFromGroup) { - if (!newGroupThread.usesSharedSenderKeys) { - // Loki: Try to establish sessions with all members involved when a group is created or updated - [LKClosedGroupsProtocol establishSessionsIfNeededWithClosedGroupMembers:newMemberIds.allObjects inThread:newGroupThread transaction:transaction]; - } + // Loki: Try to establish sessions with all members involved when a group is created or updated + [LKClosedGroupsProtocol establishSessionsIfNeededWithClosedGroupMembers:newMemberIds.allObjects inThread:newGroupThread transaction:transaction]; } [[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 4684ca7ee..6d58c65d9 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -387,9 +387,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; // // So we're using YDB behavior to ensure this invariant, which is a bit // unorthodox. - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [allAttachmentIds addObjectsFromArray:[OutgoingMessagePreparer prepareMessageForSending:message transaction:transaction]]; - } error:nil]; + if (message.attachmentIds.count > 0) { + [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [allAttachmentIds addObjectsFromArray:[OutgoingMessagePreparer prepareMessageForSending:message transaction:transaction]]; + } error:nil]; + } NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message]; @@ -516,11 +518,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; } else if (thread.isGroupThread) { TSGroupThread *groupThread = (TSGroupThread *)thread; recipientIds = [LKSessionMetaProtocol getDestinationsForOutgoingGroupMessage:message inThread:thread]; - __block NSString *userMasterHexEncodedPublicKey; + __block NSString *userMasterPublicKey; [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - userMasterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userPublicKey in:transaction] ?: userPublicKey; + userMasterPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userPublicKey in:transaction] ?: userPublicKey; }]; - if ([recipientIds containsObject:userMasterHexEncodedPublicKey]) { + if ([recipientIds containsObject:userMasterPublicKey]) { OWSFailDebug(@"Message send recipients should not include self."); } } else if ([thread isKindOfClass:TSContactThread.class]) { @@ -1063,21 +1065,21 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; publicChat = [LKDatabaseUtilities getPublicChatForThreadID:message.uniqueThreadId transaction: transaction]; }]; if (publicChat != nil) { - NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; + NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; NSString *displayName = SSKEnvironment.shared.profileManager.localProfileName; if (displayName == nil) { displayName = @"Anonymous"; } TSQuotedMessage *quote = message.quotedMessage; uint64_t quoteID = quote.timestamp; - NSString *quoteeHexEncodedPublicKey = quote.authorId; + NSString *quoteePublicKey = quote.authorId; __block uint64_t quotedMessageServerID = 0; if (quoteID != 0) { [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - quotedMessageServerID = [LKDatabaseUtilities getServerIDForQuoteWithID:quoteID quoteeHexEncodedPublicKey:quoteeHexEncodedPublicKey threadID:messageSend.thread.uniqueId transaction:transaction]; + quotedMessageServerID = [LKDatabaseUtilities getServerIDForQuoteWithID:quoteID quoteeHexEncodedPublicKey:quoteePublicKey threadID:messageSend.thread.uniqueId transaction:transaction]; }]; } NSString *body = (message.body != nil && message.body.length > 0) ? message.body : [NSString stringWithFormat:@"%@", @(message.timestamp)]; // Workaround for the fact that the back-end doesn't accept messages without a body - LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName body:body type:LKPublicChatAPI.publicChatMessageType - timestamp:message.timestamp quotedMessageTimestamp:quoteID quoteeHexEncodedPublicKey:quoteeHexEncodedPublicKey quotedMessageBody:quote.body quotedMessageServerID:quotedMessageServerID signatureData:nil signatureVersion:0]; + LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userPublicKey displayName:displayName body:body type:LKPublicChatAPI.publicChatMessageType + timestamp:message.timestamp quotedMessageTimestamp:quoteID quoteeHexEncodedPublicKey:quoteePublicKey quotedMessageBody:quote.body quotedMessageServerID:quotedMessageServerID signatureData:nil signatureVersion:0]; OWSLinkPreview *linkPreview = message.linkPreview; if (linkPreview != nil) { TSAttachmentStream *attachment = [TSAttachmentStream fetchObjectWithUniqueID:linkPreview.imageAttachmentId]; @@ -1092,7 +1094,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; NSUInteger height = attachment.shouldHaveImageSize ? @(attachment.imageSize.height).unsignedIntegerValue : 0; [groupMessage addAttachmentWithKind:@"attachment" server:publicChat.server serverID:attachment.serverId contentType:attachment.contentType size:attachment.byteCount fileName:attachment.sourceFilename flags:0 width:width height:height caption:attachment.caption url:attachment.downloadURL linkPreviewURL:nil linkPreviewTitle:nil]; } - message.actualSenderHexEncodedPublicKey = userHexEncodedPublicKey; + message.actualSenderHexEncodedPublicKey = userPublicKey; [[LKPublicChatAPI sendMessage:groupMessage toGroup:publicChat.channel onServer:publicChat.server] .thenOn(OWSDispatch.sendingQueue, ^(LKGroupMessage *groupMessage) { [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { @@ -1105,13 +1107,13 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; failedMessageSend(error); }) retainUntilComplete]; } else { - NSString *targetHexEncodedPublicKey = recipient.recipientId; - NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; + NSString *targetPublicKey = recipient.recipientId; + NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; __block BOOL isUserLinkedDevice; [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - isUserLinkedDevice = [LKDatabaseUtilities isUserLinkedDevice:targetHexEncodedPublicKey in:transaction]; + isUserLinkedDevice = [LKDatabaseUtilities isUserLinkedDevice:targetPublicKey in:transaction]; }]; - if ([targetHexEncodedPublicKey isEqual:userHexEncodedPublicKey]) { + if ([targetPublicKey isEqual:userPublicKey]) { [LKLogger print:[NSString stringWithFormat:@"[Loki] Sending %@ to self.", message.class]]; } else if (isUserLinkedDevice) { [LKLogger print:[NSString stringWithFormat:@"[Loki] Sending %@ to %@ (one of the current user's linked devices).", message.class, recipient.recipientId]]; @@ -1122,9 +1124,16 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; SSKProtoEnvelopeType type = ((NSNumber *)signalMessageInfo[@"type"]).integerValue; if ([message isKindOfClass:OWSEndSessionMessage.class]) { type = SSKProtoEnvelopeTypeFriendRequest; + } else if ([messageSend.thread isKindOfClass:TSGroupThread.class] && ((TSGroupThread *)messageSend.thread).usesSharedSenderKeys) { + type = SSKProtoEnvelopeTypeClosedGroupCiphertext; } uint64_t timestamp = message.timestamp; - NSString *senderID = type == SSKProtoEnvelopeTypeUnidentifiedSender ? @"" : userHexEncodedPublicKey; + NSString *senderID = (type == SSKProtoEnvelopeTypeUnidentifiedSender) ? @"" : userPublicKey; + if ([messageSend.thread isKindOfClass:TSGroupThread.class] && ((TSGroupThread *)messageSend.thread).usesSharedSenderKeys) { + senderID = [LKGroupUtilities getDecodedGroupID:((TSGroupThread *)messageSend.thread).groupModel.groupId]; + } else { + OWSAssertDebug([senderID isEqual:@""]); + } uint32_t senderDeviceID = type == SSKProtoEnvelopeTypeUnidentifiedSender ? 0 : OWSDevicePrimaryDeviceId; NSString *content = signalMessageInfo[@"content"]; NSString *recipientID = signalMessageInfo[@"destination"]; @@ -1472,22 +1481,22 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; OWSOutgoingSentMessageTranscript *sentMessageTranscript = [[OWSOutgoingSentMessageTranscript alloc] initWithOutgoingMessage:message isRecipientUpdate:isRecipientUpdate]; - NSString *currentDevice = self.tsAccountManager.localNumber; + NSString *userPublicKey = self.tsAccountManager.localNumber; // Loki: Send to the other device, but not self __block NSSet *linkedDevices; [self.primaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - linkedDevices = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:currentDevice in:transaction]; + linkedDevices = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:userPublicKey in:transaction]; }]; NSString *otherDevice; for (NSString *device in linkedDevices) { - if (![device isEqual:currentDevice]) { + if (![device isEqual:userPublicKey]) { otherDevice = device; break; } } - NSString *recipientId = otherDevice ?: currentDevice; + NSString *recipientId = otherDevice ?: userPublicKey; __block SignalRecipient *recipient; [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { recipient = [SignalRecipient markRecipientAsRegisteredAndGet:recipientId transaction:transaction]; @@ -1715,8 +1724,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; OWSPrimaryStorage *storage = self.primaryStorage; TSOutgoingMessage *message = messageSend.message; - // This may throw an exception - if ([LKSessionManagementProtocol isSessionRequiredForMessage:messageSend.message] + if ([LKSessionManagementProtocol isSessionRequiredForMessage:message] && ![storage containsSession:recipientID deviceId:@(OWSDevicePrimaryDeviceId).intValue protocolContext:transaction]) { NSString *missingSessionException = @"missingSessionException"; OWSRaiseException(missingSessionException, @@ -1725,10 +1733,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; @(OWSDevicePrimaryDeviceId)); } - BOOL isFriendRequestMessage = [messageSend.message isKindOfClass:LKFriendRequestMessage.class]; - BOOL isSessionRequestMessage = [messageSend.message isKindOfClass:LKSessionRequestMessage.class]; - BOOL isDeviceLinkMessage = [messageSend.message isKindOfClass:LKDeviceLinkMessage.class] - && ((LKDeviceLinkMessage *)messageSend.message).kind == LKDeviceLinkMessageKindRequest; + BOOL isFriendRequestMessage = [message isKindOfClass:LKFriendRequestMessage.class]; + BOOL isSessionRequestMessage = [message isKindOfClass:LKSessionRequestMessage.class]; + BOOL isDeviceLinkMessage = [message isKindOfClass:LKDeviceLinkMessage.class] + && ((LKDeviceLinkMessage *)message).kind == LKDeviceLinkMessageKindRequest; SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:storage preKeyStore:storage @@ -1741,66 +1749,35 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; TSWhisperMessageType messageType; if (messageSend.isUDSend) { NSError *error; + LKSessionResetImplementation *sessionResetImplementation = [[LKSessionResetImplementation alloc] initWithStorage:self.primaryStorage]; + SMKSecretSessionCipher *_Nullable secretCipher = - [[SMKSecretSessionCipher alloc] initWithSessionStore:self.primaryStorage - preKeyStore:self.primaryStorage - signedPreKeyStore:self.primaryStorage - identityStore:self.identityManager - error:&error]; + [[SMKSecretSessionCipher alloc] initWithSessionResetImplementation:sessionResetImplementation + sessionStore:self.primaryStorage + preKeyStore:self.primaryStorage + signedPreKeyStore:self.primaryStorage + identityStore:self.identityManager + sharedSenderKeysImplementation:LKSharedSenderKeysImplementation.shared + error:&error]; if (error || !secretCipher) { OWSRaiseException(@"SecretSessionCipherFailure", @"Can't create secret session cipher."); } - if ([messageSend.thread isKindOfClass:TSGroupThread.class] && ((TSGroupThread *)messageSend.thread).usesSharedSenderKeys) { - NSString *groupPublicKey = [LKGroupUtilities getDecodedGroupID:((TSGroupThread *)messageSend.thread).groupModel.groupId]; - NSString *senderPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - NSArray *ciphertextAndKeyIndex = [LKClosedGroupsProtocol encryptPlaintext:plainText.paddedMessageBody forGroupWithPublicKey:groupPublicKey senderPublicKey:senderPublicKey]; - if (ciphertextAndKeyIndex.count != 2) { - OWSFailDebug(@"Couldn't encrypt closed group message."); - return nil; - } - NSData *ivAndCiphertext = ciphertextAndKeyIndex[0]; - NSNumber *keyIndex = ciphertextAndKeyIndex[1]; - SSKProtoClosedGroupCiphertextBuilder *builder = [SSKProtoClosedGroupCiphertext builderWithCiphertext:ivAndCiphertext senderPublicKey:senderPublicKey keyIndex:keyIndex.unsignedIntValue]; - SSKProtoClosedGroupCiphertext *closedGroupCiphertext = [builder buildAndReturnError:&error]; - if (closedGroupCiphertext == nil) { - OWSFailDebug(@"Couldn't build closed group message due to error: %@.", error); - return nil; - } - ECKeyPair *keyPair = [LKStorage getKeyPairForClosedGroupWithPublicKey:groupPublicKey]; - if (keyPair == nil) { - OWSFailDebug(@"Missing key pair for closed group with public key: %@.", groupPublicKey); - return nil; - } - serializedMessage = [secretCipher throwswrapped_encryptMessageWithRecipientId:recipientID - deviceId:@(OWSDevicePrimaryDeviceId).intValue - paddedPlaintext:nil - closedGroupCiphertext:closedGroupCiphertext - senderCertificate:messageSend.senderCertificate - keyPair:keyPair - protocolContext:transaction - useFallbackSessionCipher:NO - error:&error]; - } else { - serializedMessage = [secretCipher throwswrapped_encryptMessageWithRecipientId:recipientID - deviceId:@(OWSDevicePrimaryDeviceId).intValue - paddedPlaintext:plainText.paddedMessageBody - closedGroupCiphertext:nil - senderCertificate:messageSend.senderCertificate - keyPair:nil - protocolContext:transaction - useFallbackSessionCipher:isFriendRequestMessage || isSessionRequestMessage || isDeviceLinkMessage - error:&error]; - } + serializedMessage = [secretCipher throwswrapped_encryptMessageWithRecipientPublicKey:recipientID + deviceID:@(OWSDevicePrimaryDeviceId).intValue + paddedPlaintext:plainText.paddedMessageBody + senderCertificate:messageSend.senderCertificate + protocolContext:transaction + useFallbackSessionCipher:isFriendRequestMessage || isSessionRequestMessage || isDeviceLinkMessage + error:&error]; SCKRaiseIfExceptionWrapperError(error); - if (!serializedMessage || error) { + if (serializedMessage == nil || error != nil) { OWSFailDebug(@"Error while UD encrypting message: %@.", error); return nil; } messageType = TSUnidentifiedSenderMessageType; } else { - // This may throw an exception id encryptedMessage = [cipher throws_encryptMessage:[plainText paddedMessageBody] protocolContext:transaction]; serializedMessage = encryptedMessage.serialized; @@ -1809,7 +1786,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; BOOL isSilent = message.isSilent; BOOL isOnline = message.isOnline; - BOOL isPing = NO; OWSMessageServiceParams *messageParams = [[OWSMessageServiceParams alloc] initWithType:messageType @@ -1820,12 +1796,12 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; isOnline:isOnline registrationId:[cipher throws_remoteRegistrationId:transaction] ttl:message.ttl - isPing:isPing]; + isPing:NO]; NSError *error; NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error]; - if (error) { + if (error != nil) { OWSProdError([OWSAnalyticsEvents messageSendErrorCouldNotSerializeMessageJson]); return nil; }