SecTrustEvaluate fails with kSecTrustResultRecoverableTrustFailure for a self-signed CA with an inconsistent subject name

Here's my pretty standard NSURLConnection call to authenticate using a 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 always kSecTrustResultRecoverableTrustFailure .

The certificate itself is a bit problematic. According to curl cert, the name of the subject on the server does not match the URL to which I am connecting. I contacted this third-party company and they told me that I need to accept this URL mismatch in my code. The problem is that I do not know how to do this on iOS. I can either bypass certificate verification completely (simply assuming trusted=YES and calling useCredential ), or completely fail. The first solution is obviously incorrect from a security point of view and is prone to MITM attacks.

Here's the CURL output (I used the PEM version for the same certificate 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 in iOS?

+6
source share
2 answers

You need to create a custom policy using the actual host name, and then create and evaluate serverTrust . Roughly speaking:

 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 } 

ps you do not use the policy you created.

I found a more complete explanation here .

+9
source

For this specific problem, you need to read β€œiOS 5 Programming Pressing Constraints: Developing Unusual Mobile Applications for the Apple iPhone, iPad, and iPod Touch.” (Rob Napier, Mugunt Kumar)

The book, at the bottom of page 219, says: "If you agree with the name you gave, you re-evaluate the certificate as a simple X.509 certificate, and not as part of SSL confirmation (that is, you evaluate it by ignoring the host name)"

Then it has the special function RNSecTrustEvaluateAsX509 . Thus, using this function, you actually evaluate the certificate ignoring part of the host name and establish that the part should be "trusted". See http://bit.ly/1g1RzdF (go to page 219)

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

0
source

All Articles