I am trying to use Client Certificate authentication to access a secure website. The code I am using works fine in iOS 6.1 but fails with the server returning a 403.7 error when using iOS 7.
I use the connection:willSendRequestForAuthenticationChallenge handler to check the authentication method and provide the client certificate.
My code is:
- (void)connection: (NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
NSLog(@"Trust Challenge");
SecTrustResultType trustResultType;
OSStatus err = SecTrustEvaluate(challenge.protectionSpace.serverTrust, &trustResultType);
NSLog(@"SecTrustResult %u %d",trustResultType, (int)err);
if (trustResultType == kSecTrustResultProceed || trustResultType == kSecTrustResultConfirm || trustResultType == kSecTrustResultUnspecified) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
else{
[challenge.sender cancelAuthenticationChallenge:challenge];
}
} else {
NSString *path = [[NSBundle mainBundle]pathForResource:@"mycert" ofType:@"pfx"];
NSData *p12data = [NSData dataWithContentsOfFile:path];
CFDataRef inP12data = (__bridge CFDataRef)p12data;
SecIdentityRef myIdentity;
SecTrustRef myTrust;
extractIdentityAndTrust(inP12data, &myIdentity, &myTrust);
assert(myIdentity != nil);
assert(myTrust != nil);
long count = SecTrustGetCertificateCount(myTrust);
NSMutableArray* myCertificates = nil;
if(count > 1) {
myCertificates = [NSMutableArray arrayWithCapacity:count];
for(int i = 1; i < count; ++i) {
[myCertificates addObject:(__bridge id)SecTrustGetCertificateAtIndex(myTrust, i)];
}
}
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:myCertificates persistence:NSURLCredentialPersistenceNone];
assert(credential != nil);
NSLog(@"User: %@, certificates %@ identity:%@", [credential user], [credential certificates], [credential identity]);
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
}
I use this function to extract the contents of the certificate:
OSStatus extractIdentityAndTrust(CFDataRef inP12data, SecIdentityRef *identity, SecTrustRef *trust)
{
OSStatus securityError = errSecSuccess;
CFStringRef password = CFSTR("password");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import(inP12data, options, &items);
if (securityError == 0) {
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
*identity = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust);
*trust = (SecTrustRef)tempTrust;
CFIndex count = CFArrayGetCount(items);
NSLog(@"Certificates found: %ld",count);
}
if (options) {
CFRelease(options);
}
return securityError;
}
The mycert.pfx file contains the intermediate certificate along with the client cert.
The connection:didFailWithError function is never called.
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"Error: %@", [error userInfo]);
}
So it seems like the certificate negotiation succeeds at some level.
My problem is similar to SSL - behaves differently in iOS7? but I am using Windows Server 2008 R2 with IIS 7.5. The TLS 1.1 and TLS 1.2 protocols have been enabled on the server.
A WireShark trace shows that the certificate frame during the TLS handshake is empty when using iOS 7. The certificate is sent and verified when using iOS 6.1.
I can access the site in iOS 7 using Safari.
Any help is greatly appreciated.