Debug
This commit is contained in:
parent
a6908e35a5
commit
832e70f588
30
Podfile.lock
30
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
|
||||
|
|
2
Pods
2
Pods
|
@ -1 +1 @@
|
|||
Subproject commit 8e09671add980ffbb06ba27e1bbb2aa558b5b4cf
|
||||
Subproject commit 341156442f131ceef3741497db79d67014728946
|
|
@ -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|
|
||||
|
|
|
@ -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:^{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) ]" }
|
||||
}
|
|
@ -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)) }
|
||||
|
||||
|
|
|
@ -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<Ratchet> {
|
||||
let (promise, seal) = Promise<Ratchet>.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<Ratchet> {
|
||||
let (promise, seal) = Promise<Ratchet>.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<Data> {
|
||||
return stepRatchetAsync(for: groupPublicKey, senderPublicKey: senderPublicKey, until: keyIndex).map2 { ratchet in
|
||||
let iv = ivAndCiphertext[0..<Int(ivSize)]
|
||||
let ciphertext = ivAndCiphertext[Int(ivSize)...]
|
||||
let gcm = GCM(iv: iv.bytes, tagLength: Int(gcmTagSize), mode: .combined)
|
||||
let messageKey = ratchet.messageKeys.last!
|
||||
let aes = try AES(key: Data(hex: messageKey).bytes, blockMode: gcm, padding: .noPadding)
|
||||
return Data(try aes.decrypt(ciphertext.bytes))
|
||||
}
|
||||
}
|
||||
|
||||
@objc(handleSharedSenderKeysUpdateIfNeeded:transaction:)
|
||||
public static func handleSharedSenderKeysUpdateIfNeeded(_ dataMessage: SSKProtoDataMessage, using transaction: YapDatabaseReadWriteTransaction) -> 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)
|
||||
|
|
|
@ -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..<Int(SharedSenderKeysImplementation.ivSize)]
|
||||
let ciphertext = ivAndCiphertext[Int(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)
|
||||
return Data(try aes.decrypt(ciphertext.bytes))
|
||||
}
|
||||
|
||||
public func isClosedGroup(_ publicKey: String) -> 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))!
|
||||
}
|
||||
}
|
|
@ -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<String> {
|
||||
var result: Set<String> = []
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<NSString *> *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<CipherMessage> 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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue