diff --git a/SignalMessaging/profiles/ProfileFetcherJob.swift b/SignalMessaging/profiles/ProfileFetcherJob.swift index acd4c8afc..4810f7517 100644 --- a/SignalMessaging/profiles/ProfileFetcherJob.swift +++ b/SignalMessaging/profiles/ProfileFetcherJob.swift @@ -180,29 +180,34 @@ public class ProfileFetcherJob: NSObject { profileNameEncrypted: signalServiceProfile.profileNameEncrypted, avatarUrlPath: signalServiceProfile.avatarUrlPath) - // Recipients should be in "UD delivery mode" IFF: - // - // * Their profile includes a unidentifiedAccessVerifier. - // * The unidentifiedAccessVerifier matches the "expected" value derived - // from their profile key (if any). - // - // Recipients should be in "normal delivery mode" otherwise. - var supportsUnidentifiedDelivery = false - if let unidentifiedAccessVerifier = signalServiceProfile.unidentifiedAccessVerifier, - let udAccessKey = udManager.udAccessKeyForRecipient(recipientId) { - let dataToVerify = Data(count: 32) - if let expectedVerfier = Cryptography.computeSHA256HMAC(dataToVerify, withHMACKey: udAccessKey.keyData) { - supportsUnidentifiedDelivery = expectedVerfier == unidentifiedAccessVerifier - } else { - owsFailDebug("could not verify UD") - } + updateUnidentifiedAccess(recipientId: recipientId, verifier: signalServiceProfile.unidentifiedAccessVerifier, hasUnrestrictedAccess: signalServiceProfile.hasUnrestrictedUnidentifiedAccess) + } + + private func updateUnidentifiedAccess(recipientId: String, verifier: Data?, hasUnrestrictedAccess: Bool) { + if hasUnrestrictedAccess { + udManager.setUnidentifiedAccessMode(.unrestricted, recipientId: recipientId) + return } - // TODO: We may want to only call setSupportsUnidentifiedDelivery if - // supportsUnidentifiedDelivery is true. - udManager.setSupportsUnidentifiedDelivery(supportsUnidentifiedDelivery, recipientId: recipientId) + guard let verifier = verifier, let udAccessKey = udManager.udAccessKeyForRecipient(recipientId) else { + udManager.setUnidentifiedAccessMode(.disabled, recipientId: recipientId) + return + } + + let dataToVerify = Data(count: 32) + guard let expectedVerfier = Cryptography.computeSHA256HMAC(dataToVerify, withHMACKey: udAccessKey.keyData) else { + owsFailDebug("could not compute verification") + udManager.setUnidentifiedAccessMode(.disabled, recipientId: recipientId) + return + } + + guard expectedVerfier == verifier else { + Logger.verbose("verifier mismatch, new profile key?") + udManager.setUnidentifiedAccessMode(.disabled, recipientId: recipientId) + return + } - udManager.setShouldAllowUnrestrictedAccess(recipientId: recipientId, shouldAllowUnrestrictedAccess: signalServiceProfile.hasUnrestrictedUnidentifiedAccess) + udManager.setUnidentifiedAccessMode(.enabled, recipientId: recipientId) } private func verifyIdentityUpToDateAsync(recipientId: String, latestIdentityKey: Data) { diff --git a/SignalServiceKit/src/Messages/OWSMessageSend.swift b/SignalServiceKit/src/Messages/OWSMessageSend.swift index 7918ff904..4a693e8a2 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSend.swift +++ b/SignalServiceKit/src/Messages/OWSMessageSend.swift @@ -70,9 +70,14 @@ public class OWSMessageSend: NSObject { var udAccessKey: SMKUDAccessKey? var isLocalNumber: Bool = false if let recipientId = recipient.uniqueId { - udAccessKey = (udManager.supportsUnidentifiedDelivery(recipientId: recipientId) - ? udManager.udAccessKeyForRecipient(recipientId) - : nil) + switch udManager.unidentifiedAccessMode(recipientId: recipientId) { + case .enabled: + udAccessKey = udManager.udAccessKeyForRecipient(recipientId) + case .unrestricted: + udAccessKey = udManager.generateAccessKeyForUnrestrictedRecipient() + case .disabled, .unknown: + udAccessKey = nil + } isLocalNumber = localNumber == recipientId } else { owsFailDebug("SignalRecipient missing recipientId") diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index d091dc94d..7cdda4190 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -1041,7 +1041,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; // // TODO: Do we want to discriminate based on exact error? OWSLogDebug(@"UD send failed; failing over to non-UD send."); - [self.udManager setSupportsUnidentifiedDelivery:NO recipientId:recipient.uniqueId]; + [self.udManager setUnidentifiedAccessMode:UnidentifiedAccessModeDisabled + recipientId:recipient.uniqueId]; messageSend.hasUDAuthFailed = YES; dispatch_async([OWSDispatch sendingQueue], ^{ [self sendMessageToRecipient:messageSend]; diff --git a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift index 0c4022eed..ee5758e3c 100644 --- a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift +++ b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift @@ -12,38 +12,47 @@ public enum OWSUDError: Error { case invalidData(description: String) } +@objc +public enum UnidentifiedAccessMode: Int { + case unknown + case enabled + case disabled + case unrestricted +} + @objc public protocol OWSUDManager: class { @objc func setup() @objc func trustRoot() -> ECPublicKey - // MARK: - Recipient state + // MARK: - Recipient State - @objc func supportsUnidentifiedDelivery(recipientId: String) -> Bool + @objc + func unidentifiedAccessMode(recipientId: String) -> UnidentifiedAccessMode - @objc func setSupportsUnidentifiedDelivery(_ value: Bool, recipientId: String) + @objc + func setUnidentifiedAccessMode(_ mode: UnidentifiedAccessMode, recipientId: String) // Returns the UD access key for a given recipient if they are // a UD recipient and we have a valid profile key for them. @objc func udAccessKeyForRecipient(_ recipientId: String) -> SMKUDAccessKey? - // MARK: - Sender Certificate + @objc + func generateAccessKeyForUnrestrictedRecipient() -> SMKUDAccessKey + // MARK: - Local State + + // MARK: Sender Certificate // We use completion handlers instead of a promise so that message sending - // logic can access the certificate data. + // logic can access the strongly typed certificate data. @objc func ensureSenderCertificateObjC(success:@escaping (SMKSenderCertificate) -> Void, failure:@escaping (Error) -> Void) - // MARK: - Unrestricted Access + // MARK: Unrestricted Access @objc func shouldAllowUnrestrictedAccessLocal() -> Bool - @objc func setShouldAllowUnrestrictedAccessLocal(_ value: Bool) - - @objc func shouldAllowUnrestrictedAccess(recipientId: String) -> Bool - - @objc func setShouldAllowUnrestrictedAccess(recipientId: String, shouldAllowUnrestrictedAccess: Bool) } // MARK: - @@ -53,11 +62,13 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { private let dbConnection: YapDatabaseConnection + // MARK: Local Configuration State private let kUDCollection = "kUDCollection" private let kUDCurrentSenderCertificateKey = "kUDCurrentSenderCertificateKey" private let kUDUnrestrictedAccessKey = "kUDUnrestrictedAccessKey" - private let kUDRecipientModeCollection = "kUDRecipientModeCollection" - private let kUDUnrestrictedAccessCollection = "kUDUnrestrictedAccessCollection" + + // MARK: Recipient State + private let kUnidentifiedAccessCollection = "kUnidentifiedAccessCollection" @objc public required init(primaryStorage: OWSPrimaryStorage) { @@ -101,20 +112,24 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { // MARK: - Recipient state @objc - public func supportsUnidentifiedDelivery(recipientId: String) -> Bool { + public func unidentifiedAccessMode(recipientId: String) -> UnidentifiedAccessMode { if tsAccountManager.localNumber() == recipientId { - return true + if shouldAllowUnrestrictedAccessLocal() { + return .unrestricted + } else { + return .enabled + } + } + + guard let existingValue = dbConnection.object(forKey: recipientId, inCollection: kUnidentifiedAccessCollection) as? UnidentifiedAccessMode else { + return .unknown } - return dbConnection.bool(forKey: recipientId, inCollection: kUDRecipientModeCollection, defaultValue: false) + return existingValue } @objc - public func setSupportsUnidentifiedDelivery(_ value: Bool, recipientId: String) { - if value { - dbConnection.setBool(true, forKey: recipientId, inCollection: kUDRecipientModeCollection) - } else { - dbConnection.removeObject(forKey: recipientId, inCollection: kUDRecipientModeCollection) - } + public func setUnidentifiedAccessMode(_ mode: UnidentifiedAccessMode, recipientId: String) { + dbConnection.setObject(mode, forKey: recipientId, inCollection: kUnidentifiedAccessCollection) } // Returns the UD access key for a given recipient @@ -134,6 +149,11 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { } } + @objc + public func generateAccessKeyForUnrestrictedRecipient() -> SMKUDAccessKey { + return SMKUDAccessKey(randomKeyData: ()) + } + // MARK: - Sender Certificate #if DEBUG @@ -251,14 +271,4 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { public func setShouldAllowUnrestrictedAccessLocal(_ value: Bool) { dbConnection.setBool(value, forKey: kUDUnrestrictedAccessKey, inCollection: kUDCollection) } - - @objc - public func shouldAllowUnrestrictedAccess(recipientId: String) -> Bool { - return dbConnection.bool(forKey: recipientId, inCollection: kUDUnrestrictedAccessCollection, defaultValue: false) - } - - @objc - public func setShouldAllowUnrestrictedAccess(recipientId: String, shouldAllowUnrestrictedAccess: Bool) { - dbConnection.setBool(shouldAllowUnrestrictedAccess, forKey: recipientId, inCollection: kUDUnrestrictedAccessCollection) - } }