168 lines
7.2 KiB
Markdown
168 lines
7.2 KiB
Markdown
# Release signatures and verification
|
||
|
||
[Documentation index](README.md) · [M12 evidence](m12-validation.md)
|
||
|
||
`fds-release` signs an artifact manifest and verifies its signature and every
|
||
listed file. The static ARM executable is included in the base CLI package, so
|
||
CLI, development and recovery images can all verify downloads. The workstation
|
||
build supplies an x86_64 signer with the same format.
|
||
|
||
The signing tool has passed host, static ARM and independent OpenSSL acceptance.
|
||
The complete frozen-input, versioned local 0.1.0 release passed acceptance;
|
||
see [M12 validation](m12-validation.md#final-local-release-acceptance) for its identity
|
||
and [Offline rebuilds](reproducible-builds.md) for the workflow.
|
||
The examples below describe the working signing interface; they do not identify
|
||
a published or hardware-qualified FDS release.
|
||
|
||
## Build and test the tools
|
||
|
||
After [bootstrap](getting-started.md), run:
|
||
|
||
```sh
|
||
make signing
|
||
make signing-test
|
||
```
|
||
|
||
The workstation executable is
|
||
`target/x86_64-unknown-linux-gnu/release/fds-release`. The ARM executable is
|
||
`target/aarch64-unknown-linux-musl/release/fds-release`; `make tooling` also
|
||
exports it to `out/fds-release`. An ARM file cannot execute directly on the
|
||
x86_64 workstation. `make signing-test` runs that file through the project-local
|
||
QEMU interpreter as well as testing the host executable.
|
||
|
||
OpenSSL is a **host test dependency** used as an independent Ed25519
|
||
implementation. It is not linked into the FDS verifier. The test creates private
|
||
disposable keys under its `out/m12-signing.*` directory, checks exact signatures,
|
||
then exercises wrong keys, altered manifests, damaged or missing artifacts,
|
||
symlinks, FIFOs, malformed fields and unsafe paths. Test keys are not release
|
||
keys.
|
||
|
||
## Create and retain a signing key
|
||
|
||
For a local signing identity, choose an existing private directory outside the
|
||
checkout and its build output. This example creates a new directory; choose a
|
||
different name if it already exists:
|
||
|
||
```sh
|
||
mkdir -m 700 "$HOME/fds-signing"
|
||
target/x86_64-unknown-linux-gnu/release/fds-release keygen \
|
||
"$HOME/fds-signing/release-01"
|
||
```
|
||
|
||
The command creates `release-01.key` (32 secret bytes, mode 0600) and
|
||
`release-01.pub` (the hexadecimal public key). Existing files are never
|
||
overwritten. It prints SHA-256 of the decoded 32-byte public key, never the
|
||
secret. This fingerprint differs from `sha256sum release-01.pub`, which hashes
|
||
the hexadecimal text and its newline. Retain the private key separately from release downloads and source
|
||
archives. Anyone who has that key can sign a release under that identity.
|
||
|
||
If public-key export failed after private-key creation, retain the private file
|
||
and export it to a new path:
|
||
|
||
```sh
|
||
target/x86_64-unknown-linux-gnu/release/fds-release public-key \
|
||
"$HOME/fds-signing/release-01.key" "$HOME/fds-signing/recovered.pub"
|
||
```
|
||
|
||
Private keys must be regular files owned by the invoking user with no group or
|
||
other permissions. The tool rejects symlinks and invalid key lengths. Key
|
||
generation uses Linux `getrandom`; temporary seed storage and the signing key
|
||
are zeroized when dropped.
|
||
|
||
## Sign an assembled directory
|
||
|
||
An assembled release directory must already contain its artifacts and a
|
||
`manifest.json` matching the format below. Signing verifies every artifact's
|
||
length and hash before creating `manifest.sig`:
|
||
|
||
```sh
|
||
target/x86_64-unknown-linux-gnu/release/fds-release sign \
|
||
/path/to/release-directory --key "$HOME/fds-signing/release-01.key"
|
||
```
|
||
|
||
If a signature already exists, use a new release directory. The tool deliberately
|
||
does not replace signatures or silently rewrite the manifest. A failed operation
|
||
may leave its new output file for inspection; it never declares it verified.
|
||
|
||
## Verify before using images
|
||
|
||
Obtain the signer's public key through a channel you already trust, or compare
|
||
its fingerprint against a separately authenticated value. A public key included
|
||
beside an untrusted download does not establish its identity by itself.
|
||
|
||
On FDS, including recovery:
|
||
|
||
```sh
|
||
fds-release verify /data/downloads/fds-release --key /data/keys/fds-release.pub
|
||
```
|
||
|
||
On the workstation, use the host executable instead:
|
||
|
||
```sh
|
||
target/x86_64-unknown-linux-gnu/release/fds-release verify \
|
||
/path/to/release-directory --key /path/to/trusted-fds-release.pub
|
||
```
|
||
|
||
A successful command reports `VERIFIED FDS/OS 0.1.0`, the number of checked
|
||
artifacts, and `Hardware validation: deferred`. Failure returns exit status 2
|
||
and an explanation. Keep downloaded images unchanged between verification and
|
||
use. Verification covers only files listed in the manifest; unrelated extra
|
||
files are not endorsed. The [media writer](media-tools.md) performs its own
|
||
image/layout validation and readback checks when writing a cartridge.
|
||
|
||
These are distribution signatures. The Pi firmware and stage0 currently do not
|
||
enforce them at boot, so this is not a secure-boot implementation or a claim
|
||
that the hardware has been tested.
|
||
|
||
## Manifest and signature format
|
||
|
||
The JSON document has exactly these fields:
|
||
|
||
```json
|
||
{
|
||
"format": 1,
|
||
"version": "0.1.0",
|
||
"source_epoch": 1789909701,
|
||
"source_sha256": "<64 lowercase hexadecimal characters>",
|
||
"void_commit": "02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1",
|
||
"hardware_validation": "deferred",
|
||
"files": [
|
||
{
|
||
"name": "fds-system-cli-0.1.0.img",
|
||
"bytes": 123456,
|
||
"sha256": "<64 lowercase hexadecimal characters>"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
This is a schema illustration, not a usable manifest: the size and hash
|
||
placeholders must be replaced with values from the actual artifact. Format 1
|
||
accepts the current tool version, a positive source epoch, a 40-character Void
|
||
commit, and 1–64 nonempty artifacts. Filenames must be flat safe ASCII names;
|
||
paths, duplicates, hidden names, reserved manifest filenames and unknown fields
|
||
are rejected. The manifest is bounded to 1 MiB. Files must be regular files,
|
||
opened without following symlinks; hashing streams their bytes and checks for
|
||
changes during the read.
|
||
|
||
The signature is Ed25519 over the exact bytes
|
||
`FDS/OS release manifest v1` followed by a NUL byte and the raw `manifest.json`
|
||
bytes. JSON whitespace is therefore authenticated too. `manifest.sig` contains
|
||
128 lowercase hexadecimal characters and a newline. The public-key file contains
|
||
64 lowercase hexadecimal characters and a newline. Verification rejects weak
|
||
public keys and uses strict signature verification.
|
||
|
||
The implementation uses locked `ed25519-dalek` 3.0.0 for Ed25519,
|
||
`zeroize` 1.9.0 for secret buffers, and the existing SHA-256/JSON crates.
|
||
Its locked transitive dependencies provide curve arithmetic, digest and
|
||
signature types. Default dalek features are disabled; no asynchronous runtime,
|
||
OpenSSL binding or shared target library is added. The dependencies and their
|
||
license metadata are recorded in `Cargo.lock` and the cached crate sources.
|
||
The build collects their license texts, including build dependencies and
|
||
vendored libusb notices, into `/usr/share/licenses/fds-cli/RUST-NOTICES.txt`.
|
||
The usage guide is installed at `/usr/share/doc/fds/releases.md` in every profile.
|
||
Algorithm interoperability is checked against the
|
||
[RFC 8032 test vector](https://www.rfc-editor.org/rfc/rfc8032.html#section-7.1)
|
||
and OpenSSL; strict verification follows the
|
||
[dalek API](https://docs.rs/ed25519-dalek/3.0.0/ed25519_dalek/struct.VerifyingKey.html).
|