So far there is no way to check the integrity of a web bundle. For example some responses may be removed from the web bundle or be replaced with compromised ones. To solve this problem it makes sense to have a signature that would guarantee that the web bundle was not modified.
Currently web bundles support signatures of individual responses (not of the whole bundle). In order not to mix up the signature that guarantees integrity of the whole web bundle with the signature that signs just individual responses we will call the first one an "integrity signature".
The integrity signature must guarantee the integrity of the web bundle with one signature, whose public key is trusted.
Please note that the user agent should know which public key to trust as an attacker can modify the app and resign it with another private key. The source of trust can be:
- the trusted public keys can be included in enterprise policy;
- the public key can be obtained from a trusted app distributor;
- any other trusted source.
In future it makes sense to add the ability to have more than one signature (one signature would be set by the developer, another by the app distributor) and key rotation (similar to what APK Signature V3 has). We should take this into account while proposing the solution in order to simplify the future development.
We will introduce a data structure called integrity block that will contain:
- integrity signature
- additional relevant to signature information (e.g. public key, certificate, expiry date, etc).
The exact structure of it will be discussed later.
The integrity block will be located before the unsigned web bundle. Basically a web bundle with integrity signature will be a sequence of 2 CBOR objects:
- integrity-block (defined below), which contains a signature and related data;
- web-bundle (defined here) which is the content of the signed web bundle.
In the binary form this sequence will be stored as a concatenation of the serialized integrity-block and web-bundle CBOR objects. Unfortunately there is no CDDL notation for CBOR sequences in the top level.
The integrity block structure will contain the following information: Attributes in the form of a CBOR map. The attributes must always contain exactly one of the following two entries:
- A 32-byte Ed25519 public key (the attributes map key is
ed25519PublicKey
); - A 33-byte Ecdsa P-256 public key for the Ecdsa P-256 SHA-256 signing scheme (the attributes map key is
ecdsaP256SHA256PublicKey
).
In the future there can be more attributes, e.g. expiry date, key rotation attribute, etc. The keys in the map will always be strings.
CDDL of the signature is:
integrity-signature-ed25519 = [
attributes: {
"ed25519PublicKey" => bstr .size 32, ; 32-bytes long Ed25519 public key
},
signature: bstr .size 64, ; 64 bytes long Ed25519 signature
]
integrity-signature-ecdsa-p256-sha256 = [
attributes: {
"ecdsaP256SHA256PublicKey" => bstr .size 33, ; 33-bytes long Ecdsa P-256 public key
},
signature: bstr, ; Ecdsa P-256 SHA-256 signature
]
integrity-signature = integrity-signature-ed25519 / integrity-signature-ecdsa-p256-sha256
Since web bundles might be signed by more than one signature, integrity-signature
objects are wrapped in a signature list which is represented as a CBOR array.
The CDDL notation of the integrity block in its final state with the signature list is as follows:
integrity-block = [
magic: h'F0 9F 96 8B F0 9F 93 A6',
version: bstr .size 4, ; Version value is '2\0\0\0' for release.
attributes: {
"webBundleId" => tstr
}
signature-list: [ +integrity-signature ],
]
Please note that during the signing processes the signature list may be empty, but in the final state (when the web bundle is signed and is ready for validation) there should be at least one integrity signature there.
To be able to recognize the signed bundle by first several bytes of the file we will add a magic number as the first element of the integrity block. Similar to Web Bundles let’s make integrity-block magic equals to the following hex numbers: 0xF0 0x9F 0x96 0x8B 0xF0 0x9F 0x93 0xA6 which are UTF-8 encoded “🖋📦” (U+1F58B, U+1F4E6)
The integrity block will also contain a version field. Also similar to Web Bundles we will make it a bytestring that must be 32 00 00 00 in base 16 (an ASCII "2" followed by 3 0s) for this version of integrity signature. If the recipient doesn't support the version in this field, it must reject the validation of the integrity signature and return an error.
The input for signing is an unsigned web bundle file that doesn have an integrity-block
. We assume that there is a private key and the corresponding public key. The process of signing is following:
-
Generate a minimal valid integrity-block. At this point the
integrity-block
must be deterministically encoded CBOR (see below) and consists of:- Magic.
- Version.
- Attributes.
- Empty signature list.
-
Calculate a SHA-512 hash of the serialized content of the
web-bundle
. -
Create a separate empty signature list (
temp-signature-list
) to store intermediate signing results. -
For each signing key:
- Generate signature
attributes
CBOR map with respect to theintegrity-signature-ed25519.attributes
orintegrity-signature-ecdsa-p256-sha256.attributes
specification accordingly. - Set
data-to-be-signed
as a concatenation of 6 elements listed below (the order must persist):- 64 bit big-endian integer length of the web bundle hash from step 2;
- web bundle hash from step 2;
- 64 bit big-endian integer length of the serialized
integrity-block
from step 1; - serialized
integrity-block
from step 1; - 64 bit big-endian integer length of signature
attributes
; - serialized signature
attributes
.
- Compute the binary signature of
data-to-be-signed
. - Build an
integrity-signature
object that will be an array of 2 elements:- attributes from step 2;
- the binary signature from the previous step.
- Add the
integrity-signature
totemp-signature-list
.
- Assign
integrity-block.signature-list
fromtemp-signature-list
. - Combine the serialized
integrity-block
with the web bundle file in a CBOR sequence (basically storeintegrity-block
and after it append the contents of the web bundle file).
The main assumption is that we can place the whole integrity-block into RAM.
In order for an integrity block to be deemed valid, it should pass both the signature validation requirements as well as identity validation requirements.
Validation of the signature consists of the following steps:
- Read the
integrity-block
from the web bundle with the integrity signature file. - Check that version and magic have expected values.
- Calculate a SHA-512 hash of the serialized content of the
web-bundle
. - Create a minimal valid integrity block (
integrity-block-min
) by clearingsignature-list
. - For each signature in the original integrity block's
signature-list
:
- Verify that the signature conforms to the definition of
integrity-signature
. If not, this signature is considered unknown and silently ignored. - If the
attributes
map contains any entries unknown to the current client, these are also silently discarded and do not affect the validity check. - Read the public key from
attributes
. - Validate the
signed-data
with the public key and signature obtained fromintegrity-signature
. Thesigned-data
is a concatenation of:- 64 bit big-endian integer length of the web bundle hash from step 3;
- web bundle hash from step 3;
- 64 bit big-endian integer length of the
integrity-block
from step 4; - serialized
integrity-block
from step 4; - 64 bit big-endian integer length of the attributes;
- serialized attributes of the signature.
- If there are no invalid signatures and at least one known signature, the signature validation process succeeds.
Signature validation establishes the trust of an signed web bundle; however, its identity is defined via integrity-block.attributes.webBundleId
and has to be validated separately to allow the embedder to perform key rotations.
The standard validation process goes as follows:
- Query the embedder regarding the expected public key
K
for the givenweb-bundle-id
. - If
K
is defined, ensure thatK
is present inintegrity-block.signature-list
.
- If yes, the identity vaidation succeeds; if not, fails.
- If there's no expected public key for
web-bundle-id
, check whether this id can be obtained from any public key inintegrity-block.signature-list
according to the conversion process outlined in the Signed Web Bundle IDs explainer.
- If yes, the identity validation succeeds; if not, fails.
The supported signing algorithms are
Note that the signatures are not required to be homogeneous -- signature-list
might mix in signatures of different kinds.