2020-12-25
The expiry time of Certificate Authority root certificates can be nominal (or not)
The recent news from Let's Encrypt is Extending Android Device Compatibility for Let's Encrypt Certificates, which covers how Let's Encrypt is going to keep their certificates working on old Android devices for a few more years. The core problem facing Let's Encrypt was that old Android devices don't have Let's Encrypt's own root certificate, so to trust LE issued certificates they rely on a cross-signed intermediate certificate that chains to IdenTrust's 'DST Root CA X3' certificate (cf). The problem is that both this cross-signed certificate and DST Root X3 itself expire at the end of September 2021.
DST Root CA X3 expires in 2021 mostly because it was generated at the end of September 2000, and people likely thought that 20 years ought to be long enough for any root certificate (the CA world was a different place in 2000). The LE cross-signed intermediate certificate expires at the same time because you don't issue TLS certificates that expire after the certificate they're signed by. Well, normally you don't. The workaround Let's Encrypt came up with is to generate and have cross-signed a new version of their intermediate certificate that is valid for three years, which is past the expiry time of DST Root CA X3 itself.
(Multiple versions of a single certificate can exist because a certificate is only really identified by its keypair and X.509 Subject Name.)
You might wonder how this works. The answer is that Android in particular and software in general often treats root certificates rather specially. In particular, the validity dates for root certificates are sometimes essentially advisory, with it being enough for the certificate to be in the root 'trust store'. This treatment of root certificates isn't necessarily universal (and it's certainly not standardized), so it's possible for some software in some environments to care about the expiry time of a root certificate, and other environments to not care.
(For instance, as far as I can tell the standard Go TLS certificate verification does care about the validity times of root certificates.)
There is a philosophical argument that once you've made the decision to put a CA root certificate in the trust store, you shouldn't declare it invalid just because a date has passed. In this view, validity ranges are for certificates that can be replaced by the websites supplying them, which root certificates can't be. There's another argument that you should limit CA root certificate lifetimes for the same reason that you limit the lifetimes of regular certificates; things change over time and what was safe at one point is no longer so. Perhaps in another decade there will be general agreement over how software should behave here (and all software will have been updated).
(In practice, I believe that people making long-lived pieces of hardware and software that have to use TLS should demand and turn on an option to not enforce root CA lifetimes. People always stop making software updates after a while, and that includes updates to the list of trusted CA root certificates. But how to deal with TLS and general cryptography on systems that have to live without updates for 20 years or longer is something we haven't figured out yet.)
In Python 3, types are classes (as far as repr()
is concerned)
In yesterday's entry, I put in a little
aside, saying 'the distinction between what is considered a 'type'
and what is considered a 'class' by repr()
is somewhat arbitrary'.
It turns out that this is not true in Python 3, which exposes an
interesting difference between Python 2 and Python 3 and a bit of
old Python 1 and Python 2 history too.
(So the sidebar in this old entry of mine is not applicable to Python 3.)
To start with, let's show the situation in Python 2:
>>> class A: ... pass >>> class B(object): ... pass >>> repr(A) '<class __main__.A at 0x7fd804cacf30>' >>> repr(B) "<class '__main__.B'>" >>> repr(type) "<type 'type'>"
Old style and new style classes in Python 2 are reported slightly
differently, but they are both 'class', while type
(or any other
built in type such as int
) are 'type'. This distinction is made
at a quite low level, as described in the sidebar in my old entry.
However, in Python 3 things have changed and repr()
's output is
uniform:
>>> class B(object): ... pass >>> repr(B) "<class '__main__.B'>" >>> repr(type) "<class 'type'>"
Both Python classes and built-in types are 'class'. This change was specifically introduced in Python 3, as issue 2565 (the change appeared in 3.0a5). The issue's discussion has a hint as to what was going on here.
To simplify a bit, in Python 1.x, there was no unification between
classes and built in types. As part of this difference, their
repr()
results were different in the way you'd expect; one said
'class' and the other said 'type'. When Python 2.0 came along, it
unified types with new style classes. The initial implementation
of this unification caused repr()
to report new style classes as
types. However, at some point relatively early in 2.x development,
this code was changed to report new style classes as 'class ...'
instead. What was reported for built in types was left unchanged
for backwards compatibility with the Python 1.x output of repr()
.
In the run up to Python 3, this backwards compatibility was removed
and now all built in types (or if you prefer, classes) are reported
as classes.
(I was going to say something about what type()
reports, but then
I actually thought about it. In reality type()
doesn't report any
sort of string; type()
returns an object, and if you're just
running that in an interactive session the interpreter prints it
using str()
, which for classes is normally the same as repr()
.
The reason to use 'repr(B)
' instead of 'type(B)
' in my interactive
example is that 'type(B)
' is type
.)
Sidebar: The actual commit message for the 2001 era change
issue 2565 doesn't quote the full commit message, and it turns out that the omitted bit is interesting (especially since it's a change made by Guido van Rossum):
Change repr() of a new-style class to say <class 'ClassName'> rather than <type 'ClassName'>. Exception: if it's a built-in type or an extension type, continue to call it <type 'ClassName>. Call me a wimp, but I don't want to break more user code than necessary.
As far as I can tell from reading old Python changelogs, this change appeared in Python 2.2a4. In a way, this is surprisingly late in Python 2.x development. The 'what's new' snippet about the change reiterates that not changing the output for built in types is for backward compatibility:
The repr() of new-style classes has changed; instead of <type 'M.Foo'> a new-style class is now rendered as <class 'M.Foo'>, except for built-in types, which are still rendered as <type 'Foo'> (to avoid upsetting existing code that might parse or otherwise rely on repr() of certain type objects).
Of course, at that point it was also for compatibility with people relying on what repr() of built in types reported in 2.0 and 2.1.