Files
punktfunk/crates/pf-update-check/src/lib.rs
T
f6cfe382fd refactor(update): one signed-manifest checker, shared by the host and the client
The host has known how to answer "is a newer build available for this box's
channel?" since the update-from-web-console work. The Linux client is about to
need the same answer from the same signed document, and a trust rule that lives
in two places is a trust rule that will drift.

So the parts where being wrong is a security bug now exist exactly once, in the
new `pf-update-check`: Ed25519 verification against pinned keys, the manifest
schema and its fail-closed validation, the post-redirect fetch, the version
comparison that has to reconcile four different canary spellings, and the
install-kind ladder — the last parameterised by which product is asking, since
the delivery channels are the same ones but the markers are not.

The pinned key list moves with them. Two lists could have disagreed about who
may announce a release, and the one that drifted is the one nobody would have
noticed; `publish-update-manifest.sh` follows to the new path, and a MISSING
keys file there is now fatal rather than a warning that silently skips the
cross-check the step exists to perform.

Host call sites are unchanged throughout: `store::index` and `update::manifest`
re-export from the shared crate under their old names, and `update::detect`
keeps its cached `detect()` and the host's command hints. Verified with clippy
-D warnings on Linux and on Windows (nvenc,amf-qsv,qsv), 340 host tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:41:26 +02:00

41 lines
2.1 KiB
Rust

//! Shared update-**check** core for the punktfunk host and the Linux client.
//!
//! Both products answer the same question — *does a newer build exist for this box's
//! channel?* — from the same Ed25519-signed per-channel manifest. This crate owns the part
//! where being wrong is a security bug, so that it exists exactly once:
//!
//! * [`sig`] — detached Ed25519 verification against pinned keys.
//! * [`manifest`] — the signed document's schema and its fail-closed validation rules.
//! * [`feed`] — fetching the document (and verifying over the *final*, post-redirect bytes).
//! * [`version`] — channels, and the comparison that decides "newer" across packaging formats.
//! * [`detect`] — the install-kind ladder, parameterised by [`detect::Product`].
//!
//! There is deliberately **no apply code here**. Applying an update is privileged, per-product
//! and per-platform; it lives with the product that does it (`punktfunk-host::update`,
//! `pf-client-core::update`, and the root helper in `pf-update`).
/// The Ed25519 public keys trusted for update manifests — two slots, so a key rotation is
/// "sign with the new one, ship builds trusting both, retire the old" (the plugin-store
/// `OFFICIAL_KEYS` drill) rather than a flag day. The private half is the
/// `UPDATE_MANIFEST_KEY` CI secret and the operator's offline backup.
///
/// It lives here, not in either binary, because the host and the client consume the SAME
/// signed manifest: two pin lists could disagree about who may announce a release, and the
/// one that drifted would be the one nobody noticed. `scripts/ci/publish-update-manifest.sh`
/// cross-checks the signing key against this file before it signs anything.
pub const OFFICIAL_UPDATE_KEYS: [&str; 2] = [
"ed25519:6rmlLg1aQ55cgB6icpC5BEpbMJxwPKdGaDQtDcJ0yLI=",
"", // rotation slot
];
pub mod detect;
pub mod feed;
pub mod manifest;
pub mod sig;
pub mod version;
pub use detect::{InstallKind, Product};
pub use manifest::{Manifest, MAX_MANIFEST_BYTES, SCHEMA};
pub use sig::{verify_signature, PublicKey};
pub use version::{canary_run, is_newer, triple, Channel};