From 3163069cbc319a21df8e1d1fdc449ee8c26b3ff0 Mon Sep 17 00:00:00 2001 From: Oliver Letterer Date: Sat, 1 Jun 2013 19:01:25 +0200 Subject: [PATCH] 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-----