diff --git a/SignalServiceKit/src/Messages/OWSMessageSend.swift b/SignalServiceKit/src/Messages/OWSMessageSend.swift index 85af96518..2187ff0e3 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSend.swift +++ b/SignalServiceKit/src/Messages/OWSMessageSend.swift @@ -54,7 +54,7 @@ public class OWSMessageSend: NSObject { thread: TSThread?, recipient: SignalRecipient, senderCertificate: SMKSenderCertificate?, - udManager: OWSUDManager, + udAccessKey: SMKUDAccessKey?, localNumber: String, success: @escaping () -> Void, failure: @escaping (Error) -> Void) { @@ -63,17 +63,9 @@ public class OWSMessageSend: NSObject { self.recipient = recipient self.localNumber = localNumber self.senderCertificate = senderCertificate + self.udAccessKey = udAccessKey if let recipientId = recipient.uniqueId { - if senderCertificate != nil { - let accessMode = udManager.unidentifiedAccessMode(forRecipientId: recipientId) - if accessMode == .unrestricted { - self.udAccessKey = udManager.randomUDAccessKey() - } else if accessMode == .unrestricted { - self.udAccessKey = udManager.udAccessKey(forRecipientId: recipientId) - } - } - self.isLocalNumber = localNumber == recipientId } else { owsFailDebug("SignalRecipient missing recipientId") diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 07be78079..c3e168d21 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -578,6 +578,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; message:(TSOutgoingMessage *)message thread:(nullable TSThread *)thread senderCertificate:(nullable SMKSenderCertificate *)senderCertificate + selfUDAccessKey:(nullable SMKUDAccessKey *)selfUDAccessKey sendErrors:(NSMutableArray *)sendErrors { OWSAssertDebug(recipients.count > 0); @@ -589,11 +590,16 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; for (SignalRecipient *recipient in recipients) { // Use chained promises to make the code more readable. AnyPromise *sendPromise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { + SMKUDAccessKey *_Nullable theirUDAccessKey; + if (senderCertificate != nil && selfUDAccessKey != nil) { + theirUDAccessKey = [self.udManager udSendAccessKeyForRecipientId:recipient.recipientId]; + } + OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:message thread:thread recipient:recipient senderCertificate:senderCertificate - udManager:self.udManager + udAccessKey:theirUDAccessKey localNumber:self.tsAccountManager.localNumber success:^{ // The value doesn't matter, we just need any non-NSError value. @@ -624,10 +630,16 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; { AssertIsOnSendingQueue(); + SMKUDAccessKey *_Nullable selfUDAccessKey; + if (senderCertificate) { + selfUDAccessKey = [self.udManager udSendAccessKeyForRecipientId:self.tsAccountManager.localNumber]; + } + void (^successHandler)(void) = ^() { dispatch_async([OWSDispatch sendingQueue], ^{ [self handleMessageSentLocally:message senderCertificate:senderCertificate + selfUDAccessKey:selfUDAccessKey success:^{ successHandlerParam(); } @@ -645,6 +657,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; dispatch_async([OWSDispatch sendingQueue], ^{ [self handleMessageSentLocally:message senderCertificate:senderCertificate + selfUDAccessKey:selfUDAccessKey success:^{ failureHandlerParam(error); } @@ -726,6 +739,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; message:message thread:thread senderCertificate:senderCertificate + selfUDAccessKey:selfUDAccessKey sendErrors:sendErrors] .then(^(id value) { successHandler(); @@ -1328,6 +1342,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; - (void)handleMessageSentLocally:(TSOutgoingMessage *)message senderCertificate:(nullable SMKSenderCertificate *)senderCertificate + selfUDAccessKey:(nullable SMKUDAccessKey *)selfUDAccessKey success:(void (^)(void))success failure:(RetryableFailureHandler)failure { @@ -1344,6 +1359,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; [self sendSyncTranscriptForMessage:message senderCertificate:senderCertificate + selfUDAccessKey:selfUDAccessKey success:^{ // TODO: We might send to a recipient, then to another recipient on retry. // To ensure desktop receives all "delivery status" info, we might @@ -1360,6 +1376,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; - (void)sendSyncTranscriptForMessage:(TSOutgoingMessage *)message senderCertificate:(nullable SMKSenderCertificate *)senderCertificate + selfUDAccessKey:(nullable SMKUDAccessKey *)selfUDAccessKey success:(void (^)(void))success failure:(RetryableFailureHandler)failure { @@ -1376,7 +1393,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; thread:message.thread recipient:recipient senderCertificate:senderCertificate - udManager:self.udManager + udAccessKey:selfUDAccessKey localNumber:self.tsAccountManager.localNumber success:^{ OWSLogInfo(@"Successfully sent sync transcript."); diff --git a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift index f4da1ff0b..092a73d34 100644 --- a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift +++ b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift @@ -20,6 +20,19 @@ public enum UnidentifiedAccessMode: Int { case unrestricted } +private func string(forUnidentifiedAccessMode mode: UnidentifiedAccessMode) -> String { + switch mode { + case .unknown: + return "unknown" + case .enabled: + return "enabled" + case .disabled: + return "disabled" + case .unrestricted: + return "unrestricted" + } +} + @objc public protocol OWSUDManager: class { @objc func setup() @@ -28,6 +41,8 @@ public enum UnidentifiedAccessMode: Int { @objc func isUDEnabled() -> Bool + @objc func isUDVerboseLoggingEnabled() -> Bool + // MARK: - Recipient State @objc @@ -42,6 +57,9 @@ public enum UnidentifiedAccessMode: Int { @objc func udAccessKey(forRecipientId recipientId: RecipientIdentifier) -> SMKUDAccessKey? + @objc + func udSendAccessKey(forRecipientId recipientId: RecipientIdentifier) -> SMKUDAccessKey? + // MARK: Sender Certificate // We use completion handlers instead of a promise so that message sending @@ -52,8 +70,10 @@ public enum UnidentifiedAccessMode: Int { // MARK: Unrestricted Access - @objc func shouldAllowUnrestrictedAccessLocal() -> Bool - @objc func setShouldAllowUnrestrictedAccessLocal(_ value: Bool) + @objc + func shouldAllowUnrestrictedAccessLocal() -> Bool + @objc + func setShouldAllowUnrestrictedAccessLocal(_ value: Bool) } // MARK: - @@ -104,6 +124,24 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { ensureSenderCertificate().retainUntilComplete() } + // MARK: - + + @objc + public func isUDEnabled() -> Bool { + // Only enable UD if UD is supported by all linked devices, + // so that sync messages can also be sent via UD. + guard let localNumber = tsAccountManager.localNumber() else { + return false + } + let ourAccessMode = unidentifiedAccessMode(forRecipientId: localNumber) + return ourAccessMode == .enabled || ourAccessMode == .unrestricted + } + + @objc + public func isUDVerboseLoggingEnabled() -> Bool { + return true + } + // MARK: - Dependencies private var profileManager: ProfileManagerProtocol { @@ -121,9 +159,9 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { return SMKUDAccessKey(randomKeyData: ()) } - @objc - public func unidentifiedAccessMode(forRecipientId recipientId: RecipientIdentifier) -> UnidentifiedAccessMode { - guard let existingRawValue = dbConnection.object(forKey: recipientId, inCollection: kUnidentifiedAccessCollection) as? Int else { + private func unidentifiedAccessMode(forRecipientId recipientId: RecipientIdentifier, + transaction: YapDatabaseReadTransaction) -> UnidentifiedAccessMode { + guard let existingRawValue = transaction.object(forKey: recipientId, inCollection: kUnidentifiedAccessCollection) as? Int else { return .unknown } guard let existingValue = UnidentifiedAccessMode(rawValue: existingRawValue) else { @@ -132,15 +170,32 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { return existingValue } + @objc + public func unidentifiedAccessMode(forRecipientId recipientId: RecipientIdentifier) -> UnidentifiedAccessMode { + var mode: UnidentifiedAccessMode = .unknown + dbConnection.read { (transaction) in + mode = self.unidentifiedAccessMode(forRecipientId: recipientId, transaction: transaction) + } + return mode + } + @objc public func setUnidentifiedAccessMode(_ mode: UnidentifiedAccessMode, recipientId: String) { if let localNumber = tsAccountManager.localNumber() { if recipientId == localNumber { - Logger.info("Setting local UD access mode: \(mode.rawValue)") + Logger.info("Setting local UD access mode: \(string(forUnidentifiedAccessMode: mode))") } } - dbConnection.setObject(mode.rawValue as Int, forKey: recipientId, inCollection: kUnidentifiedAccessCollection) + dbConnection.readWrite { (transaction) in + let oldMode = self.unidentifiedAccessMode(forRecipientId: recipientId, transaction: transaction) + + transaction.setObject(mode.rawValue as Int, forKey: recipientId, inCollection: self.kUnidentifiedAccessCollection) + + if mode != oldMode { + Logger.info("Setting UD access mode for \(recipientId): \(string(forUnidentifiedAccessMode: oldMode)) -> \(string(forUnidentifiedAccessMode: mode))") + } + } } // Returns the UD access key for a given recipient @@ -160,6 +215,50 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { } } + // Returns the UD access key for sending to a given recipient. + @objc + public func udSendAccessKey(forRecipientId recipientId: RecipientIdentifier) -> SMKUDAccessKey? { + // This check is currently redundant with the "send access key for local number" + // check below, but behavior of isUDEnabled() may change. + guard isUDEnabled() else { + if isUDVerboseLoggingEnabled() { + Logger.info("UD Send disabled for \(recipientId), UD disabled.") + } + return nil + } + guard let localNumber = tsAccountManager.localNumber() else { + if isUDVerboseLoggingEnabled() { + Logger.info("UD Send disabled for \(recipientId), no local number.") + } + return nil + } + if localNumber != recipientId { + guard udSendAccessKey(forRecipientId: localNumber) != nil else { + if isUDVerboseLoggingEnabled() { + Logger.info("UD Send disabled for \(recipientId), UD disabled for sync messages.") + } + return nil + } + } + let accessMode = unidentifiedAccessMode(forRecipientId: localNumber) + if accessMode == .unrestricted { + if isUDVerboseLoggingEnabled() { + Logger.info("UD Send enabled for \(recipientId) with random key.") + } + return randomUDAccessKey() + } + guard accessMode == .enabled else { + if isUDVerboseLoggingEnabled() { + Logger.info("UD Send disabled for \(recipientId), UD not enabled for this recipient.") + } + return nil + } + if isUDVerboseLoggingEnabled() { + Logger.info("UD Send enabled for \(recipientId).") + } + return udAccessKey(forRecipientId: recipientId) + } + // MARK: - Sender Certificate #if DEBUG @@ -257,17 +356,6 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { } } - @objc - public func isUDEnabled() -> Bool { - // Only enable UD if UD is supported by all linked devices, - // so that sync messages can also be sent via UD. - guard let localNumber = tsAccountManager.localNumber() else { - return false - } - let ourAccessMode = unidentifiedAccessMode(forRecipientId: localNumber) - return ourAccessMode == .enabled || ourAccessMode == .unrestricted - } - @objc public func trustRoot() -> ECPublicKey { return OWSUDManagerImpl.trustRoot()