From bde416604e0fe23bbd908432b4d81d5ab570c374 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:23:46 -0700 Subject: [PATCH] keysources: expose host certs through KeySource trait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - KeydbSource implements KeySource::host_certs(), delegating to the inherent host_certs() — surfaces the | HC | / | HC2 | certs already parsed from keydb.cfg by libfreemkv's parser, so the OEM cert route collects them across the keysource layer. No new parsing. - OnlineSource::host_certs() is a no-op stub: returns empty with zero network access (no client fetch, no server endpoint). Online host-cert serving is deferred. TODO(owner) marker left in place. Tests: trait host_certs returns the keydb HC row; empty when keydb missing; online host_certs is an empty no-op without network. --- src/keydb.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/online.rs | 24 ++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/keydb.rs b/src/keydb.rs index deda11c..03ae6f7 100644 --- a/src/keydb.rs +++ b/src/keydb.rs @@ -98,6 +98,14 @@ impl KeydbSource { } impl KeySource for KeydbSource { + /// Expose the keydb's host certs through the trait — the OEM/AACS cert-auth + /// route collects them across every source via this method. Delegates to the + /// inherent [`KeydbSource::host_certs`] (same `| HC |`/`| HC2 |` rows parsed + /// by libfreemkv's keydb parser); no new parsing. + fn host_certs(&self) -> Vec { + KeydbSource::host_certs(self) + } + fn next_key(&mut self, inputs: &DiscInputs) -> Option { // On the first ask, parse the keydb once and build the ordered candidate // list; later asks just advance the cursor. A missing/unreadable keydb @@ -235,4 +243,40 @@ mod tests { .is_empty() ); } + + /// The KeySource TRAIT method exposes the keydb's host cert(s) — this is the + /// path the OEM/AACS cert-auth route collects certs through. A keydb with a + /// `| HC |` row must surface a HostCert via `KeySource::host_certs`, so the + /// handshake (which iterates `opts.key_sources[..].host_certs()`) finds it. + /// Placeholder all-zero material (never a real key) — same convention as + /// libfreemkv's own `parse_host_cert` test. + #[test] + fn trait_host_certs_returns_keydb_hc_row() { + let dir = std::env::temp_dir().join(format!("fmk_hc_{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("keydb.cfg"); + let line = format!( + "| HC | HOST_PRIV_KEY 0x{} | HOST_CERT 0x{}\n", + "00".repeat(20), + "00".repeat(92) + ); + std::fs::write(&path, line).unwrap(); + + let src = KeydbSource::new(&path); + // Consult through the TRAIT, exactly as the OEM route does. + let certs = KeySource::host_certs(&src); + assert_eq!(certs.len(), 1, "trait host_certs must surface the HC row"); + assert_eq!(certs[0].certificate.len(), 92); + + std::fs::remove_dir_all(&dir).ok(); + } + + /// Zero certs from a (missing) keydb through the TRAIT method — the OEM route + /// sees an empty vec here and, with no other source supplying a cert, fails + /// gracefully with `AacsNoHostCert` rather than panicking. + #[test] + fn trait_host_certs_empty_when_keydb_missing() { + let src = KeydbSource::new("/nonexistent/path/keydb.cfg"); + assert!(KeySource::host_certs(&src).is_empty()); + } } diff --git a/src/online.rs b/src/online.rs index ab8d447..d501261 100644 --- a/src/online.rs +++ b/src/online.rs @@ -305,6 +305,16 @@ impl KeySource for OnlineSource { fn errored(&self) -> bool { self.errored } + + fn host_certs(&self) -> Vec { + // NO-OP STUB. The online service does not serve host certs today: there + // is no client-side fetch and no server-side endpoint for them. Returning + // empty makes the OEM cert route fall back to whatever other source + // (e.g. the keydb) supplies — and fail gracefully if none does. No + // network is touched here. + // TODO(owner): online host-cert serving — design when 0x83 cert is recovered + Vec::new() + } } fn parse_uk(hex: &str) -> Option<[u8; 16]> { @@ -381,6 +391,20 @@ mod tests { )))); } + /// The online source serves NO host certs today (no fetch, no endpoint). + /// `host_certs()` must return empty WITHOUT touching the network, so the OEM + /// route falls back to whatever else (the keydb) supplies. Uses a non-empty + /// base URL to prove the empty result isn't merely "no service configured" — + /// it's the deliberate no-op stub. + #[test] + fn host_certs_is_noop_empty_no_network() { + let src = OnlineSource::new("http://example.invalid/keys", "secret"); + assert!( + KeySource::host_certs(&src).is_empty(), + "online host_certs must be an empty no-op (no network)" + ); + } + // ── resolve_and_guard ────────────────────────────────────────────────── #[test]