This document describes a digital signature format specifically designed for WebAssembly modules.
It satisfies the following requirements:
- Is is possible to verify a module before execution.
- It is possible to add signed custom sections to an already signed module.
- It is possible to verify a subset of the module sections, at predefined boundaries.
- The entire module doesn't have to fit in memory in order for a signature to be verified.
- Signatures can be embedded in a custom section, or provided separately ("detached signatures").
- Multiple signatures and algorithms can be used to sign a single module.
- Signing an entire module doesn't require the module to be modified.
Signatures have no semantic effects. A runtime that doesn't support signatures can ignore them.
Note: this is still a work in progress.
The WebAssembly Modules Signatures repository is the main discussion venue for this project.
We also hold bimonthly open meetings. See the above repository for details.
Two custom section types are required for this signature format:
- Custom sections named
signature
, storing signature data, when this information is embedded in the module. - Custom sections named
signature_delimiter
, separating consecutive sections that can be signed and verified independently.
Example structure of a module with an embedded signature and delimiters:
sections |
---|
signature |
part (one or more consecutive sections) |
delimiter |
part (one or more consecutive sections) |
delimiter |
... |
part (one or more consecutive sections) |
delimiter |
A module can be split into one or more parts (one or more consecutive sections).
Each part is followd by a delimiter. A delimiter is a custom section named signature_delimiter
, containing a 16 byte random string.
sections |
---|
p1 = input part 1 (one or more sections) |
d1 = delimiter 1 |
p2 = input part 2 (one or more sections) |
d2 = delimiter 2 |
... |
pn = input part n (one or more sections) |
dn = delimiter n |
If a signature covers the entire module (i.e. there is only one part), the delimiter is optional.
However, its absence prevents additional sections to be added and signed later.
The signature data is a concatenation of the following:
- An identifier representing the version of the specification the module was signed with.
- An identifier representing the hash function whose output will be signed.
- A sequence of hashes and their signatures.
A hash is computed for all the parts to be signed:
hn = H(pn‖dn)
A signature is computed on the concatenation of these hashes:
hashes = h1 ‖ h2 ‖ … ‖ hn
s = Sign(k, "wasmsig" ‖ spec_version ‖ hash_id ‖ hashes)
One or more signatures can be associated with hashes
, allowing multiple parties to sign the same data.
The signature data can either be stored in the payload of a custom section named signature
, or provided separately.
If embedded in a module, the section must be the first section of the module.
The signature data contains a sequence of signatures, where the end of the last signature must coincide with the last byte of the data.
Field | Type | Description |
---|---|---|
spec_version | byte |
Specification version (0x01 ) |
hash_fn | byte |
Hash function identifier (0x01 for SHA-256) |
signed_hashes_count | varuint32 |
Number of signed hashes |
signed_hashes* | byte |
Sequence of hashes and their signatures |
where signed_hashes
is encoded as:
Field | Type | Description |
---|---|---|
hashes_count | varuint32 |
Number of the concatenated hashes in bytes |
hashes | bytes |
Concatenated hashes of the signed sections |
signature_count | varuint32 |
Number of signatures |
signatures | signature* |
Sequence of signature_count signature records |
where a signature
is encoded as:
Field | Type | Description |
---|---|---|
key_id_len | varuint32 |
Public key identifier length in bytes (can be 0 ) |
key_id | bytes |
Public key identifier |
signature_id | byte |
Signature algorithm identifier |
signature_len | varuint32 |
Signature length in bytes |
signature | bytes |
Signature for hashes that can be verified using key_id |
- Verify the presence of the signature section, extract the specification version, the hash function to use and the signatures.
- Check that at least one of the signatures is valid for
hashes
. If not, return an error and stop. - Split
hashes
(included in the signature) intoh1 … hn
- Read the module, computing the hash of every
(pi, di)
tuple withi ∈ {1 … n}
, immediately returning an error if the output doesn't matchhi
- Return an error if the number of the number of hashes doesn't match the number of parts.
The above format is compatible with partial signatures, i.e. signatures ignoring one or more parts.
In order to do so, a signer only includes hashes of relevant parts.
The format is compatible with partial verification, i.e. verification of an arbitrary subset of a module:
- Verify the presence of the header, extract the specification version, the hash function to use and the signatures.
- Check that at least one of the signatures is valid for
hashes
. If not, return an error and stop. - Split
hashes
(included in the signature) intoh1 … hm
withm
being the last section to verify. - Read the module, computing the hash of the
(pi, di)
tuples to verify, immediately returning an error if the output doesn't matchhi
. - Return an error if the number of the number of hashes doesn't match the number of parts to verify.
Notes:
- Subset verification doesn't require additional signatures, as verification is always made using the full set
hashes
. - Verifiers don't learn any information about removed sections due to delimiters containing random bits.
By default, partial signatures must be ignored by WebAssembly runtimes. An explicit configuration is required to accept a partially signed module.
Signatures can be embedded in a module, or detached, i.e. not stored in the module itself but provided separately.
A detached signature is equivalent to the payload of a signature
custom section.
Given an existing signed module with an embedded signature, the signature can be detached by:
- Copying the payload of the
signature
custom section - Removing the
signature
custom section.
Reciprocally, a detached signature can be embedded by adding a signature
custom section, whose payload is a copy of the detached signature.
Implementations should accept signatures as an optional parameter. If this parameter is not defined, the signature is assumed to be embedded, but the verification function remains the same.
Identifier for the current version of the specification: 0x01
A conformant implementation must include support for the following hash functions:
Function | Identifier |
---|---|
SHA-256 | 0x01 |
For interoperability purposes, a conformant implementation must include support for the following signature systems:
- Ed25519 (RFC8032)
Public and private keys must include the algorithm and parameters they were created for.
Key type | Serialized key size | Identifier |
---|---|---|
Ed25519 public key | 1 + 32 bytes | 0x01 |
Ed25519 key pair | 1 + 64 bytes | 0x81 |
Ed25519 algorithm identifier: 0x01
.
Representation of Ed25519 keys:
- Ed25519 public key:
0x01 ‖ public key (32 bytes)
- Ed25519 key pair:
0x81 ‖ secret key (32 bytes) ‖ public key (32 bytes)
Implementations may support additional signatures schemes and key encoding formats.
The following examples assume the following unsigned module structure as a common starting point:
0: type section
1: import section
2: function section
3: table section
4: memory section
5: global section
6: export section
7: element section
8: code section
9: data section
10: custom section: [.debug_info]
11: custom section: [.debug_macinfo]
12: custom section: [.debug_loc]
13: custom section: [.debug_ranges]
14: custom section: [.debug_abbrev]
15: custom section: [.debug_line]
16: custom section: [.debug_str]
17: custom section: [name]
18: custom section: [producers]
Signed module structure:
0: custom section: [signature]
1: type section
2: import section
3: function section
4: table section
5: memory section
6: global section
7: export section
8: element section
9: code section
10: data section
11: custom section: [.debug_info]
12: custom section: [.debug_macinfo]
13: custom section: [.debug_loc]
14: custom section: [.debug_ranges]
15: custom section: [.debug_abbrev]
16: custom section: [.debug_line]
17: custom section: [.debug_str]
18: custom section: [name]
19: custom section: [producers]
20: custom section: [signature_delimiter]
Content of the signature section, for a single signature:
0x01
(spec_version)0x01
(hash_fn)1
(signed_hashes_count)- signed_hashes:
1
(hashes_count)<32 bytes>
(hashes=SHA-256(sections 1..end))1
(signatures_count
)- signature:
0
(key_id_len
- no key ID)0x01
(Ed25519 algorithm identifier)64
(signature_len
)<64 bytes>
(Ed22519(k, hashes))
In this example, a signature can be verified for any of the following ranges of sections:
- 1..10 (until the custom sections)
- 1..17 (code, data and debugging information)
- 1..end (entire module)
0: custom section: [signature]
1: type section
2: import section
3: function section
4: table section
5: memory section
6: global section
7: export section
8: element section
9: code section
10: data section
11: custom section: [signature_delimiter]
12: custom section: [.debug_info]
13: custom section: [.debug_macinfo]
14: custom section: [.debug_loc]
15: custom section: [.debug_ranges]
16: custom section: [.debug_abbrev]
17: custom section: [.debug_line]
18: custom section: [.debug_str]
19: custom section: [signature_delimiter]
20: custom section: [name]
21: custom section: [producers]
22: custom section: [signature_delimiter]
Content of the signature section, for a single signature:
0x01
(spec_version)0x01
(hash_fn)1
(signed_hashes_count)- signed_hashes:
3
(hashes_count)<96 bytes>
(hashes=SHA-256(sections 1..12) ‖ SHA-256(sections 1..20) ‖ SHA-256(sections 1..end))1
(signatures_count)- signature:
0
(key_id_len - no key ID)0x01
(Ed25519 algorithm identifier)64
(signature_len)<64 bytes>
(Ed22519(k, hashes))
Variant with two signatures for the same content and key identifiers:
0x01
(spec_version)0x01
(hash_fn)1
(signed_hashes_count)- signed_hashes:
3
(hashes_count)<96 bytes>
(hashes=SHA-256(sections 1..12) ‖ SHA-256(sections 1..20) ‖ SHA-256(sections 1..end))2
(signatures_count)- signature_1:
5
(key_id_len)"first"
(key_id)0x01
(Ed25519 algorithm identifier)64
(signature_len
)<64 bytes>
(Ed22519(k_first, hashes))
- signature_2:
6
(key_id_len)"second"
(key_id)0x01
(Ed25519 identifier)64
(signature_len
)<64 bytes>
(Ed22519(k_second, hashes))