# 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).