Certificate trust settings to check certificate have full access or not.

is there any api that shows that Certificate 1 have full root access and Certificate 2 don't have full root access like in below image.

I have used the below code to check whether my RootCA is installed on user phone or not and I'm getting result. but I wanted to go one level above to check whether that RootCA have full access or not or that user have given my RootCA full access or not.

let bundle = Bundle(for: type(of: self))
        let rootCAName = "RootCA"
        guard let filePath = bundle.path(forResource: rootCAName, ofType: "der"),
              let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)),
              let certificate = SecCertificateCreateWithData(nil, data as CFData)
        else {
            return
        }

        // Check
        var secTrust: SecTrust?
        if SecTrustCreateWithCertificates(certificate, SecPolicyCreateBasicX509(), &secTrust) == errSecSuccess, let trust = secTrust {
            SecTrustEvaluateAsyncWithError(trust, .global()) { trust, result, error in
                print( "Cert => \(result ? "installed" : "not installed")")
            }
        }
Answered by DTS Engineer in 751756022

OK, let’s make this really concrete. Consider the following test code:

let leaf = Bundle.main.certificateNamed("sully.local")!
let policy = SecPolicyCreateSSL(true, "sully.local" as NSString)
let trust = try secCall { SecTrustCreateWithCertificates([leaf] as NSArray, policy, $0) }
var result: SecTrustResultType = .invalid
try secCall { SecTrustEvaluate(trust, &result) }
let resultDict = try secCall { SecTrustCopyResult(trust) }
switch result {
case .proceed:
    print(".proceed")
case .unspecified:
    print(".unspecified")
case .recoverableTrustFailure:
    print(".recoverableTrustFailure")
default:
    print("other (\(result.rawValue)")
}
print(resultDict)

Note This uses the helpers from Calling Security Framework from Swift and an extension on Bundle that loads a named certificate from the bundle.

Also attached below are two test credentials:

  • MouseCA.pem — This is a custom root.

  • sully.local.pem — This is a certificate issued by that root.

IMPORTANT These are in PEM format to make them easier to post here. You’ll need to convert them to binary (DER) format.

I built the above code into a test project and ran it on a test device here in my office (iOS 16.3.1, gotta update that!). Here’s what I saw:

A. MouseCA root not installed

.recoverableTrustFailure
{
    TrustEvaluationDate = "2023-04-24 09:29:30 +0000";
    TrustResultDetails = (
        {
            MissingIntermediate = 0;
        }
    );
    TrustResultValue = 5;
}

B. MouseCA root installed but not enabled

.recoverableTrustFailure
{
    TrustEvaluationDate = "2023-04-24 09:30:22 +0000";
    TrustResultDetails = (
        {
        },
        {
            AnchorTrusted = 0;
        }
    );
    TrustResultValue = 5;
}

C. MouseCA root installed and enabled

.proceed
{
    TrustEvaluationDate = "2023-04-24 09:30:53 +0000";
    TrustResultDetails = (
        {
        },
        {
        }
    );
    TrustResultValue = 1;
}

Case C is easy to detect. The challenge is distinguishing between cases A and B. Here the SecTrustResultType value is the same, .recoverableTrustFailure, but the dictionary returned by SecTrustCopyResult is different.

Share and Enjoy

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

There isn’t an API to explicitly interrogate the trust store on iOS. All you can do is observe the effects of it. This isn’t great because you’re relying on implementation details. However, I’m not too worried about that because the ability to trust an issued certificate is kinda the whole point of an installed anchor.

Anyway, the way to test whether an anchor is installed and trusted is to evaluate trust on a certificate issued by that anchor. You’ll see three cases:

  • If the anchor isn’t installed, the trust result will be .recoverableTrustFailure and SecTrustCopyResult will return a dictionary that includes a MissingIntermediate value.

  • If the anchor is installed and not trusted, the trust result will be .recoverableTrustFailure but SecTrustCopyResult will return a dictionary that includes an AnchorTrusted value set to 0.

  • If the anchor is installed and trusted, the trust result will be .proceed.

The main concern here is distinguishing between the first two cases. The contents of the SecTrustCopyResult dictionary are not well documented and there’s no symbolic constants to use. If you can avoid trying to distinguish those two cases, that’d be grand. And it seems that’s possible with the code snippet you included in your original post.

Share and Enjoy

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

Hi, eskimo as per your instruction above. I addd following code snippet after print( "Cert => \(result ? "installed" : "not installed")") in the above snippet.

var secResult: SecTrustResultType = .unspecified
let result = SecTrustGetTrustResult(trust, &secResult)

and I'm getting .proceed on the case when the certificate is not trusted and when its trusted. but not getting .recoverableTrustFailure. and SecTrustCopyResult is giving me this on both cases.

Optional({
   TrustEvaluationDate = "2023-03-28 17:23:26 +0000";
   TrustResultDetails =     (
               {
       }
   );
   TrustResultValue = 1;
})

Thanks.

Accepted Answer

OK, let’s make this really concrete. Consider the following test code:

let leaf = Bundle.main.certificateNamed("sully.local")!
let policy = SecPolicyCreateSSL(true, "sully.local" as NSString)
let trust = try secCall { SecTrustCreateWithCertificates([leaf] as NSArray, policy, $0) }
var result: SecTrustResultType = .invalid
try secCall { SecTrustEvaluate(trust, &result) }
let resultDict = try secCall { SecTrustCopyResult(trust) }
switch result {
case .proceed:
    print(".proceed")
case .unspecified:
    print(".unspecified")
case .recoverableTrustFailure:
    print(".recoverableTrustFailure")
default:
    print("other (\(result.rawValue)")
}
print(resultDict)

Note This uses the helpers from Calling Security Framework from Swift and an extension on Bundle that loads a named certificate from the bundle.

Also attached below are two test credentials:

  • MouseCA.pem — This is a custom root.

  • sully.local.pem — This is a certificate issued by that root.

IMPORTANT These are in PEM format to make them easier to post here. You’ll need to convert them to binary (DER) format.

I built the above code into a test project and ran it on a test device here in my office (iOS 16.3.1, gotta update that!). Here’s what I saw:

A. MouseCA root not installed

.recoverableTrustFailure
{
    TrustEvaluationDate = "2023-04-24 09:29:30 +0000";
    TrustResultDetails = (
        {
            MissingIntermediate = 0;
        }
    );
    TrustResultValue = 5;
}

B. MouseCA root installed but not enabled

.recoverableTrustFailure
{
    TrustEvaluationDate = "2023-04-24 09:30:22 +0000";
    TrustResultDetails = (
        {
        },
        {
            AnchorTrusted = 0;
        }
    );
    TrustResultValue = 5;
}

C. MouseCA root installed and enabled

.proceed
{
    TrustEvaluationDate = "2023-04-24 09:30:53 +0000";
    TrustResultDetails = (
        {
        },
        {
        }
    );
    TrustResultValue = 1;
}

Case C is easy to detect. The challenge is distinguishing between cases A and B. Here the SecTrustResultType value is the same, .recoverableTrustFailure, but the dictionary returned by SecTrustCopyResult is different.

Share and Enjoy

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

Thanks a lot eskimo. For your detailed explanation.

Certificate trust settings to check certificate have full access or not.
 
 
Q