diff --git a/.gitignore b/.gitignore index 17afc7250..649f2f60c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Xcode # +.DS_Store build/ *.pbxuser !default.pbxuser diff --git a/SignalServiceKit.podspec b/SignalServiceKit.podspec index b09e50e87..52f41c3c7 100644 --- a/SignalServiceKit.podspec +++ b/SignalServiceKit.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = "SignalServiceKit" - s.version = "0.8.1" + s.version = "0.9.0" s.summary = "An Objective-C library for communicating with the Signal messaging service." s.description = <<-DESC @@ -27,7 +27,8 @@ An Objective-C library for communicating with the Signal messaging service. s.requires_arc = true s.source_files = 'src/**/*.{h,m,mm}' - s.resource = 'src/Security/PinningCertificate/textsecure.cer' + s.resources = ['src/Security/PinningCertificate/textsecure.cer', + 'src/Security/PinningCertificate/GIAG2.crt'] s.prefix_header_file = 'src/TSPrefix.h' s.xcconfig = { 'OTHER_CFLAGS' => '$(inherited) -DSQLITE_HAS_CODEC' } diff --git a/src/Account/TSAccountManager.m b/src/Account/TSAccountManager.m index 1761c7623..8ef0bed4b 100644 --- a/src/Account/TSAccountManager.m +++ b/src/Account/TSAccountManager.m @@ -139,6 +139,9 @@ NS_ASSUME_NONNULL_BEGIN return; } + TSAccountManager *manager = [self sharedInstance]; + manager.phoneNumberAwaitingVerification = phoneNumber; + [[TSNetworkManager sharedManager] makeRequest:[[TSRequestVerificationCodeRequest alloc] initWithPhoneNumber:phoneNumber @@ -148,8 +151,6 @@ NS_ASSUME_NONNULL_BEGIN self.tag, phoneNumber, isSMS ? @"SMS" : @"Voice"); - TSAccountManager *manager = [self sharedInstance]; - manager.phoneNumberAwaitingVerification = phoneNumber; successBlock(); } failure:^(NSURLSessionDataTask *task, NSError *error) { 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..2b9e47961 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,21 @@ 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] init]; + 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 +61,44 @@ 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]; + AFHTTPSessionManager *sessionManager = self.signalService.HTTPSessionManager; 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 +227,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..870a03c3d --- /dev/null +++ b/src/Network/OWSSignalService.h @@ -0,0 +1,17 @@ +// Created by Michael Kirk on 12/20/16. +// Copyright © 2016 Open Whisper Systems. All rights reserved. + +NS_ASSUME_NONNULL_BEGIN + +@class TSStorageManager; +@class TSAccountManager; +@class AFHTTPSessionManager; + +@interface OWSSignalService : NSObject + +@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..30a6b08cd --- /dev/null +++ b/src/Network/OWSSignalService.m @@ -0,0 +1,138 @@ +// 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface OWSSignalService () + +@property (nonatomic, readonly, strong) OWSCensorshipConfiguration *censorshipConfiguration; + +@end + +@implementation OWSSignalService + +- (instancetype)init +{ + self = [super init]; + if (!self) { + return self; + } + + _censorshipConfiguration = [OWSCensorshipConfiguration new]; + + return self; +} + +- (BOOL)isCensored +{ + NSString *localNumber = [TSAccountManager localNumber]; + + if (localNumber) { + return [self.censorshipConfiguration isCensoredPhoneNumber:localNumber]; + } else { + DDLogError(@"no known phone number to check for censorship."); + return NO; + } +} + +- (AFHTTPSessionManager *)HTTPSessionManager +{ + if (self.isCensored) { + DDLogInfo(@"%@ using reflector HTTPSessionManager", self.tag); + return self.reflectorHTTPSessionManager; + } else { + DDLogDebug(@"%@ using default HTTPSessionManager", self.tag); + 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]; + + sessionManager.securityPolicy = [[self class] googlePinningPolicy]; + + sessionManager.requestSerializer = [AFJSONRequestSerializer serializer]; + [sessionManager.requestSerializer setValue:self.censorshipConfiguration.reflectorHost forHTTPHeaderField:@"Host"]; + + sessionManager.responseSerializer = [AFJSONResponseSerializer serializer]; + + return sessionManager; +} + +#pragma mark - Google Pinning Policy + +/** + * We use the Google Pinning Policy when connecting to our censorship circumventing reflector, + * which is hosted on Google. + */ ++ (AFSecurityPolicy *)googlePinningPolicy { + static AFSecurityPolicy *securityPolicy = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSError *error; + NSString *path = [NSBundle.mainBundle pathForResource:@"GIAG2" ofType:@"crt"]; + + if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { + @throw [NSException + exceptionWithName:@"Missing server certificate" + reason:[NSString stringWithFormat:@"Missing signing certificate for service googlePinningPolicy"] + userInfo:nil]; + } + + NSData *googleCertData = [NSData dataWithContentsOfFile:path options:0 error:&error]; + if (!googleCertData) { + if (error) { + @throw [NSException exceptionWithName:@"OWSSignalServiceHTTPSecurityPolicy" reason:@"Couln't read google pinning cert" userInfo:nil]; + } else { + NSString *reason = [NSString stringWithFormat:@"Reading google pinning cert faile with error: %@", error]; + @throw [NSException exceptionWithName:@"OWSSignalServiceHTTPSecurityPolicy" reason:reason userInfo:nil]; + } + } + + NSSet *certificates = [NSSet setWithObject:googleCertData]; + securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certificates]; + }); + return securityPolicy; +} + +#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..7cdb41d6f 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,18 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; @implementation TSSocketManager -- (instancetype)init { +- (instancetype)init +{ self = [super init]; - if (self) { - self.websocket = nil; - [self addObserver:self forKeyPath:@"status" options:0 context:kSocketStatusObservationContext]; + if (!self) { + return self; } + _signalService = [OWSSignalService new]; + _websocket = nil; + [self addObserver:self forKeyPath:@"status" options:0 context:kSocketStatusObservationContext]; + return self; } @@ -73,38 +80,46 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification"; #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 +377,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 diff --git a/src/Security/PinningCertificate/GIAG2.crt b/src/Security/PinningCertificate/GIAG2.crt new file mode 100644 index 000000000..3a0b158e1 Binary files /dev/null and b/src/Security/PinningCertificate/GIAG2.crt differ