We developed a device with which we communicate securely over SSL using a self-signed certificate. The device gets a dynamically-assigned IP address, and that is communicated to the iOS app via other means. By default, NSURLConnection
tries to validate the SSL certificate against the hostname, but it was impossible for us to create a wildcard cert that would match.
So instead, I figured out how to get Security.framework to ignore the host name. You must use the supplied trust’s certificate chain to create a new trust, which allows you to specify the trust policy. You then set the root (anchor) cert that was used to sign the self-signed cert. In our case, these are one and the same (an explanation of how to do this is included below).
- (void) connection: (NSURLConnection*) inConnection willSendRequestForAuthenticationChallenge: (NSURLAuthenticationChallenge*) inChallenge { NSLogDebug(@"Connection challenged"); // Build a new trust based on the supplied trust, so that we can set the policy… NSURLProtectionSpace* protectionSpace = inChallenge.protectionSpace; SecTrustRef trust = protectionSpace.serverTrust; CFIndex numCerts = SecTrustGetCertificateCount(trust); NSMutableArray* certs = [NSMutableArray arrayWithCapacity: numCerts]; for (CFIndex idx = 0; idx < numCerts; ++idx) { SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, idx); [certs addObject: CFBridgingRelease(cert)]; } // Create a policy that ignores the host name… SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); OSStatus err = SecTrustCreateWithCertificates(CFBridgingRetain(certs), policy, &trust); CFRelease(policy); if (err != noErr) { NSLogDebug(@"Error creating trust: %d", err); [inChallenge.sender cancelAuthenticationChallenge: inChallenge]; return; } // Set the root cert and evaluate the trust… NSArray* rootCerts = @[ CFBridgingRelease(mRootCert) ]; err = SecTrustSetAnchorCertificates(trust, CFBridgingRetain(rootCerts)); if (err == noErr) { SecTrustResultType trustResult; err = SecTrustEvaluate(trust, &trustResult); NSURLCredential* credential = [NSURLCredential credentialForTrust: trust]; CFRelease(trust); bool trusted = err == noErr; trusted = trusted && (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified); if (trusted) { [inChallenge.sender useCredential: credential forAuthenticationChallenge: inChallenge]; return; } } // An error occurred, or we don't trust the cert, so disallow it… [inChallenge.sender cancelAuthenticationChallenge: inChallenge]; }
Chances are, when you created your self-signed cert, you did something like this:
$ openssl req -x509 -nodes -days 7300 -newkey rsa:2048 -keyout my.key -out my.crt
That produces a certificate in PEM format (ASCII). Security.framework requires the certificate in DER format. You can convert the cert with the following (note that this will overwrite the PEM cert you generated):
$ openssl x509 -in my.crt -outform der -out my.crt
You can create the root SecCertificateRef
with something like this:
- (void) createRootCert { NSURL* certURL = [[NSBundle mainBundle] URLForResource: @"my" withExtension: @"crt"]; NSData* certData = [NSData dataWithContentsOfURL: certURL]; mRootCert = SecCertificateCreateWithData(kCFAllocatorDefault, CFBridgingRetain(certData)); }