Censorship circumvention in Egypt and UAE

* domain fronting
* non-websocket message fetching
* alternate pinning policy for google hosted reflector server

// FREEBIE
This commit is contained in:
Michael Kirk 2016-12-20 14:01:32 -06:00
parent b1ebfa9873
commit 78515377b1
15 changed files with 381 additions and 70 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
# Xcode
#
.DS_Store
build/
*.pbxuser
!default.pbxuser

View file

@ -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' }

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -6,22 +6,19 @@
// Copyright (c) 2013 Open Whisper Systems. All rights reserved.
//
#import <AFNetworking/AFNetworking.h>
#import "OWSHTTPSecurityPolicy.h"
#import "NSURLSessionDataTask+StatusCode.h"
#import "TSAccountManager.h"
#import "TSNetworkManager.h"
#import "NSURLSessionDataTask+StatusCode.h"
#import "OWSSignalService.h"
#import "TSAccountManager.h"
#import "TSStorageManager+keyingMaterial.h"
#import "TSVerifyCodeRequest.h"
#import <AFNetworking/AFNetworking.h>
#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];

View file

@ -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

View file

@ -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<NSString *> *)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

View file

@ -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

View file

@ -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 <AFNetworking/AFHTTPSessionManager.h>
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<NSData *> *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

View file

@ -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

Binary file not shown.