The order of Subject Alternative Names is not retained. I have observed this behaviour with all certificates issued by Let's Encrypt.
Example: I have a certificate with the common name www.wordfeud-help.nl and the SANs wordfeud-help.nl, www.wordfeud-help.nl. I would have expected the first domain (the common name) to come first in the SAN.
I use Certbot to request certificates. Certbot uses this logic in acme.crypto_util.make_csr to create a CSR with the SANs:
extensions = [
crypto.X509Extension(
b'subjectAltName',
critical=False,
value=', '.join('DNS:' + d for d in domains).encode('ascii')
),
]
This produces the SANs in the correct order:
>>> domains = ['www.wordfeud-help.nl', 'wordfeud-help.nl']
>>> ', '.join('DNS:' + d for d in domains).encode('ascii')
b'DNS:www.wordfeud-help.nl, DNS:wordfeud-help.nl'
However, the resulting certificate's SANs are in a different order. This leads me to believe the 'issue' is not with Certbot.
I'm not sure what's standard practice here, but my Sectigo certificate SANs are in the same order as I passed to their ACME API.
Can anyone explain the reasoning behind this behaviour, and if I can circumvent this? I use a Certbot pre-hook to upload the certificate to an API, which requires the SANs in the certificate to be in the same order.
Boulder, Let's Encrypt's ACME server, performs string sorting on the SANs in increasing order. Order of the names on the CSR not retained, unless it happens to already be sorted in this way.
I'm pretty sure this is intentional behavior on Let's Encrypt's part, but I'm not sure as to why. My guess is that it makes it easier to find if something is a "duplicate" certificate and thus should be applying the duplicate-cert rate limit instead of the normal rate limit and determine to not send out renewal emails and things like that, but again that's just a guess.
I'm a little confused as to why a system would need them to be in a same order, but probably the easiest practical way to work around it is to sort the SAN in your CSR first alphabetically, the same way that Let's Encrypt will be returning them.
I'm going to move this into the Issuance Policy section, since I think that this is really asking about why Let's Encrypt's code does things this way, and it might be more likely to get an answer from somebody at Let's Encrypt who can give the reason as to why it sorts the SAN list. (Even though it doesn't impact me at all, you've got me really curious about it now.)
It is bad practice to carry any fields (such as the list of SANs or other extensions like must-staple) directly from the CSR to the tbsCertificate. Doing such byte-for-byte copies has been the root cause of many misissuance incidents by various CAs over the years. So Let's Encrypt 1) extracts the SANs from the CSR; 2) performs various transformations to normalize them (such as sorting, lowercasing, and ensuring internationalized names are correctly encoded); and 3) produces a new SANs extension for the tbsCertificate.
In general, no system should rely on the ordering of names in the SANs extension of a certificate, as the ordering has no semantic meaning in the certificate itself. If your systems are relying on this ordering in order to perform equality checks of some kind, may I suggest instead computing a hash over the sorted SANs in order to get a smaller, still-statistically-unique identifier that can be used for equality checks.
As a way of thinking I've used that coheres with what @aarongable has stated, the SANs should be treated as a set rather than a list. A set does not have order and does not allow repetition while a list does have order and can allow repetition since the uniqueness is dictated by position.