I was trying to import some X509v3 certificates that were created with pyOpenSSL to a MikroTik router (RouterOS 6.1) but they were always being imported with an invalid validity period (not before 1970 and not after 1970).
Eventually I found out that this is because pyOpenSSL stores the validity field in an invalid format. Here’s the story:
cert1.pem is the pyOpenSSL certificate and cert2.pem is a certificate created with openssl. Both have mostly the same information. Decoding the certificates with openssl shows that cert1.pem actually has an older notAfter date so it’s not an issue of overflow.
$ openssl x509 -noout -startdate -enddate < cert1.pem notBefore=Jul 20 18:35:58 2013 GMT notAfter=Jan 1 00:00:00 2032 GMT $ openssl x509 -noout -startdate -enddate < cert2.pem notBefore=Aug 31 21:44:54 2012 GMT notAfter=Aug 26 21:44:54 2032 GMT
I examined the certificates in python by decoding their DER structures and looking for the validity field (copy-paste the following in a python shell).
import Crypto.Util.asn1 as asn1 import OpenSSL.crypto as c fn1="cert1.pem" fn2="cert2.pem" st1=open(fn1, 'r').read() st2=open(fn2, 'r').read() cert1=c.load_certificate(c.FILETYPE_PEM, st1) cert2=c.load_certificate(c.FILETYPE_PEM, st2) dump1=c.dump_certificate(c.FILETYPE_ASN1, cert1) dump2=c.dump_certificate(c.FILETYPE_ASN1, cert2) der1=asn1.DerSequence() der2=asn1.DerSequence() der1.decode(dump1) der2.decode(dump2) dcert1=der1[0] dcert2=der2[0] t1=asn1.DerSequence() t2=asn1.DerSequence() t1.decode(dcert1) t2.decode(dcert2) tt1=asn1.DerSequence() tt2=asn1.DerSequence() tt1.decode(t1[4]) tt2.decode(t2[4])
at this point tt1 and tt2 are sequences of the validity field (notBefore, notafter) for the two certificates . Here’s what they contain:
>>> tt1[0] 'x18x0f20130720183558Z' >>> tt1[1] 'x18x0f20320101000000Z' >>> tt2[0] 'x17r120831214454Z' >>> tt2[1] 'x17r320826214454Z'
SoaB! They differ!
Reading the X509 spec [1], section 4.1.2.5 indicates that there are two possible formats for the validity period: both notBefore and notAfter may be encoded as UTCTime or GeneralizedTime.
- UTCTime is defined as YYMMDDHHMMSSZ
- GeneralizedTime is defined as YYYYMMDDHHMMSSZ
So pyOpenSSL uses GeneralizedTime while openssl uses UTCTime. So both are valid.
However the RFC also says:
CAs conforming to this profile MUST always encode certificate
validity dates through the year 2049 as UTCTime; certificate validity
dates in 2050 or later MUST be encoded as GeneralizedTime.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARG!
So it seems that pyOpenSSL uses GeneralizedTime unconditionally which is not RFC compliant and thus rejected by RouterOS.
A quick look at pyOpenSSL’s code unfortunately proves that…