SecTrustEvaluate fails with kSecTrustResultRecoverableTrustFailure for self signed CA with non-matching subject name

StackOverflow https://stackoverflow.com/questions/21701224

Question

Here's my pretty standard NSURLConnection callback for authenticating using self signed certificate:

- (SecCertificateRef)certRefFromDerNamed:(NSString*)derFileName resultingDataRef:(CFDataRef*)dataRefPtr{
    NSString *thePath = [[NSBundle mainBundle] pathForResource:derFileName ofType:@"der"];
    NSData *certData = [[NSData alloc] initWithContentsOfFile:thePath];
    CFDataRef certDataRef = (__bridge_retained CFDataRef)certData;
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, certDataRef);
    *dataRefPtr = certDataRef;
    return cert;
}

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {

if (connection == self.connection) {

    BOOL trusted = NO;
     if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

        SecPolicyRef policyRef = SecPolicyCreateBasicX509();

        SecCertificateRef cert1;
        CFDataRef certData1;

        cert1 = [self certRefFromDerNamed:@"some3rdpartycacert" resultingDataRef:&certData1];

        SecCertificateRef certArray[1] = { cert1 };
        CFArrayRef certArrayRef = CFArrayCreate(NULL, (void *)certArray, 1, NULL);

        SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
        SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
        SecTrustResultType trustResult;

        SecTrustEvaluate(serverTrust, &trustResult);

        trusted = (trustResult == kSecTrustResultUnspecified);

        CFRelease(certArrayRef);
        CFRelease(policyRef);
        CFRelease(cert1);
        CFRelease(certData1);
    }
    if (trusted) {
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
    } else {
        [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
    }
}
}

And trustResult is always kSecTrustResultRecoverableTrustFailure.

The certificate itself is a little problematic. According to curl cert subject name on server does not match the url I'm connecting to. I've contacted that 3rd party company and they told me that I need to accept this url mismatch in my code. The problem is that I don't know how to do this on iOS. I can either bypass the certificate check completely (by simply assuming trusted=YES and calling useCredential) or fail completely. The first solution is obviously wrong from security point of view and prone to MITM attacks.

Here's the CURL output (I've used PEM version for the same cert here):

ukaszs-iMac:Preferences lukasz$  curl --verbose --cacert ~/Desktop/some3rdpartycacert.txt  https://dev-service.some3rdparty.com:50101/
* About to connect() to dev-service.some3rdparty.com port 50101 (#0)
*   Trying XXX.XXX.XXX.XXX...
* connected
* Connected to dev-service.some3rdparty.com (XXX.XXX.XXX.XXX) port 50101 (#0)
* successfully set certificate verify locations:
*   CAfile: /Users/lukasz/Desktop/some3rdpartycacert.txt
  CApath: none
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Request CERT (13):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using AES256-SHA
* Server certificate:
*    subject: C=CA; ST=Ontario; O=Some 3rdParty Corporation; CN=otherpage.some3rdparty.com; emailAddress=noc@some3rdparty.com
*    start date: 2013-10-30 16:52:14 GMT
*    expire date: 2013-10-30 16:52:14 GMT
* SSL: certificate subject name 'otherpage.some3rdparty.com' does not match target host name 'dev-service.some3rdparty.com'
* Closing connection #0
* SSLv3, TLS alert, Client hello (1):
curl: (51) SSL: certificate subject name 'otherpage.some3rdparty.com' does not match target host name 'dev-service.some3rdparty.com'

So, how to ignore this particular error on iOS?

Was it helpful?

Solution

You need to create a special policy using the actual hostname, then create and evaluate serverTrust from that. Roughly:

SecPolicyRef policyRef = SecPolicyCreateSSL(true, CFSTR("otherpage.some3rdparty.com"));

OSStatus    status;
SecTrustRef serverTrust;
status = SecTrustCreateWithCertificates(certificatesFromOriginalServerTrust, policyRef, & serverTrust);
// noErr == status?

status = SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
// noErr == status?

SecTrustResultType trustResult;
status = SecTrustEvaluate(serverTrust, &trustResult);
// noErr == status?

if(kSecTrustResultProceed == trustResult || kSecTrustResultUnspecified == trustResult) {
    // all good
}

p.s. you're not using the policy you created.

I just found a more complete explanation here.

OTHER TIPS

For this specific problem, you need to read "iOS 5 Programming Pushing the Limits: Developing Extraordinary Mobile Apps for Apple iPhone, iPad, and iPod Touch" Book. (by Rob Napier, Mugunth Kumar)

In the book, at bottom of page 219, it says: "If you're okay with the name you were passed, you reevaluate the certificate as a simple X.509 certificate rather than as part of an SSL handshake (that is, you evaluate it while ignoring the hostname)"

Then it have a special RNSecTrustEvaluateAsX509 function. So using that function actually you evaluate certificate with ignoring hostname part and setting that part to be "trusted". See http://bit.ly/1g1RzdF (go to page 219)

Code from book can be found here: https://github.com/iosptl/ios5ptl/blob/master/ch11/Connection/Connection/ConnectionViewController.m

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top