Skip to content
This repository was archived by the owner on Apr 5, 2023. It is now read-only.

Commit 10c2010

Browse files
committed
Certificate pinning for server trust verification
Pinning the following URLs against the bundled certificates - cdn.optimizely.com - logx.optimizely.com - api.optimizely.com
1 parent df0c7f8 commit 10c2010

File tree

5 files changed

+117
-2
lines changed

5 files changed

+117
-2
lines changed

OptimizelySDKCore/OptimizelySDKCore.xcodeproj/project.pbxproj

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@
211211
5E4C07F41DFF645C0042B1F8 /* UnsupportedVersionDatafile.json in Resources */ = {isa = PBXBuildFile; fileRef = 5E4C07F21DFF645C0042B1F8 /* UnsupportedVersionDatafile.json */; };
212212
5E4C07FB1DFF66B00042B1F8 /* OPTLYNetworkServiceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E4C07FA1DFF66B00042B1F8 /* OPTLYNetworkServiceTest.m */; };
213213
5E4C07FC1DFF66B00042B1F8 /* OPTLYNetworkServiceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E4C07FA1DFF66B00042B1F8 /* OPTLYNetworkServiceTest.m */; };
214+
5E92F23522CDA35C00F12C87 /* cdnDump.cer in Resources */ = {isa = PBXBuildFile; fileRef = 5E92F23222CDA35B00F12C87 /* cdnDump.cer */; };
215+
5E92F23622CDA35C00F12C87 /* apiDump.cer in Resources */ = {isa = PBXBuildFile; fileRef = 5E92F23322CDA35B00F12C87 /* apiDump.cer */; };
216+
5E92F23722CDA35C00F12C87 /* logxDump.cer in Resources */ = {isa = PBXBuildFile; fileRef = 5E92F23422CDA35C00F12C87 /* logxDump.cer */; };
214217
9084F7812150D4F700ACBA99 /* OPTLYEventTagUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 9084F77F2150D4F700ACBA99 /* OPTLYEventTagUtil.h */; };
215218
9084F7822150D4F800ACBA99 /* OPTLYEventTagUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 9084F77F2150D4F700ACBA99 /* OPTLYEventTagUtil.h */; };
216219
9084F7832150D4F800ACBA99 /* OPTLYEventTagUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 9084F7802150D4F700ACBA99 /* OPTLYEventTagUtil.m */; };
@@ -619,6 +622,9 @@
619622
59B9E1E020E35C9E002F732E /* OPTLYProjectConfigSwiftTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPTLYProjectConfigSwiftTest.swift; sourceTree = "<group>"; };
620623
5E4C07F21DFF645C0042B1F8 /* UnsupportedVersionDatafile.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = UnsupportedVersionDatafile.json; sourceTree = "<group>"; };
621624
5E4C07FA1DFF66B00042B1F8 /* OPTLYNetworkServiceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OPTLYNetworkServiceTest.m; sourceTree = "<group>"; };
625+
5E92F23222CDA35B00F12C87 /* cdnDump.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = cdnDump.cer; sourceTree = "<group>"; };
626+
5E92F23322CDA35B00F12C87 /* apiDump.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = apiDump.cer; sourceTree = "<group>"; };
627+
5E92F23422CDA35C00F12C87 /* logxDump.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = logxDump.cer; sourceTree = "<group>"; };
622628
8766E217ECD3BEA353B080F5 /* Pods_OptimizelySDKCoreiOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OptimizelySDKCoreiOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
623629
8B04E0101E180FAD8C9CA7A6 /* Pods_OptimizelySDKCoreiOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OptimizelySDKCoreiOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
624630
9084F77F2150D4F700ACBA99 /* OPTLYEventTagUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OPTLYEventTagUtil.h; sourceTree = "<group>"; };
@@ -997,6 +1003,16 @@
9971003
name = Notification;
9981004
sourceTree = "<group>";
9991005
};
1006+
5E92F23E22CDA41100F12C87 /* Resources */ = {
1007+
isa = PBXGroup;
1008+
children = (
1009+
5E92F23422CDA35C00F12C87 /* logxDump.cer */,
1010+
5E92F23322CDA35B00F12C87 /* apiDump.cer */,
1011+
5E92F23222CDA35B00F12C87 /* cdnDump.cer */,
1012+
);
1013+
path = Resources;
1014+
sourceTree = "<group>";
1015+
};
10001016
93BE4E5119FD429B5D9A3435 /* Pods */ = {
10011017
isa = PBXGroup;
10021018
children = (
@@ -1201,6 +1217,7 @@
12011217
EA3C67FB1DC1E66C00C578CA = {
12021218
isa = PBXGroup;
12031219
children = (
1220+
5E92F23E22CDA41100F12C87 /* Resources */,
12041221
3E858C5C1F42275700D53856 /* OPTLYJSONModel */,
12051222
3EA27B081F47AB0D00C0208F /* OPTLYJSONModelTests */,
12061223
EA3C68071DC1E66C00C578CA /* OptimizelySDKCore */,
@@ -1611,6 +1628,7 @@
16111628
developmentRegion = English;
16121629
hasScannedForEncodings = 0;
16131630
knownRegions = (
1631+
English,
16141632
en,
16151633
);
16161634
mainGroup = EA3C67FB1DC1E66C00C578CA;
@@ -1678,6 +1696,9 @@
16781696
buildActionMask = 2147483647;
16791697
files = (
16801698
3E858C601F42277B00D53856 /* LICENSE in Resources */,
1699+
5E92F23522CDA35C00F12C87 /* cdnDump.cer in Resources */,
1700+
5E92F23622CDA35C00F12C87 /* apiDump.cer in Resources */,
1701+
5E92F23722CDA35C00F12C87 /* logxDump.cer in Resources */,
16811702
);
16821703
runOnlyForDeploymentPostprocessing = 0;
16831704
};

OptimizelySDKCore/OptimizelySDKCore/OPTLYHTTPRequestManager.m

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
static NSString * const kHTTPHeaderFieldContentType = @"Content-Type";
2525
static NSString * const kHTTPHeaderFieldValueApplicationJSON = @"application/json";
2626

27-
@interface OPTLYHTTPRequestManager()
27+
@interface OPTLYHTTPRequestManager() <NSURLSessionDelegate>
2828

2929
- (NSURLSession *)session;
3030

@@ -41,7 +41,7 @@ - (NSURLSession *)session {
4141
NSURLSession *ephemeralSession = nil;
4242

4343
@try {
44-
ephemeralSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
44+
ephemeralSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:self delegateQueue:nil];
4545
ephemeralSession.configuration.TLSMinimumSupportedProtocol = kTLSProtocol12;
4646

4747
}
@@ -453,3 +453,97 @@ - (NSURL *)buildQueryURL:(NSURL *)url
453453
}
454454

455455
@end
456+
457+
typedef enum : NSUInteger {
458+
kOPTLY_Unknown,
459+
kOPTLY_CDN,
460+
kOPTLY_LOGX,
461+
kOPTLY_API,
462+
} OPTLYEndpointHostType;
463+
464+
NSString * const OPTLYDefaultEndpointHost = @"cdn.optimizely.com";
465+
NSString * const OPTLYLogEventEndpointHost = @"logx.optimizely.com";
466+
NSString * const OPTLYAPIEndpointHost = @"api.optimizely.com";
467+
468+
@implementation OPTLYHTTPRequestManager (NSURLSessionDelegate)
469+
- (OPTLYEndpointHostType)hostTypeForChallenge:(NSURLAuthenticationChallenge*)challenge {
470+
NSString *host = challenge.protectionSpace.host;
471+
OPTLYEndpointHostType hostType = kOPTLY_Unknown;
472+
if ([host isEqualToString:OPTLYDefaultEndpointHost]) {
473+
hostType = kOPTLY_CDN;
474+
} else if ([host isEqualToString:OPTLYLogEventEndpointHost]) {
475+
hostType = kOPTLY_LOGX;
476+
} else if ([host isEqualToString:OPTLYAPIEndpointHost]) {
477+
hostType = kOPTLY_API;
478+
}
479+
return hostType;
480+
}
481+
482+
- (NSString*)certificateFileNameForHostType:(OPTLYEndpointHostType)hostType {
483+
NSString *fileName = nil;
484+
switch (hostType) {
485+
case kOPTLY_CDN:
486+
fileName = @"cdnDump";
487+
break;
488+
case kOPTLY_LOGX:
489+
fileName = @"logxDump";
490+
break;
491+
case kOPTLY_API:
492+
fileName = @"apiDump";
493+
break;
494+
default:
495+
break;
496+
}
497+
return fileName;
498+
}
499+
500+
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
501+
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
502+
BOOL allowConnection = NO;
503+
SecTrustRef serverTrust = NULL;
504+
505+
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
506+
OPTLYEndpointHostType hostType = [self hostTypeForChallenge:challenge];
507+
NSString *bundleCertFileName = [self certificateFileNameForHostType:hostType];
508+
if (bundleCertFileName != nil) {
509+
serverTrust = challenge.protectionSpace.serverTrust;
510+
SecTrustResultType sectTrustResult = kSecTrustResultInvalid;
511+
if (serverTrust != nil && SecTrustEvaluate(serverTrust, &sectTrustResult) == errSecSuccess) {
512+
NSString *bundledCertFilePath = [[NSBundle bundleForClass:[self class]] pathForResource:bundleCertFileName ofType:@"cer"];
513+
if (bundledCertFilePath != nil) {
514+
CFIndex certCount = SecTrustGetCertificateCount(serverTrust);
515+
CFIndex certIndex;
516+
NSMutableArray* certificates = [NSMutableArray array];
517+
for (certIndex = 0; certIndex < certCount; certIndex++) {
518+
SecCertificateRef aServerCertificate = SecTrustGetCertificateAtIndex(serverTrust, certIndex);
519+
[certificates addObject:(__bridge id)aServerCertificate];
520+
}
521+
522+
SecTrustRef newTrust = NULL;
523+
SecTrustResultType newTrustResult;
524+
OSStatus err = SecTrustCreateWithCertificates((__bridge CFArrayRef) certificates, NULL, &newTrust);
525+
if (err == noErr) {
526+
err = SecTrustEvaluate(newTrust, &newTrustResult);
527+
}
528+
529+
allowConnection = NO;
530+
531+
if (err == noErr) {
532+
allowConnection = (newTrustResult == kSecTrustResultProceed) || (newTrustResult == kSecTrustResultUnspecified);
533+
}
534+
535+
if (newTrust != NULL) {
536+
CFRelease(newTrust);
537+
}
538+
}
539+
}
540+
}
541+
}
542+
543+
if (allowConnection) {
544+
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:serverTrust]);
545+
} else {
546+
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
547+
}
548+
}
549+
@end
837 Bytes
Binary file not shown.
947 Bytes
Binary file not shown.
969 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)