M7 / X.509 and PKI

Certificate chain bloat

FoundationsPractitionerAdvisor

After this lesson you can

  • State from memory the PEM size of a real, single-encoding (PEM), single-composition (ML-DSA-65 throughout) three-certificate chain
  • Show with raw data why this figure is about 6 times an RSA-2048 chain
  • Recognize, through the corpus's own 12.5 KB → 13.8 KB correction, that a 'corrected' figure can itself be wrong

Before thisRefresher: X.509 certificates and PKI

FOUNDATIONS: what is an X.509 certificate and chain? (read first)

A certificate is a document, signed by a trusted party, that binds a public key to an identity (for example a server name). Instead of trusting a single certificate, a chain is built: a root signs itself and becomes the single point everyone trusts in advance; the root signs an intermediate certificate; and the intermediate signs the leaf certificate that is actually used. This three-level structure lets the root be used rarely and kept safe. If you read the x509-pki-refresher lesson this will be familiar; if not, this short summary is enough to follow this lesson.

Mental model

An X.509 certificate chain has three parts: root, intermediate, leaf. Each part carries two things: its own public key, and a signature showing that the authority above it signed that key. With RSA-2048 each signature is 256 BDERIVED and each public key about 256 BDERIVED; the whole chain is a few kilobytes. Moving to ML-DSA-65 does not just mean “a slightly larger number”: both the signature and the public key grow to many times RSA’s, and that growth repeats three times along the chain (the root signs itself, the intermediate is signed by the root, the leaf by the intermediate).

The figure this lesson gives may have appeared in three different forms in the research documents you have read so far: 12.5 KB, 13.8 KB, ~12 KB. All three seem to answer the same question, but none comes from a demonstrable command, or, if it does, it silently measures something different. The real subject of this lesson is not just “what is the right number” but “how do you know a number is right”.

The measurement

According to FIPS 204, which has FINAL2024-08-13 status, ML-DSA-65’s public key and signature sizes are:

  • Public key: 1952 BSOURCED (7.63×DERIVED times the RSA-2048 public key)
  • Signature: 3309 BSOURCED (12.93×DERIVED times the RSA-2048 signature)

These two numbers alone do not give a chain’s total size: a certificate carries not only a signature and a public key but also the subject name, validity dates, extensions (basicConstraints, keyUsage and so on) and the overhead of PEM base64 encoding itself. So the only way to find a chain’s real size is to actually build one and measure it.

While preparing this lesson, a root, an intermediate and a leaf certificate using ML-DSA-65 throughout were generated in a local environment (OpenSSL 3.6.2, macOS arm64, 2026-09-04), and the openssl x509 -noout -text output confirmed that all three really are ML-DSA-65 (in both the Public Key Algorithm and the Signature Algorithm fields). This check matters, because as you will see, in an earlier research round exactly this check was skipped and the leaf certificate was mistakenly generated with RSA-2048.

Result:

  • 3 certificates (root + intermediate + leaf), PEM: 22945 BMEASURED
  • 2 certificates without the root (most TLS servers do not send the root), PEM: 15306 BMEASURED

The same structure, the same command sequence, with RSA-2048:

  • 3 certificates, PEM: 3739 BMEASURED
  • 2 certificates, PEM: 2502 BMEASURED

Ratio: 6.14×DERIVED (3 certificates), 6.12×DERIVED (2 certificates). The two are close, as expected: the root certificate grows at a similar rate on both the PQC and RSA sides, so it does not change the ratio much.

Why three different figures were in circulation

An earlier research round on this topic produced three different figures, none from a demonstrable command like the one in this lesson:

~12.5 KB. Where it first appeared, no derivation is shown and it is not even stated which three certificates are meant. Repeated seven times, never with a source.

~13.8 KB, “corrected”. Presented in the next round as a “real measurement”, but it has two problems. First: the leaf certificate was generated with openssl genrsa, so it is really RSA-2048; only the intermediate and root are ML-DSA-65. What was labelled an “ML-DSA-65 three-certificate chain” is actually a mixed chain. Second: the 12.5 KB figure was a DER estimate while 13.8 KB is a PEM measurement, so apples were being compared with oranges and the result was called a “correction”.

~12 KB. In a third place, a third rounding appeared, independent of the previous two, also unsourced.

The figure this lesson gives, 22945 BMEASURED, replaces all three: one encoding (PEM), one composition (all three certificates really ML-DSA-65, verified), a demonstrable command, a recorded environment. In data/numbers.json the supersedes field of this key records exactly where and why each of the three earlier wrong figures (12.5 KB, 13.8 KB, ~12 KB) was wrong; it is a trail you can show the next time someone says “but I heard 13.8 KB”.

What we learned

A number being “corrected” does not automatically make it right. A correction must itself be questioned with the same discipline (how was it measured, in which environment, with which command). Every figure you will see in the rest of this course should have passed three questions: where did it come from, what did it really measure, and can you show it when someone asks.

Numbers to know

  • ML-DSA-65 signature: 3309 B, public key: 1952 B (FIPS 204 Table 2)
  • Real three-certificate ML-DSA-65 chain (PEM): 22945 B; two certificates without the root: 15306 B
  • The same structure with RSA-2048: 3739 B (3 certificates) / 2502 B (2 certificates); the PQC chain is about 6.1 times larger

Lab: Build and measure your own ML-DSA-65 chain

Requires: OpenSSL 3.5+ (native ML-DSA support). Check your setup

shell
openssl genpkey -algorithm ML-DSA-65 -out root.key
Recorded output
(no output, the file is created)
shell
openssl req -x509 -new -key root.key -days 7300 -out root.crt -subj "/CN=Test Root CA" -sha256
Recorded output
(no output, the file is created)
shell
openssl genpkey -algorithm ML-DSA-65 -out intermediate.key
openssl req -new -key intermediate.key -out intermediate.csr -subj "/CN=Test Intermediate CA"
openssl x509 -req -in intermediate.csr -CA root.crt -CAkey root.key -CAcreateserial -days 3650 -out intermediate.crt -sha256 -extfile <(echo "basicConstraints=critical,CA:true,pathlen:0")
Recorded output
Certificate request self-signature ok
subject=CN=Test Intermediate CA
shell
openssl genpkey -algorithm ML-DSA-65 -out leaf.key
openssl req -new -key leaf.key -out leaf.csr -subj "/CN=mtls.example"
openssl x509 -req -in leaf.csr -CA intermediate.crt -CAkey intermediate.key -CAcreateserial -days 90 -out leaf.crt -sha256 -extfile <(echo "basicConstraints=critical,CA:false")
Recorded output
Certificate request self-signature ok
subject=CN=mtls.example
shell
for f in root intermediate leaf; do openssl x509 -in $f.crt -noout -text | grep -E "Public Key Algorithm|Signature Algorithm"; done
Recorded output
All three certificates should show ML-DSA-65 on both the Public Key Algorithm and the Signature Algorithm line. If even one differs (e.g. RSA), the chain is not what this lesson measures -- that is exactly the corpus's Day 10 mistake (see 'Why three different figures were in circulation' below).
shell
cat leaf.crt intermediate.crt root.crt > fullchain.pem && wc -c fullchain.pem
Recorded output
~22945 fullchain.pem (give or take a few bytes: X.509 serial numbers are generated randomly and can have variable length in DER encoding, so your run may give a number in the 22940-22950 range rather than exactly 22945 -- not an error, just natural variance of the same measurement)

At the table

How to say this in a bank meeting.

To an executive
Our certificate traffic definitely grows when we move to PQC. But we can answer 'is it 4 times or 6 times' with a real measurement, not a figure from hearsay. That strengthens our hand with auditors and vendors.
To an architect
A three-certificate ML-DSA-65 chain, root included, is about 23 KB in PEM. If you do not send the root (most TLS servers don't), it drops to 15 KB. That is about 6.1 times the same RSA-2048 structure. This figure was produced in my own environment with real openssl commands, not estimated.
Objection
“"We heard 12.5 KB. Now is it 15 KB or 23 KB? Which one?"”
Answer
Both are right; they measure different things: 23 KB with the root certificate (3 certificates), 15 KB without (2 certificates). The 12.5 KB figure itself was always an estimate whose derivation was never shown; if you ask for its source, nobody can show one, because there was none.

Sources

Checkpoint

Answer first, then compare with the model answer and score yourself against the rubric. Saved in this browser only.

  1. 01Recall

    How many bytes is an ML-DSA-65 signature? Where does the difference from an RSA-2048 signature come from (in terms of algorithm structure, not memorized numbers)?

  2. 02Recall

    What is the PEM size of a three-certificate ML-DSA-65 chain, with and without the root?

  3. 03Scenario

    A colleague tells you 'A three-certificate PQC chain is 13.8 KB, that's the exact figure.' What do you ask them, and which three things do you check?

  4. 04Hostile

    A bank architect asks you: 'Where did you get this 15 KB figure, in which environment, with which command?' Give your full answer.

Project linkThe direct reference figure for Project 2's (PQC PKI) own chain measurement; the chain produced in Project 2 must be consistent with the command sequence shown in this lesson.