Performing manual server trust authentication

I recently re-read Performing manual server trust authentication and noticed that it does not mention having to call SecTrustEvaluate (or its replacements) in client code (anymore). Is that implicitly taken care of by ATS?

Is that implicitly taken care of by ATS?

ATS applies a minimum level of server trust evaluation. If you’re using an API that’s constrained by ATS, like URLSession, then it’s reasonable to rely on it.

However, you still have to be careful. If you rely on ATS and then, at some point in the future, you or your successor disables ATS for some reason, you’ll end up with no security!

Beyond that, keep in mind that there are two reasons to override server trust evaluation:

  • To tighten security

  • To loosen security

If your goal is to tighten security then it’s easy to get default trust evaluation for ‘free’:

  1. Leave ATS enabled.

  2. Intercept the server trust authentication challenge (NSURLAuthenticationMethodServerTrust).

  3. Apply your extra checks.

  4. Complete the challenge with .performDefaultHandling.

That gets you default trust evaluation and the ATS safety belt.

Share and Enjoy

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

Thanks. Does that mean the example code

if checkValidity(of: serverTrust) {
    let credential = URLCredential(trust: serverTrust)
    completionHandler(.useCredential, credential)
}

does not perform trust evaluation?

Does that mean [this] example code … does not perform trust evaluation?

That code disables the default server trust evaluation. If you put it in an app that has ATS disabled, you’d have no protection at all.

Share and Enjoy

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

I see. However, doing this:

  1. Leave ATS enabled.
  2. Intercept the server trust authentication challenge (NSURLAuthenticationMethodServerTrust).
  3. Apply your extra checks.
  4. Complete the challenge with .performDefaultHandling.

causes the trust evaluation to be performed after the extra checks, correct? What if I wanted to have the system check be done first - then I would to resort to calling SecTrustEvaluate myself?

Can you explain more about the big picture here? What you hoping to achieve with trust evaluation?

Share and Enjoy

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

Sure: I am required to implement HPKP (aka. Public Key Pinning Extension for HTTP) as defined in RFC 7469 which is not supported out of the box (cf. FB5986841). Additionally I have to secure the first-ever connection as well by supporting preloaded pins, but I cannot use Identity Pinning (cf. thread and FB12333846). So I have to build everything from scratch.

Thanks for filing those bugs.

Given your context, I think the answer here is pretty straightforward:

  1. Leave ATS enabled.

  2. Do you own checks.

  3. If one fails, complete the request with .cancelAuthenticationChallenge.

  4. If not, complete it with .performDefaultHandling.

Share and Enjoy

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

Thank you. It was not obvious (to me) that continuing with completionHandler(.useCredential, URLCredential(trust: serverTrust)) does not benefit from the OS performing trust evaluation.

Sorry, but I have another question: To do my own checks I need to call e.g. SecTrustCopyCertificateChain(_:) on the trust object. Is that API guaranteed to return the correct certificate chain if the trust object has not been evaluated?

Technically, no. SecTrustCopyCertificateChain is a effectively a wrapper around SecTrustGetCertificateAtIndex, and its docs explicitly state that you have to evaluate trust. However, in practice I’ve found that calling the trust evaluation is handled lazily, so calling this triggers a trust evaluation.

Still, it’s probably best not to rely on that and force the trust evaluation yourself. Trust evaluations can be expensive, but:

  • The results are cached — both in the trust object itself and, for things like OCSP, across the system as a whole — and thus it’s just a question of when this cost must be paid.

  • For HTTP, there just aren’t that many connections [1].

Share and Enjoy

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

[1] Assuming you’re using HTTP/2. If you’re still using HTTP/1.1 then… well… please stop (-: Seriously though, if you’re still using HTTP/1.1 then moving to HTTP/2 is likely to yield much more performance benefits then anything you do here.

I did just that: Calling SecTrustEvaluateAsyncWithError (on a background queue) myself before calling SecTrustCopyCertificateChain (on the main queue). The latter, however, lead to Xcode printing "This method should not be called on the main thread as it may lead to UI unresponsiveness." which apparently resulted from SecTrustCopyCertificateChain calling SecTrustEvaluateIfNecessary even though I had already done that. So I ended up calling SecTrustCopyCertificateChain on the background queue as well.

The latter, however, lead to Xcode printing …

Yeah, that’s annoying. Unfortunately this mechanism doesn’t have the smarts to distinguish between the ‘already evaluated’ and ‘will actually evaluate’ cases. I encourage you to file a bug requesting that it be so educated. Please post your bug number, just for the record.

Share and Enjoy

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

This seems to be the same problem addressed here, am I right?

It’s certainly related.

Share and Enjoy

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

Performing manual server trust authentication
 
 
Q