Saturday, September 5, 2009

iPhone and SSL

One of the problems I ran into while working on our iPhone app (see past post) is handling SSL connections. It works just fine with proper certificates, but we have several machines with self-signed SSL certificates. The normal NSURLConnection in the iPhone SDK just rejects connections to a server with a self-signed certificate, and there isn't an obvious API to get around that.

A few searches turned up a way to override part of NSURLRequest or something like that, but it really seemed to be a hack -- plus it seemed to be all-or-nothing, instead of having an easy way to hook into the particulars of each request.

Long story short, I found a better way to handle this, which may be new in the 3.0 SDK. Put this in your NSURLConnection delegate (for asynchronous requests):

- (BOOL)connection:(NSURLConnection *)connection
canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *) space {
if([[space authenticationMethod] 
isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// Note: this is presently only called once per server (or URL?) until
//       you restart the app
if(shouldAllowSelfSignedCert) {
return YES; // Self-signed cert will be accepted
} else {
return NO;  // Self-signed cert will be rejected
}
// Note: it doesn't seem to matter what you return for a proper SSL cert
//       only self-signed certs
}
// If no other authentication is required, return NO for everything else
// Otherwise maybe YES for NSURLAuthenticationMethodDefault and etc.
return NO;
}



Because the delegate typically has more information about the connection, it can make a more intelligent decision in that method. For instance, our app has a config screen where you enter a server URL, username and password if needed, and a checkbox for whether to allow self-signed SSL certificates. So our code above looks up the value of that config setting for the server when the method is called with NSURLAuthenticationMethodServerTrust.

As the comment indicates, the method is only called with NSURLAuthenticationMethodServerTrust once for any given server configuration and the result apparently cached (even across brand new NSURLConnection instances). I'd be interested if there was some way to force it to reset all that and call the method again with NSURLAuthenticationMethodServerTrust.

In a future post, we'll look at how to support HTTPS client cert authentication, which also seems to have better options in the 3.0 SDK.

12 comments:

  1. Hi Aaron,

    I'm very interested in a client cert authentication you mentioned at the end of the article.

    When a server needs a credential (a certificate in this case), this method is invoked from NSURLConnection delegate:

    - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

    I want to load a certificate from a file, fill a credential and run this method:

    [[challenge sender] useCredential:[self credential] forAuthenticationChallenge:challenge];

    But I don't know how to initialize (or fill) a SecIdentityRef parameter. Here is my code that creates the credentials:

    NSString *certPath = [[NSBundle mainBundle] pathForResource:@"certificate" ofType:@"cer"];
    NSData *certData = [[NSData alloc] initWithContentsOfFile:certPath];

    SecIdentityRef myIdentity; // ???

    SecCertificateRef myCert = SecCertificateCreateWithData(NULL, (CFDataRef)certData);
    [certData release];
    SecCertificateRef certArray[1] = { myCert };
    CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
    CFRelease(myCert);
    NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity
    certificates:(NSArray *)myCerts
    persistence:NSURLCredentialPersistencePermanent];
    CFRelease(myCerts);

    Do you know a solution? Could you please help me? Thanks a lot.

    ReplyDelete
  2. I've finally found the solution... But I have another problem:

    a connection to a server that requires a client certificate authorization is always denied, the certificate I fill into the credential is never sent to the server - according to the server logs. The server closes the connection and I get "NSURLErrorDomain -1206" error message. I'm totally angry about that because I've spent a lot of time trying to find out the solution.

    The post you mentioned at the end of your article would be very helpful...

    ReplyDelete
  3. We're working on the client cert process now. You're aware that you need to set up both certificates and a private key on the client, right? Did you populate the SecIdentityRef in your first comment above?

    ReplyDelete
  4. The situation has changed a little bit since my first post. Now I'm able to load a certificate and an identity (SecIdentityRef) from a single pkcs12 file. That's what the goal was. But the credential (a client certificate) is never sent to the server (see my second post). The error -1206 is not in SDK header (NSURLError.h) but I've found out that it could refer to kCFURLErrorClientCertificateRequired in CFNetworksError.h

    So I don't know whether I fill the credential properly or not. Or should I somehow verify the certificate in the code? I can paste my current code here if it helps.

    ReplyDelete
  5. I'm not sure if I understand your question:

    "You're aware that you need to set up both certificates and a private key on the client, right?"

    I load the identity (SecIdentityRef) from the pkcs12 file (a method SecPKCS12Import()), then I use SecIdentityCopyCertificate() method to copy a certificate from the identity and fianlly I use the identity and the certificate for creating a credential ([NSURLCredential credentialWithIdentity: ...]).

    What should I do with the private key? Is anything missing in my process?

    ReplyDelete
  6. Aaron,

    I too would love an example on using client ssl certs. Thanks!

    ReplyDelete
  7. Hello Aaron,

    thanks for the nice article...

    Is there anything else one needs to do, beyond adding the delegate method you suggest, to get the OS do what one would expect (stop blaming about self signed certificates)?

    I am asking because I am still having the very same Code=-1202 UserInfo=0xfc52e0 "untrusted server certificate" error, even if I can trace the execution of the added delegate method. It returns a sound YES and still the connection fails.

    The system I am connecting to (trying to.. ;-) uses SSL but does not actually do the authentication challenge a web server would use.

    The code runs identically on my 1 Gen Touch with SDK 3.0, the 3.0 and 3.1 simulators..

    BTW: as shouldAllowSelfSignedCert I just used a BOOL value set to YES

    Thanks
    Aldo

    ReplyDelete
  8. I didnt realise that development with an iPhone was so problematic on certain types of ssl certificate.

    Jamie
    http://www.ssl247.com

    ReplyDelete
  9. Joel, Kamil: Just need time... as soon as I have worked through the whole thing I'll put a post together.

    Aldo: If you're connecting to something other than a Web server, I'm not sure whether there are differences that might cause problems. If it's a Web server that uses SSL but no authentication, that shouldn't change anything. You should be able to just return YES for any space of type NSURLAuthenticationMethodServerTrust and that should work. Just make sure the method is getting called as expected (not a typo in the signature or whatever).

    Jamie: Everything's fine for SSL certs from official certificate authorities; there are just limited and poorly documented APIs for dealing with the less common cases. :)

    ReplyDelete
  10. I have a (really, really hacky) iPhone app example that lets you load a .p12 file and use it to access a client-cert-protected SSL resource from a web server.

    http://www.aland.us/software/iPhone-SSLTest/

    I'd appreciate any feedback & suggestions you might have.

    ReplyDelete
  11. I just finished up reading your blog the first time so I thought I should comment to let you know your stuff is great and you have another follower! Keep the posts coming!

    We just bought a wildcard ssl certificate at ClickSSL. As a first time customer of ClickSSL we are very happy with the service and support.

    ReplyDelete
  12. Hi Aaron,

    I want use SSL in my iPad application,
    But my problem is

    - (BOOL)connection:(NSURLConnection *)connection
    canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *) space

    will not get fired....

    Is u can help me

    thanks in advance

    ReplyDelete