Since nobody answered this, and I did arrive at a working solution eventually, here it is.
Server trust is not a challenge from the server to the client, it's an opportunity for the client to validate the trust offered by the server. With that in mind, the code below doesn't verify that trust, but it could.
Typically you get the NSURLAuthenticationMethodServerTrust, then subsequently you get the NSURLAuthenticationMethodClientCertificate. It's not either-or. Here's the working code.
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
NSString *authenticationMethod = [protectionSpace authenticationMethod];
if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate] && self.accountCertKeychainRef != nil)
{
SecIdentityRef identity = [KeychainUtilities retrieveIdentityWithPersistentRef:self.accountCertKeychainRef];
NSURLCredential* credential = [CertificateUtilities getCredentialFromCert:identity];
if ( credential == nil )
{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
else
{
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
}
else if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
else if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodNTLM] || [authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic])
{
self.lastProtSpace = [challenge protectionSpace];
if ([challenge previousFailureCount] > 2)
{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
else
{
[[challenge sender] useCredential:[self buildCredential] forAuthenticationChallenge:challenge];
}
}
else
{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
For the question below, here's how you can get the identity:
+ (SecIdentityRef)copyIdentityAndTrustWithCertData:(CFDataRef)inPKCS12Data password:(CFStringRef)keyPassword
{
SecIdentityRef extractedIdentity = nil;
OSStatus securityError = errSecSuccess;
const void *keys[] = {kSecImportExportPassphrase};
const void *values[] = {keyPassword};
CFDictionaryRef optionsDictionary = NULL;
optionsDictionary = CFDictionaryCreate(NULL, keys, values, (keyPassword ? 1 : 0), NULL, NULL);
CFArrayRef items = NULL;
securityError = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);
if (securityError == errSecSuccess) {
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
// get identity from dictionary
extractedIdentity = (SecIdentityRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
CFRetain(extractedIdentity);
}
if (optionsDictionary) {
CFRelease(optionsDictionary);
}
if (items) {
CFRelease(items);
}
return extractedIdentity;
}
For those interested, here is getCredentialForCert:
+ (NSURLCredential *)getCredentialFromCert:(SecIdentityRef)identity
{
SecCertificateRef certificateRef = NULL;
SecIdentityCopyCertificate(identity, &certificateRef);
NSArray *certificateArray = [[NSArray alloc] initWithObjects:(__bridge_transfer id)(certificateRef), nil];
NSURLCredentialPersistence persistence = NSURLCredentialPersistenceForSession;
NSURLCredential *credential = [[NSURLCredential alloc] initWithIdentity:identity
certificates:certificateArray
persistence:persistence];
return credential;
}