Describe the signature scheme in docs/FORMAT.md as the code builds it (closes #187)
check / check (push) Waiting to run
check / check (push) Waiting to run
The Signature Scheme section now states what signatureString builds: the magic, the outer uuid as lower-case hex with no hyphens, and the outer sha256 as the lower-case hex of its multihash, which starts 1220, with an example string taken from a run of the code. It says signer is the upper-case hex fingerprint of the signing primary key and that a signature is stored only in the manifest. The mf.proto comments on fields 201 to 203 say the same, and mf.pb.go is regenerated. A new test pins the example string. Nothing about what is signed or how changes. Model: opus-5-5
This commit was merged in pull request #188.
This commit is contained in:
+37
-20
@@ -36,8 +36,8 @@ The outer message contains:
|
||||
| `sha256` | 104 | bytes | SHA-256 hash of the **compressed** `innerMessage` (corruption detection) |
|
||||
| `uuid` | 105 | bytes | Random v4 UUID; must match the inner message UUID |
|
||||
| `innerMessage` | 199 | bytes | Zstd-compressed serialized `MFFile` message |
|
||||
| `signature` | 201 | bytes (optional) | OpenPGP detached signature (ASCII-armored or binary) |
|
||||
| `signer` | 202 | bytes (optional) | Fingerprint of the signing key |
|
||||
| `signature` | 201 | bytes (optional) | One OpenPGP signature over the canonical string, armored or binary |
|
||||
| `signer` | 202 | bytes (optional) | Fingerprint of the signing primary key, in upper-case hex |
|
||||
| `signingPubKey` | 203 | bytes (optional) | Full OpenPGP public key of the signing key (ASCII-armored or binary) |
|
||||
|
||||
### SHA-256 Hash
|
||||
@@ -128,34 +128,51 @@ Implementations must be able to verify SHA-256 multihashes at minimum.
|
||||
|
||||
## Signature Scheme
|
||||
|
||||
Signing is optional. When present, the signature covers a canonical string
|
||||
constructed as:
|
||||
Signing is optional. A signature is stored only in the outer message's
|
||||
`signature` field, never in a separate file. It is one OpenPGP signature over
|
||||
the canonical string below, armored or binary, and does not contain the string,
|
||||
which a verifier builds again from the outer message.
|
||||
|
||||
The canonical string is three parts separated by hyphens (`-`):
|
||||
|
||||
```
|
||||
ZNAVSRFG-<UUID>-<SHA256>
|
||||
```
|
||||
|
||||
Where:
|
||||
- `ZNAVSRFG` is the magic, in ASCII.
|
||||
- `<UUID>` is the 16 bytes of the outer message's `uuid` as 32 lower-case hex
|
||||
digits, with no hyphens.
|
||||
- `<SHA256>` is the lower-case hex of the outer message's `sha256` encoded as a
|
||||
SHA-256 multihash (see Hash Format). That multihash is the algorithm code
|
||||
`0x12`, the digest length `0x20` and the 32 bytes of the hash, so this part is
|
||||
68 hex digits starting `1220`.
|
||||
|
||||
- `ZNAVSRFG` is the magic bytes string (literal ASCII)
|
||||
- `<UUID>` is the hex-encoded UUID from the outer message
|
||||
- `<SHA256>` is the hex-encoded SHA-256 hash from the outer message (covering
|
||||
compressed data)
|
||||
The signature covers exactly the 110 ASCII characters of the string, with no
|
||||
line ending. For example, the UUID `b0fb6918-6298-443b-ada2-1b162f4dc0b3` and a
|
||||
`sha256` of `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
|
||||
in hex give this canonical string:
|
||||
|
||||
Components are separated by hyphens. The signature is an OpenPGP detached
|
||||
signature over this canonical string, stored in the `signature` field of the
|
||||
outer message. The signing key's public key goes in `signingPubKey` and its
|
||||
fingerprint, in hex, in `signer`.
|
||||
```
|
||||
ZNAVSRFG-b0fb69186298443bada21b162f4dc0b3-1220e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
```
|
||||
|
||||
The signature covers `innerMessage` only through `sha256`, so a verifier checks
|
||||
`sha256` against `innerMessage` (see SHA-256 Hash) as well as the signature.
|
||||
|
||||
The signing key's public key goes in `signingPubKey` and the fingerprint of its
|
||||
primary key, in upper-case hex, in `signer`. The reference implementation signs
|
||||
only with version 4 keys, whose fingerprints are 40 hex digits, and writes both
|
||||
the signature and the public key armored.
|
||||
|
||||
A verifier accepts a signed manifest only if `signingPubKey` holds exactly one
|
||||
primary key, `signature` is one good signature over the canonical string made by
|
||||
that key (or one of its subkeys), and `signer` is that key's fingerprint. The
|
||||
reference implementation refuses to load a manifest that fails these checks;
|
||||
`check` and `fetch` given `--require-signature` then compare the required
|
||||
fingerprint with `signer`. It also refuses a manifest whose `signingPubKey`
|
||||
holds a DSA key or subkey, or any secret key or subkey, since checking the
|
||||
self-signatures of a DSA key or the numbers of a secret key can take hours when
|
||||
those numbers are very large.
|
||||
that key (or one of its subkeys), and `signer` is that primary key's
|
||||
fingerprint, compared without regard to case. The reference implementation
|
||||
refuses to load a manifest that fails these checks; `check` and `fetch` given
|
||||
`--require-signature` then compare the required fingerprint with `signer`. It
|
||||
also refuses a manifest whose `signingPubKey` holds a DSA key or subkey, or any
|
||||
secret key or subkey, since checking the self-signatures of a DSA key or the
|
||||
numbers of a secret key can take hours when those numbers are very large.
|
||||
|
||||
## Deterministic Serialization
|
||||
|
||||
|
||||
Reference in New Issue
Block a user