openssl / error 42 / certificate not yet valid

openssl / error 42 / certificate not yet valid

  • Written by
    Walter Doekes
  • Published on

In yesterday's post about not being able to connect to the SuperMicro iKVM IPMI, I wondered “why stunnel/openssl did not send error 45 (certificate_expired) for a not-yet-valid certificate.” Here's a closer examination.

Quick recap: yesterday, I got SSL alert/error 42 as response to a client certificate that was not yet valid. The server was living in 2015 and refused to accept a client certificate that would be valid first in 2016. That error 42 code could mean anything, so checking the period of validity of the certificate was not something that occurred to me immediately.

Here are the SSL alert codes in the 40s, taken from RFC 5246 7.2+:

code identifier description
40 handshake_failure The sender was unable to negotiate an acceptable set of security parameters with the available options.
41 no_certificate This alert was used in SSLv3 but not any version of TLS. (Don't use this.)
42 bad_certificate A certificate was corrupt, contained signatures that did not verify correctly, etc.
43 unsupported_certificate A certificate was of an unsupported type.
44 certificate_revoked A certificate was revoked by its signer.
45 certificate_expired A certificate has expired or is not currently valid.
46 certificate_unknown Some other (unspecified) issue arose in processing the certificate, rendering it unacceptable.
47 illegal_parameter A field in the handshake was out of range or inconsistent with other fields.
48 unknown_ca A valid certificate chain or partial chain was received, but the certificate was rejected because it was not signed by a trusted CA.
49 access_denied A valid certificate or PSK was received, but when access control was applied, the sender decided not to proceed with negotiation.

I would argue that a certificate that is not valid yet would be better off with error 45 than error 42. After all, the description from the RFC includes the phrase: “or is not currently valid."

It turns out it was OpenSSL that opted for the more generic 42.

Testing

Here's how you generate one too old and one too new certificate:

$ CURDATE=$(date -R) &&
    sudo date --set="$(date -R -d'-2 days')" &&
    openssl req -batch -x509 -nodes -newkey rsa:4096 -days 1 \
      -keyout not-valid-anymore.key -out not-valid-anymore.crt &&
    sudo date --set="$CURDATE"
$ CURDATE=$(date -R) &&
    sudo date --set="$(date -R -d'+2 days')" &&
    openssl req -batch -x509 -nodes -newkey rsa:4096 -days 1 \
      -keyout not-yet-valid.key -out not-yet-valid.crt &&
    sudo date --set="$CURDATE"

You can then use openssl s_server and openssl s_client to test the libssl behaviour:

$ openssl s_server -port 8443 \
    -cert server.crt -key server.key \
    -CAfile not-valid-anymore.crt \
    -verify_return_error -verify 1
$ openssl s_client -connect 127.0.0.1:8443 \
    -servername whatever -tls1_2 -showcerts -debug \
    -cert not-valid-anymore.crt -key not-valid-anymore.key
...
read from 0x55ca16174150 [0x55ca161797a8] (2 bytes => 2 (0x2))
0000 - 02 2d    .-
140480733312320:error:14094415:SSL routines:ssl3_read_bytes:
  sslv3 alert certificate expired:../ssl/record/rec_layer_s3.c:1543:
  SSL alert number 45

So, error 45 (certificate_expired) for the not valid anymore case.

And for the not yet valid case?

$ openssl s_server -port 8443 \
    -cert server.crt -key server.key \
    -CAfile not-yet-valid.crt \
    -verify_return_error -verify 1
$ openssl s_client -connect 127.0.0.1:8443 \
    -servername whatever -tls1_2 -showcerts -debug \
    -cert not-yet-valid.crt -key not-yet-valid.key
...
read from 0x55be814cd150 [0x55be814d27a8] (2 bytes => 2 (0x2))
0000 - 02 2a    .*
140374994916672:error:14094412:SSL routines:ssl3_read_bytes:
  sslv3 alert bad certificate:../ssl/record/rec_layer_s3.c:1543:
  SSL alert number 42

Ah, there's that pesky number 42 again.

Source code

Ultimately, this number is produced during the translation from internal OpenSSL X509 errors to SSL/TLS alerts. Previously in ssl_verify_alarm_type(), nowadays in ssl_x509err2alert().

Traced back to:

commit d02b48c63a58ea4367a0e905979f140b7d090f86
Author: Ralf S. Engelschall
Date:   Mon Dec 21 10:52:47 1998 +0000

    Import of old SSLeay release: SSLeay 0.8.1b

In ssl/s3_both.c there is:

int ssl_verify_alarm_type(type)
int type;
        {
        int al;

        switch(type)
                {
        case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
// ...
        case X509_V_ERR_CERT_NOT_YET_VALID:
// ...
                al=SSL3_AD_BAD_CERTIFICATE;
                break;
        case X509_V_ERR_CERT_HAS_EXPIRED:
                al=SSL3_AD_CERTIFICATE_EXPIRED;
                break;

And more recently, in ssl/statem/statem_lib.c translated by ssl_x509err2alert():

static const X509ERR2ALERT x509table[] = {
    {X509_V_ERR_APPLICATION_VERIFICATION, SSL_AD_HANDSHAKE_FAILURE},
    {X509_V_ERR_CA_KEY_TOO_SMALL, SSL_AD_BAD_CERTIFICATE},
// ...
    {X509_V_ERR_CERT_NOT_YET_VALID, SSL_AD_BAD_CERTIFICATE},
// ...
    {X509_V_ERR_CRL_HAS_EXPIRED, SSL_AD_CERTIFICATE_EXPIRED},
// ...
}

Apparently behaviour has been like this since 1998 and before. A.k.a. since forever. I guess we'll have to keep the following list in mind next time we encounter error 42:

X509_V_ERR_CA_KEY_TOO_SMALL
X509_V_ERR_CA_MD_TOO_WEAK
X509_V_ERR_CERT_NOT_YET_VALID
X509_V_ERR_CERT_REJECTED
X509_V_ERR_CERT_UNTRUSTED
X509_V_ERR_CRL_NOT_YET_VALID
X509_V_ERR_DANE_NO_MATCH
X509_V_ERR_EC_KEY_EXPLICIT_PARAMS
X509_V_ERR_EE_KEY_TOO_SMALL
X509_V_ERR_EMAIL_MISMATCH
X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD
X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD
X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD
X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD
X509_V_ERR_HOSTNAME_MISMATCH
X509_V_ERR_IP_ADDRESS_MISMATCH
X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY
X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE
X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE

That is, assuming you're talking to libssl (OpenSSL). But that's generally the case.

P.S. The GPLv2 OpenSSL replacement WolfSSL appears to do the right thing in DoCertFatalAlert(), returning certificate_expired for both ASN_AFTER_DATE_E and ASN_BEFORE_DATE_E. Yay!


Back to overview Newer post: kioxia nvme / num_err_log_entries 0xc004 / smartctl Older post: supermicro / ikvm / sslv3 alert bad certificate