keydb: round-trip AACS 2.0 host certs; tighten entry validation
- to_keydb_cfg emits the sibling `| HC2 |` line (AACS 2.0 host cert), the inverse of parse_host_cert_v2, so a round-trip never silently drops v2 host certs. - save-validation mirrors the parser's real disc-entry rule exactly: a `0x` line counts as an entry only if it also contains ` = `, so validating + persisting content that parses to zero usable entries (e.g. a stray `0xDEADBEEF` line) can no longer succeed. - disc-entry title is kept verbatim (parens and all) on parse, matching the emit path so it round-trips.
This commit is contained in:
+7
-3
@@ -90,7 +90,11 @@ impl KeydbSource {
|
|||||||
.lines()
|
.lines()
|
||||||
.filter(|l| {
|
.filter(|l| {
|
||||||
let t = l.trim();
|
let t = l.trim();
|
||||||
t.starts_with("0x")
|
// Mirror KeyDb::parse's disc-entry rule EXACTLY (keydb_format.rs:
|
||||||
|
// a "0x" line is only an entry if it also contains " = "), so
|
||||||
|
// save() never validates + persists content that parses to zero
|
||||||
|
// usable entries (e.g. a stray "0xDEADBEEF" comment line).
|
||||||
|
(t.starts_with("0x") && t.contains(" = "))
|
||||||
|| t.starts_with("| DK")
|
|| t.starts_with("| DK")
|
||||||
|| t.starts_with("| PK")
|
|| t.starts_with("| PK")
|
||||||
|| t.starts_with("| HC")
|
|| t.starts_with("| HC")
|
||||||
@@ -814,7 +818,7 @@ mod tests {
|
|||||||
let target = dir.join("nested").join("mykeys.cfg");
|
let target = dir.join("nested").join("mykeys.cfg");
|
||||||
let src = KeydbSource::new(&target);
|
let src = KeydbSource::new(&target);
|
||||||
|
|
||||||
let body = b"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF\n";
|
let body = b"0xDEADBEEFDEADBEEFDEADBEEFDEADBEEF = Test\n";
|
||||||
let result = src.save(body).expect("save must succeed");
|
let result = src.save(body).expect("save must succeed");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -840,7 +844,7 @@ mod tests {
|
|||||||
let target = dir.join("k.cfg");
|
let target = dir.join("k.cfg");
|
||||||
let src = KeydbSource::new(&target);
|
let src = KeydbSource::new(&target);
|
||||||
|
|
||||||
let body = b"0xAABBCCDDAABBCCDDAABBCCDDAABBCCDD\n".to_vec();
|
let body = b"0xAABBCCDDAABBCCDDAABBCCDDAABBCCDD = Test\n".to_vec();
|
||||||
let result = src
|
let result = src
|
||||||
.update(|_url| Ok(body.clone()), "http://example.invalid/keydb.zip")
|
.update(|_url| Ok(body.clone()), "http://example.invalid/keydb.zip")
|
||||||
.expect("update must succeed with a good fetch");
|
.expect("update must succeed with a good fetch");
|
||||||
|
|||||||
+17
-5
@@ -390,6 +390,8 @@ impl KeyDb {
|
|||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
|
|
||||||
// Host certs (AACS 1.0): | HC | HOST_PRIV_KEY 0x.. | HOST_CERT 0x.. ; Revoked in MKBv<N>
|
// Host certs (AACS 1.0): | HC | HOST_PRIV_KEY 0x.. | HOST_CERT 0x.. ; Revoked in MKBv<N>
|
||||||
|
// AACS 2.0 credentials ride a sibling `| HC2 |` line; emit it too so a
|
||||||
|
// round-trip through `to_keydb_cfg` never silently drops v2 host certs.
|
||||||
for hc in &self.host_certs {
|
for hc in &self.host_certs {
|
||||||
out.push_str("| HC | HOST_PRIV_KEY 0x");
|
out.push_str("| HC | HOST_PRIV_KEY 0x");
|
||||||
out.push_str(&hx(&hc.cert.private_key));
|
out.push_str(&hx(&hc.cert.private_key));
|
||||||
@@ -400,6 +402,17 @@ impl KeyDb {
|
|||||||
out.push_str(&n.to_string());
|
out.push_str(&n.to_string());
|
||||||
}
|
}
|
||||||
out.push('\n');
|
out.push('\n');
|
||||||
|
// AACS 2.0 (HC2): inverse of `parse_host_cert_v2`.
|
||||||
|
if let (Some(pk2), Some(cert2)) = (
|
||||||
|
hc.cert.private_key_v2.as_ref(),
|
||||||
|
hc.cert.certificate_v2.as_ref(),
|
||||||
|
) {
|
||||||
|
out.push_str("| HC2 | HOST_PRIV_KEY 0x");
|
||||||
|
out.push_str(&hx(pk2));
|
||||||
|
out.push_str(" | HOST_CERT 0x");
|
||||||
|
out.push_str(&hx(cert2));
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Device keys: | DK | DEVICE_KEY 0x.. | DEVICE_NODE 0x.. | KEY_UV 0x.. | KEY_U_MASK_SHIFT 0x..
|
// Device keys: | DK | DEVICE_KEY 0x.. | DEVICE_NODE 0x.. | KEY_UV 0x.. | KEY_U_MASK_SHIFT 0x..
|
||||||
@@ -429,9 +442,8 @@ impl KeyDb {
|
|||||||
// `0x` would double it on re-parse).
|
// `0x` would double it on re-parse).
|
||||||
out.push_str(h);
|
out.push_str(h);
|
||||||
out.push_str(" = ");
|
out.push_str(" = ");
|
||||||
// Parse stores the display title (inside parens) or the whole string
|
// Parse stores the title VERBATIM (parens and all), so emitting it
|
||||||
// when there are none; emitting the stored title bare round-trips
|
// bare round-trips through parse. Empty → "Unknown".
|
||||||
// (no parens → parser keeps it verbatim). Empty → "Unknown".
|
|
||||||
if d.title.is_empty() {
|
if d.title.is_empty() {
|
||||||
out.push_str("Unknown");
|
out.push_str("Unknown");
|
||||||
} else {
|
} else {
|
||||||
@@ -1096,8 +1108,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn disc_entry_malformed_parens_falls_back_to_whole_title() {
|
fn disc_entry_malformed_parens_falls_back_to_whole_title() {
|
||||||
// ')' before '(' would make start+1 > end; the guarded get() returns
|
// The title is kept verbatim regardless of paren placement — a malformed
|
||||||
// None and the parser falls back to the whole title (no panic).
|
// ')' before '(' is not special-cased; the whole string is the title.
|
||||||
let line = "0x00 = FILM) (X | M | 0x".to_string() + &"00".repeat(16);
|
let line = "0x00 = FILM) (X | M | 0x".to_string() + &"00".repeat(16);
|
||||||
let e = KeyDb::parse_disc_entry(&line).unwrap();
|
let e = KeyDb::parse_disc_entry(&line).unwrap();
|
||||||
assert_eq!(e.title, "FILM) (X");
|
assert_eq!(e.title, "FILM) (X");
|
||||||
|
|||||||
Reference in New Issue
Block a user