Support multiple host certs, expose handshake_error on AacsState

- host_cert: Option → host_certs: Vec (try all until one succeeds)
- handshake_error field shows why auth failed (e.g. cert rejected/revoked)
This commit is contained in:
MattJackson
2026-04-10 09:48:29 -07:00
parent 914891feef
commit 026b6e7a43
3 changed files with 29 additions and 15 deletions
+1 -1
View File
@@ -1190,7 +1190,7 @@ mod tests {
if !keydb_path.exists() { return; }
let db = crate::aacs::KeyDb::load(&keydb_path).unwrap();
if let Some(hc) = &db.host_cert {
if let Some(hc) = db.host_certs.first() {
let valid = verify_cert(&hc.certificate);
eprintln!("Host cert verification: {}", if valid { "PASS" } else { "FAIL" });
// Note: our cert is revoked but should still have valid LA signature
+7 -5
View File
@@ -27,7 +27,7 @@ pub struct KeyDb {
/// Processing keys (pre-computed media keys for specific MKB versions)
pub processing_keys: Vec<[u8; 16]>,
/// Host certificate + private key for SCSI authentication
pub host_cert: Option<HostCert>,
pub host_certs: Vec<HostCert>,
/// Per-disc VUK entries indexed by disc hash (hex lowercase)
pub disc_entries: HashMap<String, DiscEntry>,
}
@@ -105,7 +105,7 @@ impl KeyDb {
let mut db = KeyDb {
device_keys: Vec::new(),
processing_keys: Vec::new(),
host_cert: None,
host_certs: Vec::new(),
disc_entries: HashMap::new(),
};
@@ -135,7 +135,7 @@ impl KeyDb {
// Host Certificate (AACS 2.0)
if line.starts_with("| HC2") {
if let Some(ref mut hc) = db.host_cert {
if let Some(hc) = db.host_certs.last_mut() {
if let Some((pk, cert)) = Self::parse_host_cert_v2(line) {
hc.private_key_v2 = Some(pk);
hc.certificate_v2 = Some(cert);
@@ -146,7 +146,9 @@ impl KeyDb {
// Host Certificate (AACS 1.0)
if line.starts_with("| HC") {
db.host_cert = Self::parse_host_cert(line);
if let Some(hc) = Self::parse_host_cert(line) {
db.host_certs.push(hc);
}
continue;
}
@@ -1342,7 +1344,7 @@ mod tests {
assert_eq!(db.device_keys.len(), 4);
assert_eq!(db.processing_keys.len(), 3);
assert!(db.host_cert.is_some());
assert!(!db.host_certs.is_empty());
assert!(db.disc_entries.len() > 170000);
// Look up Dune: Part Two
+14 -2
View File
@@ -322,6 +322,8 @@ pub struct AacsState {
pub read_data_key: Option<[u8; 16]>,
/// Volume ID (16 bytes) -- from SCSI handshake
pub volume_id: [u8; 16],
/// Handshake error code if authentication failed (None = no HC, or success)
pub handshake_error: Option<crate::error::Error>,
}
/// How AACS keys were resolved.
@@ -567,11 +569,13 @@ impl Disc {
// AACS SCSI handshake — get Volume ID (and read data key for AACS 2.0)
let mut volume_id = [0u8; 16];
let mut read_data_key = None;
let mut handshake_error = None;
if let Some(ref hc) = keydb.host_cert {
if let Ok(mut auth) = aacs::handshake::aacs_authenticate(
for hc in &keydb.host_certs {
match aacs::handshake::aacs_authenticate(
session, &hc.private_key, &hc.certificate,
) {
Ok(mut auth) => {
// Read Volume ID (needed for MK → VUK derivation)
if let Ok(vid) = aacs::handshake::read_volume_id(session, &mut auth) {
volume_id = vid;
@@ -581,6 +585,13 @@ impl Disc {
if let Ok((rdk, _wdk)) = aacs::handshake::read_data_keys(session, &mut auth) {
read_data_key = Some(rdk);
}
handshake_error = None;
break;
}
Err(e) => {
handshake_error = Some(e);
}
}
}
@@ -609,6 +620,7 @@ impl Disc {
unit_keys: resolved.unit_keys,
read_data_key,
volume_id,
handshake_error,
})
}