Skip to content

Add the trust-set generation document and generalize the generation engine #6

Description

@sehkone

Filed from aicers/bootler#192. The RFC 0004 decomposition (umbrella aicers/bootler#208) produced this as the upstream half of a paired change, and aicers/bootler#172 settled that work touching these types lands here rather than in bootler. The tracking node stays in that tree; the pull requests for this work are opened in this repository. Cross-references below are resolved: a bare #n is an issue in this repository, and aicers/bootler#n is a node of that tree.

Add the trust-set generation document and generalize the generation engine

Summary

src/verify.rs holds a shared package verifier that takes its trust material as a caller-injected value — anchors with their revoked flags, the withdrawn-build list, the minimum accepted manifest format_version, and an active epoch — and reads nothing from disk. Nothing yet defines what that material looks like on disk, reads it safely, installs a new copy of it atomically, or decides when a delivered copy may be installed at all. This effort adds all four.

The release-signing trust set lives in its own root-owned generation tree at <etc>/release-trust/ — release-provenance trust, not PKI — and reuses the staged to validate to activate discipline that src/roxyd_trust.rs already runs for the roxyd mTLS anchor tree. Reuse means generalizing that engine, not copying it: two copies of an activation sequence drift, and only one of them gets the next fix.

Both consumers of the verifier read this material, and one of them is a root daemon that never links bootler, so the document, its reader, the engine and the install-time admission paths all live in this crate. bootler's install-time seeding of the first generation, and its operator-facing input for it, are the paired bootler issue's.

Children

  • Generalize the trust generation engine into a tree-neutral core — foundation
  • Define the trust-set generation document and its refusing reader — foundation
  • Install and read release-trust generations — mechanics
  • Admit install-time release-trust packages — policy

The two foundations touch disjoint code and can be implemented in parallel. The mechanics child needs both. The policy child needs the mechanics child and the document.

Shared background

Where this lands. Every child's pull request lands in aicers/deploy-core. None of them edits the bootler repository or moves any pin here; bootler bumps its pinned rev afterwards, and the docs/ note produced by aicers/bootler#172 is the authoritative record of that choice and of the re-pin procedure.

What the verifier owns, and what no child may change. src/verify.rs holds every cryptographic rule: Ed25519 over the manifest member's verbatim bytes, the anchor selection order, the key_id derivation, the fourteen-variant VerifyError taxonomy that downstream repositories match on, and the rule that no canonicalization algorithm exists. No child restates, reimplements or varies any of it, and no child adds a variant to that taxonomy. One child adds a pub(crate) request constructor to that file; that is the only change any of them makes to it.

Public surface. These children are a refactor plus new internal machinery, and they add almost nothing a dependent can call. The whole intended public gain is five things: the release-trust tree's layout in src/layout.rs — its path accessors, the require-trust-pin marker's name constant and that marker's accessor — the trust-set document type with its entry types and its refusal taxonomy TrustSetDocumentError, the release-trust epoch reader, the admission constructor that produces the verifier's injected trust set, and the two install-time admission entry points, together with the error types those exported functions return.

Two of those five are public for reasons the others do not share, and no child may generalise from either. The layout items, because a second repository's installer resolves the same paths and the marker has its reader in one repository and its writer in another, so a name each side spells for itself is a drift that fails silently; composing a path is not a capability, since the accessors return a PathBuf and read nothing. The document type and its taxonomy, because they are named in the signatures of the exported entry points and the admission constructor, whose consumer is a root daemon that links this crate and cannot construct any type from the installer's repository — a pub item naming a pub(crate) type is private_interfaces, fatal under -D warnings, so keeping them internal would not inconvenience that consumer but make those signatures impossible. Publishing them commits to nothing the on-disk format has not already fixed: the schema carries deny_unknown_fields at both levels and versions its own additions through trust_set_version. A public error taxonomy is also this crate's existing convention — TrustError, PayloadError and ManifestError are all pub today.

Everything else — the generalized activation core, its GenerationError, the release-trust installer, the epoch writer, the self-admission request form, the document reader and its helpers — is crate-internal, because every one of its callers is in this crate. There is no exception: no child adds a public type, function or error variant beyond that list. In particular the engine refactor publishes nothing outside src/layout.rs — its faults map onto TrustError's existing taxonomy, so a downstream exhaustive match keeps compiling. deploy-core is pinned at a git rev by two repositories, so an item that is published becomes something a consumer may depend on before anyone has decided it should be supported. Start at the most restrictive visibility and widen only when a caller outside the crate is actually named.

The envelope, common to every child that touches one. A generation is delivered as a signed .pkg whose archive block holds exactly one member, trust-set.json, and whose manifest carries exactly one artifact entry: component = the reserved trust package-id (TRUST_TARGET), version = the epoch in decimal, commit = lowercase-hex SHA-256 over that member's bytes, kind = StaticAssets. The manifest, its detached signature and its key_id hint are footer-located and are not members. StaticAssets is deliberate: the verifier pins a four-row ArtifactKind table enforced by a wildcard-free match, and a fifth variant for one document would fork that table across two repositories. The reserved trust package-id stays out of any product registry — it is trust-plane only and is never a UI-installable component.

Two trees, never conflated. The release-trust tree is a sibling of the roxyd mTLS tree at <etc>/roxyd-tls/, never inside it, and shares no directory, no generation index and no active link with it. gen-<n> is the engine's local directory index, allocated from the tree's own on-disk state; epoch is the global release-ops sequence carried inside the signed document. A host seeded at release epoch 4711 has gen-1/. Conflating them would break the engine's pruning arithmetic.

What this effort deliberately does not do. It does not implement the runtime accept path, chain replay, or re-bootstrapping a host that has fallen past the retention floor. Those belong to the runtime channel's contract and are a later effort's, built on top of what lands here; nothing in these children may foreclose them, and the last child carries an explicit compatibility requirement to that effect. It does not mint or sign a generation outside test fixtures; producing one for real is release tooling in the other repository. And it changes nothing in the bootler repository: the caller of the install-time seed, the operator input for it, and the wizard and preflight surfaces are all that repository's.

Part of aicers/bootler#194.

Execution order

Issues in the same wave have no unmet dependencies among these children and can run in parallel.

graph TD
  issue34["#34 Generalize the trust generation engine into a tree-neutral core [phase: unphased]"]
  issue35["#35 Define the trust-set generation document and its refusing reader [phase: unphased]"]
  issue36["#36 Install and read release-trust generations [phase: unphased]"]
  issue37["#37 Admit install-time release-trust packages [phase: unphased]"]
  issue34 --> issue36
  issue35 --> issue36
  issue36 --> issue37
  issue35 --> issue37
Loading

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions