From 37c98e8826fe3a1bb1ebce7c3720038641543df4 Mon Sep 17 00:00:00 2001 From: MattJackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:48:29 -0700 Subject: [PATCH] Support multiple host certs, expose handshake_error on AacsState MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - host_cert: Option → host_certs: Vec (try all until one succeeds) - handshake_error field shows why auth failed (e.g. cert rejected/revoked) --- src/aacs/handshake.rs | 2 +- src/aacs/mod.rs | 12 +++++++----- src/disc.rs | 30 +++++++++++++++++++++--------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/aacs/handshake.rs b/src/aacs/handshake.rs index d170181..b6129ba 100644 --- a/src/aacs/handshake.rs +++ b/src/aacs/handshake.rs @@ -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 diff --git a/src/aacs/mod.rs b/src/aacs/mod.rs index 28d320a..b92a954 100644 --- a/src/aacs/mod.rs +++ b/src/aacs/mod.rs @@ -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, + pub host_certs: Vec, /// Per-disc VUK entries indexed by disc hash (hex lowercase) pub disc_entries: HashMap, } @@ -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 diff --git a/src/disc.rs b/src/disc.rs index a24d44e..ecad532 100644 --- a/src/disc.rs +++ b/src/disc.rs @@ -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, } /// How AACS keys were resolved. @@ -567,19 +569,28 @@ 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, ) { - // Read Volume ID (needed for MK → VUK derivation) - if let Ok(vid) = aacs::handshake::read_volume_id(session, &mut auth) { - volume_id = vid; - } + 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; + } - // Read data keys for bus decryption (AACS 2.0 / UHD) - if let Ok((rdk, _wdk)) = aacs::handshake::read_data_keys(session, &mut auth) { - read_data_key = Some(rdk); + // Read data keys for bus decryption (AACS 2.0 / UHD) + 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, }) }