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 891dee3db9
commit 37c98e8826
3 changed files with 29 additions and 15 deletions
+1 -1
View File
@@ -1190,7 +1190,7 @@ mod tests {
if !keydb_path.exists() { return; } if !keydb_path.exists() { return; }
let db = crate::aacs::KeyDb::load(&keydb_path).unwrap(); 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); let valid = verify_cert(&hc.certificate);
eprintln!("Host cert verification: {}", if valid { "PASS" } else { "FAIL" }); eprintln!("Host cert verification: {}", if valid { "PASS" } else { "FAIL" });
// Note: our cert is revoked but should still have valid LA signature // 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) /// Processing keys (pre-computed media keys for specific MKB versions)
pub processing_keys: Vec<[u8; 16]>, pub processing_keys: Vec<[u8; 16]>,
/// Host certificate + private key for SCSI authentication /// 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) /// Per-disc VUK entries indexed by disc hash (hex lowercase)
pub disc_entries: HashMap<String, DiscEntry>, pub disc_entries: HashMap<String, DiscEntry>,
} }
@@ -105,7 +105,7 @@ impl KeyDb {
let mut db = KeyDb { let mut db = KeyDb {
device_keys: Vec::new(), device_keys: Vec::new(),
processing_keys: Vec::new(), processing_keys: Vec::new(),
host_cert: None, host_certs: Vec::new(),
disc_entries: HashMap::new(), disc_entries: HashMap::new(),
}; };
@@ -135,7 +135,7 @@ impl KeyDb {
// Host Certificate (AACS 2.0) // Host Certificate (AACS 2.0)
if line.starts_with("| HC2") { 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) { if let Some((pk, cert)) = Self::parse_host_cert_v2(line) {
hc.private_key_v2 = Some(pk); hc.private_key_v2 = Some(pk);
hc.certificate_v2 = Some(cert); hc.certificate_v2 = Some(cert);
@@ -146,7 +146,9 @@ impl KeyDb {
// Host Certificate (AACS 1.0) // Host Certificate (AACS 1.0)
if line.starts_with("| HC") { 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; continue;
} }
@@ -1342,7 +1344,7 @@ mod tests {
assert_eq!(db.device_keys.len(), 4); assert_eq!(db.device_keys.len(), 4);
assert_eq!(db.processing_keys.len(), 3); 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); assert!(db.disc_entries.len() > 170000);
// Look up Dune: Part Two // Look up Dune: Part Two
+14 -2
View File
@@ -322,6 +322,8 @@ pub struct AacsState {
pub read_data_key: Option<[u8; 16]>, pub read_data_key: Option<[u8; 16]>,
/// Volume ID (16 bytes) -- from SCSI handshake /// Volume ID (16 bytes) -- from SCSI handshake
pub volume_id: [u8; 16], 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. /// 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) // AACS SCSI handshake — get Volume ID (and read data key for AACS 2.0)
let mut volume_id = [0u8; 16]; let mut volume_id = [0u8; 16];
let mut read_data_key = None; let mut read_data_key = None;
let mut handshake_error = None;
if let Some(ref hc) = keydb.host_cert { for hc in &keydb.host_certs {
if let Ok(mut auth) = aacs::handshake::aacs_authenticate( match aacs::handshake::aacs_authenticate(
session, &hc.private_key, &hc.certificate, session, &hc.private_key, &hc.certificate,
) { ) {
Ok(mut auth) => {
// Read Volume ID (needed for MK → VUK derivation) // Read Volume ID (needed for MK → VUK derivation)
if let Ok(vid) = aacs::handshake::read_volume_id(session, &mut auth) { if let Ok(vid) = aacs::handshake::read_volume_id(session, &mut auth) {
volume_id = vid; volume_id = vid;
@@ -581,6 +585,13 @@ impl Disc {
if let Ok((rdk, _wdk)) = aacs::handshake::read_data_keys(session, &mut auth) { if let Ok((rdk, _wdk)) = aacs::handshake::read_data_keys(session, &mut auth) {
read_data_key = Some(rdk); 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, unit_keys: resolved.unit_keys,
read_data_key, read_data_key,
volume_id, volume_id,
handshake_error,
}) })
} }