From 39ba41343948ef96f83a85db0eeffce48d358df3 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 9 Oct 2018 15:14:42 -0600 Subject: [PATCH 1/4] Track UD mode enum instead of two booleans --- .../profiles/ProfileFetcherJob.swift | 45 ++++++----- .../src/Messages/OWSMessageSend.swift | 11 ++- .../src/Messages/OWSMessageSender.m | 3 +- .../src/Messages/UD/OWSUDManager.swift | 74 +++++++++++-------- 4 files changed, 77 insertions(+), 56 deletions(-) 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) - } } From 0be1f8cca26f23f8c38f1c984e15ea405000f18d Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 9 Oct 2018 19:00:25 -0600 Subject: [PATCH 2/4] Move UD auth into request initializers --- .../profiles/ProfileFetcherJob.swift | 79 +++---------------- .../src/Messages/OWSMessageSend.swift | 23 +++--- .../src/Messages/OWSMessageSender.m | 23 ++---- .../Network/API/Requests/OWSRequestFactory.h | 12 ++- .../Network/API/Requests/OWSRequestFactory.m | 25 +++++- .../src/Network/API/Requests/TSRequest.h | 2 +- .../src/Network/API/Requests/TSRequest.m | 2 +- .../Network/API/SignalServiceProfile.swift | 47 +++++++++++ .../src/Network/SignalServiceClient.swift | 31 +++++++- 9 files changed, 141 insertions(+), 103 deletions(-) create mode 100644 SignalServiceKit/src/Network/API/SignalServiceProfile.swift diff --git a/SignalMessaging/profiles/ProfileFetcherJob.swift b/SignalMessaging/profiles/ProfileFetcherJob.swift index 4810f7517..333687e66 100644 --- a/SignalMessaging/profiles/ProfileFetcherJob.swift +++ b/SignalMessaging/profiles/ProfileFetcherJob.swift @@ -56,6 +56,11 @@ public class ProfileFetcherJob: NSObject { return SSKEnvironment.shared.identityManager } + private var signalServiceClient: SignalServiceClient { + // TODO hang on SSKEnvironment + return SignalServiceRestClient() + } + // MARK: - public func run(recipientIds: [String]) { @@ -88,8 +93,7 @@ public class ProfileFetcherJob: NSObject { } enum ProfileFetcherJobError: Error { - case throttled(lastTimeInterval: TimeInterval), - unknownNetworkError + case throttled(lastTimeInterval: TimeInterval) } public func updateProfile(recipientId: String, remainingRetries: Int = 3) { @@ -130,12 +134,11 @@ public class ProfileFetcherJob: NSObject { Logger.error("getProfile: \(recipientId)") - let request = OWSRequestFactory.getProfileRequest(withRecipientId: recipientId) - - let (promise, fulfill, reject) = Promise.pending() - // TODO: Use UD socket for some profile gets. if socketManager.canMakeRequests(of: .default) { + let request = OWSRequestFactory.getProfileRequest(recipientId: recipientId, unidentifiedAccess: nil) + let (promise, fulfill, reject) = Promise.pending() + self.socketManager.make(request, webSocketType: .default, success: { (responseObject: Any?) -> Void in @@ -149,27 +152,11 @@ public class ProfileFetcherJob: NSObject { failure: { (_: NSInteger, _:Data?, error: Error) in reject(error) }) + return promise } else { - self.networkManager.makeRequest(request, - success: { (_: URLSessionDataTask?, responseObject: Any?) -> Void in - do { - let profile = try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject) - fulfill(profile) - } catch { - reject(error) - } - }, - failure: { (_: URLSessionDataTask?, error: Error?) in - - if let error = error { - reject(error) - } - - reject(ProfileFetcherJobError.unknownNetworkError) - }) + // TODO unidentified AUTH + return self.signalServiceClient.retrieveProfile(recipientId: recipientId, unidentifiedAccess: nil) } - - return promise } private func updateProfile(signalServiceProfile: SignalServiceProfile) { @@ -221,45 +208,3 @@ public class ProfileFetcherJob: NSObject { } } } - -@objc -public class SignalServiceProfile: NSObject { - - public enum ValidationError: Error { - case invalid(description: String) - case invalidIdentityKey(description: String) - case invalidProfileName(description: String) - } - - public let recipientId: String - public let identityKey: Data - public let profileNameEncrypted: Data? - public let avatarUrlPath: String? - public let unidentifiedAccessVerifier: Data? - public let hasUnrestrictedUnidentifiedAccess: Bool - - init(recipientId: String, responseObject: Any?) throws { - self.recipientId = recipientId - - guard let params = ParamParser(responseObject: responseObject) else { - throw ValidationError.invalid(description: "invalid response: \(String(describing: responseObject))") - } - - let identityKeyWithType = try params.requiredBase64EncodedData(key: "identityKey") - let kIdentityKeyLength = 33 - guard identityKeyWithType.count == kIdentityKeyLength else { - throw ValidationError.invalidIdentityKey(description: "malformed identity key \(identityKeyWithType.hexadecimalString) with decoded length: \(identityKeyWithType.count)") - } - // `removeKeyType` is an objc category method only on NSData, so temporarily cast. - self.identityKey = (identityKeyWithType as NSData).removeKeyType() as Data - - self.profileNameEncrypted = try params.optionalBase64EncodedData(key: "name") - - let avatarUrlPath: String? = try params.optional(key: "avatar") - self.avatarUrlPath = avatarUrlPath - - self.unidentifiedAccessVerifier = try params.optionalBase64EncodedData(key: "unidentifiedAccess") - - self.hasUnrestrictedUnidentifiedAccess = try params.optional(key: "unrestrictedUnidentifiedAccess") ?? false - } -} diff --git a/SignalServiceKit/src/Messages/OWSMessageSend.swift b/SignalServiceKit/src/Messages/OWSMessageSend.swift index 4a693e8a2..6245b9d8e 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSend.swift +++ b/SignalServiceKit/src/Messages/OWSMessageSend.swift @@ -36,7 +36,7 @@ public class OWSMessageSend: NSObject { public var hasUDAuthFailed = false @objc - public let udAccessKey: SMKUDAccessKey? + public let unidentifiedAccess: SSKUnidentifiedAccess? @objc public let localNumber: String @@ -44,9 +44,6 @@ public class OWSMessageSend: NSObject { @objc public let isLocalNumber: Bool - @objc - public let senderCertificate: SMKSenderCertificate? - @objc public let success: () -> Void @@ -65,10 +62,11 @@ public class OWSMessageSend: NSObject { self.message = message self.thread = thread self.recipient = recipient - self.senderCertificate = senderCertificate - var udAccessKey: SMKUDAccessKey? - var isLocalNumber: Bool = false + let senderCertificate = senderCertificate + + let udAccessKey: SMKUDAccessKey? + var isLocalNumber: Bool if let recipientId = recipient.uniqueId { switch udManager.unidentifiedAccessMode(recipientId: recipientId) { case .enabled: @@ -80,9 +78,16 @@ public class OWSMessageSend: NSObject { } isLocalNumber = localNumber == recipientId } else { + isLocalNumber = false + udAccessKey = nil owsFailDebug("SignalRecipient missing recipientId") } - self.udAccessKey = udAccessKey + if let udAccessKey = udAccessKey, let senderCertificate = senderCertificate { + self.unidentifiedAccess = SSKUnidentifiedAccess(accessKey: udAccessKey, senderCertificate: senderCertificate) + } else { + self.unidentifiedAccess = nil + } + self.localNumber = localNumber self.isLocalNumber = isLocalNumber @@ -92,6 +97,6 @@ public class OWSMessageSend: NSObject { @objc public var isUDSend: Bool { - return (!hasUDAuthFailed && udAccessKey != nil && senderCertificate != nil) + return (!hasUDAuthFailed && self.unidentifiedAccess != nil) } } diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 7cdda4190..943348052 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -998,12 +998,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; TSRequest *request = [OWSRequestFactory submitMessageRequestWithRecipient:recipient.uniqueId messages:deviceMessages - timeStamp:message.timestamp]; - - if (messageSend.isUDSend) { - DDLogVerbose(@"UD send."); - [request useUDAuth:messageSend.udAccessKey]; - } + timeStamp:message.timestamp + unidentifiedAccess:messageSend.unidentifiedAccess]; OWSWebSocketType webSocketType = (messageSend.isUDSend ? OWSWebSocketTypeUD : OWSWebSocketTypeDefault); BOOL canMakeWebsocketRequests = ([TSSocketManager.shared canMakeRequestsOfType:webSocketType] && @@ -1090,7 +1086,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; [SignalRecipient markRecipientAsRegisteredAndGet:recipient.recipientId transaction:transaction]; }]; - [self handleMessageSentLocally:messageSend.message senderCertificate:messageSend.senderCertificate]; + [self handleMessageSentLocally:messageSend.message + senderCertificate:messageSend.unidentifiedAccess.senderCertificate]; messageSend.success(); }); } @@ -1399,13 +1396,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; // To avoid deadlock, we need to ensure that our success/failure completions // are called _off_ the main thread. Otherwise we'll deadlock if the main // thread is blocked on opening a transaction. - TSRequest *request = - [OWSRequestFactory recipientPrekeyRequestWithRecipient:recipientId deviceId:[deviceId stringValue]]; - - if (messageSend.isUDSend) { - DDLogVerbose(@"UD prekey request."); - [request useUDAuth:messageSend.udAccessKey]; - } + TSRequest *request = [OWSRequestFactory recipientPrekeyRequestWithRecipient:recipientId + deviceId:[deviceId stringValue] + unidentifiedAccess:messageSend.unidentifiedAccess]; [self.networkManager makeRequest:request completionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) @@ -1509,7 +1502,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; serializedMessage = [secretCipher encryptMessageWithRecipientId:recipientId deviceId:deviceId.intValue paddedPlaintext:[plainText paddedMessageBody] - senderCertificate:messageSend.senderCertificate + senderCertificate:messageSend.unidentifiedAccess.senderCertificate protocolContext:transaction error:&error]; messageType = TSUnidentifiedSenderMessageType; diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h index c5d1c6da6..835abb4f2 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.h @@ -7,6 +7,7 @@ NS_ASSUME_NONNULL_BEGIN @class ECKeyPair; @class OWSDevice; @class PreKeyRecord; +@class SSKUnidentifiedAccess; @class SignedPreKeyRecord; @class TSRequest; @@ -35,7 +36,9 @@ typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVo + (TSRequest *)getMessagesRequest; -+ (TSRequest *)getProfileRequestWithRecipientId:(NSString *)recipientId; ++ (TSRequest *)getProfileRequestWithRecipientId:(NSString *)recipientId + unidentifiedAccess:(nullable SSKUnidentifiedAccess *)unidentifiedAccess + NS_SWIFT_NAME(getProfileRequest(recipientId:unidentifiedAccess:)); + (TSRequest *)turnServerInfoRequest; @@ -58,7 +61,8 @@ typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVo + (TSRequest *)submitMessageRequestWithRecipient:(NSString *)recipientId messages:(NSArray *)messages - timeStamp:(uint64_t)timeStamp; + timeStamp:(uint64_t)timeStamp + unidentifiedAccess:(nullable SSKUnidentifiedAccess *)unidentifiedAccess; + (TSRequest *)verifyCodeRequestWithVerificationCode:(NSString *)verificationCode forNumber:(NSString *)phoneNumber @@ -72,7 +76,9 @@ typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVo + (TSRequest *)currentSignedPreKeyRequest; -+ (TSRequest *)recipientPrekeyRequestWithRecipient:(NSString *)recipientNumber deviceId:(NSString *)deviceId; ++ (TSRequest *)recipientPrekeyRequestWithRecipient:(NSString *)recipientNumber + deviceId:(NSString *)deviceId + unidentifiedAccess:(nullable SSKUnidentifiedAccess *)unidentifiedAccess; + (TSRequest *)registerSignedPrekeyRequestWithSignedPreKeyRecord:(SignedPreKeyRecord *)signedPreKey; diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m index 6a6796541..daada24da 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m @@ -124,11 +124,16 @@ NS_ASSUME_NONNULL_BEGIN } + (TSRequest *)getProfileRequestWithRecipientId:(NSString *)recipientId + unidentifiedAccess:(nullable SSKUnidentifiedAccess *)unidentifiedAccess { OWSAssertDebug(recipientId.length > 0); NSString *path = [NSString stringWithFormat:textSecureProfileAPIFormat, recipientId]; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; + TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; + if (unidentifiedAccess != nil) { + [request useUDAuthWithAccessKey:unidentifiedAccess.accessKey]; + } + return request; } + (TSRequest *)turnServerInfoRequest @@ -181,13 +186,20 @@ NS_ASSUME_NONNULL_BEGIN return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; } -+ (TSRequest *)recipientPrekeyRequestWithRecipient:(NSString *)recipientNumber deviceId:(NSString *)deviceId ++ (TSRequest *)recipientPrekeyRequestWithRecipient:(NSString *)recipientNumber + deviceId:(NSString *)deviceId + unidentifiedAccess:(nullable SSKUnidentifiedAccess *)unidentifiedAccess; { OWSAssertDebug(recipientNumber.length > 0); OWSAssertDebug(deviceId.length > 0); NSString *path = [NSString stringWithFormat:@"%@/%@/%@", textSecureKeysAPI, recipientNumber, deviceId]; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; + + TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; + if (unidentifiedAccess != nil) { + [request useUDAuthWithAccessKey:unidentifiedAccess.accessKey]; + } + return request; } + (TSRequest *)registerForPushRequestWithPushIdentifier:(NSString *)identifier voipIdentifier:(NSString *)voipId @@ -316,6 +328,7 @@ NS_ASSUME_NONNULL_BEGIN + (TSRequest *)submitMessageRequestWithRecipient:(NSString *)recipientId messages:(NSArray *)messages timeStamp:(uint64_t)timeStamp + unidentifiedAccess:(nullable SSKUnidentifiedAccess *)unidentifiedAccess { // NOTE: messages may be empty; See comments in OWSDeviceManager. OWSAssertDebug(recipientId.length > 0); @@ -327,7 +340,11 @@ NS_ASSUME_NONNULL_BEGIN @"timestamp" : @(timeStamp), }; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:parameters]; + TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:parameters]; + if (unidentifiedAccess != nil) { + [request useUDAuthWithAccessKey:unidentifiedAccess.accessKey]; + } + return request; } + (TSRequest *)registerSignedPrekeyRequestWithSignedPreKeyRecord:(SignedPreKeyRecord *)signedPreKey diff --git a/SignalServiceKit/src/Network/API/Requests/TSRequest.h b/SignalServiceKit/src/Network/API/Requests/TSRequest.h index 3ef1142ac..600ef8fa6 100644 --- a/SignalServiceKit/src/Network/API/Requests/TSRequest.h +++ b/SignalServiceKit/src/Network/API/Requests/TSRequest.h @@ -30,6 +30,6 @@ #pragma mark - UD -- (void)useUDAuth:(SMKUDAccessKey *)udAccessKey; +- (void)useUDAuthWithAccessKey:(SMKUDAccessKey *)udAccessKey; @end diff --git a/SignalServiceKit/src/Network/API/Requests/TSRequest.m b/SignalServiceKit/src/Network/API/Requests/TSRequest.m index 98c97370d..442713522 100644 --- a/SignalServiceKit/src/Network/API/Requests/TSRequest.m +++ b/SignalServiceKit/src/Network/API/Requests/TSRequest.m @@ -114,7 +114,7 @@ #pragma mark - UD -- (void)useUDAuth:(SMKUDAccessKey *)udAccessKey +- (void)useUDAuthWithAccessKey:(SMKUDAccessKey *)udAccessKey { OWSAssertDebug(udAccessKey); diff --git a/SignalServiceKit/src/Network/API/SignalServiceProfile.swift b/SignalServiceKit/src/Network/API/SignalServiceProfile.swift new file mode 100644 index 000000000..4d79540c4 --- /dev/null +++ b/SignalServiceKit/src/Network/API/SignalServiceProfile.swift @@ -0,0 +1,47 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +public class SignalServiceProfile: NSObject { + + public enum ValidationError: Error { + case invalid(description: String) + case invalidIdentityKey(description: String) + case invalidProfileName(description: String) + } + + public let recipientId: String + public let identityKey: Data + public let profileNameEncrypted: Data? + public let avatarUrlPath: String? + public let unidentifiedAccessVerifier: Data? + public let hasUnrestrictedUnidentifiedAccess: Bool + + public init(recipientId: String, responseObject: Any?) throws { + self.recipientId = recipientId + + guard let params = ParamParser(responseObject: responseObject) else { + throw ValidationError.invalid(description: "invalid response: \(String(describing: responseObject))") + } + + let identityKeyWithType = try params.requiredBase64EncodedData(key: "identityKey") + let kIdentityKeyLength = 33 + guard identityKeyWithType.count == kIdentityKeyLength else { + throw ValidationError.invalidIdentityKey(description: "malformed identity key \(identityKeyWithType.hexadecimalString) with decoded length: \(identityKeyWithType.count)") + } + // `removeKeyType` is an objc category method only on NSData, so temporarily cast. + self.identityKey = (identityKeyWithType as NSData).removeKeyType() as Data + + self.profileNameEncrypted = try params.optionalBase64EncodedData(key: "name") + + let avatarUrlPath: String? = try params.optional(key: "avatar") + self.avatarUrlPath = avatarUrlPath + + self.unidentifiedAccessVerifier = try params.optionalBase64EncodedData(key: "unidentifiedAccess") + + self.hasUnrestrictedUnidentifiedAccess = try params.optional(key: "unrestrictedUnidentifiedAccess") ?? false + } +} diff --git a/SignalServiceKit/src/Network/SignalServiceClient.swift b/SignalServiceKit/src/Network/SignalServiceClient.swift index 96f4aab4f..e35507481 100644 --- a/SignalServiceKit/src/Network/SignalServiceClient.swift +++ b/SignalServiceKit/src/Network/SignalServiceClient.swift @@ -4,13 +4,31 @@ import Foundation import PromiseKit +import SignalMetadataKit -protocol SignalServiceClient { +public typealias RecipientIdentifier = String + +@objc +public class SSKUnidentifiedAccess: NSObject { + @objc + let accessKey: SMKUDAccessKey + + @objc + let senderCertificate: SMKSenderCertificate + + init(accessKey: SMKUDAccessKey, senderCertificate: SMKSenderCertificate) { + self.accessKey = accessKey + self.senderCertificate = senderCertificate + } +} + +public protocol SignalServiceClient { func getAvailablePreKeys() -> Promise func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise func setCurrentSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise func requestUDSenderCertificate() -> Promise func updateAcountAttributes() -> Promise + func retrieveProfile(recipientId: RecipientIdentifier, unidentifiedAccess: SSKUnidentifiedAccess?) -> Promise } /// Based on libsignal-service-java's PushServiceSocket class @@ -25,7 +43,7 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient { return OWSErrorMakeUnableToProcessServerResponseError() } - func getAvailablePreKeys() -> Promise { + public func getAvailablePreKeys() -> Promise { Logger.debug("") let request = OWSRequestFactory.availablePreKeysCountRequest() @@ -41,7 +59,7 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient { } } - func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise { + public func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise { Logger.debug("") let request = OWSRequestFactory.registerPrekeysRequest(withPrekeyArray: preKeyRecords, identityKey: identityKey, signedPreKey: signedPreKeyRecord) @@ -83,4 +101,11 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient { }) return promise } + + public func retrieveProfile(recipientId: RecipientIdentifier, unidentifiedAccess: SSKUnidentifiedAccess?) -> Promise { + let request = OWSRequestFactory.getProfileRequest(recipientId: recipientId, unidentifiedAccess: unidentifiedAccess) + return networkManager.makePromise(request: request).then { (task: URLSessionDataTask, responseObject: Any?) in + return try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject) + } + } } From fb2abdcd1a57fc506fc2191bc0a0dcf255211d8b Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 9 Oct 2018 19:24:18 -0600 Subject: [PATCH 3/4] UD auth for profile fetching Converging with Android, use UnidentifiedAccess getter --- .../profiles/ProfileFetcherJob.swift | 16 ++++--- .../src/Messages/OWSMessageSend.swift | 26 ++-------- .../src/Messages/UD/OWSUDManager.swift | 48 ++++++++++++++----- .../src/Network/SignalServiceClient.swift | 14 ------ .../src/Network/UnidentifiedAccess.swift | 31 ++++++++++++ 5 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 SignalServiceKit/src/Network/UnidentifiedAccess.swift diff --git a/SignalMessaging/profiles/ProfileFetcherJob.swift b/SignalMessaging/profiles/ProfileFetcherJob.swift index 333687e66..c63d9a445 100644 --- a/SignalMessaging/profiles/ProfileFetcherJob.swift +++ b/SignalMessaging/profiles/ProfileFetcherJob.swift @@ -134,13 +134,14 @@ public class ProfileFetcherJob: NSObject { Logger.error("getProfile: \(recipientId)") - // TODO: Use UD socket for some profile gets. - if socketManager.canMakeRequests(of: .default) { - let request = OWSRequestFactory.getProfileRequest(recipientId: recipientId, unidentifiedAccess: nil) + let unidentifiedAccess: SSKUnidentifiedAccess? = self.getUnidentifiedAccess(forRecipientId: recipientId) + let socketType: OWSWebSocketType = unidentifiedAccess == nil ? .default : .UD + if socketManager.canMakeRequests(of: socketType) { + let request = OWSRequestFactory.getProfileRequest(recipientId: recipientId, unidentifiedAccess: unidentifiedAccess) let (promise, fulfill, reject) = Promise.pending() self.socketManager.make(request, - webSocketType: .default, + webSocketType: socketType, success: { (responseObject: Any?) -> Void in do { let profile = try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject) @@ -154,8 +155,7 @@ public class ProfileFetcherJob: NSObject { }) return promise } else { - // TODO unidentified AUTH - return self.signalServiceClient.retrieveProfile(recipientId: recipientId, unidentifiedAccess: nil) + return self.signalServiceClient.retrieveProfile(recipientId: recipientId, unidentifiedAccess: unidentifiedAccess) } } @@ -207,4 +207,8 @@ public class ProfileFetcherJob: NSObject { } } } + + private func getUnidentifiedAccess(forRecipientId recipientId: RecipientIdentifier) -> SSKUnidentifiedAccess? { + return self.udManager.getAccess(forRecipientId: recipientId)?.targetUnidentifiedAccess + } } diff --git a/SignalServiceKit/src/Messages/OWSMessageSend.swift b/SignalServiceKit/src/Messages/OWSMessageSend.swift index 6245b9d8e..45a95e6fd 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSend.swift +++ b/SignalServiceKit/src/Messages/OWSMessageSend.swift @@ -62,35 +62,17 @@ public class OWSMessageSend: NSObject { self.message = message self.thread = thread self.recipient = recipient + self.localNumber = localNumber - let senderCertificate = senderCertificate - - let udAccessKey: SMKUDAccessKey? - var isLocalNumber: Bool if let recipientId = recipient.uniqueId { - switch udManager.unidentifiedAccessMode(recipientId: recipientId) { - case .enabled: - udAccessKey = udManager.udAccessKeyForRecipient(recipientId) - case .unrestricted: - udAccessKey = udManager.generateAccessKeyForUnrestrictedRecipient() - case .disabled, .unknown: - udAccessKey = nil - } - isLocalNumber = localNumber == recipientId + self.unidentifiedAccess = udManager.getAccess(forRecipientId: recipientId)?.targetUnidentifiedAccess + self.isLocalNumber = localNumber == recipientId } else { - isLocalNumber = false - udAccessKey = nil owsFailDebug("SignalRecipient missing recipientId") - } - if let udAccessKey = udAccessKey, let senderCertificate = senderCertificate { - self.unidentifiedAccess = SSKUnidentifiedAccess(accessKey: udAccessKey, senderCertificate: senderCertificate) - } else { + self.isLocalNumber = false self.unidentifiedAccess = nil } - self.localNumber = localNumber - self.isLocalNumber = isLocalNumber - self.success = success self.failure = failure } diff --git a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift index ee5758e3c..68a701175 100644 --- a/SignalServiceKit/src/Messages/UD/OWSUDManager.swift +++ b/SignalServiceKit/src/Messages/UD/OWSUDManager.swift @@ -29,17 +29,15 @@ public enum UnidentifiedAccessMode: Int { // MARK: - Recipient State @objc - func unidentifiedAccessMode(recipientId: String) -> UnidentifiedAccessMode + func setUnidentifiedAccessMode(_ mode: UnidentifiedAccessMode, recipientId: String) @objc - func setUnidentifiedAccessMode(_ mode: UnidentifiedAccessMode, recipientId: String) + func getAccess(forRecipientId recipientId: RecipientIdentifier) -> SSKUnidentifiedAccessPair? // 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? + @objc func udAccessKeyForRecipient(_ recipientId: RecipientIdentifier) -> SMKUDAccessKey? - @objc - func generateAccessKeyForUnrestrictedRecipient() -> SMKUDAccessKey // MARK: - Local State // MARK: Sender Certificate @@ -112,7 +110,38 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { // MARK: - Recipient state @objc - public func unidentifiedAccessMode(recipientId: String) -> UnidentifiedAccessMode { + public func getAccess(forRecipientId recipientId: RecipientIdentifier) -> SSKUnidentifiedAccessPair? { + guard let theirAccessKey = self.udAccessKeyForRecipient(recipientId) else { + return nil + } + + guard let ourSenderCertificate = self.senderCertificate() else { + return nil + } + + guard let ourAccessKey: SMKUDAccessKey = { + if self.shouldAllowUnrestrictedAccessLocal() { + return SMKUDAccessKey(randomKeyData: ()) + } else { + guard let localNumber = self.tsAccountManager.localNumber() else { + owsFailDebug("localNumber was unexpectedly nil") + return nil + } + + return self.udAccessKeyForRecipient(localNumber) + } + }() else { + return nil + } + + let targetUnidentifiedAccess = SSKUnidentifiedAccess(accessKey: theirAccessKey, senderCertificate: ourSenderCertificate) + let selfUnidentifiedAccess = SSKUnidentifiedAccess(accessKey: ourAccessKey, senderCertificate: ourSenderCertificate) + return SSKUnidentifiedAccessPair(targetUnidentifiedAccess: targetUnidentifiedAccess, + selfUnidentifiedAccess: selfUnidentifiedAccess) + } + + @objc + private func unidentifiedAccessMode(recipientId: RecipientIdentifier) -> UnidentifiedAccessMode { if tsAccountManager.localNumber() == recipientId { if shouldAllowUnrestrictedAccessLocal() { return .unrestricted @@ -135,7 +164,7 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { // Returns the UD access key for a given recipient // if we have a valid profile key for them. @objc - public func udAccessKeyForRecipient(_ recipientId: String) -> SMKUDAccessKey? { + public func udAccessKeyForRecipient(_ recipientId: RecipientIdentifier) -> SMKUDAccessKey? { guard let profileKey = profileManager.profileKeyData(forRecipientId: recipientId) else { // Mark as "not a UD recipient". return nil @@ -149,11 +178,6 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager { } } - @objc - public func generateAccessKeyForUnrestrictedRecipient() -> SMKUDAccessKey { - return SMKUDAccessKey(randomKeyData: ()) - } - // MARK: - Sender Certificate #if DEBUG diff --git a/SignalServiceKit/src/Network/SignalServiceClient.swift b/SignalServiceKit/src/Network/SignalServiceClient.swift index e35507481..ecfc9c20b 100644 --- a/SignalServiceKit/src/Network/SignalServiceClient.swift +++ b/SignalServiceKit/src/Network/SignalServiceClient.swift @@ -8,20 +8,6 @@ import SignalMetadataKit public typealias RecipientIdentifier = String -@objc -public class SSKUnidentifiedAccess: NSObject { - @objc - let accessKey: SMKUDAccessKey - - @objc - let senderCertificate: SMKSenderCertificate - - init(accessKey: SMKUDAccessKey, senderCertificate: SMKSenderCertificate) { - self.accessKey = accessKey - self.senderCertificate = senderCertificate - } -} - public protocol SignalServiceClient { func getAvailablePreKeys() -> Promise func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise diff --git a/SignalServiceKit/src/Network/UnidentifiedAccess.swift b/SignalServiceKit/src/Network/UnidentifiedAccess.swift new file mode 100644 index 000000000..e4ae99fc8 --- /dev/null +++ b/SignalServiceKit/src/Network/UnidentifiedAccess.swift @@ -0,0 +1,31 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import SignalMetadataKit + +@objc +public class SSKUnidentifiedAccessPair: NSObject { + public let targetUnidentifiedAccess: SSKUnidentifiedAccess + public let selfUnidentifiedAccess: SSKUnidentifiedAccess + + init(targetUnidentifiedAccess: SSKUnidentifiedAccess, selfUnidentifiedAccess: SSKUnidentifiedAccess) { + self.targetUnidentifiedAccess = targetUnidentifiedAccess + self.selfUnidentifiedAccess = selfUnidentifiedAccess + } +} + +@objc +public class SSKUnidentifiedAccess: NSObject { + @objc + let accessKey: SMKUDAccessKey + + @objc + let senderCertificate: SMKSenderCertificate + + init(accessKey: SMKUDAccessKey, senderCertificate: SMKSenderCertificate) { + self.accessKey = accessKey + self.senderCertificate = senderCertificate + } +} From a5db222c7a758d522bf73ddc3716661b469413f8 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Wed, 10 Oct 2018 11:39:29 -0600 Subject: [PATCH 4/4] move ud auth to request factory --- .../Network/API/Requests/OWSRequestFactory.m | 18 +++++++++++++++--- .../src/Network/API/Requests/TSRequest.h | 4 ---- .../src/Network/API/Requests/TSRequest.m | 12 ------------ 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m index daada24da..a68e90434 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m @@ -131,7 +131,7 @@ NS_ASSUME_NONNULL_BEGIN NSString *path = [NSString stringWithFormat:textSecureProfileAPIFormat, recipientId]; TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; if (unidentifiedAccess != nil) { - [request useUDAuthWithAccessKey:unidentifiedAccess.accessKey]; + [self useUDAuthWithRequest:request accessKey:unidentifiedAccess.accessKey]; } return request; } @@ -197,7 +197,7 @@ NS_ASSUME_NONNULL_BEGIN TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; if (unidentifiedAccess != nil) { - [request useUDAuthWithAccessKey:unidentifiedAccess.accessKey]; + [self useUDAuthWithRequest:request accessKey:unidentifiedAccess.accessKey]; } return request; } @@ -342,7 +342,7 @@ NS_ASSUME_NONNULL_BEGIN TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:parameters]; if (unidentifiedAccess != nil) { - [request useUDAuthWithAccessKey:unidentifiedAccess.accessKey]; + [self useUDAuthWithRequest:request accessKey:unidentifiedAccess.accessKey]; } return request; } @@ -479,6 +479,18 @@ NS_ASSUME_NONNULL_BEGIN return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; } ++ (void)useUDAuthWithRequest:(TSRequest *)request accessKey:(SMKUDAccessKey *)udAccessKey +{ + OWSAssertDebug(request); + OWSAssertDebug(udAccessKey); + + // Suppress normal auth headers. + request.shouldHaveAuthorizationHeaders = NO; + + // Add UD auth header. + [request setValue:[udAccessKey.keyData base64EncodedString] forHTTPHeaderField:@"Unidentified-Access-Key"]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/Requests/TSRequest.h b/SignalServiceKit/src/Network/API/Requests/TSRequest.h index 600ef8fa6..b56e113dd 100644 --- a/SignalServiceKit/src/Network/API/Requests/TSRequest.h +++ b/SignalServiceKit/src/Network/API/Requests/TSRequest.h @@ -28,8 +28,4 @@ method:(NSString *)method parameters:(nullable NSDictionary *)parameters; -#pragma mark - UD - -- (void)useUDAuthWithAccessKey:(SMKUDAccessKey *)udAccessKey; - @end diff --git a/SignalServiceKit/src/Network/API/Requests/TSRequest.m b/SignalServiceKit/src/Network/API/Requests/TSRequest.m index 442713522..b3efd0723 100644 --- a/SignalServiceKit/src/Network/API/Requests/TSRequest.m +++ b/SignalServiceKit/src/Network/API/Requests/TSRequest.m @@ -112,16 +112,4 @@ } } -#pragma mark - UD - -- (void)useUDAuthWithAccessKey:(SMKUDAccessKey *)udAccessKey -{ - OWSAssertDebug(udAccessKey); - - // Suppress normal auth headers. - self.shouldHaveAuthorizationHeaders = NO; - // Add UD auth header. - [self setValue:[udAccessKey.keyData base64EncodedString] forHTTPHeaderField:@"Unidentified-Access-Key"]; -} - @end