1

This is a question that seems to be floating around all over the place, but so far I haven't been able to find a definite answer.

I'm tasked with writing an iPhone application for my company. My company has an SSL-encrypted website with a self-signed certificate. This website is used very frequently, and my employer would like for the site to be easily accessible on an iPhone. My app's function is to provide a method of storing the credentials used to access the site, and when the user opens the app, it automatically sends the stored credentials to the site and skips the login dialog to go straight into the site.

I've set up my app to use a UIWebView and load a request for the site. I've set up the usual authentication methods, such as:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {...}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {...}

When my app reaches the part of the code where I tell the UIWebView to load the NSURLRequest, it doesn't encounter any of my authentication methods. It jumps straight to didFailLoadWithError and the error tells me that I'm connecting to a site that may only be pretending to be my site because it has a self-signed certificate.

How do I get around this? I've found a few answers involving the NSURLConnection methods, but those methods don't appear to be called before I reach the error and am forced to stop loading the page. I've also found some answers involving overriding "undocumented" methods, but when I try implementing that solution my web view never reaches either "didFailLoadWithError" or "didFinishLoad" and never displays any content.

Jay Bhalani
  • 4,142
  • 8
  • 37
  • 50
Andrew
  • 466
  • 2
  • 7
  • 22
  • I know it's not answering your question and I hate it when people make such suggestions as there may be good reasons you can't do this ... but wouldn't it be so much less effort and cost to get a non-self-signed certificate? They're cheap with someone like godaddy.com or free with http://www.startssl.com/ – Rory Feb 16 '13 at 22:39
  • Yes, another solution would be to get a signed certificate. As far as simply fixing our specific problem, that probably would have been easier. If I had spent more time on it than I did, I probably would have gone back to my boss and recommended exactly that. But it was also important to make sure iOS can handle unsigned certificates. If it wasn't possible to do it this way, that would have constituted a major flaw in Apple's API, and someone upstairs would have needed to be notified. Thanks for the tip though, I didn't know there was a place to get those free. – Andrew Feb 18 '13 at 19:14

2 Answers2

2

The UIWebKit delegate doesn't forward through any of the NSURLConnection delegate methods to your app. One way to get around this would be to load the page using NSURLConnection and then push it into the UIWebView using -loadData:MIMEType:textEncodingName:baseURL:. Once you've done that you've verified the first page, which (as long as your site doesn't have links off of it), should stay safe. So, how do we verify a self-signed certificate?

I had to solve this with an OSX App a little earlier this year and, once I figured out what I was doing, it was pretty straightforward, assuming you have a similar setup. The solution I propose here actually verifies the server certificate (although in my case I was using a private CA, so I added the CA certificate to the trust root, instead of the server certificate, it should work just as well with that).

You'll need to add tests for NSURLAuthenticationMethodServerTrust to both the -connection:canAuthenticateAgainstProtectionSpace: and -connection:didReceiveAuthenticationChallenge: methods so that you can both request interest in and process the Security challenge.

Hope this helps.

-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
    // ... implement any other authentication here, such as your client side certificates or user name/password

    if ([[protectionSpace authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust])
        return YES;
    return NO;
}

-(void)connection:(NSURLConnection *)aConnection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;

            // implement your client side auth here for NSURLAuthenticationMethodHTTPDigest or basic or your client-side cert

    if ([[protectionSpace authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {
        // install our private certificates or CAs
        NSString *myCAString = @"<string containing your CA cert>";

        SecCertificateRef certRef = SecCertificateCreateWithData ( NULL, (CFDataRef)[NSData dataFromBase64String: myCAString]);
        NSArray *anchorArray = [NSArray arrayWithObject: (NSObject*)certRef];
        SecTrustSetAnchorCertificates( challenge.protectionSpace.serverTrust, (CFArrayRef) anchorArray);

        SecTrustResultType trustResultType;
        OSStatus err=SecTrustEvaluate(challenge.protectionSpace.serverTrust, &trustResultType);

        if ((!err) && (trustResultType == kSecTrustResultProceed || trustResultType == kSecTrustResultConfirm || trustResultType == kSecTrustResultUnspecified)) {
            [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
        } else {
            CFArrayRef certChain=NULL;
            CSSM_TP_APPLE_EVIDENCE_INFO *statusChain;
            SecTrustGetResult( challenge.protectionSpace.serverTrust, &trustResultType, &certChain, &statusChain);
            [challenge.sender cancelAuthenticationChallenge:challenge];
        }

    } else {
        // Cancel if we don't know what it is about... this is supposed to be secure
        [challenge.sender cancelAuthenticationChallenge:challenge];
    }
}
gaige
  • 17,263
  • 6
  • 57
  • 68
  • What is a CTURLTransaction? I already implemented a similar method involving NSURLConnection rather than CTURLTransaction, it is one of the methods that my webview never calls during its attempt to connect to the site. – Andrew Mar 06 '12 at 20:01
  • Sorry about that. I've fixed my answer to remove our corpora shim class which is what CTURLTransaction. – gaige Mar 06 '12 at 21:09
  • The UIWebKit delegate doesn't forward through any of the NSURLConnection delegate methods to your app. One way to get around this would be to load the page using NSURLConnection and then push it into the UIWebView using ``-loadData:MIMEType:textEncodingName:baseURL:``. Once you've done that you've verified the first page, which (as long as your site doesn't have links off of it), should stay safe. – gaige Mar 06 '12 at 22:52
  • Thanks! I wondered why I wasn't running into those functions, but using NSURLConnection first should do the trick. – Andrew Mar 07 '12 at 18:58
  • Is SecTrustGetResult available under iOS? – MrTJ May 28 '12 at 15:07
  • No, it's not available under iOS and is deprecated under 10.7 (although it's the only way to do it under 10.6, which is why I used it recently). `SecTrustGetTrustResult` is available under 10.7, but not under iOS right now, however it isn't necessary for the above function (note that the results aren't used), so you can just comment it out. Amusingly, it is referenced in the `SecTrustEvaluate` documentation for iOS, but it doesn't exist in the API. – gaige May 28 '12 at 15:53
1

I have managed to do what you desire by using RestKit framework, so yes, I can confirm it can be done. Go see in the RestKit sources how they do it, or just use RestKit. This says how to use SSL with RestKit. In my case, there was one catch - the self signed certificate had to contain the certain extensions, otherwise I always got the kSecTrustResultRecoverableTrustFailure. Go see here, the answer is voted -1 but it actually fixed the problem for me.

Community
  • 1
  • 1
lawicko
  • 7,246
  • 3
  • 37
  • 49