1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! Error type for the crate.
//!
//! When the `registry` cargo feature is on (the default), the public
//! surface uses [`AacsError`] directly. We do **not** re-alias to
//! `oxideav_core::Error` because AACS is sufficiently self-contained
//! that surfacing parser/crypto failures through a generic framework
//! error would erase useful detail. The framework consumer can map
//! these via `From<AacsError>` at the boundary.
use core::fmt;
/// Errors produced by the AACS crate.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum AacsError {
/// A parser ran out of input before a field finished.
///
/// The associated string names which structure was being parsed
/// (`"MKB record header"`, `"Unit Key Block"`, etc.).
Truncated(&'static str),
/// A multi-byte length field declared a record larger than the
/// surrounding buffer.
OversizedRecord {
/// Identifier of the surrounding structure (e.g. `"MKB"`).
what: &'static str,
/// Declared length in bytes.
declared: usize,
/// Number of bytes actually remaining in the buffer.
available: usize,
},
/// A field had a value the spec doesn't define.
InvalidValue {
/// Identifier of the field (e.g. `"MKBType"`).
what: &'static str,
/// The unexpected value (formatted as decimal by the
/// `Display` impl).
value: u64,
},
/// The expected `Type and Version Record` (record type `0x10`)
/// wasn't the first record of the MKB.
MissingTypeAndVersionRecord,
/// The MKB has no `Verify Media Key Record` (record type `0x81`).
/// This is mandatory per Common spec §3.2.5.1.4 for any MKB that
/// is going to derive a usable Media Key.
MissingVerifyMediaKeyRecord,
/// `verify_media_key()` was called with a candidate Km that does
/// not pass the Verify Media Key check
/// (`[AES-128D(Km, Vd)]_msb_64 != 0x0123456789ABCDEF`).
MediaKeyVerificationFailed,
/// The Subset-Difference walk could not find an applicable
/// subset-difference for this Device Key — the device is revoked
/// by this MKB (Common spec §3.2.4 final paragraph).
DeviceRevoked,
/// The disc-mount root does not contain an `AACS/` directory, or
/// `MKB_RO.inf` / `Unit_Key_RO.inf` cannot be located in either
/// `AACS/` or `AACS/DUPLICATE/`.
MissingDiscFile(&'static str),
/// I/O failure while reading from disk.
Io(String),
/// The Aligned Unit handed to [`crate::content::decrypt_aligned_unit`]
/// is not exactly [`crate::content::ALIGNED_UNIT_SIZE`] bytes.
BadAlignedUnitLength(usize),
/// A KEYDB.cfg line did not parse. The string carries a
/// best-effort excerpt of the offending text (truncated to 80
/// chars) for diagnostics.
KeyDbParseError(String),
/// The Drive Certificate's AACS LA signature failed verification
/// during the §4.3 AKE (step 15) — the drive is not an
/// AACS-compliant device or the certificate is corrupt.
DriveCertSignatureInvalid,
/// The Host Certificate's AACS LA signature failed verification
/// during the §4.3 AKE (step 9).
HostCertSignatureInvalid,
/// The drive's `Dsig` over `Hn || Dv` failed verification (§4.3
/// step 22).
DriveSignatureInvalid,
/// The host's `Hsig` over `Dn || Hv` failed verification (§4.3
/// step 27).
HostSignatureInvalid,
/// The drive's CMAC (`Dm`) over a transferred ID did not match the
/// host's recomputed `Hm` under the Bus Key (§4.4 step 4).
VolumeIdMacInvalid,
/// A KEYDB.cfg `|`-leader header line (one of `DK` / `PK` / `HC`
/// / `DC` / `VID` / `VUK` / `MEK` / `TK` / `KCD` / `DISCID`) was
/// malformed: wrong number of fields, hex value with the wrong
/// byte-count for that field, malformed hex literal, or an
/// unrecognised leader token. The string carries a best-effort
/// excerpt of the offending line (truncated to 80 chars) plus a
/// short description.
HeaderParseError(String),
/// An MKB signature record (End-of-MKB `0x02`, Host Revocation
/// List `0x21`, or Drive Revocation List `0x20`) was missing the
/// 40-byte ECDSA signature payload, or the parsed MKB carried no
/// signature blocks for that record at all. `verify_*_signature`
/// surfaces this so the caller can distinguish "no signature to
/// verify" from "signature present but invalid".
MkbSignatureMissing,
/// An MKB signature failed `AACS_Verify(AACS_LApub, sig, data)`.
/// Returned by `verify_end_of_block_signature` and the per-block
/// `verify_{host,drive}_revocation_list` methods.
MkbSignatureInvalid,
/// A Hash Unit handed to
/// [`crate::cht::ContentHashTable::verify_hash_unit`] is not
/// exactly [`crate::cht::HASH_UNIT_SIZE`] (96 Logical Sectors =
/// 196608) bytes (BD-Prerecorded §2.3.1).
BadHashUnitLength(usize),
/// A Hash Unit's recomputed `[SHA-1(Hash_Unit)]_lsb_64` did not
/// match the Hash Value stored in the Content Hash Table
/// (BD-Prerecorded §2.3.2.1). The `index` is the Hash-Value
/// position within the table.
ContentHashMismatch {
/// Index of the mismatching Hash Value within the Content
/// Hash Table.
index: usize,
},
/// A runtime self-check entry point in [`crate::self_check`] failed.
/// The `what` tag pinpoints which leg of the §2.3 curve / §4.3 AKE
/// round-trip mismatched. Surfaces only when a consumer explicitly
/// invokes `curve_self_check` / `aacs_la_pub_self_check` /
/// `ake_ecdh_self_check` / `ake_full_self_check` / `all_self_checks`.
SelfCheckFailed {
/// Short identifier of the failing identity (e.g.
/// `"n·G != point at infinity"`, `"ECDH bus keys disagree"`).
what: &'static str,
},
}
impl fmt::Display for AacsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Truncated(what) => write!(f, "truncated input while parsing {what}"),
Self::OversizedRecord {
what,
declared,
available,
} => write!(
f,
"{what} record declares {declared} bytes but only {available} bytes remain"
),
Self::InvalidValue { what, value } => {
write!(f, "invalid {what} value: {value}")
}
Self::MissingTypeAndVersionRecord => {
f.write_str("MKB does not start with a Type-and-Version Record")
}
Self::MissingVerifyMediaKeyRecord => f.write_str("MKB has no Verify Media Key Record"),
Self::MediaKeyVerificationFailed => {
f.write_str("Verify Media Key Record rejected the derived Media Key")
}
Self::DeviceRevoked => f.write_str(
"no applicable subset-difference for this Device Key — device is revoked",
),
Self::MissingDiscFile(name) => {
write!(f, "AACS disc layout missing required file: {name}")
}
Self::Io(msg) => write!(f, "I/O error: {msg}"),
Self::BadAlignedUnitLength(n) => {
write!(f, "Aligned Unit must be exactly 6144 bytes; got {n} bytes")
}
Self::DriveCertSignatureInvalid => {
f.write_str("Drive Certificate AACS LA signature verification failed")
}
Self::HostCertSignatureInvalid => {
f.write_str("Host Certificate AACS LA signature verification failed")
}
Self::DriveSignatureInvalid => {
f.write_str("Drive signature (Dsig over Hn || Dv) verification failed")
}
Self::HostSignatureInvalid => {
f.write_str("Host signature (Hsig over Dn || Hv) verification failed")
}
Self::VolumeIdMacInvalid => f.write_str("transferred-ID CMAC mismatch under Bus Key"),
Self::KeyDbParseError(line) => write!(f, "KEYDB.cfg parse error near: {line:?}"),
Self::HeaderParseError(msg) => write!(f, "KEYDB.cfg header parse error: {msg}"),
Self::MkbSignatureMissing => {
f.write_str("MKB signature record absent or carried no signature payload to verify")
}
Self::MkbSignatureInvalid => f.write_str(
"AACS_Verify rejected the MKB signature against the supplied public key",
),
Self::BadHashUnitLength(n) => write!(
f,
"Hash Unit must be exactly 196608 bytes (96 Logical Sectors); got {n} bytes"
),
Self::ContentHashMismatch { index } => write!(
f,
"Content Hash Table hash-unit #{index} failed [SHA-1(Hash_Unit)]_lsb_64 check"
),
Self::SelfCheckFailed { what } => {
write!(f, "AACS self-check failed: {what}")
}
}
}
}
impl std::error::Error for AacsError {}
impl From<std::io::Error> for AacsError {
fn from(e: std::io::Error) -> Self {
Self::Io(e.to_string())
}
}