diff options
author | Randall Lee <randall.lee@mapbox.com> | 2018-05-07 17:57:42 -0400 |
---|---|---|
committer | Fabian Guerra Soto <fabian.guerra@mapbox.com> | 2018-06-22 13:07:14 -0700 |
commit | 1642dd9b89c46ce01f62c40f3cd16c8999d0e508 (patch) | |
tree | a9647da03c71550a15997fc8f517b62e88abc330 | |
parent | 4b2b97f18c87bc7f1f1003ad6c145e61a614ccdb (diff) | |
download | qtlocation-mapboxgl-1642dd9b89c46ce01f62c40f3cd16c8999d0e508.tar.gz |
[iOS] - Update telemetry certificate pinning (#11845)
* Update telemetry certificate pinning
* Load both CN certificates
* [ios] Use China events endpoint with China API endpoint
* Update CHANGELOG.md
# Conflicts:
# platform/ios/CHANGELOG.md
# platform/ios/ios.xcodeproj/project.pbxproj
# platform/ios/src/MGLAPIClient.m
-rw-r--r-- | platform/darwin/src/MGLNetworkConfiguration.h | 6 | ||||
-rw-r--r-- | platform/darwin/src/MGLNetworkConfiguration.m | 3 | ||||
-rw-r--r-- | platform/ios/ios.xcodeproj/project.pbxproj | 42 | ||||
-rw-r--r-- | platform/ios/resources/api_mapbox_cn-digicert_2018.der | bin | 0 -> 1704 bytes | |||
-rw-r--r-- | platform/ios/resources/api_mapbox_cn-geotrust_2018.der | bin | 0 -> 1578 bytes | |||
-rw-r--r-- | platform/ios/src/MGLAPIClient.m | 218 |
6 files changed, 268 insertions, 1 deletions
diff --git a/platform/darwin/src/MGLNetworkConfiguration.h b/platform/darwin/src/MGLNetworkConfiguration.h index 2db46d78c5..87b4cacb99 100644 --- a/platform/darwin/src/MGLNetworkConfiguration.h +++ b/platform/darwin/src/MGLNetworkConfiguration.h @@ -2,6 +2,12 @@ NS_ASSUME_NONNULL_BEGIN +/// The default base URL for Mapbox APIs other than the telemetry API. +extern NSString * const MGLDefaultMapboxAPIBaseURL; + +/// The PRC base URL for Mapbox APIs other than the telemetry API. +extern NSString * const MGLChinaMapboxAPIBaseURL; + /** The MGLNetworkConfiguration object provides a global way to set a base API URL for retrieval of map data, styles, and other resources. diff --git a/platform/darwin/src/MGLNetworkConfiguration.m b/platform/darwin/src/MGLNetworkConfiguration.m index 4cfa405a1a..5be92c10b8 100644 --- a/platform/darwin/src/MGLNetworkConfiguration.m +++ b/platform/darwin/src/MGLNetworkConfiguration.m @@ -1,5 +1,8 @@ #import "MGLNetworkConfiguration.h" +NSString * const MGLDefaultMapboxAPIBaseURL = @"https://api.mapbox.com"; +NSString * const MGLChinaMapboxAPIBaseURL = @"https://api.mapbox.cn"; + @implementation MGLNetworkConfiguration + (void)load { diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 46c10dab89..ea7869a383 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -37,6 +37,8 @@ 1F7454A91ED08AB400021D39 /* MGLLightTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F7454A61ED08AB400021D39 /* MGLLightTest.mm */; }; 1F95931D1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */; }; 1FC4817D2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */; }; + 1FB7DAB01F2A4DC200410606 /* MGLVectorSource+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FDD9D6D1F26936400252B09 /* MGLVectorSource+MGLAdditions.h */; }; + 1FB7DAB11F2A4DC800410606 /* MGLVectorSource+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FDD9D6E1F26936400252B09 /* MGLVectorSource+MGLAdditions.m */; }; 1FC4817F2098CD80000D09B4 /* NSPredicate+MGLPrivateAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FC4817B2098CBC0000D09B4 /* NSPredicate+MGLPrivateAdditions.h */; }; 30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */; }; 30E578181DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */; }; @@ -359,6 +361,10 @@ 96E5170320005A6800A02306 /* FABKitProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8848811CBB033F00AB86E3 /* FABKitProtocol.h */; }; 96E5170420005A6B00A02306 /* SMCalloutView.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8848891CBB037E00AB86E3 /* SMCalloutView.h */; }; 96F3F73C1F57124B003E2D2C /* MGLUserLocationHeadingIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */; }; + AC0C15F3209D0E6900B65675 /* api_mapbox_cn-geotrust_2018.der in Resources */ = {isa = PBXBuildFile; fileRef = AC0C15F1209D0E3600B65675 /* api_mapbox_cn-geotrust_2018.der */; }; + AC0C15F4209D0E7000B65675 /* api_mapbox_cn-geotrust_2018.der in Resources */ = {isa = PBXBuildFile; fileRef = AC0C15F1209D0E3600B65675 /* api_mapbox_cn-geotrust_2018.der */; }; + AC0C15F5209D0E7200B65675 /* api_mapbox_cn-digicert_2018.der in Resources */ = {isa = PBXBuildFile; fileRef = AC0C15F2209D0E6000B65675 /* api_mapbox_cn-digicert_2018.der */; }; + AC0C15F6209D0E7300B65675 /* api_mapbox_cn-digicert_2018.der in Resources */ = {isa = PBXBuildFile; fileRef = AC0C15F2209D0E6000B65675 /* api_mapbox_cn-digicert_2018.der */; }; AC518DFF201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; }; AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; }; AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; }; @@ -957,6 +963,8 @@ 632281DD1E6F855900D75A5D /* MBXEmbeddedMapViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXEmbeddedMapViewController.h; sourceTree = "<group>"; }; 632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXEmbeddedMapViewController.m; sourceTree = "<group>"; }; 6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLDocumentationExampleTests.swift; path = ../../darwin/test/MGLDocumentationExampleTests.swift; sourceTree = "<group>"; }; + 7E016D7C1D9E86BE00A29A21 /* MGLPolyline+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLPolyline+MGLAdditions.h"; sourceTree = "<group>"; }; + 7E016D7D1D9E86BE00A29A21 /* MGLPolyline+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLPolyline+MGLAdditions.m"; sourceTree = "<group>"; }; 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = "<group>"; }; 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = "<group>"; }; 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLSourceQueryTests.m; path = ../../darwin/test/MGLSourceQueryTests.m; sourceTree = "<group>"; }; @@ -994,6 +1002,8 @@ 96E0272D1E57C7E6004B8E66 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = "<group>"; }; 96E0272E1E57C7E7004B8E66 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; }; 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingIndicator.h; sourceTree = "<group>"; }; + AC0C15F1209D0E3600B65675 /* api_mapbox_cn-geotrust_2018.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "api_mapbox_cn-geotrust_2018.der"; sourceTree = "<group>"; }; + AC0C15F2209D0E6000B65675 /* api_mapbox_cn-digicert_2018.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "api_mapbox_cn-digicert_2018.der"; sourceTree = "<group>"; }; AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLTelemetryConfig.h; sourceTree = "<group>"; }; AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLTelemetryConfig.m; sourceTree = "<group>"; }; CA0C27932076CA19001CE5B7 /* MGLMapViewIntegrationTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewIntegrationTest.m; sourceTree = "<group>"; }; @@ -1812,8 +1822,8 @@ 4031ACFD1E9FD26900A3EA26 /* Test Helpers */, 409F43FB1E9E77D10048729D /* Swift Integration */, 357579811D502AD4000B822E /* Styling */, - 353D23951D0B0DFE002BE09D /* MGLAnnotationViewTests.m */, DAEDC4331D603417000224FF /* MGLAttributionInfoTests.m */, + 353D23951D0B0DFE002BE09D /* MGLAnnotationViewTests.m */, DA35A2C31CCA9F8300E826B2 /* MGLClockDirectionFormatterTests.m */, 35D9DDE11DA25EEC00DAAD69 /* MGLCodingTests.m */, DA35A2C41CCA9F8300E826B2 /* MGLCompassDirectionFormatterTests.m */, @@ -1929,6 +1939,13 @@ DA89339F1CCC951200E68420 /* Localizable.strings */, DAC49C5F1CD02BC9009E1AA3 /* Localizable.stringsdict */, DA8933EF1CCD387900E68420 /* strip-frameworks.sh */, + 40599F001DEE1B2400182B5D /* api_mapbox_staging.der */, + AC0C15F2209D0E6000B65675 /* api_mapbox_cn-digicert_2018.der */, + AC0C15F1209D0E3600B65675 /* api_mapbox_cn-geotrust_2018.der */, + 40599F011DEE1B2400182B5D /* api_mapbox_com-digicert_2016.der */, + 40599F021DEE1B2400182B5D /* api_mapbox_com-geotrust_2016.der */, + 40EA6BBD1EF4598900FCCDA2 /* api_mapbox_com-digicert_2017.der */, + 40EA6BBE1EF4598900FCCDA2 /* api_mapbox_com-geotrust_2017.der */, ); name = "Kit Resources"; path = resources; @@ -1956,6 +1973,16 @@ path = ../vendor/SMCalloutView; sourceTree = "<group>"; }; + DA8848911CBB049300AB86E3 /* reachability */ = { + isa = PBXGroup; + children = ( + DA88488D1CBB047F00AB86E3 /* reachability.h */, + DA88488F1CBB048E00AB86E3 /* reachability.m */, + ); + name = reachability; + path = ..; + sourceTree = "<group>"; + }; DA8933B91CCD2C6700E68420 /* Foundation Resources */ = { isa = PBXGroup; children = ( @@ -2769,10 +2796,17 @@ files = ( DA8933BC1CCD2CA100E68420 /* Foundation.strings in Resources */, DA8933A31CCC95B000E68420 /* Localizable.strings in Resources */, + AC0C15F5209D0E7200B65675 /* api_mapbox_cn-digicert_2018.der in Resources */, 960D0C361ECF5AAF008E151F /* Images.xcassets in Resources */, DA8933F01CCD387900E68420 /* strip-frameworks.sh in Resources */, DAC49C5C1CD02BC9009E1AA3 /* Localizable.stringsdict in Resources */, DA8933BF1CCD2CAD00E68420 /* Foundation.stringsdict in Resources */, + 40EA6BC11EF4599600FCCDA2 /* api_mapbox_com-digicert_2017.der in Resources */, + 408982E91DEE208200754016 /* api_mapbox_staging.der in Resources */, + AC0C15F3209D0E6900B65675 /* api_mapbox_cn-geotrust_2018.der in Resources */, + 408982EA1DEE208B00754016 /* api_mapbox_com-digicert_2016.der in Resources */, + 40EA6BC31EF4599D00FCCDA2 /* api_mapbox_com-geotrust_2017.der in Resources */, + 408982EB1DEE209100754016 /* api_mapbox_com-geotrust_2016.der in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2780,11 +2814,17 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + AC0C15F4209D0E7000B65675 /* api_mapbox_cn-geotrust_2018.der in Resources */, DA8933E01CCD31DF00E68420 /* Localizable.strings in Resources */, DA8933DB1CCD31D400E68420 /* Foundation.strings in Resources */, 960D0C371ECF5AAF008E151F /* Images.xcassets in Resources */, DA8933DC1CCD31D400E68420 /* Foundation.stringsdict in Resources */, DAC49C5D1CD02BC9009E1AA3 /* Localizable.stringsdict in Resources */, + 40599F0C1DEE1B7600182B5D /* api_mapbox_staging.der in Resources */, + 40599F0D1DEE1B7A00182B5D /* api_mapbox_com-digicert_2016.der in Resources */, + 40599F0E1DEE1B7E00182B5D /* api_mapbox_com-geotrust_2016.der in Resources */, + AC0C15F6209D0E7300B65675 /* api_mapbox_cn-digicert_2018.der in Resources */, + 40EA6BC21EF4599700FCCDA2 /* api_mapbox_com-digicert_2017.der in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/platform/ios/resources/api_mapbox_cn-digicert_2018.der b/platform/ios/resources/api_mapbox_cn-digicert_2018.der Binary files differnew file mode 100644 index 0000000000..e458713337 --- /dev/null +++ b/platform/ios/resources/api_mapbox_cn-digicert_2018.der diff --git a/platform/ios/resources/api_mapbox_cn-geotrust_2018.der b/platform/ios/resources/api_mapbox_cn-geotrust_2018.der Binary files differnew file mode 100644 index 0000000000..e3d4b222ae --- /dev/null +++ b/platform/ios/resources/api_mapbox_cn-geotrust_2018.der diff --git a/platform/ios/src/MGLAPIClient.m b/platform/ios/src/MGLAPIClient.m new file mode 100644 index 0000000000..68e78835c3 --- /dev/null +++ b/platform/ios/src/MGLAPIClient.m @@ -0,0 +1,218 @@ +#import "MGLAPIClient.h" +#import "NSBundle+MGLAdditions.h" +#import "NSData+MGLAdditions.h" +#import "MGLAccountManager.h" +#import "MGLNetworkConfiguration.h" + +static NSString * const MGLAPIClientUserAgentBase = @"MapboxEventsiOS"; +static NSString * const MGLAPIClientBaseURL = @"https://events.mapbox.com"; +static NSString * const MGLAPIClientChinaBaseURL = @"https://events.mapbox.cn"; +static NSString * const MGLAPIClientEventsPath = @"events/v2"; + +static NSString * const MGLAPIClientHeaderFieldUserAgentKey = @"User-Agent"; +static NSString * const MGLAPIClientHeaderFieldContentTypeKey = @"Content-Type"; +static NSString * const MGLAPIClientHeaderFieldContentTypeValue = @"application/json"; +static NSString * const MGLAPIClientHeaderFieldContentEncodingKey = @"Content-Encoding"; +static NSString * const MGLAPIClientHTTPMethodPost = @"POST"; + +@interface MGLAPIClient () + +@property (nonatomic, copy) NSURLSession *session; +@property (nonatomic, copy) NSURL *baseURL; +@property (nonatomic, copy) NSData *digicertCert_2016; +@property (nonatomic, copy) NSData *geoTrustCert_2016; +@property (nonatomic, copy) NSData *digicertCert_2017; +@property (nonatomic, copy) NSData *geoTrustCert_2017; +@property (nonatomic, copy) NSData *digicertCert_cn_2018; +@property (nonatomic, copy) NSData *geoTrustCert_cn_2018; +@property (nonatomic, copy) NSData *testServerCert; +@property (nonatomic, copy) NSString *userAgent; +@property (nonatomic) BOOL usesTestServer; + +@end + +@implementation MGLAPIClient + +- (instancetype)init { + self = [super init]; + if (self) { + _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] + delegate:self delegateQueue:nil]; + [self loadCertificates]; + [self setupBaseURL]; + [self setupUserAgent]; + } + return self; +} + +#pragma mark Public API + +- (void)postEvents:(nonnull NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events completionHandler:(nullable void (^)(NSError * _Nullable error))completionHandler { + __block NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self requestForEvents:events] + completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + NSError *statusError = nil; + if (httpResponse.statusCode >= 400) { + NSString *description = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"API_CLIENT_400_DESC", nil, nil, @"The session data task failed. Original request was: %@", nil), dataTask.originalRequest]; + NSString *reason = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"API_CLIENT_400_REASON", nil, nil, @"The status code was %ld", nil), (long)httpResponse.statusCode]; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description, + NSLocalizedFailureReasonErrorKey: reason}; + statusError = [NSError errorWithDomain:MGLErrorDomain code:1 userInfo:userInfo]; + } + if (completionHandler) { + error = error ?: statusError; + completionHandler(error); + } + dataTask = nil; + }]; + [dataTask resume]; +} + +- (void)postEvent:(nonnull MGLMapboxEventAttributes *)event completionHandler:(nullable void (^)(NSError * _Nullable error))completionHandler { + [self postEvents:@[event] completionHandler:completionHandler]; +} + +#pragma mark Utilities + +- (NSURLRequest *)requestForEvents:(NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events { + NSString *path = [NSString stringWithFormat:@"%@?access_token=%@", MGLAPIClientEventsPath, [MGLAccountManager accessToken]]; + NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL]; + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + [request setValue:self.userAgent forHTTPHeaderField:MGLAPIClientHeaderFieldUserAgentKey]; + [request setValue:MGLAPIClientHeaderFieldContentTypeValue forHTTPHeaderField:MGLAPIClientHeaderFieldContentTypeKey]; + [request setHTTPMethod:MGLAPIClientHTTPMethodPost]; + + NSData *jsonData = [self serializedDataForEvents:events]; + + // Compressing less than 3 events can have a negative impact on the size. + if (events.count > 2) { + NSData *compressedData = [jsonData mgl_compressedData]; + [request setValue:@"deflate" forHTTPHeaderField:MGLAPIClientHeaderFieldContentEncodingKey]; + [request setHTTPBody:compressedData]; + } + + // Set JSON data if events.count were less than 3 or something went wrong with compressing HTTP body data. + if (!request.HTTPBody) { + [request setValue:nil forHTTPHeaderField:MGLAPIClientHeaderFieldContentEncodingKey]; + [request setHTTPBody:jsonData]; + } + + return [request copy]; +} + +- (void)setupBaseURL { + NSString *testServerURLString = [[NSUserDefaults standardUserDefaults] stringForKey:@"MGLTelemetryTestServerURL"]; + NSURL *testServerURL = [NSURL URLWithString:testServerURLString]; + if (testServerURL && [testServerURL.scheme isEqualToString:@"https"]) { + self.baseURL = testServerURL; + self.usesTestServer = YES; + } else if ([[[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLMapboxAPIBaseURL"] isEqualToString:MGLChinaMapboxAPIBaseURL]) { + self.baseURL = [NSURL URLWithString:MGLAPIClientChinaBaseURL]; + } else { + self.baseURL = [NSURL URLWithString:MGLAPIClientBaseURL]; + } +} + +- (void)loadCertificates { + NSData *certificate; + [self loadCertificate:&certificate withResource:@"api_mapbox_com-geotrust_2016"]; + self.geoTrustCert_2016 = certificate; + [self loadCertificate:&certificate withResource:@"api_mapbox_com-digicert_2016"]; + self.digicertCert_2016 = certificate; + [self loadCertificate:&certificate withResource:@"api_mapbox_com-geotrust_2017"]; + self.geoTrustCert_2017 = certificate; + [self loadCertificate:&certificate withResource:@"api_mapbox_com-digicert_2017"]; + self.digicertCert_2017 = certificate; + [self loadCertificate:&certificate withResource:@"api_mapbox_cn-geotrust_2018"]; + self.geoTrustCert_cn_2018 = certificate; + [self loadCertificate:&certificate withResource:@"api_mapbox_cn-digicert_2018"]; + self.digicertCert_cn_2018 = certificate; + [self loadCertificate:&certificate withResource:@"api_mapbox_staging"]; + self.testServerCert = certificate; +} + +- (void)loadCertificate:(NSData **)certificate withResource:(NSString *)resource { + NSBundle *frameworkBundle = [NSBundle mgl_frameworkBundle]; + NSString *cerPath = [frameworkBundle pathForResource:resource ofType:@"der"]; + if (cerPath != nil) { + *certificate = [NSData dataWithContentsOfFile:cerPath]; + } +} + +- (void)setupUserAgent { + NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]; + NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + NSString *appBuildNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; + NSString *semanticVersion = [NSBundle mgl_frameworkInfoDictionary][@"MGLSemanticVersionString"]; + NSString *shortVersion = [NSBundle mgl_frameworkInfoDictionary][@"CFBundleShortVersionString"]; + NSString *sdkVersion = semanticVersion ?: shortVersion; + _userAgent = [NSString stringWithFormat:@"%@/%@/%@ %@/%@", appName, appVersion, appBuildNumber, MGLAPIClientUserAgentBase, sdkVersion]; +} + +#pragma mark - JSON Serialization + +- (NSData *)serializedDataForEvents:(NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events { + return [NSJSONSerialization dataWithJSONObject:events options:0 error:nil]; +} + +#pragma mark NSURLSessionDelegate + +- (BOOL)evaluateCertificateWithCertificateData:(NSData *)certificateData keyCount:(CFIndex)keyCount serverTrust:(SecTrustRef)serverTrust challenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^) (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { + for (int lc = 0; lc < keyCount; lc++) { + SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc); + NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate)); + if ([remoteCertificateData isEqualToData:certificateData]) { + completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + return YES; + } + } + return NO; +} + +- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^) (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { + + if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust]; + SecTrustResultType trustResult; + + // Validate the certificate chain with the device's trust store anyway this *might* use revocation checking + SecTrustEvaluate(serverTrust, &trustResult); + + BOOL found = NO; // For clarity; we start in a state where the challange has not been completed and no certificate has been found + + if (trustResult == kSecTrustResultUnspecified) { + // Look for a pinned certificate in the server's certificate chain + CFIndex numKeys = SecTrustGetCertificateCount(serverTrust); + + // Check certs in the following order: digicert 2016, digicert 2017, digicert CN 2018, geotrust 2016, geotrust 2017, geotrust CN 2018 + found = [self evaluateCertificateWithCertificateData:self.digicertCert_2016 keyCount:numKeys serverTrust:serverTrust challenge:challenge completionHandler:completionHandler]; + if (!found) { + found = [self evaluateCertificateWithCertificateData:self.digicertCert_2017 keyCount:numKeys serverTrust:serverTrust challenge:challenge completionHandler:completionHandler]; + } + if (!found) { + found = [self evaluateCertificateWithCertificateData:self.digicertCert_cn_2018 keyCount:numKeys serverTrust:serverTrust challenge:challenge completionHandler:completionHandler]; + } + if (!found) { + found = [self evaluateCertificateWithCertificateData:self.geoTrustCert_2016 keyCount:numKeys serverTrust:serverTrust challenge:challenge completionHandler:completionHandler]; + } + if (!found) { + found = [self evaluateCertificateWithCertificateData:self.geoTrustCert_2017 keyCount:numKeys serverTrust:serverTrust challenge:challenge completionHandler:completionHandler]; + } + if (!found) { + found = [self evaluateCertificateWithCertificateData:self.geoTrustCert_cn_2018 keyCount:numKeys serverTrust:serverTrust challenge:challenge completionHandler:completionHandler]; + } + + // If challenge can't be completed with any of the above certs, then try the test server if the app is configured to use the test server + if (!found && _usesTestServer) { + found = [self evaluateCertificateWithCertificateData:self.testServerCert keyCount:numKeys serverTrust:serverTrust challenge:challenge completionHandler:completionHandler]; + } + } + + if (!found) { + // No certificate was found so cancel the connection. + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + } + } +} + +@end |