aacs: source OEM host certs from keysource layer

Complete the OEM/AACS cert baseline so host certs are a KeySource output,
never compiled in. With an unlocker present the OEM route is unused
(unlocker_read_volume_id short-circuits); without one, the cert handshake
runs when a keysource supplies a host cert and fails gracefully when none
does.

- KeySource trait gains host_certs() (default empty), reusing the existing
  aacs::HostCert type. A source holds certs as its second kind of AACS
  material alongside decryption keys.
- ScanOptions gains key_sources so the handshake can collect certs across
  the app's keysource layer, unioned with DriveCredentials.
- do_handshake_cert collects certs via collect_host_certs (credentials +
  every key source). Zero certs from any source now returns the new
  graceful Error::AacsNoHostCert (code 7024, sentinel <no host cert>)
  instead of silently skipping; resolution still falls back to the
  path-1 disc-hash -> VUK lookup, which drops the error on a hit.
- error.rs: add E_AACS_NO_HOST_CERT / Error::AacsNoHostCert, wired into
  code(), Display, and the round-trip + sentinel tests.

HandshakeResult { volume_id, read_data_key } unchanged: the cert path
still yields both the VID and the bus key.
This commit is contained in:
Matthew Jackson
2026-06-22 11:23:38 -07:00
parent 25acd09504
commit 4f606ae9a3
4 changed files with 183 additions and 15 deletions
+25
View File
@@ -86,6 +86,7 @@ pub const E_DRIVE_PROFILE_MISSING: u16 = 7020;
pub const E_VID_CDB_UNAVAILABLE: u16 = 7021;
pub const E_NO_DISC_KEY: u16 = 7022;
pub const E_CSS_KEY_MISSING: u16 = 7023;
pub const E_AACS_NO_HOST_CERT: u16 = 7024;
// Keydb (8xxx)
pub const E_KEYDB_CONNECT: u16 = 8000;
@@ -311,6 +312,18 @@ pub enum Error {
/// re-cracked). Muxing would emit scrambled ciphertext, so the caller
/// fails fast instead. CSS analogue of [`Error::NoDiscKey`].
CssKeyMissing,
/// The live-drive AACS cert-auth handshake (the OEM/AACS baseline route)
/// could not run because NO host certificate was available from any key
/// source. Host certs are keysource-served, never compiled in, so without
/// a keysource that supplies one the OEM route fails gracefully here — this
/// is the intended outcome, not a panic. Resolution still proceeds with a
/// zero Volume ID and relies on the path-1 disc-hash → VUK lookup, so the
/// error is dropped when that lookup hits. `path` carries the sentinel
/// `<no host cert>` (mirroring [`Error::KeydbLoad`]'s sentinel) so a CLI can
/// render "No Host Certs Found."
AacsNoHostCert {
path: String,
},
// Keydb (8xxx)
KeydbConnect {
@@ -487,6 +500,7 @@ impl Error {
Error::VidCdbUnavailable => E_VID_CDB_UNAVAILABLE,
Error::NoDiscKey { .. } => E_NO_DISC_KEY,
Error::CssKeyMissing => E_CSS_KEY_MISSING,
Error::AacsNoHostCert { .. } => E_AACS_NO_HOST_CERT,
Error::KeydbConnect { .. } => E_KEYDB_CONNECT,
Error::KeydbHttp { .. } => E_KEYDB_HTTP,
Error::KeydbInvalid => E_KEYDB_INVALID,
@@ -632,6 +646,7 @@ impl std::fmt::Display for Error {
Error::KeydbHttp { status } => write!(f, "E{}: {}", self.code(), status),
Error::KeydbWrite { path } => write!(f, "E{}: {}", self.code(), path),
Error::KeydbLoad { path } => write!(f, "E{}: {}", self.code(), path),
Error::AacsNoHostCert { path } => write!(f, "E{}: {}", self.code(), path),
Error::KeydbUnsupportedScheme { scheme } => {
write!(f, "E{}: {}", self.code(), scheme)
}
@@ -1078,6 +1093,8 @@ mod tests {
E_DRIVE_PROFILE_MISSING,
E_VID_CDB_UNAVAILABLE,
E_NO_DISC_KEY,
E_CSS_KEY_MISSING,
E_AACS_NO_HOST_CERT,
E_KEYDB_CONNECT,
E_KEYDB_HTTP,
E_KEYDB_INVALID,
@@ -1478,6 +1495,14 @@ mod tests {
"KeydbLoad display must include the sentinel path"
);
let e_no_cert = Error::AacsNoHostCert {
path: "<no host cert>".into(),
};
assert!(
e_no_cert.to_string().contains("<no host cert>"),
"AacsNoHostCert display must include the sentinel path"
);
let e_scheme = Error::KeydbUnsupportedScheme {
scheme: "ftp".into(),
};