I created a self signed CA and use it to generate/sign a client cert using openssl. Then I use the self signed client cert to do TLS client authentication with my server (which also uses the self signed CA). The issue I have is when I validate the self signed CA, by calling SecTrustEvaluateAsyncWithError, it always complains this error “'DigiCert Global Root G3' certificate is not trusted". However that CA (DigiCert Global Root G3) is not my self signed CA (my CA is 'MQTTSampleCA' and I attached a dump of the my CA cert in the PR in the end of this post), so I'm confused why the API keeps complaining that CA. After some researching, I see that is a well known CA so I download its cert from https://www.digicert.com/kb/digicert-root-certificates.htm, install and trust it on my iOS device, but that doesn't help and I still get the same error. I provide all the repro steps in this PR: https://github.com/liumiaojq/EmCuTeeTee/pull/1, including how I generate the certs and the source codes of a test app that I used to do cert validation. I appreciate if anyone can share insights how to resolve this error.
I presume you’re implementing mutual TLS, as defined in TLS for App Developers. If so, there are two trust evaluations in play:
-
The server evaluating trust on the client’s certificate.
-
The client evaluating trust on the server’s certificate.
I presume that you’re primarily concerned with the second one. Is that right?
IMPORTANT If you’re doing server trust evaluation on your own client certificate then you should just stop doing that. That’s a job for the server.
If the above is correct then you can completely ignore the client side of this and instead focus just on server trust evaluation. I followed your link [1] and it’s quite hard to understand. You have a bunch of different concepts in play, including SwiftNIO, NIO Transport Services, MQTT NIO, and so on. And you don’t actually show the code that calls SecTrustEvaluateAsyncWithError
.
When dealing with problems like this I usually try to build a tiny test program that just does the server trust evaluation. Typically that involves:
-
Connect to the server using something general, like the
openssl
command-line tool. -
Use that to extract the certificate chain presented by the server.
-
Use that certificate chain to create a trust object.
-
Evaluate trust on that.
That way I have a focused test program to explore just the trust evaluation issue. In the next section I’m gonna walk you through that using CAcert as an example.
If you use this technique to boil your issue down into a more focus test, I’d be happy to take an in-depth look at that.
First, connect to the server and dump its certificate chain:
% openssl s_client -connect cacert.org:443 -showcerts
…
-----BEGIN CERTIFICATE-----
MIIHbDCCBVSgAwIBAgIDAwTHMA0GCSqGSIb3DQEBDQUAMFQxFDASBgNVBAoTC0NB
…
tXyDc/ix/NnZItjMOA6Q/jWwRRiL5hPnqsOLLWnllwM=
-----END CERTIFICATE-----
…
-----BEGIN CERTIFICATE-----
MIIGPTCCBCWgAwIBAgIDFOIoMA0GCSqGSIb3DQEBDQUAMHkxEDAOBgNVBAoTB1Jv
…
cSvOK6eB1kdGKLA8ymXxZp8=
-----END CERTIFICATE-----
…
-----BEGIN CERTIFICATE-----
MIIG7jCCBNagAwIBAgIBDzANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQKEwdSb290
…
Gwc=
-----END CERTIFICATE-----
…
^C
Save each certificate to a PEM file, and then convert those to DER:
% openssl x509 -in leaf.pem -out leaf.cer -outform der
% openssl x509 -in intermediate.pem -out intermediate.cer -outform der
% openssl x509 -in root.pem -out root.cer -outform der
Now create a small test project and add those certificates to that project. Then add a test routine like this:
func testTrustEvaluation() throws {
let leaf = try secCall { Bundle.main.certificateNamed("leaf") }
let intermediate = try secCall { Bundle.main.certificateNamed("intermediate") }
let root = try secCall { Bundle.main.certificateNamed("root") }
let policy = SecPolicyCreateSSL(true, "cacert.org" as NSString)
let trust = try secCall { SecTrustCreateWithCertificates([leaf, intermediate, root] as NSArray, policy, $0) }
// try secCall { SecTrustSetAnchorCertificates(trust, [root] as NSArray) }
// try secCall { SecTrustSetAnchorCertificatesOnly(trust, false) }
try secCall { SecTrustEvaluateWithError(trust, $0) }
}
Note This uses the secCall(…)
helpers from here and the bundle helper that I’ve included at the end of this post.
This fails because CAcert’s root is not trusted by default. The SecTrustEvaluateWithError
call throws [2] this error:
Error Domain=NSOSStatusErrorDomain Code=-67843 "“CA Cert Signing
Authority” certificate is not trusted"
UserInfo={NSLocalizedDescription=“CA Cert Signing Authority”
certificate is not trusted, NSUnderlyingError=0x600000c9a7f0
{Error Domain=NSOSStatusErrorDomain Code=-67843 "Certificate 2
“CA Cert Signing Authority” has errors: Root is not trusted;"
UserInfo={NSLocalizedDescription=Certificate 2 “CA Cert Signing
Authority” has errors: Root is not trusted;}}}
But if I uncomment the trust anchor override lines, trust evaluation works.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] https://github.com/liumiaojq/EmCuTeeTee/pull/1,
[2] Testing in the iOS 18.3 simulator.
extension Bundle {
func certificateNamed(_ name: String) -> SecCertificate? {
guard
let certURL = self.url(forResource: name, withExtension: "cer"),
let certData = try? Data(contentsOf: certURL),
let cert = SecCertificateCreateWithData(nil, certData as NSData)
else {
return nil
}
return cert
}
}