Zion Boggan zionboggan.com ↗

oversight-container: recognize OSGT-HW-P256-v1 suite

Adds SUITE_HW_P256_V1_ID = 3 and extends suite_id_for_manifest so the
new hardware-backed suite can ride the existing 1-byte header dispatch
without changing the container layout. The seal_hw_p256 entry point
itself lands as a follow-up; this is the prerequisite mapping.

New test asserts the full suite -> id table and that unknown manifest
suites still return None (so adding a future suite without updating the
match can't silently downgrade to an UnsupportedManifestSuite at seal
time without showing up in CI).

oversight-container 12/12; workspace builds clean.
07f9f91   Zion Boggan committed on May 7, 2026 (1 month ago)
CHANGELOG.md +8 -0
@@ -2,6 +2,14 @@
## Unreleased
+- **`oversight-container`: `OSGT-HW-P256-v1` recognized by the binary
+ container (2026-05-07).** Added `SUITE_HW_P256_V1_ID = 3` and extended
+ `suite_id_for_manifest` to map the new manifest suite. This is the bridge
+ that lets a future `seal_hw_p256` ride the existing container layout
+ without reinventing it. New unit test covers the full mapping and asserts
+ unknown suites still return `None`. 12/12 container tests; workspace
+ build clean.
+
- **`oversight-crypto`: `OSGT-HW-P256-v1` suite implementation (2026-05-07).**
P-256 ECDH wrap/unwrap landed alongside the X25519 path so hardware-backed
recipients (YubiKey / Nitrokey / OnlyKey via PIV) have a complete pure-Rust
oversight-rust/oversight-container/src/lib.rs +16 -1
@@ -9,7 +9,7 @@
//! ------ -------- ---------------------------------------
//! 0 6 magic: b"OSGT\x01\x00"
//! 6 1 format_version (=1)
-//! 7 1 suite_id (1=CLASSIC_V1, 2=HYBRID_V1)
+//! 7 1 suite_id (1=CLASSIC_V1, 2=HYBRID_V1, 3=HW_P256_V1)
//! 8 4 manifest_len (u32 BE)
//! 12 M manifest (canonical JSON, signed)
//! 12+M 4 wrapped_dek_len (u32 BE)
@@ -27,6 +27,9 @@ use thiserror::Error;
pub const MAGIC: [u8; 6] = *b"OSGT\x01\x00";
pub const SUITE_CLASSIC_V1_ID: u8 = 1;
pub const SUITE_HYBRID_V1_ID: u8 = 2;
+/// Hardware-backed P-256 ECDH suite for PIV-compatible tokens.
+/// See `docs/HARDWARE_KEYS.md` and `oversight_crypto::SUITE_HW_P256_V1`.
+pub const SUITE_HW_P256_V1_ID: u8 = 3;
// Hard caps to prevent DoS via attacker-controlled length fields.
pub const MAX_MANIFEST_BYTES: usize = 4 * 1024 * 1024;
@@ -85,6 +88,7 @@ fn suite_id_for_manifest(suite: &str) -> Option<u8> {
match suite {
crypto::SUITE_CLASSIC_V1 => Some(SUITE_CLASSIC_V1_ID),
crypto::SUITE_HYBRID_V1 => Some(SUITE_HYBRID_V1_ID),
+ crypto::SUITE_HW_P256_V1 => Some(SUITE_HW_P256_V1_ID),
_ => None,
}
}
@@ -433,6 +437,17 @@ mod tests {
assert!(SealedFile::from_bytes(&blob).is_err());
}
+ #[test]
+ fn suite_id_for_manifest_covers_all_known_suites() {
+ // Each manifest suite must map to a unique container header byte;
+ // adding a new suite without updating this match would otherwise
+ // silently shape-shift into an UnsupportedManifestSuite at seal time.
+ assert_eq!(suite_id_for_manifest(crypto::SUITE_CLASSIC_V1), Some(SUITE_CLASSIC_V1_ID));
+ assert_eq!(suite_id_for_manifest(crypto::SUITE_HYBRID_V1), Some(SUITE_HYBRID_V1_ID));
+ assert_eq!(suite_id_for_manifest(crypto::SUITE_HW_P256_V1), Some(SUITE_HW_P256_V1_ID));
+ assert_eq!(suite_id_for_manifest("OSGT-UNKNOWN"), None);
+ }
+
#[test]
fn suite_id_tamper_rejected() {
let issuer = ClassicIdentity::generate();