A CRX3 header can carry several proofs, each a public key and a signature. Every one signs the same bytes. Chromium requires every signature to verify, and requires that one key matches the ID in the signed data.
What is signed
0Context the 15 ASCII charactersCRX3 SignedDataand a zero byte16 bytes16Size the length of the signed data, little-endian4 bytes20Signed data the value ofsigned_header_datavaries…Archive the rest of the file after the headervaries
RSA proofs (field 2 of the header) are checked by Chromium as RSA PKCS #1 v1.5 with
SHA-256; a comment in crx3.proto calls them PSS signatures, but the
verifier does not. ECDSA proofs (field 3) use the NIST P-256 curve with SHA-256. Each
public key must be a SubjectPublicKeyInfo of the matching type.
The extension ID
An extension’s ID comes from a public key. Take the SHA-256 of the key’s bytes (the
X.509 SubjectPublicKeyInfo) and keep the first 16 bytes. Those 16 bytes, written as
hexadecimal, are 32 digits, and each digit becomes a letter from
a to p: 0 becomes a, 9 becomes j,
and A to F become k to p. So an ID is 32 letters and never
a number.
In CRX3, the crx_id in the signed data holds those 16 bytes. The
developer’s key is the proof whose SHA-256 begins with the same 16 bytes. If there is
none, the file does not verify.
In CRX2, the ID comes the same way from the file’s only public key.
The manifest may also have a key, the same public key in base64. An
unpacked extension gets its ID from it, so a developer can keep the same ID while
working on a copy.
Chrome Web Store
A file from the Chrome Web Store carries the developer’s proof and the store’s, and
can carry others. Chromium can require the store’s proof, which it recognises by the
SHA-256 of its public key,
61 F7 F2 A6 BF CF 74 CD 0B C1 FE 24 97 CC 9B 04 25 4C 65 8F 79 F2 14 53 92 86 7E A8 36 63 67 CF.