I have code where we're evaluating SSL certificates in sec_protocol_options_set_verify_block.
We have the following code:
let secTrust = sec_trust_copy_ref(trust).takeRetainedValue()
isValidCertificate = SecTrustEvaluateWithError(secTrust, &error)
I'm getting the error that the maximum temporal validity period has been exceeded:
Error Domain=NSOSStatusErrorDomain Code=-67901 "“server.com” certificate is not standards compliant" UserInfo={NSLocalizedDescription=“server.com” certificate is not standards compliant, NSUnderlyingError=0x300ddd350 {Error Domain=NSOSStatusErrorDomain Code=-67901 "Certificate 0 “server.com” has errors: Certificate exceeds maximum temporal validity period;" UserInfo={NSLocalizedDescription=Certificate 0 “server.com” has errors: Certificate exceeds maximum temporal validity period;}}}
When I inspect the certificate, it's valid for 394 days (4/16/2024 through 5/15/2025) and other than being a wildcard certificate, should be fully trusted. I can't find any information about this specific error. Is Apple requiring SSL certs to be less than 398 days now?
Which brings me to the second part - we're OK using this to workaround it
var trustFailureExceptions: CFData? = SecTrustCopyExceptions(secTrust)
SecTrustSetExceptions(secTrust, trustFailureExceptions)
But I haven't found anyway to be able to inspect trustFailureExceptions
to ensure it only is this specific error. I'm concerned that otherwise this is going to open up validity exceptions for any certificate problem, which is definitely not what I want to do.
For those reading along at home, FT-cfoy shared their certificates with me via a code-level support request.
There are a number of issues with these certificates )-:
Your leaf certificate has a Basic Constraints extension where it doesn’t need one:
% dumpasn1 -p cert1.cer
SEQUENCE {
SEQUENCE {
…
[3] {
SEQUENCE {
SEQUENCE {
OBJECT IDENTIFIER basicConstraints (2 5 29 19)
BOOLEAN TRUE
OCTET STRING, encapsulates {
SEQUENCE {}
}
}
…
}
}
}
…
}
That’s not a showstopper, but it’s certainly weird.
Your leaf certificate Extended Key Usage supports both TLS client and server:
% dumpasn1 -p cert1.cer
SEQUENCE {
SEQUENCE {
…
[3] {
SEQUENCE {
…
SEQUENCE {
OBJECT IDENTIFIER extKeyUsage (2 5 29 37)
BOOLEAN TRUE
OCTET STRING, encapsulates {
SEQUENCE {
OBJECT IDENTIFIER clientAuth (1 3 6 1 5 5 7 3 2)
OBJECT IDENTIFIER serverAuth (1 3 6 1 5 5 7 3 1)
}
}
}
}
}
}
…
}
Again, not a showstopper, but almost never what you want.
Your certificate serial numbers don’t follow current recommendations:
% dumpasn1 -p cert1.cer
SEQUENCE {
SEQUENCE {
…
INTEGER 25864
…
}
…
}
These days serial numbers are meant to be large random values.
Again, not a showstopper.
Your leaf certificate is missing its Subject Alternative Name extension. This is a showstopper. Historically it was OK to put the server DNS name in the Common Name field of the Subject, but that’s not been recommended for decades [1] and Apple’s OSes now require that you list the server DNS name in a Subject Alternative Name extension.
This explains the SSL hostname does not match name(s) in certificate
error you’re seeing.
And then there’s those Certificate exceeds maximum temporal validity period
errors. And it’s not wrong:
% dumpasn1 -p cert1.cer
SEQUENCE {
SEQUENCE {
…
SEQUENCE {
UTCTime 01/02/2023 21:24:11 GMT
UTCTime 30/01/2030 21:24:11 GMT
}
…
}
…
}
That’s roughly 7 years. Yowsers!
Earlier I pointed you at About upcoming limits on trusted certificates, which lists a 398 day limit. However, there’s also Requirements for trusted certificates in iOS 13 and macOS 10.15, which lists a 825 day limit. The first limit isn’t applied to custom CAs but the second limit it.
Note I link to both of these in Networking Resources, which is a good page to bookmark (-:
At this point I think you need to have a chat with the folks running your CA. They need to invest some time in updating their policies. You might want to introduce them to RFC 5280 and its more recent updates.
Note that iOS 13, where that 825 day limit was introduced, is now 5 years old.
As to a workaround that you can apply immediately, you wrote:
I don't like it mainly because I don't know what other exceptions might be caught up in this
I don’t like it either, for the same reasons.
What I suggest you do is:
-
Run a basic X.509 trust evaluation on the certificate just, to confirm that things are vaguely OK.
-
Pin that to your CA’s root, because you don’t want certificates issued by other CA’s taking advantage of your security exception.
-
Confirm the leaf name matches your expectations.
Here’s how I tested this with the certificates you sent me:
let leaf: SecCertificate = Bundle.main.certificateNamed("cert1")!
let intermediate: SecCertificate = Bundle.main.certificateNamed("cert2")!
let root: SecCertificate = Bundle.main.certificateNamed("cert3")!
let leafName = try secCall { SecCertificateCopyCommonName(leaf, $0) } as String
let policy = SecPolicyCreateBasicX509()
let trust = try secCall { SecTrustCreateWithCertificates([leaf, intermediate] as NSArray, policy, $0) }
try secCall { SecTrustSetAnchorCertificates(trust, [root] as NSArray) }
// The following is redundant given the documentation semantics of
// `SecTrustSetAnchorCertificates`, but we’re making a point here.
try secCall { SecTrustSetAnchorCertificatesOnly(trust, true) }
SecTrustEvaluateAsyncWithError(trust, .main) { trust, isTrusted, errorCF in
guard isTrusted else {
print("NG, basic X.509")
return
}
guard leafName == "… host name redacted …" else {
print("NG, leaf name")
return
}
print("OK")
}
This is using the secCall(…)
helpers from here and the bundle extension shown below.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] See RFC 2818.
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
}
}