0.28.0 review fixes: propagate mux key-apply error; scrub key-shaped fixtures

- mux input(): propagate a failed decrypt_with rather than muxing an
  undecryptable stream silently (review M6).
- Scrub synthetic but key-SHAPED hex (HOST_PRIV_KEY/DEVICE_KEY/disc-entry) from
  the keydb parser tests to obvious zero/repeated-byte placeholders. No real or
  real-looking key material in code.
This commit is contained in:
MattJackson
2026-06-04 16:38:58 -07:00
parent c1eb74dfa5
commit bb32fb993b
2 changed files with 33 additions and 16 deletions
+29 -14
View File
@@ -455,8 +455,13 @@ mod tests {
#[test] #[test]
fn test_parse_disc_entry() { fn test_parse_disc_entry() {
let line = r#"0x000102030405060708090A0B0C0D0E0F10111213 = SAMPLE_FILM (Sample Film) | D | 2024-01-01 | M | 0x000102030405060708090A0B0C0D0E0F | I | 0x101112131415161718191A1B1C1D1E1F | V | 0x202122232425262728292A2B2C2D2E2F | U | 1-0x303132333435363738393A3B3C3D3E3F ; MKBv77"#; // All-zero placeholders — synthetic; no real key material in code.
let entry = KeyDb::parse_disc_entry(line).unwrap(); let z40 = "00".repeat(20);
let z32 = "00".repeat(16);
let line = format!(
"0x{z40} = SAMPLE_FILM (Sample Film) | D | 2024-01-01 | M | 0x{z32} | I | 0x{z32} | V | 0x{z32} | U | 1-0x{z32} ; MKBv77"
);
let entry = KeyDb::parse_disc_entry(&line).unwrap();
assert_eq!(entry.title, "Sample Film"); assert_eq!(entry.title, "Sample Film");
assert!(entry.media_key.is_some()); assert!(entry.media_key.is_some());
assert!(entry.vuk.is_some()); assert!(entry.vuk.is_some());
@@ -464,9 +469,14 @@ mod tests {
assert_eq!(entry.unit_keys[0].0, 1); assert_eq!(entry.unit_keys[0].0, 1);
} }
// NOTE: key fields below use obvious repeated-byte / zero placeholders
// (0x01.., 0x02.., 0x03.., 0x00..). NEVER put real — or real-looking — host,
// device, or processing key material in code; these tests exercise the
// parser's field-splitting only, not any genuine key.
#[test] #[test]
fn test_parse_device_key() { fn test_parse_device_key() {
let line = "| DK | DEVICE_KEY 0x000102030405060708090A0B0C0D0E0F | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17 ; MKBv01-MKBv48"; let line = "| DK | DEVICE_KEY 0x00000000000000000000000000000000 | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17 ; MKBv01-MKBv48";
let dk = KeyDb::parse_device_key(line).unwrap(); let dk = KeyDb::parse_device_key(line).unwrap();
assert_eq!(dk.node, 0x0800); assert_eq!(dk.node, 0x0800);
assert_eq!(dk.u_mask_shift, 0x17); assert_eq!(dk.u_mask_shift, 0x17);
@@ -479,9 +489,9 @@ mod tests {
// candidate: it lands in `processing_keys` and the brute walker // candidate: it lands in `processing_keys` and the brute walker
// handles it. // handles it.
let cfg = r#" let cfg = r#"
| DK | DEVICE_KEY 0xDEADBEEF0001020304050607080900AA ; orphan, no position fields | DK | DEVICE_KEY 0x01010101010101010101010101010101 ; orphan, no position fields
| DK | DEVICE_KEY 0x000102030405060708090A0B0C0D0E0F | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17 ; positioned MKBv01-MKBv48 | DK | DEVICE_KEY 0x02020202020202020202020202020202 | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17 ; positioned MKBv01-MKBv48
| PK | 0xCAFEBABE0001020304050607080900BB ; legacy PK row still works | PK | 0x03030303030303030303030303030303 ; legacy PK row still works
"#; "#;
let db = KeyDb::parse(cfg); let db = KeyDb::parse(cfg);
assert_eq!( assert_eq!(
@@ -495,29 +505,34 @@ mod tests {
2, 2,
"orphan DK row + legacy PK row both belong in processing_keys" "orphan DK row + legacy PK row both belong in processing_keys"
); );
assert_eq!(db.processing_keys[0][..4], [0xDE, 0xAD, 0xBE, 0xEF]); assert_eq!(db.processing_keys[0][..4], [0x01, 0x01, 0x01, 0x01]);
assert_eq!(db.processing_keys[1][..4], [0xCA, 0xFE, 0xBA, 0xBE]); assert_eq!(db.processing_keys[1][..4], [0x03, 0x03, 0x03, 0x03]);
} }
#[test] #[test]
fn test_parse_orphan_dk_rejects_lines_with_position_fields() { fn test_parse_orphan_dk_rejects_lines_with_position_fields() {
// The parser must NOT pick up a positioned DK row as an orphan // The parser must NOT pick up a positioned DK row as an orphan
// (that would double-count). parse_orphan_dk explicitly checks. // (that would double-count). parse_orphan_dk explicitly checks.
let positioned = "| DK | DEVICE_KEY 0x000102030405060708090A0B0C0D0E0F | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17"; let positioned = "| DK | DEVICE_KEY 0x02020202020202020202020202020202 | DEVICE_NODE 0x0800 | KEY_UV 0x00000400 | KEY_U_MASK_SHIFT 0x17";
assert!( assert!(
KeyDb::parse_orphan_dk(positioned).is_none(), KeyDb::parse_orphan_dk(positioned).is_none(),
"positioned DK must not match orphan parser" "positioned DK must not match orphan parser"
); );
let orphan = "| DK | DEVICE_KEY 0xDEADBEEF0001020304050607080900AA"; let orphan = "| DK | DEVICE_KEY 0x01010101010101010101010101010101";
let key = KeyDb::parse_orphan_dk(orphan).expect("orphan should parse"); let key = KeyDb::parse_orphan_dk(orphan).expect("orphan should parse");
assert_eq!(key[..4], [0xDE, 0xAD, 0xBE, 0xEF]); assert_eq!(key[..4], [0x01, 0x01, 0x01, 0x01]);
} }
#[test] #[test]
fn test_parse_host_cert() { fn test_parse_host_cert() {
let line = "| HC | HOST_PRIV_KEY 0xDEADBEEF000102030405060708090A0B0C0D0E0F | HOST_CERT 0x000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B ; Revoked"; // 20-byte priv + 92-byte cert, all zeros — placeholders, not a key.
let hc = KeyDb::parse_host_cert(line).unwrap(); let line = format!(
assert_eq!(hc.private_key[0], 0xDE); "| HC | HOST_PRIV_KEY 0x{} | HOST_CERT 0x{} ; Revoked",
"00".repeat(20),
"00".repeat(92)
);
let hc = KeyDb::parse_host_cert(&line).unwrap();
assert_eq!(hc.private_key, [0u8; 20]);
assert_eq!(hc.certificate.len(), 92); assert_eq!(hc.certificate.len(), 92);
} }
+4 -2
View File
@@ -199,9 +199,11 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
) )
.map_err(|e| -> io::Error { e.into() })?; .map_err(|e| -> io::Error { e.into() })?;
// Apply the caller-resolved keys (lookup-free); decrypt_keys() then // Apply the caller-resolved keys (lookup-free); decrypt_keys() then
// yields them for the stream below. // yields them for the stream below. Propagate a failed application
// rather than silently muxing an undecryptable stream.
if !opts.unit_keys.is_empty() { if !opts.unit_keys.is_empty() {
let _ = disc.decrypt_with(crate::disc::Key::Unit(opts.unit_keys.clone())); disc.decrypt_with(crate::disc::Key::Unit(opts.unit_keys.clone()))
.map_err(|e| -> io::Error { e.into() })?;
} }
if disc.titles.is_empty() { if disc.titles.is_empty() {
return Err(crate::error::Error::NoStreams.into()); return Err(crate::error::Error::NoStreams.into());