You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SignalServiceKit/src/Network/OWSCensorshipConfiguration.m

241 lines
8.0 KiB
Objective-C

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSCensorshipConfiguration.h"
#import "OWSCountryMetadata.h"
#import "OWSError.h"
#import "OWSPrimaryStorage.h"
#import "TSConstants.h"
#import <AFNetworking/AFHTTPSessionManager.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const OWSCensorshipConfiguration_SouqFrontingHost = @"cms.souqcdn.com";
NSString *const OWSCensorshipConfiguration_YahooViewFrontingHost = @"view.yahoo.com";
NSString *const OWSCensorshipConfiguration_DefaultFrontingHost = OWSCensorshipConfiguration_YahooViewFrontingHost;
@implementation OWSCensorshipConfiguration
// returns nil if phone number is not known to be censored
+ (nullable instancetype)censorshipConfigurationWithPhoneNumber:(NSString *)e164PhoneNumber
{
NSString *countryCode = [self censoredCountryCodeWithPhoneNumber:e164PhoneNumber];
if (countryCode.length == 0) {
return nil;
}
return [self censorshipConfigurationWithCountryCode:countryCode];
}
// returns best censorship configuration for country code. Will return a default if one hasn't
// been specifically configured.
+ (instancetype)censorshipConfigurationWithCountryCode:(NSString *)countryCode
{
OWSCountryMetadata *countryMetadadata = [OWSCountryMetadata countryMetadataForCountryCode:countryCode];
OWSAssert(countryMetadadata);
NSString *_Nullable specifiedDomain = countryMetadadata.frontingDomain;
NSURL *baseURL;
AFSecurityPolicy *securityPolicy;
if (specifiedDomain.length > 0) {
NSString *frontingURLString = [NSString stringWithFormat:@"https://%@", specifiedDomain];
baseURL = [NSURL URLWithString:frontingURLString];
securityPolicy = [self securityPolicyForDomain:(NSString *)specifiedDomain];
} else {
NSString *frontingURLString =
[NSString stringWithFormat:@"https://%@", OWSCensorshipConfiguration_DefaultFrontingHost];
baseURL = [NSURL URLWithString:frontingURLString];
securityPolicy = [self securityPolicyForDomain:OWSCensorshipConfiguration_DefaultFrontingHost];
}
OWSAssert(baseURL);
OWSAssert(securityPolicy);
return [[OWSCensorshipConfiguration alloc] initWithDomainFrontBaseURL:baseURL securityPolicy:securityPolicy];
}
- (instancetype)initWithDomainFrontBaseURL:(NSURL *)domainFrontBaseURL securityPolicy:(AFSecurityPolicy *)securityPolicy
{
OWSAssert(domainFrontBaseURL);
OWSAssert(securityPolicy);
self = [super init];
if (!self) {
return self;
}
_domainFrontBaseURL = domainFrontBaseURL;
_domainFrontSecurityPolicy = securityPolicy;
return self;
}
// MARK: Public Getters
- (NSString *)signalServiceReflectorHost
{
return textSecureServiceReflectorHost;
}
- (NSString *)CDNReflectorHost
{
return textSecureCDNReflectorHost;
}
// MARK: Util
+ (NSDictionary<NSString *, NSString *> *)censoredCountryCodes
{
// The set of countries for which domain fronting should be automatically enabled.
//
// If you want to use a domain front other than the default, specify the domain front
// in OWSCountryMetadata, and ensure we have a Security Policy for that domain in
// `securityPolicyForDomain:`
return @{
// Egypt
@"+20" : @"EG",
// Oman
@"+968" : @"OM",
// Qatar
@"+974" : @"QA",
// UAE
@"+971" : @"AE",
};
}
// Returns nil if the phone number is not known to be censored
+ (BOOL)isCensoredPhoneNumber:(NSString *)e164PhoneNumber;
{
return [self censoredCountryCodeWithPhoneNumber:e164PhoneNumber].length > 0;
}
// Returns nil if the phone number is not known to be censored
+ (nullable NSString *)censoredCountryCodeWithPhoneNumber:(NSString *)e164PhoneNumber
{
NSDictionary<NSString *, NSString *> *censoredCountryCodes = self.censoredCountryCodes;
for (NSString *callingCode in censoredCountryCodes) {
if ([e164PhoneNumber hasPrefix:callingCode]) {
return censoredCountryCodes[callingCode];
}
}
return nil;
}
#pragma mark - Reflector Pinning Policy
// When using censorship circumvention, we pin to the fronted domain host.
// Adding a new domain front entails adding a corresponding AFSecurityPolicy
// and pinning to it's CA.
// If the security policy requires new certificates, include them in the SSK bundle
+ (AFSecurityPolicy *)securityPolicyForDomain:(NSString *)domain
{
if ([domain isEqualToString:OWSCensorshipConfiguration_SouqFrontingHost]) {
return [self souqPinningPolicy];
} else if ([domain isEqualToString:OWSCensorshipConfiguration_YahooViewFrontingHost]) {
return [self yahooViewPinningPolicy];
} else {
OWSFail(@"unknown pinning domain.");
return [self yahooViewPinningPolicy];
}
}
+ (AFSecurityPolicy *)pinningPolicyWithCertNames:(NSArray<NSString *> *)certNames
{
NSMutableSet<NSData *> *certificates = [NSMutableSet new];
for (NSString *certName in certNames) {
NSError *error;
NSData *certData = [self certificateDataWithName:certName error:&error];
if (error) {
DDLogError(@"%@ reading data for certificate: %@ failed with error: %@", self.logTag, certName, error);
OWSRaiseException(@"OWSSignalService_UnableToReadCertificate", @"%@", error.description);
}
if (!certData) {
DDLogError(@"%@ No data for certificate: %@", self.logTag, certName);
OWSRaiseException(@"OWSSignalService_UnableToReadCertificate", @"%@", error.description);
}
[certificates addObject:certData];
}
return [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certificates];
}
+ (nullable NSData *)certificateDataWithName:(NSString *)name error:(NSError **)error
{
if (!name.length) {
NSString *failureDescription = [NSString stringWithFormat:@"%@ expected name with length > 0", self.logTag];
*error = OWSErrorMakeAssertionError(failureDescription);
return nil;
}
NSBundle *bundle = [NSBundle bundleForClass:self.class];
NSString *path = [bundle pathForResource:name ofType:@"crt"];
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSString *failureDescription =
[NSString stringWithFormat:@"%@ Missing certificate for name: %@", self.logTag, name];
*error = OWSErrorMakeAssertionError(failureDescription);
return nil;
}
NSData *_Nullable certData = [NSData dataWithContentsOfFile:path options:0 error:error];
if (*error != nil) {
OWSFail(@"%@ Failed to read cert file with path: %@", self.logTag, path);
return nil;
}
if (certData.length == 0) {
OWSFail(@"%@ empty certData for name: %@", self.logTag, name);
return nil;
}
DDLogVerbose(@"%@ read cert data with name: %@ length: %lu", self.logTag, name, (unsigned long)certData.length);
return certData;
}
+ (AFSecurityPolicy *)yahooViewPinningPolicy
{
static AFSecurityPolicy *securityPolicy = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// DigiCertGlobalRootG2 - view.yahoo.com
NSArray<NSString *> *certNames = @[ @"DigiCertSHA2HighAssuranceServerCA" ];
securityPolicy = [self pinningPolicyWithCertNames:certNames];
});
return securityPolicy;
}
+ (AFSecurityPolicy *)souqPinningPolicy
{
static AFSecurityPolicy *securityPolicy = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// SFSRootCAG2 - cms.souqcdn.com
NSArray<NSString *> *certNames = @[ @"SFSRootCAG2" ];
securityPolicy = [self pinningPolicyWithCertNames:certNames];
});
return securityPolicy;
}
+ (AFSecurityPolicy *)googlePinningPolicy_deprecated
{
static AFSecurityPolicy *securityPolicy = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// GIAG2 cert plus root certs from pki.goog
NSArray<NSString *> *certNames = @[ @"GIAG2", @"GSR2", @"GSR4", @"GTSR1", @"GTSR2", @"GTSR3", @"GTSR4" ];
securityPolicy = [self pinningPolicyWithCertNames:certNames];
});
return securityPolicy;
}
@end
NS_ASSUME_NONNULL_END