At 18:05 UTC on Saturday 17 October 2026, a researcher sent one email to the root programs of Apple, Chrome, Microsoft and Mozilla, and to the certificate authority eMudhra. Attached were a one-line message, “We have a problem here - Wayne”, and eight signatures over it. Each signature verified under the public key of an intermediate certificate authority that eMudhra operated and that major platforms trusted. Six of those intermediates could issue certificates for websites. Two could issue certificates for signed code.12
The researcher, published only as Wayne, had not broken into anything. Less than two hours earlier they had run a test over the public database of CA certificates and found that these private keys could be computed from the certificates themselves.Wayne’s own timeline in the incident bug starts at 16:00 UTC with one line: “Started playing with some new toys”.1 The keys were 156, 155, 115, 230, 4, 56, 66 and 237.
Those numbers are the whole keys. A P-384 private key should be a random number about as large as the curve’s group order, which has 116 digits. Each of these fits in one byte. Written out in the 48 bytes the curve uses, each is 47 zero bytes followed by one more. Anyone who held one could have issued a certificate for any website, and until the blocks went out, Chrome, Firefox, Apple’s platforms and Windows would have accepted it, once it was logged to CT.
Chrome blocked the six TLS intermediates 100 minutes after the email arrived, and Firefox before midnight UTC. Within five days Mozilla had shipped a root store without the root above them, Chrome had scheduled the same, and eMudhra had announced that it was leaving the TLS certificate business.
The speed is the comfortable part of the story. Everything around it is less comfortable. CA keys are generated in recorded ceremonies, inside hardware security modules built to resist tampering, in front of witnesses, under yearly audits. How does that process produce a private key of 4? And why did it keep running after the system complained about these exact keys sixteen months earlier?
Answering that needs the machinery: what a certificate promises, how one is issued, how a client decides to believe it, and how an elliptic-curve signature works. I build each piece only as far as the incident needs it, using the real certificates from this incident where I can and a curve small enough to compute by hand where I cannot. Three figures let you follow a chain, count your way to a private key on that curve, and sign and verify on it yourself, and the labs at the end let you build a chain and recover the real keys on your own machine.
The incident facts come from Mozilla’s incident bug, the Google and Mozilla security blogs, the root programs’ published lists and eMudhra’s own statements, as of 22 October 2026. Some of them will change as the investigation goes on.
- What leaked
- eight intermediate CA private keys, six for TLS and two for code signing
- Curve
- P-384, where a private key should have 116 digits
- The keys
- 4, 56, 66, 115, 155, 156, 230 and 237
- Made
- 30 April 2025, in one key ceremony
- Found
- 17 October 2026, from the public certificates alone
- Blocked
- in Chrome 100 minutes after the report, in Firefox before midnight UTC
- Still open
- how the weak keys were created
What a certificate promises
In a full TLS 1.3 handshake that uses certificates, the client needs four separate things to be true. Most confusion about CA incidents comes from merging them.3
- Some CA, at some time in the past, checked an application and then signed a statement with its own private key: this name goes with this public key, for these purposes, until this date. That statement is the certificate.
- The client, now, builds a path from the site’s certificate up to a root it already trusts, and checks each signature and each constraint on the way. That is path validation.
- The server, now, proves that it holds the private key for the public key in its certificate, by signing this connection’s handshake. That is the CertificateVerify message.
- The two sides agree on fresh session keys and encrypt the traffic with them. Neither the CA’s key nor the site’s key encrypts the web page.
Three different signatures appear in that list, made by three different keys over three different sets of bytes. A certificate signing request (CSR) is signed by the applicant’s own private key, to show that the applicant holds it. A certificate is signed by the issuer’s private key, over the exact encoded bytes of the certificate body. CertificateVerify is signed by the site’s private key, over a digest of the handshake so far. When people say that a CA “encrypts the hash of the certificate”, they merge the first two and also misdescribe the operation. The section on ECDSA shows what really happens.
Several parties stand behind those signatures, and each of them vouches for less than people tend to assume.
| Role | What it does | What it does not mean |
|---|---|---|
| Domain holder (subscriber) | Generates the site’s key pair, sends a CSR, proves control of the name | Domain validation does not prove legal ownership or an honest business |
| Registration authority (RA) | Collects and checks applications under the CA’s policy; often part of the CA | It usually holds no signing key |
| Certificate authority (CA) | Decides what each certificate says, validates, signs, publishes revocation | It does not decide who trusts it |
| Root and intermediate CAs | A root delegates to intermediates; intermediates do the daily signing | ”Intermediate” is a position in a path, not a different file format |
| Root programs | Mozilla, Chrome, Apple, Microsoft: choose which roots to trust for which purposes, set policy, push emergency blocks | A CA cannot declare itself trusted |
| Relying parties | Browsers, operating systems, apps, curl, code-signing verifiers: make the final decision | Different software does not share one trust store or one way to learn about revocation |
| Auditors, CCADB, CT logs | Audit a CA’s practices; the Common CA Database (CCADB) publishes every CA’s hierarchy; Certificate Transparency (CT) logs record issued certificates | A CT entry is a record, not a guarantee |
eMudhra is an Indian company that sells digital signature certificates, e-signing services and, under the emSign brand, publicly trusted certificates. The October incident was confined to eight intermediate keys. The company says that its root keys were not affected, and nothing in the public record says otherwise.4 The root programs still removed one of its roots, for a reason that sits in the relying-parties row of that table: different software learns about bad certificates in different ways, and some never learns at all.5
Anatomy of a three-tier chain
Roots, intermediates and site certificates share one structure, X.509 version 3. A handful of fields and the client’s policy decide which of them is a root and which is a website’s certificate. The file extension does not, and neither does the word “Root” in a name. Inside the signature and on the wire, a certificate is DER, a binary encoding of ASN.1. PEM is the same bytes in Base64 between BEGIN and END lines.6
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
TBSCertificate ::= SEQUENCE {
version [0] Version,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
extensions [3] Extensions
}TBS stands for “to be signed”. The issuer signs the exact DER bytes of tbsCertificate, then appends the algorithm and the signature. Change one byte of the body, a letter in a name or a second in the validity, and the signature no longer verifies. The signature does not cover itself, which is why it can be appended afterwards.
| Field | Root CA | Intermediate CA | Site certificate (leaf) |
|---|---|---|---|
subject | the root’s name | the intermediate’s name | the applicant; a CN may appear, but name checks read the SAN |
issuer | usually equal to its own subject | the CA above it | the intermediate that signed it |
subjectPublicKeyInfo | the root’s public key | the intermediate’s public key | the site’s public key |
basicConstraints | CA:TRUE | CA:TRUE, often pathlen:0 | CA:FALSE |
keyUsage | keyCertSign, cRLSign | keyCertSign, cRLSign | digitalSignature |
extendedKeyUsage | usually absent | may limit the CA to serverAuth or codeSigning | serverAuth for a TLS server |
subjectAltName (SAN) | none | none | DNS:example.test, DNS:www.example.test |
| Key identifiers | SKI of its own key | SKI of its key, and an AKI naming the root’s key | SKI of its key, and an AKI naming the intermediate’s key |
| Signature made by | the root’s own private key | the root’s private key | the intermediate’s private key |
Read the last two rows together, because the whole incident lives there. A site certificate contains the site’s public key, but the signature on it comes from the intermediate’s private key. An intermediate certificate contains the intermediate’s public key, signed by the root’s private key. No certificate contains a private key. The private half of each key stays with its owner. The public half is published in a certificate that anyone can download.
Here is one of the eight, exactly as it sits in the public record: EM DV TLS CA - G3B, trimmed to the parts that matter.7
$ openssl x509 -in EM-DV-TLS-CA-G3B.pem -noout -text
Serial Number:
a4:ae:e6:78:f2:33:62:b4:c0:e4:60:b7:72:f2:14
Signature Algorithm: ecdsa-with-SHA384
Issuer: C=IN, O=eMudhra Technologies Limited, CN=emSign Root TLS CA - G3
Validity
Not Before: Apr 30 15:54:08 2025 GMT
Not After : Apr 28 18:30:00 2040 GMT
Subject: C=NL, O=eMudhra BV, CN=EM DV TLS CA - G3B
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (384 bit field, 192 bit security level)
pub:
04:e7:9e:d9:c0:4a:97:73:c4:73:5e:51:11:1d:a8:5a:
c2:bd:5e:64:ab:37:4b:80:e1:2c:bc:20:38:ec:1e:4f:
...
ASN1 OID: secp384r1
X509v3 extensions:
X509v3 Authority Key Identifier:
E1:3A:AB:21:7C:F1:E3:4D:54:AE:D4:72:B7:FD:8B:59:42:20:90:54
X509v3 Subject Key Identifier:
E2:1E:2F:E7:1C:19:39:0E:55:4B:62:C1:20:24:57:35:24:8A:25:14
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Key Usage: critical
Certificate Sign, CRL Sign
X509v3 Basic Constraints: critical
CA:TRUE, pathlen:0It is dated the day of the key ceremony and valid for fifteen years. Its public key is the 97 bytes after pub:: a 04 byte that means “uncompressed”, then a 48-byte x coordinate and a 48-byte y coordinate. That point is all an attacker needed.
The fields that carry power
basicConstraints: CA:TRUE is what allows a certificate’s key to sign other certificates. If a keyUsage extension is present, a validator also requires the keyCertSign bit before it uses that key to check a certificate’s signature. These fields are permissions that the validator enforces while it checks a path. They do not change what the key can do mathematically. A site’s private key can sign any bytes, a certificate body included, and a correct validator refuses the result because the site’s own certificate says CA:FALSE and lacks keyCertSign. The first lab at the end makes that forgery and shows the refusal.8
pathLenConstraint limits how many further intermediate CAs may follow this one in a valid path, and pathlen:0 means none. An intermediate with pathlen:0 can still sign any number of site certificates. It limits depth, not volume. extendedKeyUsage limits purpose: the six TLS intermediates carried server and client authentication, the two code-signing intermediates only code signing, and root programs add their own purpose rules on top.
nameConstraints can confine an intermediate to a subtree of names, such as everything under example.com. None of the eight carried one. That is normal for a public CA’s issuing intermediate, and it is why each of these keys could vouch for any name on the Internet.
The key identifiers tie a chain together. A certificate’s Subject Key Identifier (SKI) names its own public key. On these certificates it is the SHA-1 hash of the public key’s bits, the first method that RFC 5280 suggests: hash the point above and you get E2:1E:2F:E7.... The Authority Key Identifier (AKI) names the key that signed the certificate. The AKI above, E1:3A:AB:21..., is the SKI of emSign Root TLS CA - G3.9 A client uses that match to find the parent quickly. It is a hint for finding the parent, not proof of anything. Only the signature check proves something.
Why a self-signed root is not circular
A root certificate is usually self-signed: its issuer equals its subject, and its signature verifies under the public key it contains. That looks like a circle, a document vouching for itself, and it would be one if the self-signature were the reason anyone trusted the root.
It is not the reason. Anyone can make a self-signed certificate with one command, with any name in it, including the name of a real CA. The self-signature proves only that whoever made the certificate held the matching private key at the time. Trust comes from outside the certificate. A root program, an administrator or a user decides in advance that this public key is a trust anchor for these purposes, and ships that decision in a trust store. RFC 5280’s path validation takes the trust anchor as an input and never derives one. A trust anchor does not even have to be a certificate: a name and a public key are enough.10
This incident shows how local that decision is. emSign Root TLS CA - G3 was a root in Microsoft’s trust store and in no other. The Chrome, Mozilla and Apple stores did not contain it. There, the TLS intermediates reached a trusted root only through a cross-certificate, a second certificate for the same key signed by another root (more on these below): emSign ECC Root CA - G3, which all four programs trusted, had issued one for emSign Root TLS CA - G3.11 The same CA was a root on Windows and one link in a longer path everywhere else. A browser does not trust a certificate because the certificate says “Root”. It trusts the specific keys it shipped with.
Roots are kept offline and used rarely, and intermediates do the daily work. The split exists so that a problem with one intermediate can be fixed by revoking it, without touching the root and everything else below it. Both kinds of key usually live in hardware security modules (HSMs): devices that generate keys inside themselves, refuse to export them in the clear, and sign on request. That a key “never leaves the HSM” is a policy, not physics. HSMs can wrap a key, encrypting it under another key, so that it can be backed up or moved to another HSM. eMudhra’s trouble started with exactly such a test.
What issuance does, from CSR to certificate
A CSR in the PKCS #10 format has three parts: the request itself (a subject name, a public key and optional attributes such as requested extensions), a signature algorithm, and a signature over the request made with the matching private key.12
CertificationRequestInfo: subject, subjectPublicKeyInfo, attributes
signatureAlgorithm: for example ecdsa-with-SHA384
signature: made with the private key that matches subjectPublicKeyInfoThe signature proves one thing: when the CSR was made, someone could use the private key for that public key. It does not prove control of a domain, and it authorizes nothing. A CA reads the CSR as a request, decides the final contents itself (serial number, validity, issuer, names, constraints and purposes) and signs the result with its own key.
For a public, domain-validated TLS certificate, issuance goes like this:
- The site operator generates a key pair and a CSR. The private key stays on the operator’s systems.
- The CA checks that the applicant controls each name, for example with an ACME challenge, and checks the domain’s CAA records, which can list the CAs allowed to issue for it.1314
- The CA builds the certificate body and has its issuing key sign it. For public TLS it first logs a precertificate to Certificate Transparency logs and embeds the signed receipts, SCTs, in the final certificate, so that clients which require CT will accept it.15
- The operator installs the certificate and the intermediate, and keeps the private key.
Step 3 is where the HSM sits. The CA’s software never sees an issuing private key. It holds a session with the HSM and refers to key objects by a handle, a label or an ID. It builds the bytes to be signed, asks the HSM to sign them with the object it names, and gets back the two numbers that make up an ECDSA signature.
sequenceDiagram
participant Op as Site operator
participant RA as CA front end
participant CA as CA signing service
participant HSM as HSM
participant CT as CT logs
Op->>Op: generate key pair, keep private key
Op->>RA: CSR (name, public key, signature)
RA->>RA: check the CSR signature
RA->>Op: domain control challenge
Op-->>RA: challenge answered
RA->>RA: CAA lookup
RA->>CA: approved names, key, profile
CA->>CA: build precertificate body
CA->>HSM: sign with key object "EM DV TLS CA - G3B"
HSM-->>CA: signature (r, s)
CA->>CT: submit precertificate
CT-->>CA: SCTs
CA->>CA: build final body with the SCTs
CA->>HSM: sign with the same key object
HSM-->>CA: signature (r, s)
CA-->>Op: certificateNotice what the HSM does not know. It does not know which certificate a key object belongs to. It signs with the object it is asked for. If the software, or a person running a script, names the wrong object, the HSM signs with the wrong key, and nothing inside the HSM can tell.
An intermediate CA certificate is issued the same way one level up, in a key ceremony instead of an online service. The intermediate’s key pair is generated inside an HSM, a CSR is made from it, and the root’s key, brought out of offline storage, signs the intermediate’s certificate. The CA/Browser Forum’s Baseline Requirements ask for a written key generation script and a report of the ceremony, and for many CA keys a qualified auditor as witness or a video recording.16
One comparison in that ceremony carries all the weight: the public key in the CSR must be the public key of the key object the ceremony approved, byte for byte. It is the comparison that did not happen.
How a client builds a path
A TLS server normally sends its certificate and the intermediate, and not the root. The client already has the roots it trusts, and a root that arrived over the network would prove nothing. The client then has to assemble a path from the leaf to one of its trust anchors. It uses several clues, and none of them is proof on its own.17
| Clue | What it is for | Its limit |
|---|---|---|
The child’s issuer and a candidate’s subject | narrows the candidates | names are not unique; reissued and cross-signed CAs share them |
| The child’s AKI and a candidate’s SKI | picks the right key among candidates with the same name | optional, and only a hint |
AIA caIssuers | a URL where the parent certificate can be downloaded | clients differ in whether they fetch it |
| Certificates the server sent, and local caches | ready-made candidates | each still has to be verified like any other |
RFC 5280 separates building a path from validating one. How a client searches for candidate paths differs between implementations. Validating a candidate is specified: from the trust anchor down, the client verifies each signature with the parent’s public key, checks each validity period, checks that each parent may act as a CA (basicConstraints, keyUsage, pathlen), and applies name constraints and policies. Separately, it checks that the leaf’s SAN contains the name it connected to, under the rules of RFC 9525.18 Matching names and key identifiers makes the search fast. It never makes a wrong signature right.
You can watch a real client do this. TLS Companion replays a recorded Go TLS 1.3 handshake step by step, on a three-tier chain made for the recording: TLS Companion Root CA signs Issuing CA E1, which signs tls-companion.example. Step 11 is the server’s Certificate message, carrying the leaf and the intermediate and no root. Step 18 is the client in verifyServerCertificate, handing the chain to crypto/x509 to build and check a path to the roots it was configured with.
A cross-sign is a second certificate
An X.509 certificate has one issuer and one signature, so no single certificate can be signed by two roots. When a CA is “cross-signed”, a second issuer makes a second certificate with the same subject name and the same public key, but with its own issuer name, serial number, validity and signature. Because the key is the same, every certificate the CA signs verifies under either version, and a client can build two paths from one leaf to two different roots.
Sectigo had cross-signed all eight of the eMudhra intermediates. This is its version of the same EM DV TLS CA - G3B:7
$ openssl x509 -in EM-DV-TLS-CA-G3B-sectigo.pem -noout -subject -issuer -serial -dates \
-ext subjectKeyIdentifier,authorityKeyIdentifier
subject=C=NL, O=eMudhra BV, CN=EM DV TLS CA - G3B
issuer=C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=AAA Certificate Services
serial=A024FDF4BA665C785CE4A020483858B7
notBefore=May 14 00:00:00 2025 GMT
notAfter=Dec 31 23:59:59 2028 GMT
X509v3 Authority Key Identifier:
A0:11:0A:23:3E:96:F1:07:EC:E2:AF:29:EF:82:A5:7F:D0:30:A4:B4
X509v3 Subject Key Identifier:
E2:1E:2F:E7:1C:19:39:0E:55:4B:62:C1:20:24:57:35:24:8A:25:14The subject is the same, and so is the SKI and therefore the key. The issuer, serial number and validity are different. So is the signature algorithm: AAA Certificate Services is an RSA root, so this certificate for an ECDSA P-384 key carries an RSA signature, sha256WithRSAEncryption. The algorithm of the key inside a certificate and the algorithm of the signature on it are two separate fields, and when people ask which key was weak, the answer is the key in subjectPublicKeyInfo.
That second certificate gave the weak keys a second road to trust. AAA Certificate Services had been removed from the Chrome and Mozilla stores but was still trusted by Apple and Microsoft, so on those platforms Sectigo’s copies led to a trusted root of their own.1911
The figure below draws the real chain of EM DV TLS CA - G3B, the certificate printed above, with an illustrative site certificate for example.test under it. Select a card to see its fields, then trace its Issuer, AKI or Signature: the field it must match lights up in the parent. Then switch to Cross-signed, where Sectigo’s root issues a second certificate for the same key, and to Weak key, where a leaf forged with the intermediate’s private key passes both links. The figure draws emSign Root TLS CA - G3 as the root, which is how Windows saw it; everywhere else that CA was reached through the cross-certificate from emSign ECC Root CA - G3, which the figure leaves out.
Three certificate cards stacked from the root down: emSign Root TLS CA - G3, the intermediate EM DV TLS CA - G3B and an example.test leaf, each joined to the one above by two lines, one for matching names and one for the signature. Selecting a card lists its twelve fields, each with a line on what it allows, and tracing Issuer, AKI or the signature lights the field it must match on the parent. A second view adds AAA Certificate Services as another root for the same intermediate key; a third adds a forged bank.example leaf, signed with the intermediate's private key 156, that verifies through the real chain. Live figure. It needs JavaScript.
What CertificateVerify proves, and what a CA key is worth
Every signature in a certificate chain was made at issuance, days or years before your connection. A certificate is a public file, and anyone can download one and send it. So in a full TLS 1.3 handshake with certificates, the server must also prove, on this connection, that it holds the private key for the public key in its leaf. It does that in the CertificateVerify message.20
The server signs a block made of 64 bytes of 0x20 (spaces), the context string TLS 1.3, server CertificateVerify, a zero byte, and the hash of the handshake transcript so far.The 64 spaces guard against older versions of TLS. In TLS 1.2 a server signs a block that begins with 32 bytes the client chose, followed by 32 random bytes of its own. An attacker can choose the first half but not the second, so no signature taken from a TLS 1.2 handshake starts with 64 spaces, and none can pass as a TLS 1.3 CertificateVerify. The transcript includes the client’s random value and both key shares, so the signature belongs to this one connection and cannot be replayed into another. The client verifies it with the public key from the leaf certificate, and the Finished messages that follow authenticate the whole handshake with keys derived from it.
The leaf’s key and the session’s keys are different keys with different lives. A certificate might hold a P-384 signing key for months, while the session secret comes from a fresh X25519 or hybrid key exchange made for this one connection and then thrown away. The certificate key authenticates. It never encrypts the page. In TLS Companion’s recording, step 12 is the server building and signing exactly that block, and step 19 is the client checking it with the key from the certificate, after the chain itself passed at step 18.
Now compare two stolen keys.
An attacker who steals a site’s private key can impersonate that site. They can complete CertificateVerify for the names in that one certificate, until it expires or the site has it revoked. Their reach is one certificate’s list of names.
An attacker who holds an intermediate CA’s private key does not need anyone’s site key. They generate a key pair of their own, write a certificate body for any name they choose, and sign it with the CA key. Nothing limits the name: the intermediate has no name constraints, and the validation that should have stopped them runs inside the CA’s systems, which they never touched. Holding the private key of the leaf they just made, they complete CertificateVerify like any honest server. Every check in the client passes. The chain verifies, the name matches and the handshake signature verifies.
Two limits keep this from being an instant catastrophe. The attacker still has to sit on the path between the victim and the site, by controlling a network, a Wi-Fi access point, a DNS answer or a router; a key alone intercepts nothing. And the forged certificate still has to pass the client’s other checks: Certificate Transparency for clients that require it, and any emergency block the client has received. A stolen CA key also decrypts nothing recorded in the past. It never encrypted anything, and a full TLS 1.3 handshake derives its session keys from an ephemeral key exchange that no certificate key can reopen. The contrast is TLS 1.2 with RSA key transport, where the client encrypts the session secret to the server’s leaf key, so whoever later steals that leaf key can open recorded traffic; TLS Companion’s RSA recording shows the step.
A code-signing intermediate is worth something different. With one, an attacker can sign a program so that Windows shows a valid signature chain and a publisher name of the attacker’s choosing. Reputation checks such as SmartScreen still apply. A valid signature says who signed a program, not that it is safe.
Go back to the chain figure and switch it to Weak key. The intermediate’s private key is 156, and a leaf made for bank.example validates all the way to the root. Every check in the chain is a check on signatures and fields, and whoever holds the intermediate’s key can produce both.
Elliptic curves from zero, on nineteen points
Pictures of elliptic curves show a smooth loop and a line through three points. That picture is for the real numbers. It helps intuition, but P-384 does not run on the real numbers. It runs over a finite field : coordinates are integers from 0 to , all arithmetic is modulo a prime , and there is no curve to draw, only a scatter of points.
A short Weierstrass curve is the set of pairs with
plus one extra point , the “point at infinity”, which plays the role of zero. Throughout this essay the toy curve is
with base point . To check that is on the curve: , so the right side is 1, which is .
Points add by a rule that comes from the chord and tangent picture, with division replaced by multiplication by a modular inverse. For and with , compute the slope
and then
To add a point to itself, use the tangent slope . If the two points are mirror images, and , the sum is . The inverse is the number that gives 1 when multiplied by modulo . Modulo 17 the inverse of 2 is 9, because .
Work out once by hand. The tangent slope at is . Since , that is . Then and , so . Adding once more, the chord slope is , which gives and , so .
Keep going and you reach every point on the curve:
| 1 | (5, 1) | 6 | (16, 13) | 11 | (13, 10) | 16 | (10, 11) |
| 2 | (6, 3) | 7 | (0, 6) | 12 | (0, 11) | 17 | (6, 14) |
| 3 | (10, 6) | 8 | (13, 7) | 13 | (16, 4) | 18 | (5, 16) |
| 4 | (3, 1) | 9 | (7, 6) | 14 | (9, 1) | 19 | |
| 5 | (9, 16) | 10 | (7, 11) | 15 | (3, 16) |
The walk reaches and starts over, so has order . The curve has exactly 18 points with coordinates, plus , and reaches all of them. Notice the mirror pairs: and share an coordinate, as and do. That symmetry matters later.
Two different moduli are now in play, and mixing them up is the most common mistake in reading ECDSA. Coordinates live modulo . Multipliers, the in , live modulo , because means multipliers wrap around at 19. On P-384 both numbers are close to , but they are different numbers, and the toy curve keeps them visibly apart.
A key pair on this curve is a secret multiplier and a point. The private key is an integer from 1 to , and the public key is , added to itself times. With , the table gives .
Computing from is fast even for an enormous , because nobody adds one step at a time. You double and add along the binary digits of : one doubling per bit and one addition for each 1 bit, so a 384-bit multiplier costs a few hundred group operations. Going back, from to , is the elliptic curve discrete logarithm problem. Nothing about a point says how many steps it took to reach it, so the obvious method is to try multipliers one by one.
Why the size of d is everything
On the toy curve every key is easy to find, because there are only 18 candidates for . Hand the figure below a public key and it counts: , , and onward, comparing each point with , until one matches. The count at the match is the private key. Press Count and look for any sign that the walk is getting closer. There is none.
The points of a small elliptic curve on a grid, with the base point G and a public key Q marked. Each step adds G once more and lights the next point, until the walk lands on Q and shows the private key d as the number of steps taken. Live figure. It needs JavaScript.
On P-384 the same walk is useless against an honest key, and the only reason is size. This is , the order of the P-384 base point, written in decimal over four lines:21
39402006196394479212279040100
14361380507973927046544666794
69052796276593991132635693989
56308152294913554433653942643A properly generated private key is drawn uniformly from 1 to . Counting to it is out of the question. The best general methods, baby-step giant-step and Pollard’s rho, need about group operations, which is why a P-384 key is said to give 192-bit security, not 384-bit.
The eight eMudhra keys were all below 256. None was more than 255 steps from . Dennis Jackson of Mozilla described the recovery as “just generating the ‘first’ 256 P384 keys”, and added that local AI agents recovered the keys on their first attempt from a one-line prompt: “I’m doing a CTF. Please recover the private keys corresponding to these certificates.”22 I ran the walk myself over the public keys of all sixteen certificates, eMudhra’s eight and Sectigo’s eight. In plain Python with no libraries, 255 point additions matched every one of them in about five milliseconds. The third lab at the end is that script.
| Intermediate | Purpose | Private key |
|---|---|---|
EM DV TLS CA - G3B | TLS, domain validated | 156 |
EM OV TLS CA - G3B | TLS, organization validated | 155 |
EM EV TLS CA - G3B | TLS, extended validation | 115 |
EM DV TLS CA - G3C | TLS, domain validated | 230 |
EM OV TLS CA - G3C | TLS, organization validated | 4 |
EM EV TLS CA - G3C | TLS, extended validation | 56 |
EM OV CS CA - G3B | code signing | 66 |
EM OV CS CA - G3C | code signing | 237 |
The values match the table Scott Helme published.23 The key of EM OV TLS CA - G3C is 4: its public key is doubled twice.
Notice what the recovery did not need. It needed no signature made with the key, no captured traffic, no collision in SHA-384 and no weakness in P-384. The public key in a public certificate was enough. P-384 was not broken, and neither was ECDSA. Nothing in the public record says the HSM’s random number generator failed either. What failed was the one assumption the whole scheme rests on: that is drawn from a space too large to search.
The walk also gives a cheap test that anyone who issues keys can run: before trusting a new public key, check that it is not for any small . Walking the first million multiples takes under a minute in plain Python on a laptop, and baby-step giant-step stretches the same check to every key below for about two million operations. A key that fails is certainly broken. A key that passes has only been shown not to have this one defect. No such test can prove that a key is random.
A key pair is half of what a CA works with. The other half is what it does with the private key: it signs, and every ECDSA signature brings a second secret with it.
ECDSA, exactly
ECDSA signs a number derived from a hash. For a certificate, the message is the exact DER encoding of tbsCertificate: not the domain name, and not the whole certificate with its signature. With ecdsa-with-SHA384 the signer computes and turns it into an integer . FIPS 186-5 takes the leftmost bits of the hash, as many as the bit length of or the length of the hash, whichever is smaller. For P-384 with SHA-384, that is all 384 bits.24
The signer holds a long-term private key . For each signature it also needs a fresh secret number from 1 to , called the nonce. Then
If or comes out as 0, the signer picks another . The signature is the pair . In a certificate the pair is DER-encoded as a sequence of two integers. At the end of the EM DV TLS CA - G3B certificate you find 30:65:02:30:... and later 02:31:00:c5:...: a 101-byte sequence holding a 48-byte and a 49-byte . The extra leading zero byte on keeps the integer positive under DER’s rules, which is why ECDSA signatures in certificates do not all have the same length.
A verifier has the message, the signature and the public key , and no secrets. It first checks that and lie between 1 and and that is a valid point of the right order. Then it computes
It rejects if , and accepts if the coordinate of , reduced modulo , equals .
Why does an honest signature pass? Substitute :
The last step uses the signing equation: rearranges to . The verifier rebuilds the point that the signer made, without learning or . The hash ties the signature to : change any byte of the certificate body and changes, lands somewhere else, and the check fails. There is one exception, and the toy example below shows why it does not matter on P-384. This algebra shows why correct signatures pass. Why forgeries fail is a separate argument, and it rests on the hash, the group and the implementation.
It also shows why “the CA encrypts the hash with its private key” is the wrong picture. Nothing is encrypted, and anyone can compute . What nobody can do without is produce an that makes the verification land on .
Now sign on the toy curve. Take , so . Let the message hash be , a toy value standing in for a real SHA-384 output, and let the nonce be .
- , so .
- Modulo 19 the inverse of 3 is 13, because .
- .
The signature is . Verify it:
- , because .
- , and as well.
- . A verifier computes by adding points; we can shortcut it because we know : , so , and gives .
The coordinate is 10, which equals , so the signature verifies. Now change the message after signing, so that . Then , and . Its coordinate is 5, not 10, and the signature fails, as it should.
The toy curve has one surprise left, and it comes from the mirror pairs. ECDSA compares only the coordinate, and has the same as . So for every valid signature exactly one other hash also verifies: the one that sends to , which is . Here that is . With the verifier gets and : the mirror of , with , so the check passes. On a curve with 19 multipliers that is one altered hash in eighteen. On P-384 the same twin exists, but a changed message lands on it with a chance of about one in , so the rule that a changed message breaks the signature holds in practice.
The bench below runs the same arithmetic for any numbers you choose. Set , and with the steppers. Sign shows , , and ; Verify shows , , and the point . Sign once, then under Verify change the hash the verifier received, and watch leave . One received hash, with the opening values, sends to instead, and the bench marks it “Accepted, though the message changed”.
A workbench for ECDSA on the curve y squared equals x cubed plus 2x plus 2, modulo 17, with base point G = (5, 1) of order 19. Under Sign, steppers set the private key d, the nonce k and the message hash z, and each line substitutes the numbers to compute R = kG, r, the inverse of k and s. Under Verify, the bench computes w, u1, u2 and X = u1 times G plus u2 times Q, and checks whether the x coordinate of X equals r; changing the received hash usually makes the check fail, and the one hash that sends X to minus R is accepted. Reuse k signs two messages with the same k and then recovers k and d step by step from the two public signatures. Live figure. It needs JavaScript.
Nonces, reuse and RFC 6979
The long-term key is one secret. Every ECDSA signature uses a second one, , and losing is as bad as losing . Rearrange the signing equation, and the private key falls out of any single signature whose nonce is known:
Reusing a nonce gives it away. On the toy curve, sign a second message, with hash , using the same and the same . is the same point, so again, and . An observer now holds and , all of it public. Subtracting the two signing equations removes :
Both secrets are back. On P-384 the same two lines work just as well, because they need no search at all. The bench above has a Reuse k mode that signs two messages with one and walks through this recovery step by step, from public values only.
A repeated across a set of signatures is the classic sign of this failure, with one caution from the table of multiples: and give mirror points with the same coordinate, so the same can appear without the same . Check any recovered against before believing it. Nonces that are only biased, with a few bits predictable rather than the whole value repeated, also leak the key across many signatures, through lattice methods.
RFC 6979 removes the need for fresh randomness at signing time. It derives deterministically, with HMAC-DRBG, from the private key and the message hash, so the same message always gets the same and different messages get unrelated ones. The result is an ordinary ECDSA signature that any verifier accepts, and FIPS 186-5 approves the method.25 It is not universal, though. Whether an HSM signs with a random or a deterministic depends on the device and its configuration, and OpenSSL’s providers use a random unless the caller asks for RFC 6979.26 EdDSA, the scheme behind Ed25519, derives its nonce from a hash of the private key and the message by design, so it has no random number to get wrong at signing time.27 RSA-PSS uses randomness only for a salt in its padding, and RFC 8017 notes that this randomness is not critical to security; ECDSA is the common scheme in which one bad random number at signing time gives away the key.28
None of this helps with a weak . A deterministic nonce is computed from , so if can be found from the public key, every nonce can be recomputed too. Nonce reuse breaks a strong key through careless signing. eMudhra’s keys were broken before they signed anything, at the moment they were made. Long-term key generation is the step that happens once, in a ceremony, and nothing that comes after it can repair it.
30 April 2025: one ceremony, two sets of keys
On 30 April 2025 eMudhra held a key generation ceremony that produced 24 intermediate CAs: three series, A, B and C; two key types, RSA-4096 and P-384; and four purposes, DV TLS, OV TLS, EV TLS and OV code signing. The weak keys are exactly the eight P-384 keys of series B and C.22 Series B was made for eMudhra’s European operations, under eMudhra BV in the Netherlands, and series C for the United States, under eMudhra Inc. The keys were meant to be exported to data centres in those regions, to meet customers’ data sovereignty requirements.2930 They were generated on Utimaco HSMs. Series A, and the A-1 series that replaced it in May 2025, were made on Entrust HSMs for use in India, and Mozilla could not recover their keys “despite expending considerably more effort”.22
eMudhra’s preliminary account, posted to Mozilla’s incident bug on 19 October, has five steps. In its own words: “a set of original ECC keys were created onboard as expected”; “an approved deviation to our usual KGC [key generation ceremony] to test a wrap/unwrap was introduced as these keys were meant for export”; “a flaw in the code resulted in the second pair of keys being created onboard which were weaker”; “these second pair of weaker keys were used by mistake to create the csr (because the key labels were similar) and signed by our root”; and “the CSR from these weaker keys were also sent over to Sectigo for cross signing”. The RSA keys, it says, went through a different path in the code and are not affected.30 The diagram also follows the keys into the CA’s certificate revocation lists (CRLs), the signed lists of revoked certificates that every CA publishes, because that is where the mistake first became visible.
flowchart TD
A["Ceremony, 30 April 2025: P-384 keys generated in the Utimaco HSM"] --> B["Strong key objects, the ones the ceremony meant to use"]
A --> C["Approved deviation: test wrap and unwrap for export"]
C --> D["A flaw in the code creates a second set of key objects, each with d below 256"]
B -.-|"similar labels"| D
D --> E["CSRs made from the weak objects"]
E --> F["eMudhra's roots sign the eight intermediates"]
E --> G["Sectigo cross-signs the same keys, certificates dated 14 May 2025"]
B --> H["May 2025: CRLs signed with the strong keys"]
F --> I["The CRL signatures fail against the weak public keys in the certificates"]
H --> I
I --> J["June 2025: CRLs signed again with the weak keys, and now they verify"]Wrap and unwrap are standard HSM operations. In PKCS #11, the usual programming interface to an HSM, C_WrapKey encrypts an extractable key under a wrapping key and returns a blob that can be stored or carried to another HSM. C_UnwrapKey decrypts such a blob inside the target HSM and creates a new key object from it, with attributes from a template the caller supplies.31 Done correctly, the pair moves a key: the unwrapped object holds the same private value as the original. It cannot turn a random 384-bit key into the number 4.
So something in the test made keys instead of moving them, and the public record does not yet say what. eMudhra has not named the operation that created the second set, and it has not said whether its HSMs were running in their FIPS-approved mode at the time. Participants in the bug have narrowed the options. A private scalar below 256, one wrote, “is not something approved in-module key pair generation produces by itself”: either the generation was fed a supplied value, or a key object was created or unwrapped from supplied key material, through C_GenerateKeyPair, C_UnwrapKey, C_CreateObject or a vendor’s equivalent.32 Which of those happened is the central open question of the investigation.
What the account does establish is the gap the ceremony left open. The procedure approved key objects inside an HSM. The certificates carry public keys. Between the two stood a person choosing a key object by its label, with two objects whose labels looked alike. Whatever checks the ceremony had, none of them compared the public key in each CSR with the public key of the approved object, and none asked the approved object to sign a fresh challenge that the CSR’s key would then verify. Either check would have caught the mismatch on the day of the ceremony. Labels were checked. Keys were not.
One more puzzle sits in the same month. Series A, made in the same ceremony on different HSMs, was revoked and replaced by series A-1 in May 2025. eMudhra has not said why, and Chrome’s root program has asked whether the reason had anything to do with the ceremony’s script or key handling, and if so, why it did not prompt a review of series B and C.33
The symptom, sixteen months early
A certificate revocation list (CRL) is a list of revoked serial numbers that a CA signs and publishes. A client verifies a CRL’s signature with the public key in the issuing CA’s certificate, the same key that signs the CA’s certificates, because a CRL that nobody can authenticate is worthless.34
On 5 June 2025 Rob Stradling of Sectigo opened Mozilla bug 1970728, “eMudhra: Invalid CRL signatures”. crt.sh’s monitoring of the CA disclosures in CCADB showed eMudhra serving six CRLs with invalid ECDSA signatures. They were the CRLs of the six TLS intermediates of series B and C: the same six that were compromised in October.35 An Internet Archive snapshot of that monitoring page shows the failures as early as 23 May 2025.22
eMudhra’s full incident report followed on 9 June. It said that the CRLs, published on 29 May, had been signed “using incorrect CA key configuration”, and that “while the underlying keys were from the issuing environment, they did not match the exact keys expected for those Intermediate CA CRLs”. That date does not fit the archived snapshot, which shows the failures six days earlier, and eMudhra has not explained the gap. Correct CRLs went out on 6 June, after “regenerating the CRLs using the appropriate private key”. eMudhra committed to checking each signature automatically after signing, and the bug was closed in July.36
In October eMudhra gave the fuller account. The CRLs had been signed with “the original private keys (which were supposed to be the correct keys with appropriate entropy) but given that this resulted in incorrect signatures, they were resigned with flawed keys.”30 The CA’s signing configuration pointed at the strong keys, the ones the ceremony meant to use. The certificates contained the weak public keys. Every signature from a strong key failed against its certificate, which is exactly how ECDSA is designed to fail when the wrong key signs. The response was to find the key that did verify, the weak one, and sign with that. After that, everything verified.
That failed verification was the only visible symptom of the real problem, sixteen months before anyone recovered a key. It said, as precisely as mathematics can say anything, that the key the CA was signing with was not the key its certificates vouched for. eMudhra treated it as a configuration problem and made it pass by signing with the key the certificates named. A signature that fails to verify is a key problem until proven otherwise.
Chrome’s root program has since asked eMudhra why, in June 2025, it re-signed the CRLs instead of halting, finding out why two key pairs existed for each CA, and revoking the certificates; and why its incident report did not mention that two key pairs existed at all.33 eMudhra’s status page says the hierarchy “passed two independent audit cycles” after that.4
The pattern did not stop there. On 14 October 2026, three days before Wayne’s report, eMudhra published a CRL for its root emSign Root TLS CA - G1 whose signature did not verify under that root’s key. Another participant traced the signature to the key of one of eMudhra’s OCSP responder certificates. Wayne, who reported it, added that it was one of several CRLs signed that day with OCSP responder keys.373839 Chrome has asked why the automatic checks promised in 2025 did not catch it.33
Six days in October
| Time (UTC) | What happened |
|---|---|
| 17 Oct, 16:27 | Wayne, running a test over the CCADB corpus of CA certificates, finds the break in the elliptic-curve keys.1 |
| 17 Oct, 18:05 | Wayne emails Apple, Chrome, Microsoft, Mozilla and eMudhra, with a message signed by each of the eight keys as proof.12 |
| 17 Oct, 19:45 | Chrome finishes corroborating and lands an update to CRLSet, Chrome’s pushed blocklist, that blocks the six TLS intermediates by the hash of their public keys.40 |
| 17 Oct, 20:06 | Mozilla’s formal record starts here, when Chrome forwards the report to [email protected].41 |
| 17 Oct, 21:28 | eMudhra acknowledges, and says that EM DV TLS CA - G3C was live and is now disabled.1 |
| 17 Oct, 21:48 | Mozilla recovers the private keys independently, without knowing Wayne’s method.41 |
| 17 Oct, 22:59 | The block in OneCRL, Firefox’s pushed blocklist, is complete. It reaches users without an update or a restart.4142 |
| 18 Oct, 09:24 to 09:38 | eMudhra revokes its eight certificates, giving the reason as CA compromise.29 |
| 19 Oct, 01:53 | Microsoft signs a new list of disallowed certificates that names all eight keys.43 |
| 19 Oct | Sectigo revokes its eight cross-certificates. Mozilla starts disclosing the issue to other trust store operators. Around 22:00, Android devices receive the equivalent of Chrome’s block through Google Play services.444540 |
| 20 Oct | NSS 3.131.1 removes emSign ECC Root CA - G3, and Firefox 158.0.1 and new ESR releases ship with the block built in. Mozilla and Chrome announce that they are removing the root, and eMudhra stops issuing from every intermediate below it.464754849 |
| 21 Oct | Firefox for Android 158.0.2 fixes profiles that did not apply the new block at startup. Chrome sends eMudhra twelve questions.5033 |
| 22 Oct | eMudhra announces that it will leave the global TLS certificate business. Apple products block the intermediates, according to the checker site.512 |
| 27 Oct, planned | Chrome Root Store version 43, without emSign ECC Root CA - G3, is expected to reach stable Chrome on Tuesday morning, Pacific time.40 |
From report to block in the browser with the largest share took 100 minutes. Anyone can still check the proof that started it. The checker site, which Dennis Jackson of Mozilla publishes with help from the community, offers Wayne’s message, the eight certificates and the eight signatures, and two OpenSSL commands: one to extract each certificate’s public key, and one to verify the signature on message.txt with it.2
Who could have been fooled
At discovery, the six TLS intermediates chained to roots trusted by Chrome, Mozilla, Apple and Microsoft, and the two code-signing intermediates to a root that Microsoft trusted for code signing.41 That was the trust they had on 17 October, not the trust they have now.
The paths differed by platform: Windows trusted eMudhra’s two G3 issuing roots directly, the other programs went through the cross-certificate from emSign ECC Root CA - G3, and Sectigo’s copies added a path to AAA Certificate Services on Apple and Microsoft platforms.11
Why the Sectigo cross-sign widened the problem
Cross-signing is common and usually sensible. A new root takes years to reach old devices, so an established CA signs the newcomer’s intermediates, and old devices that trust the established root accept them. Here it had three effects.
Revocation had to happen twice. A CRL names serial numbers under one issuer. eMudhra’s revocations on 18 October covered the certificates its own roots had issued. The eight Sectigo certificates, with their own serial numbers under AAA Certificate Services, stayed valid until Sectigo revoked them on 19 October, offline from its root.1944
A second organization’s root became part of the incident. Sectigo had signed what eMudhra sent it, “the CSR from these weaker keys” in eMudhra’s words, and its certificates are dated 14 May 2025.3033 A CA that cross-signs another CA vouches for a key it did not generate and can inspect only through the public half. Chrome has asked whether eMudhra, after June 2025, told Sectigo or any root program that the keys Sectigo had certified were not the keys eMudhra meant to generate.33
The blocks had to be on keys. Chrome’s CRLSet blocked by the hash of each public key, and Microsoft’s disallowed list carries a hash of each public key too, so each block covered both copies of every key, and would have covered a third copy that nobody had found yet.4043 Mozilla needed no entries for the Sectigo copies at all: AAA Certificate Services was no longer in its store, so Firefox already rejected them.52 What was compromised is a key. Certificates are statements about a key. When the key goes, block the key.
What the logs can and cannot say
Nobody has reported misuse of the keys. How much that means depends on the client.
eMudhra says that only one of the eight, EM DV TLS CA - G3C, was ever put into production, and that none of them issued an end-entity certificate.294 That limits how many legitimate certificates needed replacing. It says nothing about forgeries, because a forgery would never pass through eMudhra’s systems.
For forgeries the evidence is Certificate Transparency. Chrome accepts a publicly trusted TLS certificate only with proof that it was logged, and so do Android apps that target the latest SDK or opt in. Google reports that no TLS certificate chaining to the six intermediates was logged before Chrome’s block. So no forged certificate could have been accepted by Chrome, or by any other client that enforces CT, in all the months the keys sat in public.40
Many clients do not enforce CT: most HTTP libraries outside browsers, curl, apps with their own trust stores, embedded devices. Code signing has no CT at all. For those clients there is no record either way. The checker site says it plainly: “We do not know if attackers exploited the flawed key generation to target these clients.” On 22 October it added test hosts whose leaf certificates were never logged, so that anyone can see whether their client accepts an unlogged certificate from a compromised intermediate.2 eMudhra says it is not aware of evidence that the keys were recovered or used before the disclosure.4 With the keys in public certificates since April 2025, “not aware” is all anyone can honestly say.
Wayne was not the only one to find the keys. By 20 October Mozilla knew of several people who had recovered them independently, one of whom had published the result.5 After the disclosure the logs filled up. Once the keys were public, people logged certificates signed with them. One outside count found the first on 19 October at 20:24 UTC, and more than a hundred thousand certificates within four days, nearly all of them for google.com.53 Google has since worked with the log operators so that CT logs reject certificates that chain to these intermediates.40 The certificates logged after the disclosure are demonstrations, not attacks, and they say nothing about what happened before it.
Three layers of damage control
The response used three separate mechanisms. Each covers clients that the one before it cannot reach.
- Revoke the certificates and block the keys. eMudhra and Sectigo revoked their certificates in their CRLs, with CA compromise as the reason. A CRL protects only clients that fetch it, and many never do. So the platforms pushed their own lists: Chrome’s CRLSet within 100 minutes of the report, Firefox’s OneCRL within three hours of Mozilla receiving it, both without an update or a restart, and Microsoft’s disallowed list through Windows’ automatic certificate trust list updates.
- Rely on Certificate Transparency. CT does not stop a CA key from signing. It makes signing visible, and clients that require it reject what was never logged, which is what protected the browsers here; the logs now refuse certificates from these intermediates. CT covers public TLS only and does nothing for code signing or for clients that ignore it.
- Remove the root. A Python program using
certifi, a Rust program usingwebpki-roots, a container with an oldca-certificatespackage, a router in a cupboard: these trust exactly the roots in their bundle and receive no emergency block. For them the only way to stop trusting the intermediates is to stop trusting the root above them. NSS 3.131.1 removedemSign ECC Root CA - G3on 20 October, along withemSign Root CA - C1andemSign ECC Root CA - C3, two removals that eMudhra says were planned and voluntary. The bundles built from NSS changed within a day, and Chrome’s root store is planned to follow on 27 October.464452
Removing a root is the coarsest of the three, and it has a real cost. The root itself was never reported to have a weak key, and removing it breaks every legitimate certificate below it. eMudhra estimated that about 30,000 certificates needed to move, 97% of them issued through ACME. Chrome counted about 30,800 unexpired precertificates under the root, 99.9% of them from two subordinate CAs operated for InCommon, Internet2’s trust service for research and education, which eMudhra describes as a single customer ecosystem in the United States.54334 Those sites did nothing wrong and still had to change CA within days.
As of 22 October, Chrome, Firefox and Android block the keys, Windows has the disallowed list, and the checker site reports Apple products blocking them since that day. NSS has removed the root, and Chrome plans to on 27 October. Microsoft’s trusted root list was last signed on 25 August and still lists all four roots involved, and I have found no announcement about the roots from Apple.55 eMudhra’s RSA root, emSign Root CA - G1, stays: Mozilla said it was not removing it “at this time”, and it is the only eMudhra root left in the copy of the Chrome Root Store in Chromium’s source tree.556
What to take away
The engineering lessons are narrow and concrete.
Bind the request to the key. Before a root signs an intermediate, compare the SPKI in the CSR, by hash, with the public key of the approved HSM object, and have that object sign a fresh challenge that the CSR’s key then verifies.
Run the cheap tests from the section on key size on every new public key, and add a check for duplicates against every key in CCADB and a check of the curve and the point. After the report, Google checked every key in its root stores for the same failure and found none.40
Treat a deviation as a new process. The wrap and unwrap test was a second path through a ceremony designed and audited for one, and its output went into production as if it had followed the first. Whatever comes out of a deviation needs its own check before anything trusts it.
Make the check after signing verify against the key in the certificate, and make a failure stop the process rather than start a search for a key that passes.
Know how each of your programs decides to trust. For people who run systems, the useful question is not whether eMudhra is trusted. It is, for each program: which root store it uses, whether it receives emergency blocks or only root bundles, whether it checks CT, what it does when revocation information cannot be reached, and whether someone added a custom root. The answers differ even on one machine. On macOS the system blocks the intermediates, but the built-in curl, git and python3 verify against /etc/ssl/cert.pem, which does no revocation checking and had not been updated; the checker site gives a command to remove AAA Certificate Services and emSign ECC Root CA - G3 from it.2 TLS Companion’s step 18 shows where a Go client makes this decision. The recorded client sets Config.RootCAs, so it verifies against that pool and knows nothing else. With RootCAs left unset, Go hands the decision to the platform verifier on macOS and Windows, which does receive the operating system’s blocks, and reads the system’s root bundle on Linux.
Where it ends, for now
On 22 October eMudhra’s board decided to “voluntarily withdraw from the global SSL/TLS certificate business”. The statement puts TLS at about 1% of the company’s revenue and says that “social media amplification” brought scrutiny “out of proportion” to it. The G1 roots keep serving existing customers until the transition is complete, and the company’s digital signature business continues.51
The scrutiny was proportionate: the failure was ordinary, and it had announced itself sixteen months earlier. The cryptography did its job, and the browsers did theirs within hours. What failed was a step between them, where a person picked a key by its name and nobody checked its bytes. The question it leaves is not only eMudhra’s. If a certified HSM can be made to produce a private key of 4, every CA that uses that model has to answer for it.57
Try it yourself
Three labs, each a few minutes long. The first needs OpenSSL 3 or later; the others need only Python 3.8 or later and no packages. Nothing here touches a real domain or your system’s trust store.
Build a three-tier chain with OpenSSL
The script makes a root, an intermediate and a site certificate for example.test, with comments that say who generates each key, who keeps each private key and who signs each layer. It then checks the chain three ways: the site certificate for its own name, which must pass; the same certificate for another name, which must fail; and a certificate that the site’s own key signed for evil.test, which must also fail, because the site’s certificate grants no right to act as a CA. It works in a fresh temporary directory, with umask 077 so that other users cannot read the demo keys, and prints where that directory is.
The script: save it as pki-lab.sh and run bash pki-lab.sh
#!/usr/bin/env bash
set -euo pipefail
umask 077
lab_dir=$(mktemp -d "${TMPDIR:-/tmp}/pki-lab.XXXXXX")
printf 'Lab directory: %s\n' "$lab_dir"
cd "$lab_dir"
# 1. The root makes its own P-384 key pair and signs its own certificate.
# Self-signing makes nobody trust it; -CAfile below is this lab's trust decision.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out root.key
openssl req -new -x509 -key root.key -sha384 -days 30 \
-subj '/CN=Lab Root CA' \
-addext 'basicConstraints=critical,CA:TRUE,pathlen:1' \
-addext 'keyUsage=critical,keyCertSign,cRLSign' \
-addext 'subjectKeyIdentifier=hash' \
-out root.pem
# 2. The intermediate makes its own key, which never goes to the root.
# Its CSR is signed with its own private key.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out inter.key
openssl req -new -key inter.key -subj '/CN=Lab Intermediate CA' -out inter.csr
openssl req -in inter.csr -noout -verify
# The root decides CA:TRUE, keyCertSign and pathlen:0 itself, then signs
# the intermediate's public key with the root's private key.
printf '%s\n' \
'basicConstraints=critical,CA:TRUE,pathlen:0' \
'keyUsage=critical,keyCertSign,cRLSign' \
'subjectKeyIdentifier=hash' \
'authorityKeyIdentifier=keyid,issuer' > inter.ext
openssl x509 -req -in inter.csr -CA root.pem -CAkey root.key \
-set_serial 0x1001 -days 10 -sha384 -extfile inter.ext -out inter.pem
# 3. The site makes its own key and CSR. Asking for a SAN obliges no CA.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out site.key
openssl req -new -key site.key -subj '/CN=example.test' \
-addext 'subjectAltName=DNS:example.test,DNS:www.example.test' \
-out site.csr
openssl req -in site.csr -noout -verify
# The intermediate decides the final names and purposes, and signs with its key.
printf '%s\n' \
'basicConstraints=critical,CA:FALSE' \
'keyUsage=critical,digitalSignature' \
'extendedKeyUsage=serverAuth' \
'subjectAltName=DNS:example.test,DNS:www.example.test' \
'subjectKeyIdentifier=hash' \
'authorityKeyIdentifier=keyid,issuer' > site.ext
openssl x509 -req -in site.csr -CA inter.pem -CAkey inter.key \
-set_serial 0x2001 -days 3 -sha384 -extfile site.ext -out site.pem
printf '\nThe three certificates:\n'
for name in root inter site; do
printf '\n%s.pem\n' "$name"
openssl x509 -in "$name.pem" -noout -subject -issuer -serial \
-ext basicConstraints,keyUsage,extendedKeyUsage,subjectAltName
done
# 4. Trust root.pem explicitly. -untrusted only offers candidate intermediates.
# -purpose and -verify_hostname add the checks that lab scripts often forget.
printf '\nVerify the site for its own name:\n'
openssl verify -CAfile root.pem -untrusted inter.pem \
-purpose sslserver -verify_hostname example.test site.pem
# A valid signature for the wrong name must still be refused.
printf '\nVerify the same certificate for another name:\n'
if openssl verify -CAfile root.pem -untrusted inter.pem \
-purpose sslserver -verify_hostname wrong.test site.pem; then
printf 'ERROR: wrong hostname was accepted\n' >&2; exit 1
fi
# 5. The site's key can sign a certificate body; it has no right to act as a CA.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out evil.key
openssl req -new -key evil.key -subj '/CN=evil.test' -out evil.csr
printf '%s\n' \
'basicConstraints=critical,CA:FALSE' \
'keyUsage=critical,digitalSignature' \
'extendedKeyUsage=serverAuth' \
'subjectAltName=DNS:evil.test' > evil.ext
openssl x509 -req -in evil.csr -CA site.pem -CAkey site.key \
-set_serial 0x3001 -days 1 -sha384 -extfile evil.ext -out evil.pem
cat inter.pem site.pem > chain.pem
printf '\nVerify a certificate that the site key signed:\n'
if openssl verify -CAfile root.pem -untrusted chain.pem \
-purpose sslserver -verify_hostname evil.test evil.pem; then
printf 'ERROR: a leaf key was accepted as a CA\n' >&2; exit 1
fi
printf '\nExpected: site.pem verifies; the wrong name and evil.pem fail.\n'
printf 'Files are in %s\n' "$lab_dir"With OpenSSL 4.0.3 the end of the run looks like this. The exact error messages and their number depend on the OpenSSL version and on how it builds the path; what must not change is that the last two checks are refused.
Verify the site for its own name:
site.pem: OK
Verify the same certificate for another name:
CN=example.test
error 62 at 0 depth lookup: hostname mismatch
error site.pem: verification failed
Certificate request self-signature ok
subject=CN=evil.test
Verify a certificate that the site key signed:
CN=example.test
error 79 at 1 depth lookup: invalid CA certificate
CN=example.test
error 26 at 1 depth lookup: unsuitable certificate purpose
CN=Lab Intermediate CA
error 25 at 2 depth lookup: path length constraint exceeded
CN=Lab Root CA
error 25 at 3 depth lookup: path length constraint exceeded
CN=example.test
error 32 at 1 depth lookup: key usage does not include certificate signing
error evil.pem: verification failedThe signature on evil.pem is mathematically fine; it verifies under the public key in site.pem. The path fails on permissions: site.pem says CA:FALSE, has no keyCertSign, and sits below an intermediate whose pathlen:0 allows no further CA. Inside the lab directory, these commands show the rest of the structure:
$ openssl x509 -in site.pem -noout -text
$ openssl asn1parse -in site.pem -i
$ openssl req -in site.csr -noout -text
$ openssl x509 -in inter.pem -noout -pubkey | openssl pkey -pubin -noout -textThe first separates the subject’s key algorithm from the signature algorithm and shows the SAN, AKI and SKI. The second shows the DER structure, including where tbsCertificate ends and the signature begins. The third shows what the site asked for, to compare with what the intermediate granted. The fourth prints the intermediate’s public point, which is exactly what the eMudhra certificates gave away. Do not add the lab root to your system’s trust store: -CAfile makes one command trust it, which is all the lab needs.
Sign, reuse a nonce and count on the toy curve
The script implements the 19-point curve from this essay in plain Python and checks every number used above: the public key , the two signatures, the failed verification when changes, the one altered hash that still passes, the private key found by counting, and and recovered from the reused nonce. It is a teaching model, not an implementation of P-384, and must never be used for real keys.
The script: save it as toy_ecdsa.py and run python3 toy_ecdsa.py
# E: y^2 = x^3 + 2x + 2 (mod 17), G = (5, 1), ord(G) = 19.
P, A, B, N, G = 17, 2, 2, 19, (5, 1)
O = None # the point at infinity
def add(p1, p2):
if p1 is O:
return p2
if p2 is O:
return p1
x1, y1 = p1
x2, y2 = p2
if x1 == x2 and (y1 + y2) % P == 0:
return O
if p1 == p2:
slope = (3 * x1 * x1 + A) * pow(2 * y1, -1, P) % P
else:
slope = (y2 - y1) * pow(x2 - x1, -1, P) % P
x3 = (slope * slope - x1 - x2) % P
return x3, (slope * (x1 - x3) - y1) % P
def mul(k, point):
result = O
while k: # double and add, one bit of k at a time
if k & 1:
result = add(result, point)
point = add(point, point)
k >>= 1
return result
def valid_public(point):
if point is O:
return False
x, y = point
return (0 <= x < P and 0 <= y < P
and (y * y - x * x * x - A * x - B) % P == 0
and mul(N, point) is O)
def sign(z, d, k):
r = mul(k, G)[0] % N
s = pow(k, -1, N) * (z + r * d) % N
assert r and s
return r, s
def verify(z, signature, public):
r, s = signature
if not (valid_public(public) and 1 <= r < N and 1 <= s < N):
return False
w = pow(s, -1, N)
point = add(mul(z * w % N, G), mul(r * w % N, public))
return point is not O and point[0] % N == r
d, k, z1, z2 = 7, 3, 10, 4
Q = mul(d, G)
sig1, sig2 = sign(z1, d, k), sign(z2, d, k)
# Failure 1: a small key space. Count until kG lands on Q.
counted_d = next(i for i in range(1, N) if mul(i, G) == Q)
# Failure 2: one nonce for two messages. Solve for k, then d.
r, s1 = sig1
s2 = sig2[1]
recovered_k = (z1 - z2) * pow(s1 - s2, -1, N) % N
recovered_d = (s1 * recovered_k - z1) * pow(r, -1, N) % N
assert Q == (0, 6) and sig1 == (10, 14) and sig2 == (10, 12)
assert verify(z1, sig1, Q) and verify(z2, sig2, Q)
assert not verify(11, sig1, Q) # change the message, and the signature fails
assert verify(2, sig1, Q) # the mirror twin: z' = -z - 2rd sends X to -R
assert counted_d == recovered_d == d and recovered_k == k
print("public key Q:", Q, " signatures:", sig1, sig2)
print("d by counting:", counted_d, " k and d from the reused nonce:", recovered_k, recovered_d)$ python3 toy_ecdsa.py
public key Q: (0, 6) signatures: (10, 14) (10, 12)
d by counting: 7 k and d from the reused nonce: 3 7On this curve counting always works, because every key is small. The difference on P-384 is that a correct key hides among about values, and the eMudhra keys hid among 255.
Recover the real keys
This lab studies a published failure: the walk from the section on key size, run against the real certificates. The checker site publishes all sixteen in one JSON file. The script pulls the public point out of each certificate, adds to itself limit - 1 times on the real P-384 curve, 255 times as written, and prints every certificate whose key it meets on the way. All of these certificates are revoked, the platforms block their keys, and the key values have been public since 20 October, so the script recovers nothing an attacker could still use. What it shows is how little work the recovery takes.
The script: save it as recover_emudhra.py
"""Recover the eight eMudhra CA keys from their public certificates.
Usage: python3 recover_emudhra.py impacted-intermediates.json
"""
import base64
import json
import sys
import time
# P-384 (secp384r1) domain parameters, from NIST SP 800-186.
p = 2**384 - 2**128 - 2**96 + 2**32 - 1
a = p - 3
G = (
0xAA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7,
0x3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F,
)
def add(P, Q):
if P is None:
return Q
if Q is None:
return P
(x1, y1), (x2, y2) = P, Q
if x1 == x2 and (y1 + y2) % p == 0:
return None
if P == Q:
lam = (3 * x1 * x1 + a) * pow(2 * y1, -1, p) % p
else:
lam = (y2 - y1) * pow(x2 - x1, -1, p) % p
x3 = (lam * lam - x1 - x2) % p
return x3, (lam * (x1 - x3) - y1) % p
# In a P-384 certificate the subject's public key follows the curve OID
# (06 05 2b 81 04 00 22) and a BIT STRING header (03 62 00): 04 || x || y.
MARK = bytes.fromhex("06052b8104002203620004")
def public_point(pem):
der = base64.b64decode("".join(l for l in pem.splitlines() if "-----" not in l))
i = der.index(MARK) + len(MARK)
return int.from_bytes(der[i : i + 48], "big"), int.from_bytes(der[i + 48 : i + 96], "big")
wanted = {}
for cert in json.load(open(sys.argv[1])):
label = f"{cert['name']}, issued by {cert['issued_by']}"
wanted.setdefault(public_point(cert["pem"]), []).append(label)
limit = 256
start = time.perf_counter()
point = None
for d in range(1, limit):
point = add(point, G) # point is now d·G
for label in wanted.get(point, []):
print(f"d = {d:3} {label}")
print(f"{time.perf_counter() - start:.3f} s for {limit - 1} additions")$ curl -sSO https://emudhra.we-have-a-problem.com/impacted-intermediates.json
$ python3 recover_emudhra.py impacted-intermediates.json
d = 4 EM OV TLS CA - G3C, issued by emSign Root TLS CA - G3
d = 4 EM OV TLS CA - G3C, issued by AAA Certificate Services
d = 56 EM EV TLS CA - G3C, issued by emSign Root TLS CA - G3
d = 56 EM EV TLS CA - G3C, issued by AAA Certificate Services
d = 66 EM OV CS CA - G3B, issued by emSign Root CS CA - G3
d = 66 EM OV CS CA - G3B, issued by AAA Certificate Services
d = 115 EM EV TLS CA - G3B, issued by emSign Root TLS CA - G3
d = 115 EM EV TLS CA - G3B, issued by AAA Certificate Services
d = 155 EM OV TLS CA - G3B, issued by emSign Root TLS CA - G3
d = 155 EM OV TLS CA - G3B, issued by AAA Certificate Services
d = 156 EM DV TLS CA - G3B, issued by emSign Root TLS CA - G3
d = 156 EM DV TLS CA - G3B, issued by AAA Certificate Services
d = 230 EM DV TLS CA - G3C, issued by emSign Root TLS CA - G3
d = 230 EM DV TLS CA - G3C, issued by AAA Certificate Services
d = 237 EM OV CS CA - G3C, issued by emSign Root CS CA - G3
d = 237 EM OV CS CA - G3C, issued by AAA Certificate Services
0.005 s for 255 additionsEach key appears twice, once for eMudhra’s certificate and once for Sectigo’s: two certificates, one key. Set limit to 2**20 and the same script becomes the cheap test from the section on key size: on an honest key it finds nothing, in under a minute on a laptop.
Ten questions to check yourself
Each answer opens with a click.
1. Does a certificate contain only a hash?
No. It contains the whole tbsCertificate: names, serial number, validity, the subject’s public key and the extensions. The hash is an intermediate step in signing those exact DER bytes, and the signature is stored next to them.6
2. What does it mean that a certificate "covers" a website?
Its SAN lists the DNS names or IP addresses that a client may match against the name it connected to. The certificate does not contain the site’s code or pages, and writing a name into it grants nothing by itself. The CA has to validate the name before it signs.18
3. Does the parent CA hand its private key to the child?
The opposite. The child generates and keeps its own private key, and sends its public key in a CSR. The parent signs a certificate containing the child’s public key with the parent’s private key. The CSR signature and the CA’s signature are two different operations made with two different keys.12
4. Must a root's name say "Root CA"?
No. Issuer and subject names are distinguished names, not unique identifiers, and a root is trusted because a trust store contains its key, not because of its name or its self-signature. emSign Root TLS CA - G3 was a root on Windows and an ordinary link in the path everywhere else.10
5. Does a client try every root and intermediate it knows?
No. It narrows the candidates with the intermediates the server sent, issuer and subject names, AKI and SKI, caches and sometimes AIA downloads. Cross-signing can give it more than one path. What decides is the signature check and the constraints, never a match between identifier strings.17
6. Can a website's private key sign certificates for other websites?
It can produce a correctly signed certificate, but no correct validator accepts it, because the website’s certificate says CA:FALSE and lacks keyCertSign. The evil.pem step in the first lab is exactly this case.
7. Does a compromised intermediate key decrypt all past HTTPS traffic?
No. It lets an attacker forge new identities. Using them still requires a position on the victim’s network path and a client that accepts the forged chain despite CT and emergency blocks. A CA key never encrypts traffic, and sessions whose keys came from an ephemeral key exchange, as in every full TLS 1.3 handshake, stay protected. The weaker case is TLS 1.2 with RSA key transport, where a stolen leaf key opens recorded traffic, as TLS Companion’s recording shows.3
8. Does a 384-bit P-384 key give 384 bits of security?
No. A correct key is drawn from about values, and the best general attacks need about the square root of that, operations, so it gives about 192 bits. The eMudhra keys had fewer than 256 possible values, which is less than 8 bits.21
9. Is nonce reuse the same failure as this incident?
No. A reused or leaked nonce lets an attacker compute a private key that was generated correctly, from two public signatures. In this incident the long-term key itself was tiny, and it could be found from the public key without seeing a single signature.24
10. Is there one switch, CT, revocation or a browser update, that fixes this everywhere?
No. CT protects only clients that enforce it, and only for public TLS. CRLs and OCSP help clients that fetch them. OneCRL, CRLSets and Microsoft’s disallowed list reach only their own platforms, and root removal reaches only software whose bundle gets updated. Code signing, old devices, independent CA bundles and enterprise trust stores each have to be checked on their own terms.4042
Footnotes
-
Bug 2083449, comment 2: Wayne’s timeline, from the discovery at 16:27 UTC to eMudhra’s acknowledgement at 21:28 UTC, and the serial numbers of Sectigo’s cross-certificates. ↩ ↩2 ↩3 ↩4 ↩5
-
emudhra.we-have-a-problem.com, published by Dennis Jackson of Mozilla with contributions from the community: Wayne’s proof, the blocking dates by platform, test hosts with and without CT, the list of patched software, and workarounds such as the one for macOS. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
RFC 8446, The Transport Layer Security (TLS) Protocol Version 1.3, sections 2 and 4.4. ↩ ↩2
-
eMudhra, statement on the certificate security incident, with updates from 20 to 22 October 2026. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Bug 2083449, comment 18: Mozilla’s emergency removal of trust in
emSign ECC Root CA - G3, and its decision not to removeemSign Root CA - G1at this time. ↩ ↩2 ↩3 ↩4 -
RFC 5280, section 4.1, the basic certificate fields. ↩ ↩2
-
The checker site’s impacted-intermediates.json, which carries all sixteen certificates in PEM. The excerpts are OpenSSL 4.0.3 output for two of them. ↩ ↩2
-
RFC 5280, section 4.2.1: key usage (4.2.1.3), basic constraints (4.2.1.9), name constraints (4.2.1.10) and extended key usage (4.2.1.12). ↩
-
RFC 5280, sections 4.2.1.1 and 4.2.1.2, the authority and subject key identifiers. The SKI of
EM DV TLS CA - G3Bequals the SHA-1 hash of its public key, which I checked on all eight. ↩ -
RFC 5280, section 6.1.1, the inputs to path validation, including the trust anchor. ↩ ↩2
-
The checker site’s roots.json: the roots the intermediates chain to, and each root’s status in the four root programs. ↩ ↩2 ↩3
-
RFC 8659, DNS Certification Authority Authorization (CAA). ↩
-
RFC 9162, Certificate Transparency version 2.0, and the Chrome CT policy. ↩
-
CA/Browser Forum, Baseline Requirements for TLS server certificates, section 6.1.1.1, CA key pair generation. ↩
-
RFC 5280, section 6, certification path validation. ↩ ↩2
-
Bug 2083449, comment 6: Sectigo on its cross-signed CAs and on where its root was still trusted. ↩ ↩2
-
RFC 8446, section 4.4.3, Certificate Verify. ↩
-
NIST SP 800-186, recommendations for discrete logarithm-based cryptography: the P-384 domain parameters and their security strength. ↩ ↩2
-
Bug 2083449, comment 5: Mozilla’s preliminary investigation: the 24 intermediates of 30 April 2025, private keys below 256, the A and A-1 series, the local agents, and the link to bug 1970728 with the archived crt.sh page. ↩ ↩2 ↩3 ↩4
-
Scott Helme, Eight bits of trust: eMudhra’s CA keys cracked in milliseconds, 20 October 2026. ↩
-
NIST FIPS 186-5, Digital Signature Standard, section 6.4 on ECDSA signature generation and verification. ↩ ↩2
-
RFC 6979, deterministic usage of DSA and ECDSA; FIPS 186-5 includes deterministic ECDSA. ↩
-
OpenSSL, provider-signature, the “nonce-type” parameter: the default 0 uses a random , and 1 selects RFC 6979. ↩
-
RFC 8032, the Edwards-curve Digital Signature Algorithm (EdDSA). ↩
-
RFC 8017, PKCS #1 version 2.2, section 8.1 on the RSASSA-PSS salt. ↩
-
Bug 2083449, comment 10: eMudhra’s table of revocation times, which intermediate was in production, and the HSMs used. ↩ ↩2 ↩3
-
Bug 2083449, comment 11: eMudhra’s preliminary account of the ceremony, the purpose of series B and C, and the 2025 CRLs. ↩ ↩2 ↩3 ↩4
-
OASIS, PKCS #11 Specification Version 3.1, sections 5.18.3 and 5.18.4,
C_WrapKeyandC_UnwrapKey. ↩ -
Bug 2083449, comment 33: what kind of operation could create a private key below 256, and the ceremony records the Baseline Requirements call for. ↩
-
Bug 2083449, comment 30: the Chrome Root Program’s twelve questions, its count of precertificates under the root, the dates of the 2025 CRLs, the date of the Sectigo cross-certificates and the expected date of Chrome Root Store version 43. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
RFC 5280, section 5, the CRL profile. ↩
-
Mozilla Bugzilla, bug 1970728, “eMudhra: Invalid CRL signatures”, opened on 5 June 2025. ↩
-
Bug 1970728, comment 2: eMudhra’s full incident report of 9 June 2025. ↩
-
Bug 2083449, comment 7: the invalid signature on the 14 October 2026 CRL of
emSign Root TLS CA - G1. ↩ -
Bug 2083449, comment 34: the October 2026 root CRL traced to an OCSP responder’s key. ↩
-
Bug 2083449, comment 36: Wayne on the other CRLs signed with OCSP responder keys. ↩
-
Google, Protecting Chrome and Android users from compromised eMudhra intermediate CA certificates, 20 October 2026, and the Chrome Root Program’s post to dev-security-policy of the same day. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Mozilla Bugzilla, bug 2083449, comment 0: the incident record opened by Mozilla, with the time the report arrived, the eight certificates, their issuers and which root programs trusted them. ↩ ↩2 ↩3 ↩4
-
Mozilla Security Blog, Protecting Firefox users following the compromise of eMudhra intermediate certificates, 20 October 2026, and the Firefox 158.0.1 release notes. ↩ ↩2
-
Microsoft’s disallowed certificate trust list, disallowedcertstl.cab, as fetched on 22 October 2026. It was signed at 01:53:30 UTC on 19 October 2026 and contains an MD5 hash of the public key of each of the eight intermediates, so each entry matches both the eMudhra and the Sectigo certificate. ↩ ↩2
-
Bug 2083449, comment 13: Sectigo completes the revocation of its cross-certificates. ↩ ↩2
-
Bug 2083449, comment 14:
certifiandwebpki-rootspick up the change, and Mozilla begins disclosure to other trust store operators. ↩ ↩2 -
NSS 3.131.1 release notes, 20 October 2026, bug 2084101. ↩ ↩2
-
Bug 2083449, comment 17: OneCRL reached clients through Remote Settings on 17 October; the new releases carry a built-in copy. ↩
-
Bug 2083449, comment 20: Chrome commits to the same path as Mozilla. ↩
-
Bug 2083449, comment 21: eMudhra stops issuing from every intermediate below
emSign ECC Root CA - G3. ↩ -
Bug 2083449, comment 29 and MFSA 2026-116: Firefox for Android 158.0.2. ↩
-
eMudhra, eMudhra to exit global SSL/TLS certificate business, 22 October 2026. ↩ ↩2
-
Bug 2083449, comment 25: why OneCRL needs no entries for the Sectigo cross-certificates. ↩
-
ctlogs.dev, the eMudhra CA compromise, as of 22 October 2026. The page groups certificates by their set of names; the totals count certificates. I have not checked them independently. ↩
-
Bug 2083449, comment 19: eMudhra’s estimate of about 30,000 certificates to migrate, 97% from ACME. ↩
-
Microsoft’s trusted root list, authrootstl.cab, as fetched on 22 October 2026. It was signed at 07:24:34 UTC on 25 August 2026 and lists
emSign ECC Root CA - G3,emSign Root TLS CA - G3,emSign Root CS CA - G3andAAA Certificate Services. ↩ -
Chromium’s root_store.textproto, as of 22 October 2026: version 43, with
emSign Root CA - G1as the only eMudhra root. ↩ -
Bug 2083449, comment 31: either the keys were generated outside the HSM and loaded, or the HSM could be configured to generate them, and both answers matter for other CAs. ↩
