UD send via REST.

pull/1/head
Matthew Chen 7 years ago
parent 24b0eed1f6
commit 1e10a86635

@ -42,14 +42,20 @@ public class OWSMessageSend: NSObject {
@objc
public let isLocalNumber: Bool
@objc
public let senderCertificate: SMKSenderCertificate?
@objc
public init(message: TSOutgoingMessage,
thread: TSThread?,
recipient: SignalRecipient, udManager: OWSUDManager,
localNumber: String) {
recipient: SignalRecipient,
senderCertificate: SMKSenderCertificate?,
udManager: OWSUDManager,
localNumber: String) {
self.message = message
self.thread = thread
self.recipient = recipient
self.senderCertificate = senderCertificate
var udAccessKey: SMKUDAccessKey?
var isLocalNumber: Bool = false

@ -39,6 +39,7 @@
#import "TSOutgoingMessage.h"
#import "TSPreKeyManager.h"
#import "TSQuotedMessage.h"
#import "TSRequest+UD.h"
#import "TSRequest.h"
#import "TSSocketManager.h"
#import "TSThread.h"
@ -266,6 +267,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return TSAccountManager.sharedInstance;
}
- (OWSIdentityManager *)identityManager
{
return SSKEnvironment.shared.identityManager;
}
#pragma mark -
- (NSOperationQueue *)sendingQueueForMessage:(TSOutgoingMessage *)message
@ -458,6 +464,23 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
- (void)sendMessageToService:(TSOutgoingMessage *)message
success:(void (^)(void))success
failure:(RetryableFailureHandler)failure
{
[self.udManager
ensureSenderCertificateObjCWithSuccess:^(SMKSenderCertificate *senderCertificate) {
[self sendMessageToService:message senderCertificate:senderCertificate success:success failure:failure];
}
failure:^(NSError *error) {
OWSLogError(@"Could not obtain UD sender certificate: %@", error);
// Proceed using non-UD message sends.
[self sendMessageToService:message senderCertificate:nil success:success failure:failure];
}];
}
- (void)sendMessageToService:(TSOutgoingMessage *)message
senderCertificate:(nullable SMKSenderCertificate *)senderCertificate
success:(void (^)(void))successHandler
failure:(RetryableFailureHandler)failureHandler
{
@ -477,7 +500,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
}];
[self handleMessageSentLocally:message];
[self handleMessageSentLocally:message senderCertificate:senderCertificate];
successHandler();
return;
@ -570,6 +593,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[[OWSMessageSend alloc] initWithMessage:message
thread:thread
recipient:recipient
senderCertificate:senderCertificate
udManager:self.udManager
localNumber:self.tsAccountManager.localNumber];
[messageSends addObject:messageSend];
@ -767,7 +791,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
NSData *newIdentityKey = [newIdentityKeyWithVersion removeKeyType];
[[OWSIdentityManager sharedManager] saveRemoteIdentity:newIdentityKey recipientId:recipient.recipientId];
[self.identityManager saveRemoteIdentity:newIdentityKey recipientId:recipient.recipientId];
return nil;
}
@ -853,7 +877,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// Consume an attempt.
messageSend.remainingAttempts = messageSend.remainingAttempts - 1;
BOOL isUDSend = messageSend.canUseUD && messageSend.udAccessKey != nil;
BOOL isUDSend = (messageSend.canUseUD && messageSend.udAccessKey != nil && messageSend.senderCertificate != nil);
NSError *deviceMessagesError;
NSArray<NSDictionary *> *_Nullable deviceMessages =
@ -936,8 +960,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
if (isUDSend) {
DDLogVerbose(@"UD send.");
request.shouldHaveAuthorizationHeaders = YES;
[request setValue:[messageSend.udAccessKey.keyData base64EncodedString] forKey:@"Unidentified-Access-Key"];
[request useUDAuth:messageSend.udAccessKey];
}
// TODO: UD sends over websocket.
@ -1023,7 +1046,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[SignalRecipient markRecipientAsRegisteredAndGet:recipient.recipientId transaction:transaction];
}];
[self handleMessageSentLocally:messageSend.message];
[self handleMessageSentLocally:messageSend.message senderCertificate:messageSend.senderCertificate];
successHandler();
});
}
@ -1196,6 +1219,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
- (void)handleMessageSentLocally:(TSOutgoingMessage *)message
senderCertificate:(nullable SMKSenderCertificate *)senderCertificate
{
if (message.shouldSyncTranscript) {
// TODO: I suspect we shouldn't optimistically set hasSyncedTranscript.
@ -1203,7 +1227,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[message updateWithHasSyncedTranscript:YES transaction:transaction];
}];
[self sendSyncTranscriptForMessage:message];
[self sendSyncTranscriptForMessage:message senderCertificate:senderCertificate];
}
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
@ -1214,6 +1238,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
- (void)sendSyncTranscriptForMessage:(TSOutgoingMessage *)message
senderCertificate:(nullable SMKSenderCertificate *)senderCertificate
{
OWSOutgoingSentMessageTranscript *sentMessageTranscript =
[[OWSOutgoingSentMessageTranscript alloc] initWithOutgoingMessage:message];
@ -1227,6 +1252,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:sentMessageTranscript
thread:message.thread
recipient:recipient
senderCertificate:senderCertificate
udManager:self.udManager
localNumber:self.tsAccountManager.localNumber];
@ -1302,24 +1328,22 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return [messagesArray copy];
}
- (NSDictionary *)encryptedMessageForMessageSend:(OWSMessageSend *)messageSend
deviceId:(NSNumber *)deviceNumber
plainText:(NSData *)plainText
isUDSend:(BOOL)isUDSend
transaction:(YapDatabaseReadWriteTransaction *)transaction
// NOTE: This method uses exceptions for control flow.
- (void)ensureRecipientHasSessionForMessageSend:(OWSMessageSend *)messageSend
deviceId:(NSNumber *)deviceId
isUDSend:(BOOL)isUDSend
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(messageSend);
OWSAssertDebug(deviceNumber);
OWSAssertDebug(plainText);
OWSAssertDebug(deviceId);
OWSAssertDebug(transaction);
OWSPrimaryStorage *storage = self.primaryStorage;
TSOutgoingMessage *message = messageSend.message;
SignalRecipient *recipient = messageSend.recipient;
NSString *recipientId = recipient.recipientId;
OWSAssertDebug(recipientId.length > 0);
if (![storage containsSession:recipientId deviceId:[deviceNumber intValue] protocolContext:transaction]) {
if (![storage containsSession:recipientId deviceId:[deviceId intValue] protocolContext:transaction]) {
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block PreKeyBundle *_Nullable bundle;
__block NSException *_Nullable exception;
@ -1328,12 +1352,17 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// 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:[deviceNumber stringValue]];
[OWSRequestFactory recipientPrekeyRequestWithRecipient:recipientId deviceId:[deviceId stringValue]];
if (isUDSend) {
DDLogVerbose(@"UD prekey request.");
[request useUDAuth:messageSend.udAccessKey];
}
[self.networkManager makeRequest:request
completionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
success:^(NSURLSessionDataTask *task, id responseObject) {
bundle = [PreKeyBundle preKeyBundleFromDictionary:responseObject forDeviceNumber:deviceNumber];
bundle = [PreKeyBundle preKeyBundleFromDictionary:responseObject forDeviceNumber:deviceId];
dispatch_semaphore_signal(sema);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
@ -1369,9 +1398,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
SessionBuilder *builder = [[SessionBuilder alloc] initWithSessionStore:storage
preKeyStore:storage
signedPreKeyStore:storage
identityKeyStore:[OWSIdentityManager sharedManager]
identityKeyStore:self.identityManager
recipientId:recipientId
deviceId:[deviceNumber intValue]];
deviceId:[deviceId intValue]];
@try {
[builder processPrekeyBundle:bundle protocolContext:transaction];
} @catch (NSException *exception) {
@ -1384,26 +1413,72 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
}
}
}
// NOTE: This method uses exceptions for control flow.
- (NSDictionary *)encryptedMessageForMessageSend:(OWSMessageSend *)messageSend
deviceId:(NSNumber *)deviceId
plainText:(NSData *)plainText
isUDSend:(BOOL)isUDSend
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(messageSend);
OWSAssertDebug(deviceId);
OWSAssertDebug(plainText);
OWSAssertDebug(transaction);
OWSPrimaryStorage *storage = self.primaryStorage;
TSOutgoingMessage *message = messageSend.message;
SignalRecipient *recipient = messageSend.recipient;
NSString *recipientId = recipient.recipientId;
OWSAssertDebug(recipientId.length > 0);
// This may throw an exception.
[self ensureRecipientHasSessionForMessageSend:messageSend
deviceId:deviceId
isUDSend:isUDSend
transaction:transaction];
SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:storage
preKeyStore:storage
signedPreKeyStore:storage
identityKeyStore:[OWSIdentityManager sharedManager]
identityKeyStore:self.identityManager
recipientId:recipientId
deviceId:[deviceNumber intValue]];
id<CipherMessage> encryptedMessage =
[cipher encryptMessage:[plainText paddedMessageBody] protocolContext:transaction];
deviceId:[deviceId intValue]];
NSData *_Nullable serializedMessage;
TSWhisperMessageType messageType;
if (isUDSend) {
NSError *error;
SMKSecretSessionCipher *_Nullable secretCipher =
[[SMKSecretSessionCipher alloc] initWithSessionStore:self.primaryStorage
preKeyStore:self.primaryStorage
signedPreKeyStore:self.primaryStorage
identityStore:self.identityManager
error:&error];
if (error || !secretCipher) {
OWSRaiseException(@"SecretSessionCipherFailure", @"Can't create secret session cipher.");
}
NSData *serializedMessage = encryptedMessage.serialized;
TSWhisperMessageType messageType = [self messageTypeForCipherMessage:encryptedMessage];
serializedMessage = [secretCipher encryptMessageWithRecipientId:recipientId
deviceId:deviceId.intValue
paddedPlaintext:[plainText paddedMessageBody]
senderCertificate:messageSend.senderCertificate
protocolContext:transaction
error:&error];
messageType = TSUnidentifiedSenderMessageType;
} else {
id<CipherMessage> encryptedMessage =
[cipher encryptMessage:[plainText paddedMessageBody] protocolContext:transaction];
serializedMessage = encryptedMessage.serialized;
messageType = [self messageTypeForCipherMessage:encryptedMessage];
}
BOOL isSilent = message.isSilent;
OWSMessageServiceParams *messageParams =
[[OWSMessageServiceParams alloc] initWithType:messageType
recipientId:recipientId
device:[deviceNumber intValue]
device:[deviceId intValue]
content:serializedMessage
isSilent:isSilent
registrationId:[cipher remoteRegistrationId:transaction]];

@ -34,7 +34,7 @@ public enum OWSUDError: Error {
// We use completion handlers instead of a promise so that message sending
// logic can access the certificate data.
@objc func ensureSenderCertificateObjC(success:@escaping (Data) -> Void,
@objc func ensureSenderCertificateObjC(success:@escaping (SMKSenderCertificate) -> Void,
failure:@escaping (Error) -> Void)
// MARK: - Unrestricted Access
@ -139,17 +139,24 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
}
#endif
private func senderCertificate() -> Data? {
private func senderCertificate() -> SMKSenderCertificate? {
guard let certificateData = dbConnection.object(forKey: kUDCurrentSenderCertificateKey, inCollection: kUDCollection) as? Data else {
return nil
}
guard isValidCertificate(certificateData: certificateData) else {
Logger.warn("Current sender certificate is not valid.")
do {
let certificate = try SMKSenderCertificate.parse(data: certificateData)
guard isValidCertificate(certificate: certificate) else {
Logger.warn("Current sender certificate is not valid.")
return nil
}
return certificate
} catch {
OWSLogger.error("Certificate could not be parsed: \(error)")
return nil
}
return certificateData
}
private func setSenderCertificate(_ certificateData: Data) {
@ -157,55 +164,52 @@ public class OWSUDManagerImpl: NSObject, OWSUDManager {
}
@objc
public func ensureSenderCertificateObjC(success:@escaping (Data) -> Void,
failure:@escaping (Error) -> Void) {
public func ensureSenderCertificateObjC(success:@escaping (SMKSenderCertificate) -> Void,
failure:@escaping (Error) -> Void) {
ensureSenderCertificate()
.then(execute: { certificateData in
success(certificateData)
.then(execute: { certificate in
success(certificate)
})
.catch(execute: { (error) in
failure(error)
}).retainUntilComplete()
}
public func ensureSenderCertificate() -> Promise<Data> {
public func ensureSenderCertificate() -> Promise<SMKSenderCertificate> {
// If there is a valid cached sender certificate, use that.
if let certificateData = senderCertificate() {
return Promise(value: certificateData)
if let certificate = senderCertificate() {
return Promise(value: certificate)
}
// Try to obtain a new sender certificate.
return requestSenderCertificate().then { (certificateData) in
return requestSenderCertificate().then { (certificateData, certificate) in
// Cache the current sender certificate.
self.setSenderCertificate(certificateData)
return Promise(value: certificateData)
return Promise(value: certificate)
}
}
private func requestSenderCertificate() -> Promise<Data> {
return SignalServiceRestClient().requestUDSenderCertificate().then { (certificateData) in
guard self.isValidCertificate(certificateData: certificateData) else {
private func requestSenderCertificate() -> Promise<(Data, SMKSenderCertificate)> {
return SignalServiceRestClient().requestUDSenderCertificate().then { (certificateData) -> Promise<(Data, SMKSenderCertificate)> in
let certificate = try SMKSenderCertificate.parse(data: certificateData)
guard self.isValidCertificate(certificate: certificate) else {
throw OWSUDError.invalidData(description: "Invalid sender certificate returned by server")
}
return Promise(value: certificateData)
return Promise(value: (certificateData, certificate) )
}
}
private func isValidCertificate(certificateData: Data) -> Bool {
do {
let certificate = try SMKSenderCertificate.parse(data: certificateData)
let expirationMs = certificate.expirationTimestamp
let nowMs = NSDate.ows_millisecondTimeStamp()
// Ensure that the certificate will not expire in the next hour.
// We want a threshold long enough to ensure that any outgoing message
// sends will complete before the expiration.
let isValid = nowMs + kHourInMs < expirationMs
return isValid
} catch {
OWSLogger.error("Certificate could not be parsed: \(error)")
return false
}
private func isValidCertificate(certificate: SMKSenderCertificate) -> Bool {
let expirationMs = certificate.expirationTimestamp
let nowMs = NSDate.ows_millisecondTimeStamp()
// Ensure that the certificate will not expire in the next hour.
// We want a threshold long enough to ensure that any outgoing message
// sends will complete before the expiration.
let isValid = nowMs + kHourInMs < expirationMs
return isValid
}
// MARK: - Unrestricted Access

@ -0,0 +1,13 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "TSRequest.h"
@class SMKUDAccessKey;
@interface TSRequest (UD)
- (void)useUDAuth:(SMKUDAccessKey *)udAccessKey;
@end

@ -0,0 +1,21 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "TSRequest+UD.h"
#import <SignalCoreKit/NSData+OWS.h>
#import <SignalMetadataKit/SignalMetadataKit-Swift.h>
@implementation TSRequest (UD)
- (void)useUDAuth:(SMKUDAccessKey *)udAccessKey
{
OWSAssertDebug(udAccessKey);
// Suppress normal auth headers.
self.shouldHaveAuthorizationHeaders = NO;
// Add UD auth header.
[self setValue:[udAccessKey.keyData base64EncodedString] forKey:@"Unidentified-Access-Key"];
}
@end

@ -6,11 +6,12 @@
#define TextSecureKit_Constants_h
typedef NS_ENUM(NSInteger, TSWhisperMessageType) {
TSUnknownMessageType = 0,
TSEncryptedWhisperMessageType = 1,
TSUnknownMessageType = 0,
TSEncryptedWhisperMessageType = 1,
TSIgnoreOnIOSWhisperMessageType = 2, // on droid this is the prekey bundle message irrelevant for us
TSPreKeyWhisperMessageType = 3,
TSPreKeyWhisperMessageType = 3,
TSUnencryptedWhisperMessageType = 4,
TSUnidentifiedSenderMessageType = 6,
};
#pragma mark Server Address

@ -56,15 +56,15 @@ public class OWSFakeUDManager: NSObject, OWSUDManager {
// MARK: - Server Certificate
// Tests can control the behavior of this mock by setting this property.
@objc public var nextSenderCertificate: Data?
@objc public var nextSenderCertificate: SMKSenderCertificate?
@objc public func ensureSenderCertificateObjC(success:@escaping (Data) -> Void,
@objc public func ensureSenderCertificateObjC(success:@escaping (SMKSenderCertificate) -> Void,
failure:@escaping (Error) -> Void) {
guard let certificateData = nextSenderCertificate else {
failure(OWSUDError.assertionError(description: "No mock server certificate data"))
guard let certificate = nextSenderCertificate else {
failure(OWSUDError.assertionError(description: "No mock server certificate"))
return
}
success(certificateData)
success(certificate)
}
// MARK: - Unrestricted Access

Loading…
Cancel
Save