Certificate exceeds maximum temporal validity period

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.

So I'm reminded I could inspect the CFError and set individual policies, for example we ignore Hostname policies when using custom certificates:

let sslWithoutHostnamePolicy = SecPolicyCreateSSL(true, nil)
SecTrustSetPolicies(secTrust, [sslWithoutHostnamePolicy] as CFArray)
SecTrustSetAnchorCertificates(secTrust, customCerts as CFArray)
SecTrustSetAnchorCertificatesOnly(secTrust, false)

So if there's a policy we could use for validity to disable that check, I'm definitely OK with that as a workaround.

AFAIK the limits described in About upcoming limits on trusted certificates still apply. You can see the code that applies this in Darwin [1].

I take it that you can reproduce this issue reliably. If so, do that while monitoring the system log for leaf validity period. What do you see?

If you’re unfamiliar with the system log, see Your Friend the System Log.

Share and Enjoy

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

[1] With the usual caveat that the Darwin open source is not always 100% exactly the same as the code running in any given build of macOS, iOS, or whatever.

Thanks as always Quinn! In running Console I don't see any specific messages about leaf validity period, only these two messages.

I have opened a support request already that has the domain we're attaching to when experiencing the issue if that's helpful. As far as we can tell these certificates meet all of the requirements from that article - for example, it's only trusted for 394 days, not expired, etc. These aren't self-signed, but root-trusted certificates. Happy to dig in more if there's something else I can provide!

Here's the full code block where we're validating the certificates. Effectively we're given the certificate chain and we attempt initially to validate without it against the standard installed certs, and then add the custom ones and try again (ignoring hostname for custom certs). It's in the second verification that it's failing solely because of the temporal validity issue:

sec_protocol_options_set_verify_block(securityOptions, { (_, trust, completionHandler) in
    self.logger.debug("Entering Verify Block")
    
    let secTrust = sec_trust_copy_ref(trust).takeRetainedValue()
    isValidCertificate = SecTrustEvaluateWithError(secTrust, &error)
    
    if !isValidCertificate {
        self.logger.debug("Server not trusted with default certs, seeing if we have custom ones")
        var customCerts: [SecCertificate] = []
        
        let trustCerts = SettingsStore.global.serverCertificateTruststore
        self.logger.debug("Truststore contains \(trustCerts.count) cert(s)")
        if !trustCerts.isEmpty {
            self.logger.debug("Trusting Trust Store Certs")
            trustCerts.forEach { cert in
                if let convertedCert = SecCertificateCreateWithData(nil, cert as CFData) {
                    customCerts.append(convertedCert)
                }
            }
        }
        
        if !customCerts.isEmpty {
            // Disable hostname validation if using custom certs
            let sslWithoutHostnamePolicy = SecPolicyCreateSSL(true, nil)
            
            SecTrustSetPolicies(secTrust, [sslWithoutHostnamePolicy] as CFArray)
            SecTrustSetAnchorCertificates(secTrust, customCerts as CFArray)
        }
        
        // Make sure we still trust our normal root certificates
        SecTrustSetAnchorCertificatesOnly(secTrust, false)
        
        // Try again
        isValidCertificate = SecTrustEvaluateWithError(secTrust, &error)
        
        if let error {
            self.logger.error("SecTrustEvaluate failed with error: \(error)")
        }
    }

    completionHandler(isValidCertificate)
}, .global())
Certificate exceeds maximum temporal validity period
 
 
Q