feat(signing): sign the boot FIT from the key registry via signing.fit_key - #224
feat(signing): sign the boot FIT from the key registry via signing.fit_key#224mobileoverlord wants to merge 6 commits into
Conversation
…t_key The FIT signing key came in through AVOCADO_FIT_KEY_DIR / AVOCADO_FIT_UNSIGNED, so a signed boot image was not reproducible from avocado.yaml. Move it into config and the signing-key registry: - `runtimes.<name>.signing.fit_key` names an RSA PEM key in the registry (by name or key id); the runtime build materializes it as FIT.key/FIT.crt in a private temp dir mounted read-only at /tmp/fit-keys, where the FIT assembly already looks. `signing.fit_unsigned: true` is the explicit opt-out; both set is an error. The env variables are no longer read (a hint is printed). - The registry gains RSA PEM entries (rsa2048/rsa4096): `signing-keys import <name> --key --cert` stores an existing pair, `signing-keys create <name> --algorithm rsa2048` generates one with the host's openssl. Key id is the SHA-256 of the certificate DER. PKCS#11 and ed25519 entries are refused for FIT signing with a message saying why.
|
End-to-end on the imx8mp-evk sample project: key imported with |
There was a problem hiding this comment.
Pull request overview
This PR moves boot FIT signing configuration from ad-hoc environment variables into avocado.yaml, by resolving an RSA PEM key from the signing-key registry and staging it for mkimage during runtime builds. This makes signed boot images reproducible from configuration alone and adds CLI support for creating/importing the required RSA key material.
Changes:
- Added
runtimes.<name>.signing.fit_key/fit_unsignedto runtime signing config, plus an accessor to read the per-runtime choice. - Extended the signing-key registry to support file-backed RSA PEM keypairs (key id derived from cert DER) and added
signing-keys import+signing-keys create --algorithm rsa2048/rsa4096. - Updated runtime build to materialize
FIT.key/FIT.crtfrom the registry and mount them into the SDK formkimageFIT signing; updated docs/changelog accordingly.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/pkcs11_integration_test.rs | Updates integration test calls to include the new algorithm parameter for signing key creation. |
| src/utils/signing_keys.rs | Adds PEM RSA helpers (algorithm gating, cert-derived key id, save/load PEM keypair files) and unit tests. |
| src/utils/config.rs | Introduces fit_key / fit_unsigned fields and a per-runtime accessor with tests. |
| src/main.rs | Adds CLI flags for signing key algorithm selection and wires in the new signing-keys import command. |
| src/commands/signing_keys/mod.rs | Exposes the new import subcommand module. |
| src/commands/signing_keys/import.rs | Implements importing an existing RSA PEM key+cert into the signing key registry. |
| src/commands/signing_keys/create.rs | Extends key creation to support RSA PEM generation via host openssl and stores into the registry. |
| src/commands/runtime/build.rs | Switches FIT signing to registry-backed fit_key/fit_unsigned, stages FIT.key/FIT.crt in a temp dir, and updates script messages. |
| docs/signing-keys.md | Documents Boot-FIT RSA key workflows and runtime configuration. |
| CHANGELOG.md | Records the new config keys and deprecation of the interim env var path. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| pub fn keyid_for_pem_cert(cert_pem: &str) -> Result<String> { | ||
| let body: String = cert_pem | ||
| .lines() | ||
| .filter(|l| !l.starts_with("-----")) | ||
| .map(str::trim) | ||
| .collect(); | ||
| let der = BASE64_STANDARD |
| fs::write(&key_path, key_pem) | ||
| .with_context(|| format!("Failed to write private key: {}", key_path.display()))?; | ||
| #[cfg(unix)] | ||
| { | ||
| use std::os::unix::fs::PermissionsExt; | ||
| fs::set_permissions(&key_path, std::fs::Permissions::from_mode(0o600)) | ||
| .with_context(|| format!("Failed to set permissions on {}", key_path.display()))?; | ||
| } |
…IT is actually signed - signing.fit_key_in_bootloader (default true with fit_key): after assembling the FIT, run the feed's imx-boot-tools/rekey-imx-boot.sh so U-Boot enforces the project key, then prove it with fit_check_sign against the keyed control DTB. A feed without the tooling fails the build rather than shipping a bootloader that ignores the key. - A feed built without verified-boot ships a FIT template whose configuration nodes carry no signature-* subnode, and `mkimage -r` then signs nothing without complaint - the previous "(signed)" was not. Inject a signature-1 node (algo from the registry key, key-name-hint FIT, sign-images from the configuration's own image properties) when the template lacks them, and fail the build unless `mkimage -l` shows a signature afterwards.
|
End-to-end on the imx8mp-evk sample with the merged edge build: |
… first boot The var partition is sized from the image (stone --partition-size) and only grown to the disk later, on the device. A runtime that encrypts /var on first boot therefore found a partition 3 MiB larger than the filesystem filling it, and cryptsetup-var's in-place reencrypt (which needs 32 MiB in front of the data) could neither find the room nor shrink a 285 MiB btrfs to make it - observed on a freshly provisioned imx8mp-evk. Add 64 MiB to the partition when the runtime opts into var.encrypt; plaintext runtimes keep the exact image size.
Not only when the runtime encrypts: a device flashed plaintext must be able to turn var.encrypt on over an update later, and without the dev extension its partition is never grown, so the headroom has to be there from the first flash. var is the last partition and the image is sparse-flashed, so the only cost is 64 MiB of zeros; the layout no longer depends on a runtime flag.
stone now adds it to every override-sized partition, for the runtime bundle and for provisioning alike; carrying a second copy here would double it.
…S2 header The first boot converts /var to LUKS2 in place, which needs 32 MiB in front of the data that cryptsetup-var obtains by shrinking the filesystem. mkfs.btrfs -r packs its chunks to the content, so the flashed image had nothing to shrink into and the conversion failed on a freshly provisioned board. When the runtime declares var.encrypt, rebuild the image at its tight size plus 64 MiB so the room is inside the filesystem that asked for it. Partition sizing is untouched everywhere (still exactly the image size), and a plaintext runtime's image is unchanged.
|
Board result (imx8mp-evk, fresh provision from a build with |
The FIT signing key for FIT-booting machines came in through
AVOCADO_FIT_KEY_DIR/AVOCADO_FIT_UNSIGNED, so a signed boot image was not reproducible fromavocado.yaml.What
How
rsa2048/rsa4096) stored as<keyid>.key(0600) /<keyid>.crt, key id = SHA-256 of the certificate DER.signing.fit_key, stages the pair asFIT.key/FIT.crtin a private temp dir and mounts it read-only at/tmp/fit-keys, where the FIT assembly already looks. ed25519 and PKCS#11 entries are refused for FIT signing with a message saying why.Tests: config accessor, key id derivation, algorithm gating; 3,665 pass. Hardware check on imx8mp-evk to follow (same signed FIT as the env-var path produced).
https://claude.ai/code/session_01S75qGgqVA2cQUsZdW46qUz