diff --git a/src/Network/API/Requests/OWSAcknowledgeMessageDeliveryRequest.h b/src/Network/API/Requests/OWSAcknowledgeMessageDeliveryRequest.h new file mode 100644 index 000000000..8a1147f17 --- /dev/null +++ b/src/Network/API/Requests/OWSAcknowledgeMessageDeliveryRequest.h @@ -0,0 +1,14 @@ +// Created by Michael Kirk on 12/19/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "TSRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSAcknowledgeMessageDeliveryRequest : TSRequest + +- (instancetype)initWithSource:(NSString *)source timestamp:(UInt64)timestamp; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Network/API/Requests/OWSAcknowledgeMessageDeliveryRequest.m b/src/Network/API/Requests/OWSAcknowledgeMessageDeliveryRequest.m new file mode 100644 index 000000000..120926c64 --- /dev/null +++ b/src/Network/API/Requests/OWSAcknowledgeMessageDeliveryRequest.m @@ -0,0 +1,23 @@ +// Created by Michael Kirk on 12/19/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSAcknowledgeMessageDeliveryRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OWSAcknowledgeMessageDeliveryRequest + +- (instancetype)initWithSource:(NSString *)source timestamp:(UInt64)timestamp +{ + NSString *path = [NSString stringWithFormat:@"v1/messages/%@/%llu", source, timestamp]; + NSURL *url = [NSURL URLWithString:path]; + + self = [super initWithURL:url]; + self.HTTPMethod = @"DELETE"; + + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Network/API/Requests/OWSGetMessagesRequest.h b/src/Network/API/Requests/OWSGetMessagesRequest.h new file mode 100644 index 000000000..0844878cd --- /dev/null +++ b/src/Network/API/Requests/OWSGetMessagesRequest.h @@ -0,0 +1,12 @@ +// Created by Michael Kirk on 12/19/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "TSRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSGetMessagesRequest : TSRequest + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Network/API/Requests/OWSGetMessagesRequest.m b/src/Network/API/Requests/OWSGetMessagesRequest.m new file mode 100644 index 000000000..867cb93d6 --- /dev/null +++ b/src/Network/API/Requests/OWSGetMessagesRequest.m @@ -0,0 +1,18 @@ +// Created by Michael Kirk on 12/19/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSGetMessagesRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OWSGetMessagesRequest + +- (instancetype)init +{ + NSURL *url = [NSURL URLWithString:@"v1/messages"]; + return [super initWithURL:url]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Network/API/TSNetworkManager.h b/src/Network/API/TSNetworkManager.h index 982f6a1db..d4d54a75f 100644 --- a/src/Network/API/TSNetworkManager.h +++ b/src/Network/API/TSNetworkManager.h @@ -34,6 +34,6 @@ - (void)makeRequest:(TSRequest *)request success:(void (^)(NSURLSessionDataTask *task, id responseObject))success - failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure; + failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure NS_SWIFT_NAME(makeRequest(_:success:failure:)); @end diff --git a/src/Network/API/TSNetworkManager.m b/src/Network/API/TSNetworkManager.m index 3ddd76957..a27c42bbd 100644 --- a/src/Network/API/TSNetworkManager.m +++ b/src/Network/API/TSNetworkManager.m @@ -6,22 +6,19 @@ // Copyright (c) 2013 Open Whisper Systems. All rights reserved. // -#import - -#import "OWSHTTPSecurityPolicy.h" - +#import "TSNetworkManager.h" #import "NSURLSessionDataTask+StatusCode.h" +#import "OWSSignalService.h" #import "TSAccountManager.h" -#import "TSNetworkManager.h" #import "TSStorageManager+keyingMaterial.h" #import "TSVerifyCodeRequest.h" +#import #define TSNetworkManagerDomain @"org.whispersystems.signal.networkManager" @interface TSNetworkManager () -@property AFHTTPSessionManager *operationManager; - +@property (nonatomic, readonly, strong) OWSSignalService *signalService; typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error); @end @@ -34,30 +31,22 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error); static TSNetworkManager *sharedMyManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedMyManager = [[self alloc] initWithDefaultOperationManager]; + OWSSignalService *signalService = + [[OWSSignalService alloc] initWithStorageManager:[TSStorageManager sharedManager]]; + sharedMyManager = [[self alloc] initWithSignalService:signalService]; }); return sharedMyManager; } -- (instancetype)initWithDefaultOperationManager -{ - NSURLSessionConfiguration *sessionConf = NSURLSessionConfiguration.ephemeralSessionConfiguration; - NSURL *baseURL = [[NSURL alloc] initWithString:textSecureServerURL]; - AFHTTPSessionManager *operationManager = - [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL sessionConfiguration:sessionConf]; - operationManager.securityPolicy = [OWSHTTPSecurityPolicy sharedPolicy]; - - return [self initWithOperationManager:operationManager]; -} -- (instancetype)initWithOperationManager:(AFHTTPSessionManager *)operationManager +- (instancetype)initWithSignalService:(OWSSignalService *)signalService { self = [super init]; if (!self) { return self; } - _operationManager = operationManager; + _signalService = signalService; return self; } @@ -73,48 +62,52 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error); void (^failure)(NSURLSessionDataTask *task, NSError *error) = [TSNetworkManager errorPrettifyingForFailureBlock:failureBlock]; - self.operationManager.requestSerializer = [AFJSONRequestSerializer serializer]; - self.operationManager.responseSerializer = [AFJSONResponseSerializer serializer]; + // FIXME TODO these were being rebuilt each request because we're mangling the serializer's auth headers depending + // on the request type. + // But it's kind of messy considering we want to be able to change headres. + // self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer]; + // self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer]; + + AFHTTPSessionManager *sessionManager = self.signalService.HTTPSessionManager; + + // FIXME TODO And what about baseDomain when doing s3 uploading? if ([request isKindOfClass:[TSVerifyCodeRequest class]]) { // We plant the Authorization parameter ourselves, no need to double add. - [self.operationManager.requestSerializer + [sessionManager.requestSerializer setAuthorizationHeaderFieldWithUsername:((TSVerifyCodeRequest *)request).numberToValidate password:[request.parameters objectForKey:@"AuthKey"]]; [request.parameters removeObjectForKey:@"AuthKey"]; - [self.operationManager PUT:[textSecureServerURL stringByAppendingString:request.URL.absoluteString] - parameters:request.parameters - success:success - failure:failure]; + [sessionManager PUT:request.URL.absoluteString parameters:request.parameters success:success failure:failure]; } else { if (![request isKindOfClass:[TSRequestVerificationCodeRequest class]]) { - [self.operationManager.requestSerializer + [sessionManager.requestSerializer setAuthorizationHeaderFieldWithUsername:[TSAccountManager localNumber] password:[TSStorageManager serverAuthToken]]; } if ([request.HTTPMethod isEqualToString:@"GET"]) { - [self.operationManager GET:[textSecureServerURL stringByAppendingString:request.URL.absoluteString] - parameters:request.parameters - progress:nil - success:success - failure:failure]; + [sessionManager GET:request.URL.absoluteString + parameters:request.parameters + progress:nil + success:success + failure:failure]; } else if ([request.HTTPMethod isEqualToString:@"POST"]) { - [self.operationManager POST:[textSecureServerURL stringByAppendingString:request.URL.absoluteString] - parameters:request.parameters - progress:nil - success:success - failure:failure]; + [sessionManager POST:request.URL.absoluteString + parameters:request.parameters + progress:nil + success:success + failure:failure]; } else if ([request.HTTPMethod isEqualToString:@"PUT"]) { - [self.operationManager PUT:[textSecureServerURL stringByAppendingString:request.URL.absoluteString] - parameters:request.parameters - success:success - failure:failure]; + [sessionManager PUT:request.URL.absoluteString + parameters:request.parameters + success:success + failure:failure]; } else if ([request.HTTPMethod isEqualToString:@"DELETE"]) { - [self.operationManager DELETE:[textSecureServerURL stringByAppendingString:request.URL.absoluteString] - parameters:request.parameters - success:success - failure:failure]; + [sessionManager DELETE:request.URL.absoluteString + parameters:request.parameters + success:success + failure:failure]; } else { DDLogError(@"Trying to perform HTTP operation with unknown verb: %@", request.HTTPMethod); } @@ -243,6 +236,8 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error); return [NSError errorWithDomain:TSNetworkManagerDomain code:code userInfo:dict]; } +#pragma mark - Logging + + (NSString *)tag { return [NSString stringWithFormat:@"[%@]", self.class]; diff --git a/src/Network/OWSCensorshipConfiguration.h b/src/Network/OWSCensorshipConfiguration.h new file mode 100644 index 000000000..03a46da61 --- /dev/null +++ b/src/Network/OWSCensorshipConfiguration.h @@ -0,0 +1,16 @@ +// Created by Michael Kirk on 12/20/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +NS_ASSUME_NONNULL_BEGIN + +@class TSStorageManager; + +@interface OWSCensorshipConfiguration : NSObject + +- (NSString *)frontingHost; +- (NSString *)reflectorHost; +- (BOOL)isCensoredPhoneNumber:(NSString *)e164PhonNumber; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Network/OWSCensorshipConfiguration.m b/src/Network/OWSCensorshipConfiguration.m new file mode 100644 index 000000000..466547aa8 --- /dev/null +++ b/src/Network/OWSCensorshipConfiguration.m @@ -0,0 +1,57 @@ +// Created by Michael Kirk on 12/20/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSCensorshipConfiguration.h" +#import "TSStorageManager.h" + +NS_ASSUME_NONNULL_BEGIN + +NSString *const OWSCensorshipConfigurationFrontingHost = @"https://google.com"; +NSString *const OWSCensorshipConfigurationReflectorHost = @"signal-reflector-meek.appspot.com"; + +@implementation OWSCensorshipConfiguration + +- (NSString *)frontingHost +{ + return OWSCensorshipConfigurationFrontingHost; +} + +- (NSString *)reflectorHost +{ + return OWSCensorshipConfigurationReflectorHost; +} + +- (NSArray *)censoredCountryCodes +{ + // Reports of censorship in: + // Egypt + // UAE + return @[@"+20", + @"+971"]; +} + +- (BOOL)isCensoredPhoneNumber:(NSString *)e164PhonNumber +{ + for (NSString *countryCode in self.censoredCountryCodes) { + if ([e164PhonNumber hasPrefix:countryCode]) { + return YES; + } + } + return NO; +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Network/OWSSignalService.h b/src/Network/OWSSignalService.h new file mode 100644 index 000000000..eab0598ec --- /dev/null +++ b/src/Network/OWSSignalService.h @@ -0,0 +1,18 @@ +// Created by Michael Kirk on 12/20/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +NS_ASSUME_NONNULL_BEGIN + +@class TSStorageManager; +@class AFHTTPSessionManager; + +@interface OWSSignalService : NSObject + +- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager; + +@property (nonatomic, readonly) BOOL isCensored; +@property (nonatomic, readonly) AFHTTPSessionManager *HTTPSessionManager; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Network/OWSSignalService.m b/src/Network/OWSSignalService.m new file mode 100644 index 000000000..42bc97cea --- /dev/null +++ b/src/Network/OWSSignalService.m @@ -0,0 +1,129 @@ +// Created by Michael Kirk on 12/20/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +#import "OWSSignalService.h" +#import "OWSCensorshipConfiguration.h" +#import "OWSHTTPSecurityPolicy.h" +#import "TSConstants.h" +#import "TSAccountManager.h" +#import "TSStorageManager+keyingMaterial.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface TSAccountManager (OWSSignalService) + +@property (nullable, nonatomic, readonly) NSString *phoneNumberAwaitingVerification; + +@end + +@interface OWSSignalService () + +@property (nonatomic, readonly, strong) TSStorageManager *storageManager; +@property (nonatomic, readonly, strong) TSAccountManager *tsAccountManager; +@property (nonatomic, readonly, strong) OWSCensorshipConfiguration *censorshipConfiguration; + +@end + +@implementation OWSSignalService + +- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager + tsAccountManager:(TSAccountManager *)tsAccountManager +{ + self = [super init]; + if (!self) { + return self; + } + + _storageManager = storageManager; + _tsAccountManager = tsAccountManager; + _censorshipConfiguration = [OWSCensorshipConfiguration new]; + + return self; +} + +- (BOOL)isCensored +{ + + NSString *localNumber = self.storageManager.localNumber; + NSString *pendingNumber = self.tsAccountManager.phoneNumberAwaitingVerification; + + if (localNumber) { + if ([self.censorshipConfiguration isCensoredPhoneNumber:localNumber]) { + DDLogInfo(@"%@ assumed censorship for localNumber: %@", self.tag, localNumber); + return YES; + } else { + DDLogInfo(@"%@ assumed no censorship for localNumber: %@", self.tag, localNumber); + return NO; + } + } else if (pendingNumber) { + if ([self.censorshipConfiguration isCensoredPhoneNumber:pendingNumber]) { + DDLogInfo(@"%@ assumed censorship for pending Number: %@", self.tag, pendingNumber); + return YES; + } else { + DDLogInfo(@"%@ assumed no censorship for pending Number: %@", self.tag, pendingNumber); + return NO; + } + } else { + DDLogError(@"no known phone number to check for censorship."); + return NO; + } +} + +- (AFHTTPSessionManager *)HTTPSessionManager +{ + if (self.isCensored) { + return self.reflectorHTTPSessionManager; + } else { + return self.defaultHTTPSessionManager; + } +} + +- (AFHTTPSessionManager *)defaultHTTPSessionManager +{ + NSURL *baseURL = [[NSURL alloc] initWithString:textSecureServerURL]; + NSURLSessionConfiguration *sessionConf = NSURLSessionConfiguration.ephemeralSessionConfiguration; + AFHTTPSessionManager *sessionManager = + [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL sessionConfiguration:sessionConf]; + + sessionManager.securityPolicy = [OWSHTTPSecurityPolicy sharedPolicy]; + sessionManager.requestSerializer = [AFJSONRequestSerializer serializer]; + sessionManager.responseSerializer = [AFJSONResponseSerializer serializer]; + + return sessionManager; +} + +- (AFHTTPSessionManager *)reflectorHTTPSessionManager +{ + // Target fronting domain + NSURL *baseURL = [[NSURL alloc] initWithString:self.censorshipConfiguration.frontingHost]; + NSURLSessionConfiguration *sessionConf = NSURLSessionConfiguration.ephemeralSessionConfiguration; + AFHTTPSessionManager *sessionManager = + [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL sessionConfiguration:sessionConf]; + + // FIXME TODO can we still pin SSL while fronting? + // sessionManager.securityPolicy = [OWSHTTPSecurityPolicy sharedPolicy]; + + sessionManager.requestSerializer = [AFJSONRequestSerializer serializer]; + [sessionManager.requestSerializer setValue:self.censorshipConfiguration.reflectorHost forHTTPHeaderField:@"Host"]; + + sessionManager.responseSerializer = [AFJSONResponseSerializer serializer]; + + return sessionManager; +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Network/WebSockets/TSSocketManager.m b/src/Network/WebSockets/TSSocketManager.m index bc62759a2..7997bba27 100644 --- a/src/Network/WebSockets/TSSocketManager.m +++ b/src/Network/WebSockets/TSSocketManager.m @@ -8,15 +8,15 @@ #import "SubProtocol.pb.h" +#import "Cryptography.h" +#import "OWSSignalService.h" +#import "OWSWebsocketSecurityPolicy.h" #import "TSAccountManager.h" #import "TSConstants.h" #import "TSMessagesManager.h" #import "TSSocketManager.h" #import "TSStorageManager+keyingMaterial.h" -#import "OWSWebsocketSecurityPolicy.h" -#import "Cryptography.h" - #define kWebSocketHeartBeat 30 #define kWebSocketReconnectTry 5 #define kBackgroundConnectTimer 25 @@ -27,6 +27,9 @@ NSString *const SocketClosedNotification = @"SocketClosedNotification"; NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; @interface TSSocketManager () + +@property (nonatomic, readonly, strong) OWSSignalService *signalService; + @property (nonatomic, retain) NSTimer *pingTimer; @property (nonatomic, retain) NSTimer *reconnectTimer; @@ -47,14 +50,19 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; @implementation TSSocketManager -- (instancetype)init { +- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager +{ self = [super init]; - if (self) { - self.websocket = nil; - [self addObserver:self forKeyPath:@"status" options:0 context:kSocketStatusObservationContext]; + if (!self) { + return self; } + _websocket = nil; + _signalService = [[OWSSignalService alloc] initWithStorageManager:storageManager]; + [self addObserver:self forKeyPath:@"status" options:0 context:kSocketStatusObservationContext]; + + return self; } @@ -62,49 +70,57 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; static TSSocketManager *sharedMyManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedMyManager = [[self alloc] init]; - sharedMyManager.fetchingTaskIdentifier = UIBackgroundTaskInvalid; - sharedMyManager.didConnectBg = NO; - sharedMyManager.shouldDownloadMessage = NO; - sharedMyManager.didRetreiveMessageBg = NO; + sharedMyManager = [[self alloc] initWithStorageManager:[TSStorageManager sharedManager]]; + sharedMyManager.fetchingTaskIdentifier = UIBackgroundTaskInvalid; + sharedMyManager.didConnectBg = NO; + sharedMyManager.shouldDownloadMessage = NO; + sharedMyManager.didRetreiveMessageBg = NO; }); return sharedMyManager; } #pragma mark - Manage Socket -+ (void)becomeActive { - TSSocketManager *sharedInstance = [self sharedManager]; - SRWebSocket *socket = [sharedInstance websocket]; ++ (void)becomeActive +{ + [[self sharedManager] becomeActive]; +} +- (void)becomeActive +{ + if (self.signalService.isCensored) { + DDLogWarn(@"%@ Refusing to start websocket in `becomeActive`.", self.tag); + return; + } + + SRWebSocket *socket = self.websocket; if (socket) { switch ([socket readyState]) { case SR_OPEN: DDLogVerbose(@"WebSocket already open on connection request"); - sharedInstance.status = kSocketStatusOpen; + self.status = kSocketStatusOpen; return; case SR_CONNECTING: DDLogVerbose(@"WebSocket is already connecting"); - sharedInstance.status = kSocketStatusConnecting; + self.status = kSocketStatusConnecting; return; default: [socket close]; - sharedInstance.status = kSocketStatusClosed; + self.status = kSocketStatusClosed; socket.delegate = nil; socket = nil; break; } } - NSString *webSocketConnect = - [textSecureWebSocketAPI stringByAppendingString:[[self sharedManager] webSocketAuthenticationString]]; + NSString *webSocketConnect = [textSecureWebSocketAPI stringByAppendingString:[self webSocketAuthenticationString]]; NSURL *webSocketConnectURL = [NSURL URLWithString:webSocketConnect]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:webSocketConnectURL]; - socket = [[SRWebSocket alloc] initWithURLRequest:request securityPolicy:[OWSWebsocketSecurityPolicy sharedPolicy]]; - socket.delegate = [self sharedManager]; + socket = [[SRWebSocket alloc] initWithURLRequest:request securityPolicy:[OWSWebsocketSecurityPolicy sharedPolicy]]; + socket.delegate = self; - [[self sharedManager] setWebsocket:socket]; + [self setWebsocket:socket]; [socket open]; } @@ -362,4 +378,16 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; [[self sharedManager] notifyStatusChange]; } +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + @end