SecTrustResultType for manually installed certificate?

Back in March iOS 10.3 changed the way manually installed certificate profiles are treated (cf. https://support.apple.com/en-us/HT204477): certificates installed in such a way are no longer trusted by default; instead trust has to be explicitly granted using a switch under Settings > General > About > Certificate Trust Settings.


What apparently also changed is how those certificates are treated by the Security.framework. Prior to iOS 10.3 (where the switch to "Enable full trust for root certificates" was on by default) calling SecTrustEvaluate for a chain containing such a certificate returned kSecTrustResultUnspecified if trust was granted and kSecTrustResultRecoverableTrustFailure if trust was denied. However, in iOS 11 at least - and I suppose ever since iOS 10.3 - this appears to have changed: SecTrustEvaluate still returns kSecTrustResultRecoverableTrustFailure if the switch was off, but if it was on the result is kSecTrustResultProceed. I cannot find any mention of this change anywhere and am wondering whether that was intentional - which would arguably make sense since the documentation for SecTrustEvaluate explicitly states that if kSecTrustResultProceed is being returned "the user explicitly chose to trust a certificate in the chain (usually by clicking a button in a certificate trust panel). Your app should trust the chain."

Replies

Woo hoo,

kSecTrustResultProceed
finally makes an appearance on iOS!

The semantic difference between

kSecTrustResultUnspecified
and
kSecTrustResultProceed
is that the former indicates that the certificate is trusted via some default path and the latter indicates that the user has explicitly trusted the certificate. On macOS both results have always been possible because you can modify certificate trust settings in Keychain Access. On iOS it was historically the case that only
kSecTrustResultUnspecified
was possible, because iOS provided no way for the user to modify certificate trust settings. That changed in iOS 10.3 with the introduction of Settings > General > About > Certificate Trust Settings.

While I was aware of the iOS 10.3 change in general (I just finished updating QA1948 to cover that specific point) I didn’t fully grok this aspect of it. However, I don’t think this is a bug: the API has always allowed for both trust results, and now that potential has been realised on iOS as well as macOS.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn, I know; and I agree: it probably is no bug. I just wish this subtle change of behaviour - as consistent with the API documentation as it may be - had been mentioned somewhere.

Btw, Technical Note TN2232 HTTPS Server Trust Evaluation still states the following (emphasis mine):

"This code allows the connection if the trust result is either

kSecTrustResultProceed
or
kSecTrustResultUnspecified
. In most cases the result is actually the latter, indicating that the user hasn't specified a trust preference, so your app should do the default thing (that is, connect). The distinction between these two values is irrelevant on iOS [...]"; this should be updated also.

Btw, Technical Note TN2232 HTTPS Server Trust Evaluation still states the following …

Indeed it does. Please file a bug against that doc.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Done: rdar://35611859

On the Simulator (iOS11.2) it ALWAYS returns kSecTrustResultRecoverableTrustFailure (even if the switch is ON).

This is very unfortunate because it's still working on iOS10.3 Simulator (where I am getting kSecTrustResultUnspecified).

On the Simulator (iOS11.2) it ALWAYS returns

kSecTrustResultRecoverableTrustFailure
(even if the switch is ON).

That’s not my experience. Just to be sure, I tried this again today:

  1. I erased my 11.2 simulator (Hardware > Erase All Content and Settings), just to make sure I’m starting afresh.

  2. I installed the root certificate that issued my server’s certificate, as per QA1948 HTTPS and Test Servers. That includes enabling it in Settings > General > About > Certificate Trust Settings.

  3. I ran a small test app that fetches the root page of my server; the relevant code is pasted in at the end of this response.

  4. I ran that code and it loaded the resource just fine.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
let url = URL(string: "https://***")!
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
URLSession.shared.dataTask(with: request) { (data, response, error) in
    if let error = error as NSError? {
        NSLog("task transport error %@ / %d", error.domain, error.code)
        return
    }
    let response = response as! HTTPURLResponse
    let data = data!
    NSLog("task finished with status %d, bytes %d", response.statusCode, data.count)
}.resume()