Using a Custom Certificate Authority fails URLAuthenticationChallenge

We have certificate pinning for our app used by millions of customers, and we recently moved from using an Amazon CA to our company custom CA certificate. We followed the following these steps:

  1. Include a copy of the certificate authority's root certificate in the app
  2. Once you have the trust object, create a certificate object from the certificate data (SecCertificateCreateWithData) and then set that certificate as a trusted anchor for the trust object (SecTrustSetAnchorCertificates)
  3. SecTrustSetAnchorCertificates sets a flag that prevents the trust object from trusting any other anchors, if you also want to trust the default system anchors, call SecTrustSetAnchorCertificatesOnly to clear that flag
  4. Evaluate the trust object as per usual

SecTrustEvaluate returns .unspecified and we call completionHandler(.useCredential, credential) on the completion handler for urlSession:didReceive:completionHandler.

However, the error we get is:

Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=(
  "<cert(0x12e0e8800) s: mydomain.com i: mydomain.com>"
), NSErrorClientCertificateStateKey=0, NSErrorFailingURLKey=https://mydomain.com/xxxxx, NSErrorFailingURLStringKey=https://mydomain.com/xxxxx, NSUnderlyingError=0x283885110 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x2804e15f0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
  "<cert(0x12e0e8800) s: mydomain.com/xcom i: mydomain.com>"
)}}, _NSURLErrorRelatedURLSessionTaskErrorKey=(
  "LocalDataTask <56BD63E5-3D28-4EBF-83DC-C11C744DF4E4>.<1>"
), _kCFStreamErrorCodeKey=-9802, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <56BD63E5-3D28-4EBF-83DC-C11C744DF4E4>.<1>, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x2804e15f0>, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made.}

The only solution so far was to install the certificate on the simulator then it worked. However, we are not in a position to get customers to install the certificate - whether via MDM or otherwise. Is there a seamless solution for using custom certificates as I don't think Apple recommends (or even allows nowadays) to add exception domains in the plist?

I don't think Apple recommends … to add exception domains in the plist?

That’s correct. IMO you should not use a custom CA here. Rather, use a standard CA — one that’s trusted by default — and then, if you feel the need, add certificate pinning on top of that [1]. Your current approach requires you to disable ATS, at least in part, and we strongly recommend against doing that. The whole goal of ATS is for the system to enforce a minimum level of security regardless of whatever bugs you might have in your code, and disabling ATS undermines that goal.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Either by handling the server trust evaluation authentication challenge or via the shiny new NSPinnedDomains property.

Using a Custom Certificate Authority fails URLAuthenticationChallenge
 
 
Q