CRX3 is the format Chromium reads today. A short fixed header gives the size of a
Protocol Buffers message, and the ZIP archive follows it. Numbers in the fixed part are
unsigned and little-endian. The message is defined in Chromium’s
crx3.proto.
The file
0Magic number43 72 32 34,Cr244 bytes4Version03 00 00 004 bytes8Header size N, the size of the message that follows4 bytes12CrxFileHeader a Protocol Buffers messageN bytes12+NZIP archive the extension’s filesthe rest
| Offset | Size | Name | Description |
|---|---|---|---|
0 | 4 | Magic | The ASCII characters Cr24. |
4 | 4 | Version | 3. |
8 | 4 | Header size | N, the length of the message in bytes. Readers should turn down a value too large to hold in memory. |
12 | N | Header | The binary encoding of a CrxFileHeader. |
12+N | Archive | A ZIP archive, to the end of the file. Its offsets count from its own start. |
Chromium’s verifier also scans the header for the ZIP end markers
50 4B 05 06, 50 4B 06 07 and 50 4B 06 06, and
refuses a file whose header contains one.
A delta update file starts with CrOD instead of Cr24. It
is not covered here.
Protocol Buffers
The header is Protocol Buffers binary. Each field is a varint tag (the field number times 8, plus a wire type) and then the value. Every field here has wire type 2, so a varint length comes next, then that many bytes.
For example, the tag of field 2 is 12, of field 3 is 1A
and of field 4 is 22. Field 10000 needs a three-byte tag,
82 F1 04.
CrxFileHeader
| Field | Name | Type | Description |
|---|---|---|---|
| 2 | sha256_with_rsa | repeated AsymmetricKeyProof | An RSA proof: a key and a signature with SHA-256. |
| 3 | sha256_with_ecdsa | repeated AsymmetricKeyProof | An ECDSA proof: a key on the NIST P-256 curve, in named-curve form, and a signature. |
| 4 | verified_contents | bytes | Signatures over the archive’s files: verified contents. Optional. |
| 10000 | signed_header_data | bytes | A SignedData message, stored as bytes because every proof signs exactly these bytes. |
AsymmetricKeyProof
| Field | Name | Type | Description |
|---|---|---|---|
| 1 | public_key | bytes | The public key, an X.509 SubjectPublicKeyInfo structure for RSA. |
| 2 | signature | bytes | The signature. See what is signed. |
SignedData
| Field | Name | Type | Description |
|---|---|---|---|
| 1 | crx_id | bytes | Exactly 16 raw bytes, the extension’s ID. See how the ID is formed. |
So the end of a header normally reads 82 F1 04 (field 10000),
12 (its length, 18), 0A (field 1 of
SignedData), 10 (16), and the 16 bytes of the ID.
Verified contents
verified_contents holds a JSON document, encoded as UTF-8 and then
compressed with gzip. Chromium reads it as an array, and uses the entry whose
description is treehash per file. That entry has a
signed_content object:
| Key | Holds |
|---|---|
payload | The signed content, as base64url text. |
signatures | An array. Chromium uses the entry whose header.kid is webstore, with its protected value (base64url) and signature (base64url). The signature, RSA with SHA-256, covers the protected text, a full stop and the payload text. |
The payload, once decoded, is JSON too:
| Key | Holds |
|---|---|
item_id | The extension’s ID. |
item_version | The extension’s version. |
content_hashes | An array; Chromium uses the first entry whose format is treehash. |
block_size, hash_block_size | In that entry: the block size of the tree hash. The two must be equal. |
files | In that entry: an array of path (a relative path) and root_hash (base64url). |