From 3163069cbc319a21df8e1d1fdc449ee8c26b3ff0 Mon Sep 17 00:00:00 2001 From: Oliver Letterer Date: Sat, 1 Jun 2013 19:01:25 +0200 Subject: [PATCH 1/3] Adds tests for basic SSL pinning. --- .../project.pbxproj | 14 +++ Tests/AFTestURLProtocol.m | 24 ++-- Tests/AFURLConnectionOperationTests.m | 112 ++++++++++++++++++ Tests/Resources/root_certificate.cer | Bin 0 -> 573 bytes Tests/Resources/root_certificate.key | 15 +++ 5 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 Tests/Resources/root_certificate.cer create mode 100644 Tests/Resources/root_certificate.key diff --git a/Tests/AFNetworking Tests.xcodeproj/project.pbxproj b/Tests/AFNetworking Tests.xcodeproj/project.pbxproj index 38a2293..4e64c3d 100644 --- a/Tests/AFNetworking Tests.xcodeproj/project.pbxproj +++ b/Tests/AFNetworking Tests.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 25C4EC42173D86B60083E116 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25C4EC30173D7DCA0083E116 /* MobileCoreServices.framework */; }; 29A9CE2117456336002360C8 /* AFJSONRequestOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A9CE2017456336002360C8 /* AFJSONRequestOperationTests.m */; }; 29A9CE2217456336002360C8 /* AFJSONRequestOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A9CE2017456336002360C8 /* AFJSONRequestOperationTests.m */; }; + A70F4A96175A529400386DF5 /* root_certificate.cer in Resources */ = {isa = PBXBuildFile; fileRef = A70F4A95175A529400386DF5 /* root_certificate.cer */; }; + A70F4A97175A529400386DF5 /* root_certificate.cer in Resources */ = {isa = PBXBuildFile; fileRef = A70F4A95175A529400386DF5 /* root_certificate.cer */; }; A7DC62A617592E4200EBEC2F /* AFTestURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A517592E4200EBEC2F /* AFTestURLProtocol.m */; }; A7DC62A717592E4200EBEC2F /* AFTestURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A517592E4200EBEC2F /* AFTestURLProtocol.m */; }; A7DC62A917592E4800EBEC2F /* AFURLConnectionOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */; }; @@ -78,6 +80,7 @@ 2B6D24F8E1B74E10A269E8B3 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 55E73C267F33406A9F92476C /* libPods-ios.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ios.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 96A923755B00464187DEDBAF /* libPods-osx.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-osx.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A70F4A95175A529400386DF5 /* root_certificate.cer */ = {isa = PBXFileReference; lastKnownFileType = file; name = root_certificate.cer; path = Resources/root_certificate.cer; sourceTree = ""; }; A7DC62A417592E4200EBEC2F /* AFTestURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFTestURLProtocol.h; sourceTree = ""; }; A7DC62A517592E4200EBEC2F /* AFTestURLProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFTestURLProtocol.m; sourceTree = ""; }; A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLConnectionOperationTests.m; sourceTree = ""; }; @@ -119,6 +122,7 @@ 25801549173EB4B40026AA6E /* Pods-ios.xcconfig */, 2580154A173EB4B40026AA6E /* Pods-osx.xcconfig */, 25801548173EB3B00026AA6E /* Tests */, + A70F4A91175A4E0000386DF5 /* Certificates */, 2544EC37173BE382004117E8 /* AFNetworking */, 25A753091747FC7E00F04F2F /* Resources */, 2544EC34173BE382004117E8 /* Frameworks */, @@ -215,6 +219,14 @@ name = Resources; sourceTree = ""; }; + A70F4A91175A4E0000386DF5 /* Certificates */ = { + isa = PBXGroup; + children = ( + A70F4A95175A529400386DF5 /* root_certificate.cer */, + ); + name = Certificates; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -287,6 +299,7 @@ buildActionMask = 2147483647; files = ( F8C6F282174D2C6200B154D5 /* Icon.png in Resources */, + A70F4A96175A529400386DF5 /* root_certificate.cer in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -295,6 +308,7 @@ buildActionMask = 2147483647; files = ( F8C6F283174D2C6200B154D5 /* Icon.png in Resources */, + A70F4A97175A529400386DF5 /* root_certificate.cer in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/AFTestURLProtocol.m b/Tests/AFTestURLProtocol.m index 40af355..6d20dce 100644 --- a/Tests/AFTestURLProtocol.m +++ b/Tests/AFTestURLProtocol.m @@ -58,14 +58,12 @@ static AFTestURLProtocolInitializationCallback _initializationCallback = nil; cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id )client { - NSParameterAssert(_initializationCallback); - self = [super initWithRequest:request cachedResponse:cachedResponse client:client]; if (!self) { return nil; } - self = _initializationCallback(self); + self = _initializationCallback ? _initializationCallback(self) : self; _matchingURL = nil; _initializationCallback = nil; @@ -79,15 +77,25 @@ static AFTestURLProtocolInitializationCallback _initializationCallback = nil; #pragma mark - NSURLAuthenticationChallengeSender -- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} +- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + [self doesNotRecognizeSelector:_cmd]; +} -- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} +- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + [self doesNotRecognizeSelector:_cmd]; +} - (void)useCredential:(NSURLCredential *)credential -forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} +forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + [self doesNotRecognizeSelector:_cmd]; +} -- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} +- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + [self doesNotRecognizeSelector:_cmd]; +} -- (void)rejectProtectionSpaceAndContinueWithChallenge:(NSURLAuthenticationChallenge *)challenge {} +- (void)rejectProtectionSpaceAndContinueWithChallenge:(NSURLAuthenticationChallenge *)challenge { + [self doesNotRecognizeSelector:_cmd]; +} @end diff --git a/Tests/AFURLConnectionOperationTests.m b/Tests/AFURLConnectionOperationTests.m index 7308583..d575339 100644 --- a/Tests/AFURLConnectionOperationTests.m +++ b/Tests/AFURLConnectionOperationTests.m @@ -68,4 +68,116 @@ [operation cancel]; } +- (void)testThatAFURLConnectionOperationTrustsPinnedCertificates { + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/path" relativeToURL:self.baseURL]]; + AFURLConnectionOperation *operation = [[AFURLConnectionOperation alloc] initWithRequest:request]; + operation.SSLPinningMode = AFSSLPinningModeCertificate; + + __block BOOL useCredentialInvoked = NO; + + NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:request.URL.host port:request.URL.port.integerValue protocol:request.URL.scheme realm:nil authenticationMethod:NSURLAuthenticationMethodServerTrust]; + + NSData *certificateData = [NSData dataWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForResource:@"root_certificate" ofType:@"cer"]]; + NSParameterAssert(certificateData); + + SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData); + NSParameterAssert(certificate); + + SecCertificateRef allowedCertificates[] = {certificate}; + CFArrayRef certificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL); + + SecPolicyRef policy = SecPolicyCreateBasicX509(); + SecTrustRef trust = NULL; + OSStatus status = SecTrustCreateWithCertificates(certificates, policy, &trust); + NSAssert(status == errSecSuccess, @"SecTrustCreateWithCertificates error: %ld", (long int)status); + + SecTrustResultType result; + status = SecTrustEvaluate(trust, &result); + NSAssert(status == errSecSuccess, @"SecTrustEvaluate error: %ld", (long int)status); + + id mockedProtectionSpace = [OCMockObject partialMockForObject:protectionSpace]; + + [[[mockedProtectionSpace stub] andDo:^(NSInvocation *invocation) { + [invocation setReturnValue:(void *)&trust]; + }] serverTrust]; + + AFTestURLProtocol *protocol = [[AFTestURLProtocol alloc] initWithRequest:request cachedResponse:nil client:nil]; + id mockedProtocol = [OCMockObject partialMockForObject:protocol]; + + void(^useCredential)(NSInvocation *invocation) = ^(NSInvocation *invocation) { + useCredentialInvoked = YES; + }; + + [[[mockedProtocol stub] andDo:useCredential] useCredential:OCMOCK_ANY forAuthenticationChallenge:OCMOCK_ANY]; + + NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:trust]; + NSURLAuthenticationChallenge *authenticationChallenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:protectionSpace proposedCredential:credential previousFailureCount:0 failureResponse:nil error:nil sender:mockedProtocol]; + [protocol.client URLProtocol:mockedProtocol didReceiveAuthenticationChallenge:authenticationChallenge]; + + [operation connection:nil willSendRequestForAuthenticationChallenge:authenticationChallenge]; + + CFRelease(trust); + CFRelease(policy); + CFRelease(certificates); + CFRelease(certificate); + + expect(useCredentialInvoked).will.beTruthy(); +} + +- (void)testThatAFURLConnectionOperationTrustsPinnedPublicKeys { + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/path" relativeToURL:self.baseURL]]; + AFURLConnectionOperation *operation = [[AFURLConnectionOperation alloc] initWithRequest:request]; + operation.SSLPinningMode = AFSSLPinningModePublicKey; + + __block BOOL useCredentialInvoked = NO; + + NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:request.URL.host port:request.URL.port.integerValue protocol:request.URL.scheme realm:nil authenticationMethod:NSURLAuthenticationMethodServerTrust]; + + NSData *certificateData = [NSData dataWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForResource:@"root_certificate" ofType:@"cer"]]; + NSParameterAssert(certificateData); + + SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData); + NSParameterAssert(certificate); + + SecCertificateRef allowedCertificates[] = {certificate}; + CFArrayRef certificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL); + + SecPolicyRef policy = SecPolicyCreateBasicX509(); + SecTrustRef trust = NULL; + OSStatus status = SecTrustCreateWithCertificates(certificates, policy, &trust); + NSAssert(status == errSecSuccess, @"SecTrustCreateWithCertificates error: %ld", (long int)status); + + SecTrustResultType result; + status = SecTrustEvaluate(trust, &result); + NSAssert(status == errSecSuccess, @"SecTrustEvaluate error: %ld", (long int)status); + + id mockedProtectionSpace = [OCMockObject partialMockForObject:protectionSpace]; + + [[[mockedProtectionSpace stub] andDo:^(NSInvocation *invocation) { + [invocation setReturnValue:(void *)&trust]; + }] serverTrust]; + + AFTestURLProtocol *protocol = [[AFTestURLProtocol alloc] initWithRequest:request cachedResponse:nil client:nil]; + id mockedProtocol = [OCMockObject partialMockForObject:protocol]; + + void(^useCredential)(NSInvocation *invocation) = ^(NSInvocation *invocation) { + useCredentialInvoked = YES; + }; + + [[[mockedProtocol stub] andDo:useCredential] useCredential:OCMOCK_ANY forAuthenticationChallenge:OCMOCK_ANY]; + + NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:trust]; + NSURLAuthenticationChallenge *authenticationChallenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:protectionSpace proposedCredential:credential previousFailureCount:0 failureResponse:nil error:nil sender:mockedProtocol]; + [protocol.client URLProtocol:mockedProtocol didReceiveAuthenticationChallenge:authenticationChallenge]; + + [operation connection:nil willSendRequestForAuthenticationChallenge:authenticationChallenge]; + + CFRelease(trust); + CFRelease(policy); + CFRelease(certificates); + CFRelease(certificate); + + expect(useCredentialInvoked).will.beTruthy(); +} + @end diff --git a/Tests/Resources/root_certificate.cer b/Tests/Resources/root_certificate.cer new file mode 100644 index 0000000000000000000000000000000000000000..12c891435eb4e7323b95826f5f4d84ce0a0de008 GIT binary patch literal 573 zcmXqLVzM-7VqC<;$#BBT{D`ak%nAcuHcqWJkGAi;jEt(AdDtz|hdt#L&bbN`l|W5GY`3U;!1N zgBu#>Bm0|?m4Ugjm%*U1lc}+hVa``3FOglE6-@^|ml?Yy#?38~Y5c^I(RQD6WAwiK z=i3?9rf=^mTG+^(((%uwMJ3B)(wtY9VrE~R@ADa$|3OI}kP5_Goady)Ez zSqsbSF0tN@S^56f@YNQ!*SqoKW0}T9z|cZ_bXcTKF+a x#EDO2N_AFdz>j;omTX?J(|b)yg{HvCjyd#! literal 0 HcmV?d00001 diff --git a/Tests/Resources/root_certificate.key b/Tests/Resources/root_certificate.key new file mode 100644 index 0000000..8a8df65 --- /dev/null +++ b/Tests/Resources/root_certificate.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCc9QJKFLopeILA83YzRmFenXIcgfIIaIbfCbFbvm/ntwCtZ7eK +cqGBA2SI/kSEJGpIkpzq0lyb1ZJB8ayGD8p8lKfxnmOsc1RSzXz3cieomqF3ftIF +21yp79qnwo8roWV2AAolWnmcpCwbrWmgbWueupl7ieu4gcSOXU4EikwGKwIDAQAB +AoGASq0zmqWD8Sk6JK1xJnIs74Q/f5q/2gpJaSLGdJ0FxxxFwTsgk0l419YSZi97 +z9c3jjHbYMoXb7lMbf2bFOm8b4zQvmdVpLbiHC9Lned30VHgZJ55WSPd0GQJl9EJ +uw4C9J2Uk7uUjQbbgGPHwO5w/75F++Cp5jN91M/7fqgxO9kCQQDMC15vSVrHR0hU +GO235KeaDIUlIWBQYcPXZTn2kBfpSE2T3aYuIqfM5fx0z5A4nSM9Ylo/FOrm+y/p +ogT+APTFAkEAxOxAn09r9vx46drP8ap8ca2+0x46+1xVoaAdUv/OlXV4Ftgo5l7h +5ZRvs+JILmtvErtzkaUiCudh96F3tLAeLwJAOXO2ClW4NsYuamd+f7nlKy39S2Aj +c16juwFombEm2mueVFUjlnfxkXLsa6OJ8zbjlkQcLwjfv1vYuMsC5tY0FQJBAJcd +hWm7lOpwTImI9NJLNjw2TJ3OMQz7imsBZ/9tdqaTApjlQF2oqkl3Y1DzcNjOcOo7 +FzDJPBqJ/U/+hNIP5NkCQCUhCnfTXxx48sL7XKjlTr66rEbk3e1rZL7vx+24jcax +xUooGhnRWaQsbEynVteYbPg7I8e8N6YEHtW5jwxhFaE= +-----END RSA PRIVATE KEY----- From f1cfb96ad796c7d8123639fb44094518d915347d Mon Sep 17 00:00:00 2001 From: Oliver Letterer Date: Sat, 1 Jun 2013 19:28:40 +0200 Subject: [PATCH 2/3] Fixes AFSSLPinningModePublicKey on OS X. --- AFNetworking/AFURLConnectionOperation.m | 32 +++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index 0f03915..52fe7f9 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -107,6 +107,26 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat } } +#if !defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +static NSData *AFSecKeyGetData(SecKeyRef key) { + CFDataRef data = NULL; + + OSStatus status = SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data); + NSCAssert(status == errSecSuccess, @"SecItemExport error: %ld", (long int)status); + NSCParameterAssert(data); + + return (__bridge_transfer NSData *)data; +} +#endif + +static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) { +#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) + return [(__bridge id)key1 isEqual:(__bridge id)key2]; +#else + return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)]; +#endif +} + @interface AFURLConnectionOperation () @property (readwrite, nonatomic, assign) AFOperationState state; @property (readwrite, nonatomic, assign, getter = isCancelled) BOOL cancelled; @@ -616,11 +636,15 @@ willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challe switch (self.SSLPinningMode) { case AFSSLPinningModePublicKey: { + NSArray *pinnedPublicKeys = [self.class pinnedPublicKeys]; + for (id publicKey in trustChain) { - if ([[self.class pinnedPublicKeys] containsObject:publicKey]) { - NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; - [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; - return; + for (id pinnedPublicKey in pinnedPublicKeys) { + if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)publicKey, (__bridge SecKeyRef)pinnedPublicKey)) { + NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; + [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; + return; + } } } From 5dcfe99c93fe76b76bcad87eafe78a3fd027c5d8 Mon Sep 17 00:00:00 2001 From: Oliver Letterer Date: Sat, 1 Jun 2013 20:38:08 +0200 Subject: [PATCH 3/3] Adding tests for trusting derived certificates. --- .../project.pbxproj | 12 ++ Tests/AFURLConnectionOperationTests.m | 126 ++++++++++++++++++ Tests/Resources/ca.cer | Bin 0 -> 879 bytes Tests/Resources/derived.cert | Bin 0 -> 916 bytes 4 files changed, 138 insertions(+) create mode 100644 Tests/Resources/ca.cer create mode 100644 Tests/Resources/derived.cert diff --git a/Tests/AFNetworking Tests.xcodeproj/project.pbxproj b/Tests/AFNetworking Tests.xcodeproj/project.pbxproj index 4e64c3d..f4c273d 100644 --- a/Tests/AFNetworking Tests.xcodeproj/project.pbxproj +++ b/Tests/AFNetworking Tests.xcodeproj/project.pbxproj @@ -27,6 +27,10 @@ 29A9CE2217456336002360C8 /* AFJSONRequestOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A9CE2017456336002360C8 /* AFJSONRequestOperationTests.m */; }; A70F4A96175A529400386DF5 /* root_certificate.cer in Resources */ = {isa = PBXBuildFile; fileRef = A70F4A95175A529400386DF5 /* root_certificate.cer */; }; A70F4A97175A529400386DF5 /* root_certificate.cer in Resources */ = {isa = PBXBuildFile; fileRef = A70F4A95175A529400386DF5 /* root_certificate.cer */; }; + A70F4A9E175A726B00386DF5 /* ca.cer in Resources */ = {isa = PBXBuildFile; fileRef = A70F4A9C175A726B00386DF5 /* ca.cer */; }; + A70F4A9F175A726B00386DF5 /* ca.cer in Resources */ = {isa = PBXBuildFile; fileRef = A70F4A9C175A726B00386DF5 /* ca.cer */; }; + A70F4AA0175A726B00386DF5 /* derived.cert in Resources */ = {isa = PBXBuildFile; fileRef = A70F4A9D175A726B00386DF5 /* derived.cert */; }; + A70F4AA1175A726B00386DF5 /* derived.cert in Resources */ = {isa = PBXBuildFile; fileRef = A70F4A9D175A726B00386DF5 /* derived.cert */; }; A7DC62A617592E4200EBEC2F /* AFTestURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A517592E4200EBEC2F /* AFTestURLProtocol.m */; }; A7DC62A717592E4200EBEC2F /* AFTestURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A517592E4200EBEC2F /* AFTestURLProtocol.m */; }; A7DC62A917592E4800EBEC2F /* AFURLConnectionOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */; }; @@ -81,6 +85,8 @@ 55E73C267F33406A9F92476C /* libPods-ios.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ios.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 96A923755B00464187DEDBAF /* libPods-osx.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-osx.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A70F4A95175A529400386DF5 /* root_certificate.cer */ = {isa = PBXFileReference; lastKnownFileType = file; name = root_certificate.cer; path = Resources/root_certificate.cer; sourceTree = ""; }; + A70F4A9C175A726B00386DF5 /* ca.cer */ = {isa = PBXFileReference; lastKnownFileType = file; name = ca.cer; path = Resources/ca.cer; sourceTree = ""; }; + A70F4A9D175A726B00386DF5 /* derived.cert */ = {isa = PBXFileReference; lastKnownFileType = file; name = derived.cert; path = Resources/derived.cert; sourceTree = ""; }; A7DC62A417592E4200EBEC2F /* AFTestURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFTestURLProtocol.h; sourceTree = ""; }; A7DC62A517592E4200EBEC2F /* AFTestURLProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFTestURLProtocol.m; sourceTree = ""; }; A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLConnectionOperationTests.m; sourceTree = ""; }; @@ -222,6 +228,8 @@ A70F4A91175A4E0000386DF5 /* Certificates */ = { isa = PBXGroup; children = ( + A70F4A9C175A726B00386DF5 /* ca.cer */, + A70F4A9D175A726B00386DF5 /* derived.cert */, A70F4A95175A529400386DF5 /* root_certificate.cer */, ); name = Certificates; @@ -300,6 +308,8 @@ files = ( F8C6F282174D2C6200B154D5 /* Icon.png in Resources */, A70F4A96175A529400386DF5 /* root_certificate.cer in Resources */, + A70F4A9E175A726B00386DF5 /* ca.cer in Resources */, + A70F4AA0175A726B00386DF5 /* derived.cert in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -309,6 +319,8 @@ files = ( F8C6F283174D2C6200B154D5 /* Icon.png in Resources */, A70F4A97175A529400386DF5 /* root_certificate.cer in Resources */, + A70F4A9F175A726B00386DF5 /* ca.cer in Resources */, + A70F4AA1175A726B00386DF5 /* derived.cert in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/AFURLConnectionOperationTests.m b/Tests/AFURLConnectionOperationTests.m index d575339..44cc410 100644 --- a/Tests/AFURLConnectionOperationTests.m +++ b/Tests/AFURLConnectionOperationTests.m @@ -180,4 +180,130 @@ expect(useCredentialInvoked).will.beTruthy(); } +- (void)testThatAFURLConnectionOperationTrustsPublicKeysOfDerivedCertificates { + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/path" relativeToURL:self.baseURL]]; + AFURLConnectionOperation *operation = [[AFURLConnectionOperation alloc] initWithRequest:request]; + operation.SSLPinningMode = AFSSLPinningModePublicKey; + + __block BOOL useCredentialInvoked = NO; + + NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:request.URL.host port:request.URL.port.integerValue protocol:request.URL.scheme realm:nil authenticationMethod:NSURLAuthenticationMethodServerTrust]; + + NSData *caCertificateData = [NSData dataWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForResource:@"ca" ofType:@"cer"]]; + NSParameterAssert(caCertificateData); + + SecCertificateRef caCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCertificateData); + NSParameterAssert(caCertificate); + + NSData *hostCertificateData = [NSData dataWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForResource:@"derived" ofType:@"cert"]]; + NSParameterAssert(hostCertificateData); + + SecCertificateRef hostCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCertificateData); + NSParameterAssert(hostCertificate); + + SecCertificateRef allowedCertificates[] = {caCertificate, hostCertificate}; + CFArrayRef certificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 2, NULL); + + SecPolicyRef policy = SecPolicyCreateBasicX509(); + SecTrustRef trust = NULL; + OSStatus status = SecTrustCreateWithCertificates(certificates, policy, &trust); + NSAssert(status == errSecSuccess, @"SecTrustCreateWithCertificates error: %ld", (long int)status); + + SecTrustResultType result; + status = SecTrustEvaluate(trust, &result); + NSAssert(status == errSecSuccess, @"SecTrustEvaluate error: %ld", (long int)status); + + id mockedProtectionSpace = [OCMockObject partialMockForObject:protectionSpace]; + + [[[mockedProtectionSpace stub] andDo:^(NSInvocation *invocation) { + [invocation setReturnValue:(void *)&trust]; + }] serverTrust]; + + AFTestURLProtocol *protocol = [[AFTestURLProtocol alloc] initWithRequest:request cachedResponse:nil client:nil]; + id mockedProtocol = [OCMockObject partialMockForObject:protocol]; + + void(^useCredential)(NSInvocation *invocation) = ^(NSInvocation *invocation) { + useCredentialInvoked = YES; + }; + + [[[mockedProtocol stub] andDo:useCredential] useCredential:OCMOCK_ANY forAuthenticationChallenge:OCMOCK_ANY]; + + NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:trust]; + NSURLAuthenticationChallenge *authenticationChallenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:protectionSpace proposedCredential:credential previousFailureCount:0 failureResponse:nil error:nil sender:mockedProtocol]; + [protocol.client URLProtocol:mockedProtocol didReceiveAuthenticationChallenge:authenticationChallenge]; + + [operation connection:nil willSendRequestForAuthenticationChallenge:authenticationChallenge]; + + CFRelease(trust); + CFRelease(policy); + CFRelease(certificates); + CFRelease(caCertificate); + CFRelease(hostCertificate); + + expect(useCredentialInvoked).will.beTruthy(); +} + +- (void)testThatAFURLConnectionOperationTrustsDerivedCertificates { + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/path" relativeToURL:self.baseURL]]; + AFURLConnectionOperation *operation = [[AFURLConnectionOperation alloc] initWithRequest:request]; + operation.SSLPinningMode = AFSSLPinningModeCertificate; + + __block BOOL useCredentialInvoked = NO; + + NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:request.URL.host port:request.URL.port.integerValue protocol:request.URL.scheme realm:nil authenticationMethod:NSURLAuthenticationMethodServerTrust]; + + NSData *caCertificateData = [NSData dataWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForResource:@"ca" ofType:@"cer"]]; + NSParameterAssert(caCertificateData); + + SecCertificateRef caCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCertificateData); + NSParameterAssert(caCertificate); + + NSData *hostCertificateData = [NSData dataWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForResource:@"derived" ofType:@"cert"]]; + NSParameterAssert(hostCertificateData); + + SecCertificateRef hostCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCertificateData); + NSParameterAssert(hostCertificate); + + SecCertificateRef allowedCertificates[] = {caCertificate, hostCertificate}; + CFArrayRef certificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 2, NULL); + + SecPolicyRef policy = SecPolicyCreateBasicX509(); + SecTrustRef trust = NULL; + OSStatus status = SecTrustCreateWithCertificates(certificates, policy, &trust); + NSAssert(status == errSecSuccess, @"SecTrustCreateWithCertificates error: %ld", (long int)status); + + SecTrustResultType result; + status = SecTrustEvaluate(trust, &result); + NSAssert(status == errSecSuccess, @"SecTrustEvaluate error: %ld", (long int)status); + + id mockedProtectionSpace = [OCMockObject partialMockForObject:protectionSpace]; + + [[[mockedProtectionSpace stub] andDo:^(NSInvocation *invocation) { + [invocation setReturnValue:(void *)&trust]; + }] serverTrust]; + + AFTestURLProtocol *protocol = [[AFTestURLProtocol alloc] initWithRequest:request cachedResponse:nil client:nil]; + id mockedProtocol = [OCMockObject partialMockForObject:protocol]; + + void(^useCredential)(NSInvocation *invocation) = ^(NSInvocation *invocation) { + useCredentialInvoked = YES; + }; + + [[[mockedProtocol stub] andDo:useCredential] useCredential:OCMOCK_ANY forAuthenticationChallenge:OCMOCK_ANY]; + + NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:trust]; + NSURLAuthenticationChallenge *authenticationChallenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:protectionSpace proposedCredential:credential previousFailureCount:0 failureResponse:nil error:nil sender:mockedProtocol]; + [protocol.client URLProtocol:mockedProtocol didReceiveAuthenticationChallenge:authenticationChallenge]; + + [operation connection:nil willSendRequestForAuthenticationChallenge:authenticationChallenge]; + + CFRelease(trust); + CFRelease(policy); + CFRelease(certificates); + CFRelease(caCertificate); + CFRelease(hostCertificate); + + expect(useCredentialInvoked).will.beTruthy(); +} + @end diff --git a/Tests/Resources/ca.cer b/Tests/Resources/ca.cer new file mode 100644 index 0000000000000000000000000000000000000000..b04dc96b72b33a01235ed4925df78ca5e2274d60 GIT binary patch literal 879 zcmXqLV$L>bV!E<`nTe5!iIZV=uR^>?-h@pCylk9WZ60mkc^Mg5Ss4r(8w|M(IN6v( zS=fY`TwD!B41_=&4qD9G zq?lQl-#;g_EVW3%C$*#`wJ5d7KtY_>(AdDtz|hdb(A2;(N`l|W5GY`3U;z~v432D^ zj~sH0tPISJy};1xWNK_=xR_KN8okU+=0^O+mz-DYW^U13%2An>vi4Qf#&_ax^1nnl znytuw5EPly&3qz#!P}X#byqf=t*mR_|72^zhG|X>GeS*uIsFa$@(;aynP6I(uf_hR zLUVrIdvTXu#Yy|r#2Cabt8`b}T;d6RCwKG9`E~De=1+asyM{@7pX_TbnN9sn%!~|- ziyL1UG(I(u1qQM#ABz}^Nd8ZyuII9==3lr_Q@(GrmQ46AO&f#8tsr@2md3RPjjI~4 zs#)0BwxqFn5QKRXQmW+vIa`>8)qt6i@jr4*0MjrtFbbq^u}yN^f6uC0==w_w$*bz- z^8=rj9IbX}-LSJfyPKhsu|7ESpx4?-H~7D-o7X<;>Jo*Wn?zHUHcVZ4GOWDbkS#e% z`)BC}BbMgW+I7|OENhDoMd#bBUHaGd@n7|$jf+GlU0A;>{`W444O2dPulU-`EV1HZ S|MZ7X?fPy;=Rezf`!N7=#u;1y literal 0 HcmV?d00001 diff --git a/Tests/Resources/derived.cert b/Tests/Resources/derived.cert new file mode 100644 index 0000000000000000000000000000000000000000..46bf343f3dbed87bfad0e30badd1219c0a86a03e GIT binary patch literal 916 zcmXqLVxC~o#Po9kGZP~d6CgCq%%usJU^u2ObdvCZw<5rNoGE3uHgT_@2BKbd+x}M9f znt$O!P5HjfS~B6gG;J0(wk>IF9t6?V#K|zbS0P>`Z^9Qub=mQdhXBa bnB5ak{CFi3Upv=k?qrFO^zVw-d28|kU#A<6 literal 0 HcmV?d00001