I seem to be struggling with lots of things lately that should be easy, but I just can’t find an easy way to do them. My latest struggle has been using NSURLConnection to connect to an SSL site that is secured using my root certificate. There is no easy reference anywhere for how to do this without loading the root cert into the user’s general key store – which is fiddly and not the best in some circumstances.

In my case I use cacert.org as my root cert and I don’t want to force the end user to trust all sites signed by cacert.org, I just want my SSL connection to work inside my app.

So it turns out there are two methods that you need to use in the NSURLConnection delegate class. Both are referenced in many places, but it’s generally to tell you how to completely bypass any check. You can also use them to check against your own root cert.

First of all, you implement -(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace so that for server authentication requests the NSURLConnection class knows you can handle them.

Then you implement - (void)connection:(NSURLConnection *)conn didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge to actually handle the check. The code is pretty simple.

As a note – rootCert in the attached code is a reference to a certificate I loaded from a file (DER encoded).

#pragma mark -
#pragma mark Connection Authentication Handling

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
    
    #pragma unused(conn)

    NSString * challenge = [protectionSpace authenticationMethod];
    if ([challenge compare:NSURLAuthenticationMethodServerTrust] == 0) {
        return YES;
    }
    
    return NO;
    
}

/* Look to see if we can handle the challenge */

- (void)connection:(NSURLConnection *)conn didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    #pragma unused(conn)
    
    NSLog(@"didReceiveAuthenticationChallenge %@ %zd", [[challenge protectionSpace] authenticationMethod], (ssize_t) [challenge previousFailureCount]);
    
    NSURLCredential *   credential = nil;
    NSURLProtectionSpace *  protectionSpace;
    SecTrustRef             trust;
    int                        err;

    /* Setup */        
    protectionSpace = [challenge protectionSpace];
    trust = [protectionSpace serverTrust];
    credential = [NSURLCredential credentialForTrust:trust];

    /* Set up the array of certs we will authenticate against and create cred */
    NSArray * certs = [[NSArray alloc] initWithObjects:(id)rootCert,nil];
    credential = [NSURLCredential credentialForTrust:trust];
        
    /* Build up the trust anchor using our root cert */    
    
    err = SecTrustSetAnchorCertificates(trust, (CFArrayRef) certs);
    SecTrustResultType trustResult = 0;
    if (err == noErr) {
        err = SecTrustEvaluate(trust, &trustResult);
    }
    
    [certs release];
    
    BOOL trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));

    // Return based on whether we decided to trust or not

    if (trusted)
        [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
    else {
        NSLog(@"Trust evaluation failed for service root certificate");
        [[challenge sender] cancelAuthenticationChallenge:challenge];
    }

}

Again – I reckon there will be a quicker way to do this and probably some bugs in what I’ve done – feel free to comment and set me on the straight and narrow!