From 76abaef2c66785128b9367bb65ad52f09e0403fb Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Wed, 17 Apr 2019 19:56:44 -0600 Subject: [PATCH] Censorship circumvention with attachments v2 --- .../Attachments/OWSAttachmentDownloads.m | 5 +-- .../Network/API/Requests/OWSRequestFactory.m | 29 +++++++++---- .../src/Network/API/Requests/TSRequest.h | 2 + .../src/Network/API/TSNetworkManager.m | 43 ++++++++++++++----- .../src/Network/OWSSignalService.h | 5 ++- .../src/Network/OWSSignalService.m | 18 ++++++-- SignalServiceKit/src/TSConstants.h | 17 ++++++-- 7 files changed, 88 insertions(+), 31 deletions(-) diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m index ef630260f4..0ee94df2cb 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentDownloads.m @@ -494,11 +494,7 @@ typedef void (^AttachmentDownloadFailure)(NSError *error); TSAttachmentPointer *attachmentPointer = job.attachmentPointer; AFHTTPSessionManager *manager = self.cdnSessionManager; - manager.requestSerializer = [AFHTTPRequestSerializer serializer]; - [manager.requestSerializer setValue:OWSMimeTypeApplicationOctetStream forHTTPHeaderField:@"Content-Type"]; - manager.responseSerializer = [AFHTTPResponseSerializer serializer]; manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - NSString *urlPath = [NSString stringWithFormat:@"attachments/%llu", attachmentPointer.serverId]; NSURL *url = [[NSURL alloc] initWithString:urlPath relativeToURL:manager.baseURL]; @@ -527,6 +523,7 @@ typedef void (^AttachmentDownloadFailure)(NSError *error); URLString:url.absoluteString parameters:nil error:&serializationError]; + [request setValue:OWSMimeTypeApplicationOctetStream forHTTPHeaderField:@"Content-Type"]; if (serializationError) { return failureHandler(serializationError); } diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m index 30bde219bc..50ca3faefc 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m @@ -144,6 +144,15 @@ NS_ASSUME_NONNULL_BEGIN return [TSRequest requestWithUrl:[NSURL URLWithString:@"v2/attachments/form/upload"] method:@"GET" parameters:@{}]; } ++ (TSRequest *)attachmentRequestWithAttachmentId:(UInt64)attachmentId +{ + OWSAssertDebug(attachmentId > 0); + + NSString *path = [NSString stringWithFormat:@"%@/%llu", textSecureAttachmentsAPI, attachmentId]; + + return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; +} + + (TSRequest *)availablePreKeysCountRequest { NSString *path = [NSString stringWithFormat:@"%@", textSecureKeysAPI]; @@ -207,15 +216,15 @@ NS_ASSUME_NONNULL_BEGIN + (TSRequest *)updateAttributesRequest { - NSString *path = [textSecureAccountsAPI stringByAppendingString:textSecureAttributesAPI]; - NSString *authKey = self.tsAccountManager.serverAuthToken; OWSAssertDebug(authKey.length > 0); NSString *_Nullable pin = [self.ows2FAManager pinCode]; NSDictionary *accountAttributes = [self accountAttributesWithPin:pin authKey:authKey]; - return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:accountAttributes]; + return [TSRequest requestWithUrl:[NSURL URLWithString:textSecureAttributesAPI] + method:@"PUT" + parameters:accountAttributes]; } + (TSRequest *)unregisterAccountRequest @@ -426,7 +435,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAssertDebug(authUsername.length > 0); OWSAssertDebug(authPassword.length > 0); - NSString *path = [NSString stringWithFormat:@"%@/v1/attestation/%@", contactDiscoveryURL, enclaveId]; + NSString *path = [NSString stringWithFormat:@"v1/attestation/%@", enclaveId]; TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:@{ @@ -435,6 +444,8 @@ NS_ASSUME_NONNULL_BEGIN }]; request.authUsername = authUsername; request.authPassword = authPassword; + request.customHost = contactDiscoveryURL; + request.customCensorshipCircumventionPrefix = contactDiscoveryCensorshipPrefix; // Don't bother with the default cookie store; // these cookies are ephemeral. @@ -455,7 +466,7 @@ NS_ASSUME_NONNULL_BEGIN authPassword:(NSString *)authPassword cookies:(NSArray *)cookies { - NSString *path = [NSString stringWithFormat:@"%@/v1/discovery/%@", contactDiscoveryURL, enclaveId]; + NSString *path = [NSString stringWithFormat:@"v1/discovery/%@", enclaveId]; TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" @@ -469,6 +480,8 @@ NS_ASSUME_NONNULL_BEGIN request.authUsername = authUsername; request.authPassword = authPassword; + request.customHost = contactDiscoveryURL; + request.customCensorshipCircumventionPrefix = contactDiscoveryCensorshipPrefix; // Don't bother with the default cookie store; // these cookies are ephemeral. @@ -484,7 +497,7 @@ NS_ASSUME_NONNULL_BEGIN + (TSRequest *)remoteAttestationAuthRequest { - NSString *path = @"/v1/directory/auth"; + NSString *path = @"v1/directory/auth"; return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; } @@ -506,7 +519,7 @@ NS_ASSUME_NONNULL_BEGIN } parameters = @{ @"reason": limitedReason }; } - NSString *path = [NSString stringWithFormat:@"/v1/directory/feedback-v3/%@", status]; + NSString *path = [NSString stringWithFormat:@"v1/directory/feedback-v3/%@", status]; return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:parameters]; } @@ -514,7 +527,7 @@ NS_ASSUME_NONNULL_BEGIN + (TSRequest *)udSenderCertificateRequest { - NSString *path = @"/v1/certificate/delivery"; + NSString *path = @"v1/certificate/delivery"; return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}]; } diff --git a/SignalServiceKit/src/Network/API/Requests/TSRequest.h b/SignalServiceKit/src/Network/API/Requests/TSRequest.h index 25e9c3933b..61c875c369 100644 --- a/SignalServiceKit/src/Network/API/Requests/TSRequest.h +++ b/SignalServiceKit/src/Network/API/Requests/TSRequest.h @@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) BOOL shouldHaveAuthorizationHeaders; @property (atomic, nullable) NSString *authUsername; @property (atomic, nullable) NSString *authPassword; +@property (atomic, nullable) NSString *customHost; +@property (atomic, nullable) NSString *customCensorshipCircumventionPrefix; @property (nonatomic, readonly) NSDictionary *parameters; diff --git a/SignalServiceKit/src/Network/API/TSNetworkManager.m b/SignalServiceKit/src/Network/API/TSNetworkManager.m index eb69e8be08..03d2c70bd7 100644 --- a/SignalServiceKit/src/Network/API/TSNetworkManager.m +++ b/SignalServiceKit/src/Network/API/TSNetworkManager.m @@ -103,6 +103,35 @@ dispatch_queue_t NetworkManagerQueue() password:request.authPassword]; } + // Most of TSNetwork requests are destined for the Signal Service. + // When we are domain fronting, we have to target a different host and add a path prefix. + // For common Signal-Service requests the host/path-prefix logic is handled by the + // sessionManager. + // + // However, for CDS requests, we need to: + // With CC enabled, use the service fronting Hostname but a custom path-prefix + // With CC disabled, use the custom directory host, and no path-prefix + NSString *requestURLString; + if (self.signalService.isCensorshipCircumventionActive && request.customCensorshipCircumventionPrefix.length > 0) { + // All fronted requests go through the same host + NSURL *customBaseURL = [self.signalService.domainFrontBaseURL + URLByAppendingPathComponent:request.customCensorshipCircumventionPrefix]; + // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected + if (![customBaseURL.absoluteString hasSuffix:@"/"]) { + customBaseURL = [customBaseURL URLByAppendingPathComponent:@""]; + } + OWSAssertDebug(customBaseURL); + requestURLString = [NSURL URLWithString:request.URL.absoluteString relativeToURL:customBaseURL].absoluteString; + } else if (request.customHost) { + NSURL *customBaseURL = [NSURL URLWithString:request.customHost]; + OWSAssertDebug(customBaseURL); + requestURLString = [NSURL URLWithString:request.URL.absoluteString relativeToURL:customBaseURL].absoluteString; + } else { + // requests for the signal-service (with or without censorship circumvention) + requestURLString = request.URL.absoluteString; + } + OWSAssertDebug(requestURLString.length > 0); + // Honor the request's headers. for (NSString *headerField in request.allHTTPHeaderFields) { NSString *headerValue = request.allHTTPHeaderFields[headerField]; @@ -110,27 +139,21 @@ dispatch_queue_t NetworkManagerQueue() } if ([request.HTTPMethod isEqualToString:@"GET"]) { - [self.sessionManager GET:request.URL.absoluteString + [self.sessionManager GET:requestURLString parameters:request.parameters progress:nil success:success failure:failure]; } else if ([request.HTTPMethod isEqualToString:@"POST"]) { - [self.sessionManager POST:request.URL.absoluteString + [self.sessionManager POST:requestURLString parameters:request.parameters progress:nil success:success failure:failure]; } else if ([request.HTTPMethod isEqualToString:@"PUT"]) { - [self.sessionManager PUT:request.URL.absoluteString - parameters:request.parameters - success:success - failure:failure]; + [self.sessionManager PUT:requestURLString parameters:request.parameters success:success failure:failure]; } else if ([request.HTTPMethod isEqualToString:@"DELETE"]) { - [self.sessionManager DELETE:request.URL.absoluteString - parameters:request.parameters - success:success - failure:failure]; + [self.sessionManager DELETE:requestURLString parameters:request.parameters success:success failure:failure]; } else { OWSLogError(@"Trying to perform HTTP operation with unknown verb: %@", request.HTTPMethod); } diff --git a/SignalServiceKit/src/Network/OWSSignalService.h b/SignalServiceKit/src/Network/OWSSignalService.h index 0c1621b250..0cf98ac3b2 100644 --- a/SignalServiceKit/src/Network/OWSSignalService.h +++ b/SignalServiceKit/src/Network/OWSSignalService.h @@ -12,7 +12,7 @@ extern NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidCha @interface OWSSignalService : NSObject -/// For uploading avatar assets. +/// For uploading and downloading avatar assets and attachments @property (nonatomic, readonly) AFHTTPSessionManager *CDNSessionManager; + (instancetype)sharedInstance; @@ -27,6 +27,9 @@ extern NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidCha @property (atomic) BOOL isCensorshipCircumventionManuallyDisabled; @property (atomic, nullable) NSString *manualCensorshipCircumventionCountryCode; +/// should only be accessed if censorship circumvention is active. +@property (nonatomic, readonly) NSURL *domainFrontBaseURL; + /// For interacting with the Signal Service - (AFHTTPSessionManager *)buildSignalServiceSessionManager; diff --git a/SignalServiceKit/src/Network/OWSSignalService.m b/SignalServiceKit/src/Network/OWSSignalService.m index 4784d21ce8..4089c1a275 100644 --- a/SignalServiceKit/src/Network/OWSSignalService.m +++ b/SignalServiceKit/src/Network/OWSSignalService.m @@ -172,6 +172,13 @@ NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidChange = } } +- (NSURL *)domainFrontBaseURL +{ + OWSAssertDebug(self.isCensorshipCircumventionActive); + OWSCensorshipConfiguration *censorshipConfiguration = [self buildCensorshipConfiguration]; + return censorshipConfiguration.domainFrontBaseURL; +} + - (AFHTTPSessionManager *)buildSignalServiceSessionManager { if (self.isCensorshipCircumventionActive) { @@ -204,9 +211,11 @@ NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidChange = (OWSCensorshipConfiguration *)censorshipConfiguration { NSURLSessionConfiguration *sessionConf = NSURLSessionConfiguration.ephemeralSessionConfiguration; + + NSURL *frontingURL = censorshipConfiguration.domainFrontBaseURL; + NSURL *baseURL = [frontingURL URLByAppendingPathComponent:serviceCensorshipPrefix]; AFHTTPSessionManager *sessionManager = - [[AFHTTPSessionManager alloc] initWithBaseURL:censorshipConfiguration.domainFrontBaseURL - sessionConfiguration:sessionConf]; + [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL sessionConfiguration:sessionConf]; sessionManager.securityPolicy = censorshipConfiguration.domainFrontSecurityPolicy; @@ -255,9 +264,10 @@ NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidChange = { NSURLSessionConfiguration *sessionConf = NSURLSessionConfiguration.ephemeralSessionConfiguration; + NSURL *frontingURL = censorshipConfiguration.domainFrontBaseURL; + NSURL *baseURL = [frontingURL URLByAppendingPathComponent:cdnCensorshipPrefix]; AFHTTPSessionManager *sessionManager = - [[AFHTTPSessionManager alloc] initWithBaseURL:censorshipConfiguration.domainFrontBaseURL - sessionConfiguration:sessionConf]; + [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL sessionConfiguration:sessionConf]; sessionManager.securityPolicy = censorshipConfiguration.domainFrontSecurityPolicy; diff --git a/SignalServiceKit/src/TSConstants.h b/SignalServiceKit/src/TSConstants.h index e838abdce0..7eab9a536f 100644 --- a/SignalServiceKit/src/TSConstants.h +++ b/SignalServiceKit/src/TSConstants.h @@ -33,6 +33,11 @@ typedef NS_ENUM(NSInteger, TSWhisperMessageType) { #define textSecureCDNReflectorHost @"europe-west1-signal-cdn-reflector.cloudfunctions.net" #define contactDiscoveryURL @"https://api.directory.signal.org" #define kUDTrustRoot @"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF" + +#define serviceCensorshipPrefix @"service" +#define cdnCensorshipPrefix @"cdn" +#define contactDiscoveryCensorshipPrefix @"directory" + #define USING_PRODUCTION_SERVICE //#else @@ -41,17 +46,21 @@ typedef NS_ENUM(NSInteger, TSWhisperMessageType) { //#define textSecureWebSocketAPI @"wss://textsecure-service-staging.whispersystems.org/v1/websocket/" //#define textSecureServerURL @"https://textsecure-service-staging.whispersystems.org/" //#define textSecureCDNServerURL @"https://cdn-staging.signal.org" -//#define textSecureServiceReflectorHost @"meek-signal-service-staging.appspot.com"; -//#define textSecureCDNReflectorHost @"meek-signal-cdn-staging.appspot.com"; +//#define textSecureServiceReflectorHost @"europe-west1-signal-cdn-reflector.cloudfunctions.net"; +//#define textSecureCDNReflectorHost @"europe-west1-signal-cdn-reflector.cloudfunctions.net"; //#define contactDiscoveryURL @"https://api-staging.directory.signal.org" //#define kUDTrustRoot @"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx" +// +//#define serviceCensorshipPrefix @"service-staging" +//#define cdnCensorshipPrefix @"cdn-staging" +//#define contactDiscoveryCensorshipPrefix @"directory-staging" //#endif BOOL IsUsingProductionService(void); #define textSecureAccountsAPI @"v1/accounts" -#define textSecureAttributesAPI @"/attributes/" +#define textSecureAttributesAPI @"v1/accounts/attributes/" #define textSecureMessagesAPI @"v1/messages/" #define textSecureKeysAPI @"v2/keys" @@ -64,7 +73,7 @@ BOOL IsUsingProductionService(void); #define textSecureProfileAPIFormat @"v1/profile/%@" #define textSecureSetProfileNameAPIFormat @"v1/profile/name/%@" #define textSecureProfileAvatarFormAPI @"v1/profile/form/avatar" -#define textSecure2FAAPI @"/v1/accounts/pin" +#define textSecure2FAAPI @"v1/accounts/pin" #define SignalApplicationGroup @"group.org.whispersystems.signal.group"