use crate::AacsError;
pub const REPORT_KEY_OPCODE: u8 = 0xA4;
pub const SEND_KEY_OPCODE: u8 = 0xA3;
pub const READ_DISC_STRUCTURE_OPCODE: u8 = 0xAD;
pub const MMC_CDB_LEN: usize = 12;
pub const KEY_CLASS_CSS: u8 = 0x00;
pub const KEY_CLASS_AACS: u8 = 0x02;
pub const KF_REPORT_AACS_AGID: u8 = 0x00;
pub const KF_REPORT_AACS_DRIVE_CERT_CHAL: u8 = 0x01;
pub const KF_REPORT_AACS_DRIVE_KEY: u8 = 0x02;
pub const KF_REPORT_AACS_BINDING_NONCE_GEN: u8 = 0x20;
pub const KF_REPORT_AACS_BINDING_NONCE_READ: u8 = 0x21;
pub const KF_REPORT_AACS_DRIVE_CERT: u8 = 0x38;
pub const KF_REPORT_AACS_INVALIDATE_AGID: u8 = 0x3F;
pub const KF_SEND_AACS_HOST_CERT_CHAL: u8 = 0x01;
pub const KF_SEND_AACS_HOST_KEY: u8 = 0x02;
pub const KF_SEND_AACS_INVALIDATE_AGID: u8 = 0x3F;
pub const FORMAT_AACS_VOLUME_ID: u8 = 0x80;
pub const FORMAT_AACS_MEDIA_SERIAL: u8 = 0x81;
pub const FORMAT_AACS_MEDIA_ID: u8 = 0x82;
pub const FORMAT_AACS_MEDIA_KEY_BLOCK: u8 = 0x83;
pub const MEDIA_TYPE_BD: u8 = 0x01;
pub const MEDIA_TYPE_DVD: u8 = 0x00;
pub const HOST_NONCE_LEN: usize = 20;
pub const DRIVE_NONCE_LEN: usize = 20;
pub const HOST_CERT_LEN: usize = 92;
pub const DRIVE_CERT_LEN: usize = 92;
pub const EC_POINT_LEN: usize = 40;
pub const EC_SIG_LEN: usize = 40;
pub const VOLUME_ID_LEN: usize = 16;
pub const ID_MAC_LEN: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReportKey {
pub key_class: u8,
pub key_format: u8,
pub agid: u8,
pub lba_or_starting_offset: u32,
pub block_count_function: u8,
pub allocation_length: u16,
pub control: u8,
}
impl ReportKey {
pub fn aacs_agid() -> Self {
Self {
key_class: KEY_CLASS_AACS,
key_format: KF_REPORT_AACS_AGID,
agid: 0,
lba_or_starting_offset: 0,
block_count_function: 0,
allocation_length: 8,
control: 0,
}
}
pub fn aacs_drive_cert_challenge(agid: u8) -> Self {
Self {
key_class: KEY_CLASS_AACS,
key_format: KF_REPORT_AACS_DRIVE_CERT_CHAL,
agid: agid & 0x03,
lba_or_starting_offset: 0,
block_count_function: 0,
allocation_length: 116,
control: 0,
}
}
pub fn aacs_drive_key(agid: u8) -> Self {
Self {
key_class: KEY_CLASS_AACS,
key_format: KF_REPORT_AACS_DRIVE_KEY,
agid: agid & 0x03,
lba_or_starting_offset: 0,
block_count_function: 0,
allocation_length: 84,
control: 0,
}
}
pub fn aacs_drive_cert() -> Self {
Self {
key_class: KEY_CLASS_AACS,
key_format: KF_REPORT_AACS_DRIVE_CERT,
agid: 0,
lba_or_starting_offset: 0,
block_count_function: 0,
allocation_length: 96,
control: 0,
}
}
pub fn aacs_invalidate_agid(agid: u8) -> Self {
Self {
key_class: KEY_CLASS_AACS,
key_format: KF_REPORT_AACS_INVALIDATE_AGID,
agid: agid & 0x03,
lba_or_starting_offset: 0,
block_count_function: 0,
allocation_length: 0,
control: 0,
}
}
pub fn cdb(&self) -> [u8; MMC_CDB_LEN] {
let mut cdb = [0u8; MMC_CDB_LEN];
cdb[0] = REPORT_KEY_OPCODE;
cdb[1] = 0;
cdb[2] = (self.lba_or_starting_offset >> 24) as u8;
cdb[3] = (self.lba_or_starting_offset >> 16) as u8;
cdb[4] = (self.lba_or_starting_offset >> 8) as u8;
cdb[5] = self.lba_or_starting_offset as u8;
cdb[6] = self.block_count_function;
cdb[7] = self.key_class;
cdb[8] = (self.allocation_length >> 8) as u8;
cdb[9] = self.allocation_length as u8;
cdb[10] = ((self.agid & 0x03) << 6) | (self.key_format & 0x3F);
cdb[11] = self.control;
cdb
}
pub fn parse_cdb(cdb: &[u8; MMC_CDB_LEN]) -> Result<Self, AacsError> {
if cdb[0] != REPORT_KEY_OPCODE {
return Err(AacsError::InvalidValue {
what: "REPORT_KEY opcode",
value: cdb[0] as u64,
});
}
Ok(Self {
key_class: cdb[7],
key_format: cdb[10] & 0x3F,
agid: (cdb[10] >> 6) & 0x03,
lba_or_starting_offset: ((cdb[2] as u32) << 24)
| ((cdb[3] as u32) << 16)
| ((cdb[4] as u32) << 8)
| (cdb[5] as u32),
block_count_function: cdb[6],
allocation_length: ((cdb[8] as u16) << 8) | (cdb[9] as u16),
control: cdb[11],
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SendKey {
pub key_class: u8,
pub key_format: u8,
pub agid: u8,
pub parameter_list_length: u16,
pub control: u8,
}
impl SendKey {
pub fn aacs_host_cert_challenge(agid: u8) -> Self {
Self {
key_class: KEY_CLASS_AACS,
key_format: KF_SEND_AACS_HOST_CERT_CHAL,
agid: agid & 0x03,
parameter_list_length: 116,
control: 0,
}
}
pub fn aacs_host_key(agid: u8) -> Self {
Self {
key_class: KEY_CLASS_AACS,
key_format: KF_SEND_AACS_HOST_KEY,
agid: agid & 0x03,
parameter_list_length: 84,
control: 0,
}
}
pub fn aacs_invalidate_agid(agid: u8) -> Self {
Self {
key_class: KEY_CLASS_AACS,
key_format: KF_SEND_AACS_INVALIDATE_AGID,
agid: agid & 0x03,
parameter_list_length: 0,
control: 0,
}
}
pub fn cdb(&self) -> [u8; MMC_CDB_LEN] {
let mut cdb = [0u8; MMC_CDB_LEN];
cdb[0] = SEND_KEY_OPCODE;
cdb[1] = 0;
cdb[2] = 0;
cdb[3] = 0;
cdb[4] = 0;
cdb[5] = 0;
cdb[6] = 0;
cdb[7] = self.key_class;
cdb[8] = (self.parameter_list_length >> 8) as u8;
cdb[9] = self.parameter_list_length as u8;
cdb[10] = ((self.agid & 0x03) << 6) | (self.key_format & 0x3F);
cdb[11] = self.control;
cdb
}
pub fn parse_cdb(cdb: &[u8; MMC_CDB_LEN]) -> Result<Self, AacsError> {
if cdb[0] != SEND_KEY_OPCODE {
return Err(AacsError::InvalidValue {
what: "SEND_KEY opcode",
value: cdb[0] as u64,
});
}
Ok(Self {
key_class: cdb[7],
key_format: cdb[10] & 0x3F,
agid: (cdb[10] >> 6) & 0x03,
parameter_list_length: ((cdb[8] as u16) << 8) | (cdb[9] as u16),
control: cdb[11],
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReadDiscStructure {
pub media_type: u8,
pub address: u32,
pub layer_number: u8,
pub format: u8,
pub allocation_length: u16,
pub agid: u8,
pub control: u8,
}
impl ReadDiscStructure {
pub fn aacs_volume_id(agid: u8) -> Self {
Self {
media_type: MEDIA_TYPE_BD,
address: 0,
layer_number: 0,
format: FORMAT_AACS_VOLUME_ID,
allocation_length: 36,
agid: agid & 0x03,
control: 0,
}
}
pub fn aacs_media_serial(agid: u8) -> Self {
Self {
media_type: MEDIA_TYPE_BD,
address: 0,
layer_number: 0,
format: FORMAT_AACS_MEDIA_SERIAL,
allocation_length: 36,
agid: agid & 0x03,
control: 0,
}
}
pub fn aacs_media_id(agid: u8) -> Self {
Self {
media_type: MEDIA_TYPE_BD,
address: 0,
layer_number: 0,
format: FORMAT_AACS_MEDIA_ID,
allocation_length: 36,
agid: agid & 0x03,
control: 0,
}
}
pub fn aacs_media_key_block_pack(agid: u8, pack_number: u32, layer: u8) -> Self {
Self {
media_type: MEDIA_TYPE_BD,
address: pack_number,
layer_number: layer,
format: FORMAT_AACS_MEDIA_KEY_BLOCK,
allocation_length: 32 * 1024 + 4,
agid: agid & 0x03,
control: 0,
}
}
pub fn cdb(&self) -> [u8; MMC_CDB_LEN] {
let mut cdb = [0u8; MMC_CDB_LEN];
cdb[0] = READ_DISC_STRUCTURE_OPCODE;
cdb[1] = self.media_type & 0x0F;
cdb[2] = (self.address >> 24) as u8;
cdb[3] = (self.address >> 16) as u8;
cdb[4] = (self.address >> 8) as u8;
cdb[5] = self.address as u8;
cdb[6] = self.layer_number;
cdb[7] = self.format;
cdb[8] = (self.allocation_length >> 8) as u8;
cdb[9] = self.allocation_length as u8;
cdb[10] = (self.agid & 0x03) << 6;
cdb[11] = self.control;
cdb
}
pub fn parse_cdb(cdb: &[u8; MMC_CDB_LEN]) -> Result<Self, AacsError> {
if cdb[0] != READ_DISC_STRUCTURE_OPCODE {
return Err(AacsError::InvalidValue {
what: "READ_DISC_STRUCTURE opcode",
value: cdb[0] as u64,
});
}
Ok(Self {
media_type: cdb[1] & 0x0F,
address: ((cdb[2] as u32) << 24)
| ((cdb[3] as u32) << 16)
| ((cdb[4] as u32) << 8)
| (cdb[5] as u32),
layer_number: cdb[6],
format: cdb[7],
allocation_length: ((cdb[8] as u16) << 8) | (cdb[9] as u16),
agid: (cdb[10] >> 6) & 0x03,
control: cdb[11],
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AgidResponse {
pub agid: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DriveCertChallengeResponse {
pub drive_nonce: [u8; DRIVE_NONCE_LEN],
pub drive_cert: [u8; DRIVE_CERT_LEN],
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DriveKeyResponse {
pub dv: [u8; EC_POINT_LEN],
pub dsig: [u8; EC_SIG_LEN],
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DriveCertResponse {
pub drive_cert: [u8; DRIVE_CERT_LEN],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VolumeIdResponse {
pub volume_id: [u8; VOLUME_ID_LEN],
pub mac: [u8; ID_MAC_LEN],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MediaSerialNumberResponse {
pub pmsn: [u8; VOLUME_ID_LEN],
pub mac: [u8; ID_MAC_LEN],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MediaIdentifierResponse {
pub media_id: [u8; VOLUME_ID_LEN],
pub mac: [u8; ID_MAC_LEN],
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MkbPackResponse {
pub total_packs: u8,
pub pack_data: Vec<u8>,
}
fn read_u16_be(buf: &[u8], what: &'static str) -> Result<u16, AacsError> {
if buf.len() < 2 {
return Err(AacsError::Truncated(what));
}
Ok(((buf[0] as u16) << 8) | (buf[1] as u16))
}
pub fn parse_report_key_agid(buf: &[u8]) -> Result<AgidResponse, AacsError> {
let length = read_u16_be(buf, "REPORT_KEY AGID header")?;
if length != 0x0006 {
return Err(AacsError::InvalidValue {
what: "REPORT_KEY AGID length",
value: length as u64,
});
}
if buf.len() < 8 {
return Err(AacsError::Truncated("REPORT_KEY AGID payload"));
}
Ok(AgidResponse {
agid: (buf[7] >> 6) & 0x03,
})
}
pub fn parse_report_key_drive_cert_chal(
buf: &[u8],
) -> Result<DriveCertChallengeResponse, AacsError> {
let length = read_u16_be(buf, "REPORT_KEY Drive Cert Challenge header")?;
if length != 0x0072 {
return Err(AacsError::InvalidValue {
what: "REPORT_KEY Drive Cert Challenge length",
value: length as u64,
});
}
if buf.len() < 116 {
return Err(AacsError::Truncated(
"REPORT_KEY Drive Cert Challenge payload",
));
}
let mut drive_nonce = [0u8; DRIVE_NONCE_LEN];
drive_nonce.copy_from_slice(&buf[4..4 + DRIVE_NONCE_LEN]);
let mut drive_cert = [0u8; DRIVE_CERT_LEN];
drive_cert.copy_from_slice(&buf[24..24 + DRIVE_CERT_LEN]);
Ok(DriveCertChallengeResponse {
drive_nonce,
drive_cert,
})
}
pub fn parse_report_key_drive_key(buf: &[u8]) -> Result<DriveKeyResponse, AacsError> {
let length = read_u16_be(buf, "REPORT_KEY Drive Key header")?;
if length != 0x0052 {
return Err(AacsError::InvalidValue {
what: "REPORT_KEY Drive Key length",
value: length as u64,
});
}
if buf.len() < 84 {
return Err(AacsError::Truncated("REPORT_KEY Drive Key payload"));
}
let mut dv = [0u8; EC_POINT_LEN];
dv.copy_from_slice(&buf[4..4 + EC_POINT_LEN]);
let mut dsig = [0u8; EC_SIG_LEN];
dsig.copy_from_slice(&buf[44..44 + EC_SIG_LEN]);
Ok(DriveKeyResponse { dv, dsig })
}
pub fn parse_report_key_drive_cert(buf: &[u8]) -> Result<DriveCertResponse, AacsError> {
let length = read_u16_be(buf, "REPORT_KEY Drive Cert header")?;
if length != 0x005E {
return Err(AacsError::InvalidValue {
what: "REPORT_KEY Drive Cert length",
value: length as u64,
});
}
if buf.len() < 96 {
return Err(AacsError::Truncated("REPORT_KEY Drive Cert payload"));
}
let mut drive_cert = [0u8; DRIVE_CERT_LEN];
drive_cert.copy_from_slice(&buf[4..4 + DRIVE_CERT_LEN]);
Ok(DriveCertResponse { drive_cert })
}
pub fn parse_volume_id_response(buf: &[u8]) -> Result<VolumeIdResponse, AacsError> {
let length = read_u16_be(buf, "Volume ID response header")?;
if length != 0x0022 {
return Err(AacsError::InvalidValue {
what: "Volume ID response length",
value: length as u64,
});
}
if buf.len() < 36 {
return Err(AacsError::Truncated("Volume ID response payload"));
}
let mut volume_id = [0u8; VOLUME_ID_LEN];
volume_id.copy_from_slice(&buf[4..4 + VOLUME_ID_LEN]);
let mut mac = [0u8; ID_MAC_LEN];
mac.copy_from_slice(&buf[20..20 + ID_MAC_LEN]);
Ok(VolumeIdResponse { volume_id, mac })
}
pub fn parse_media_serial_response(buf: &[u8]) -> Result<MediaSerialNumberResponse, AacsError> {
let length = read_u16_be(buf, "PMSN response header")?;
if length != 0x0022 {
return Err(AacsError::InvalidValue {
what: "PMSN response length",
value: length as u64,
});
}
if buf.len() < 36 {
return Err(AacsError::Truncated("PMSN response payload"));
}
let mut pmsn = [0u8; VOLUME_ID_LEN];
pmsn.copy_from_slice(&buf[4..4 + VOLUME_ID_LEN]);
let mut mac = [0u8; ID_MAC_LEN];
mac.copy_from_slice(&buf[20..20 + ID_MAC_LEN]);
Ok(MediaSerialNumberResponse { pmsn, mac })
}
pub fn parse_media_id_response(buf: &[u8]) -> Result<MediaIdentifierResponse, AacsError> {
let length = read_u16_be(buf, "Media ID response header")?;
if length != 0x0022 {
return Err(AacsError::InvalidValue {
what: "Media ID response length",
value: length as u64,
});
}
if buf.len() < 36 {
return Err(AacsError::Truncated("Media ID response payload"));
}
let mut media_id = [0u8; VOLUME_ID_LEN];
media_id.copy_from_slice(&buf[4..4 + VOLUME_ID_LEN]);
let mut mac = [0u8; ID_MAC_LEN];
mac.copy_from_slice(&buf[20..20 + ID_MAC_LEN]);
Ok(MediaIdentifierResponse { media_id, mac })
}
pub fn parse_mkb_pack_response(buf: &[u8]) -> Result<MkbPackResponse, AacsError> {
let length = read_u16_be(buf, "MKB pack response header")? as usize;
if length < 2 {
return Err(AacsError::InvalidValue {
what: "MKB pack response length",
value: length as u64,
});
}
let body_len = length - 2;
if buf.len() < 4 + body_len {
return Err(AacsError::Truncated("MKB pack response payload"));
}
let total_packs = buf[3];
let pack_data = buf[4..4 + body_len].to_vec();
Ok(MkbPackResponse {
total_packs,
pack_data,
})
}
pub fn build_send_key_host_cert_chal(
host_nonce: &[u8; HOST_NONCE_LEN],
host_cert: &[u8; HOST_CERT_LEN],
) -> Vec<u8> {
let mut out = Vec::with_capacity(4 + HOST_NONCE_LEN + HOST_CERT_LEN);
out.extend_from_slice(&[0x00, 0x72, 0x00, 0x00]);
out.extend_from_slice(host_nonce);
out.extend_from_slice(host_cert);
debug_assert_eq!(out.len(), 116);
out
}
pub fn build_send_key_host_key(hv: &[u8; EC_POINT_LEN], hsig: &[u8; EC_SIG_LEN]) -> Vec<u8> {
let mut out = Vec::with_capacity(4 + EC_POINT_LEN + EC_SIG_LEN);
out.extend_from_slice(&[0x00, 0x52, 0x00, 0x00]);
out.extend_from_slice(hv);
out.extend_from_slice(hsig);
debug_assert_eq!(out.len(), 84);
out
}
pub fn parse_send_key_host_cert_chal(
buf: &[u8],
) -> Result<([u8; HOST_NONCE_LEN], [u8; HOST_CERT_LEN]), AacsError> {
let length = read_u16_be(buf, "SEND_KEY Host Cert Challenge header")?;
if length != 0x0072 {
return Err(AacsError::InvalidValue {
what: "SEND_KEY Host Cert Challenge length",
value: length as u64,
});
}
if buf.len() < 116 {
return Err(AacsError::Truncated("SEND_KEY Host Cert Challenge payload"));
}
let mut host_nonce = [0u8; HOST_NONCE_LEN];
host_nonce.copy_from_slice(&buf[4..4 + HOST_NONCE_LEN]);
let mut host_cert = [0u8; HOST_CERT_LEN];
host_cert.copy_from_slice(&buf[24..24 + HOST_CERT_LEN]);
Ok((host_nonce, host_cert))
}
pub fn parse_send_key_host_key(
buf: &[u8],
) -> Result<([u8; EC_POINT_LEN], [u8; EC_SIG_LEN]), AacsError> {
let length = read_u16_be(buf, "SEND_KEY Host Key header")?;
if length != 0x0052 {
return Err(AacsError::InvalidValue {
what: "SEND_KEY Host Key length",
value: length as u64,
});
}
if buf.len() < 84 {
return Err(AacsError::Truncated("SEND_KEY Host Key payload"));
}
let mut hv = [0u8; EC_POINT_LEN];
hv.copy_from_slice(&buf[4..4 + EC_POINT_LEN]);
let mut hsig = [0u8; EC_SIG_LEN];
hsig.copy_from_slice(&buf[44..44 + EC_SIG_LEN]);
Ok((hv, hsig))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataDirection {
None,
FromDevice,
ToDevice,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScsiResponse {
pub status: u8,
pub data: Vec<u8>,
}
impl ScsiResponse {
pub fn good(data: Vec<u8>) -> Self {
Self { status: 0x00, data }
}
}
pub trait DriveCommand {
fn execute(
&mut self,
cdb: &[u8; MMC_CDB_LEN],
direction: DataDirection,
data_out: &[u8],
allocation_length: u16,
) -> Result<ScsiResponse, AacsError>;
}
#[derive(Debug, Clone)]
pub struct MockDrive {
pub agid_to_return: u8,
pub drive_nonce: [u8; DRIVE_NONCE_LEN],
pub drive_cert: [u8; DRIVE_CERT_LEN],
pub drive_dv: [u8; EC_POINT_LEN],
pub drive_dsig: [u8; EC_SIG_LEN],
pub volume_id: [u8; VOLUME_ID_LEN],
pub volume_id_mac: [u8; ID_MAC_LEN],
pub media_serial_number: [u8; VOLUME_ID_LEN],
pub media_serial_mac: [u8; ID_MAC_LEN],
pub media_identifier: [u8; VOLUME_ID_LEN],
pub media_id_mac: [u8; ID_MAC_LEN],
pub last_host_cert_chal: Option<Vec<u8>>,
pub last_host_key: Option<Vec<u8>>,
pub agid_invalidated: bool,
pub auth: Option<crate::ake::DriveAuthState>,
}
impl Default for MockDrive {
fn default() -> Self {
Self {
agid_to_return: 0,
drive_nonce: [0u8; DRIVE_NONCE_LEN],
drive_cert: [0u8; DRIVE_CERT_LEN],
drive_dv: [0u8; EC_POINT_LEN],
drive_dsig: [0u8; EC_SIG_LEN],
volume_id: [0u8; VOLUME_ID_LEN],
volume_id_mac: [0u8; ID_MAC_LEN],
media_serial_number: [0u8; VOLUME_ID_LEN],
media_serial_mac: [0u8; ID_MAC_LEN],
media_identifier: [0u8; VOLUME_ID_LEN],
media_id_mac: [0u8; ID_MAC_LEN],
last_host_cert_chal: None,
last_host_key: None,
agid_invalidated: false,
auth: None,
}
}
}
impl MockDrive {
pub fn with_test_fixture() -> Self {
let mut drive_cert = [0u8; DRIVE_CERT_LEN];
drive_cert[0] = 0x01;
drive_cert[2] = 0x00;
drive_cert[3] = 0x5C;
drive_cert[4] = 0x01;
drive_cert[5] = 0x02;
drive_cert[6] = 0x03;
drive_cert[7] = 0x04;
drive_cert[8] = 0x05;
drive_cert[9] = 0x06;
for (i, b) in drive_cert.iter_mut().enumerate().skip(10) {
*b = i as u8;
}
let mut drive_nonce = [0u8; DRIVE_NONCE_LEN];
for (i, b) in drive_nonce.iter_mut().enumerate() {
*b = 0xA0 | (i as u8);
}
let mut drive_dv = [0u8; EC_POINT_LEN];
for (i, b) in drive_dv.iter_mut().enumerate() {
*b = 0xC0 ^ (i as u8);
}
let mut drive_dsig = [0u8; EC_SIG_LEN];
for (i, b) in drive_dsig.iter_mut().enumerate() {
*b = 0xE0 ^ (i as u8);
}
let mut volume_id = [0u8; VOLUME_ID_LEN];
for (i, b) in volume_id.iter_mut().enumerate() {
*b = 0xB0 | (i as u8);
}
let mut volume_id_mac = [0u8; ID_MAC_LEN];
for (i, b) in volume_id_mac.iter_mut().enumerate() {
*b = 0x40 ^ (i as u8);
}
let mut media_serial_number = [0u8; VOLUME_ID_LEN];
for (i, b) in media_serial_number.iter_mut().enumerate() {
*b = 0x70 | (i as u8);
}
let mut media_serial_mac = [0u8; ID_MAC_LEN];
for (i, b) in media_serial_mac.iter_mut().enumerate() {
*b = 0x50 ^ (i as u8);
}
let mut media_identifier = [0u8; VOLUME_ID_LEN];
for (i, b) in media_identifier.iter_mut().enumerate() {
*b = 0x30 | (i as u8);
}
let mut media_id_mac = [0u8; ID_MAC_LEN];
for (i, b) in media_id_mac.iter_mut().enumerate() {
*b = 0x60 ^ (i as u8);
}
Self {
agid_to_return: 1,
drive_nonce,
drive_cert,
drive_dv,
drive_dsig,
volume_id,
volume_id_mac,
media_serial_number,
media_serial_mac,
media_identifier,
media_id_mac,
last_host_cert_chal: None,
last_host_key: None,
agid_invalidated: false,
auth: None,
}
}
}
impl DriveCommand for MockDrive {
fn execute(
&mut self,
cdb: &[u8; MMC_CDB_LEN],
direction: DataDirection,
data_out: &[u8],
_allocation_length: u16,
) -> Result<ScsiResponse, AacsError> {
match cdb[0] {
REPORT_KEY_OPCODE => {
let rk = ReportKey::parse_cdb(cdb)?;
if rk.key_class != KEY_CLASS_AACS {
return Err(AacsError::InvalidValue {
what: "MockDrive REPORT_KEY Key Class",
value: rk.key_class as u64,
});
}
match rk.key_format {
KF_REPORT_AACS_AGID => {
let mut out = vec![0u8; 8];
out[0] = 0x00;
out[1] = 0x06;
out[7] = (self.agid_to_return & 0x03) << 6;
Ok(ScsiResponse::good(out))
}
KF_REPORT_AACS_DRIVE_CERT_CHAL => {
let (nonce, cert): ([u8; DRIVE_NONCE_LEN], [u8; DRIVE_CERT_LEN]) =
match &self.auth {
Some(a) => (a.drive_nonce, a.drive_cert),
None => (self.drive_nonce, self.drive_cert),
};
let mut out = Vec::with_capacity(116);
out.extend_from_slice(&[0x00, 0x72, 0x00, 0x00]);
out.extend_from_slice(&nonce);
out.extend_from_slice(&cert);
Ok(ScsiResponse::good(out))
}
KF_REPORT_AACS_DRIVE_KEY => {
let (dv, dsig): ([u8; EC_POINT_LEN], [u8; EC_SIG_LEN]) = match &self.auth {
Some(a) => a.drive_key_response()?,
None => (self.drive_dv, self.drive_dsig),
};
let mut out = Vec::with_capacity(84);
out.extend_from_slice(&[0x00, 0x52, 0x00, 0x00]);
out.extend_from_slice(&dv);
out.extend_from_slice(&dsig);
Ok(ScsiResponse::good(out))
}
KF_REPORT_AACS_DRIVE_CERT => {
let mut out = Vec::with_capacity(96);
out.extend_from_slice(&[0x00, 0x5E, 0x00, 0x00]);
out.extend_from_slice(&self.drive_cert);
Ok(ScsiResponse::good(out))
}
KF_REPORT_AACS_INVALIDATE_AGID => {
self.agid_invalidated = true;
Ok(ScsiResponse::good(Vec::new()))
}
other => Err(AacsError::InvalidValue {
what: "MockDrive REPORT_KEY Key Format",
value: other as u64,
}),
}
}
SEND_KEY_OPCODE => {
let sk = SendKey::parse_cdb(cdb)?;
if sk.key_class != KEY_CLASS_AACS {
return Err(AacsError::InvalidValue {
what: "MockDrive SEND_KEY Key Class",
value: sk.key_class as u64,
});
}
if direction != DataDirection::ToDevice
&& sk.key_format != KF_SEND_AACS_INVALIDATE_AGID
{
return Err(AacsError::InvalidValue {
what: "MockDrive SEND_KEY data direction",
value: 0,
});
}
match sk.key_format {
KF_SEND_AACS_HOST_CERT_CHAL => {
let (hn, hcert) = parse_send_key_host_cert_chal(data_out)?;
if let Some(auth) = self.auth.as_mut() {
auth.accept_host_cert_challenge(&hn, &hcert)?;
}
self.last_host_cert_chal = Some(data_out.to_vec());
Ok(ScsiResponse::good(Vec::new()))
}
KF_SEND_AACS_HOST_KEY => {
let (hv, hsig) = parse_send_key_host_key(data_out)?;
if let Some(auth) = self.auth.as_mut() {
auth.accept_host_key(&hv, &hsig)?;
}
self.last_host_key = Some(data_out.to_vec());
Ok(ScsiResponse::good(Vec::new()))
}
KF_SEND_AACS_INVALIDATE_AGID => {
self.agid_invalidated = true;
Ok(ScsiResponse::good(Vec::new()))
}
other => Err(AacsError::InvalidValue {
what: "MockDrive SEND_KEY Key Format",
value: other as u64,
}),
}
}
READ_DISC_STRUCTURE_OPCODE => {
let rds = ReadDiscStructure::parse_cdb(cdb)?;
match rds.format {
FORMAT_AACS_VOLUME_ID => {
let mac: [u8; ID_MAC_LEN] = match &self.auth {
Some(a) if a.bus_key.is_some() => {
crate::aes::aes_128_cmac(&a.bus_key.unwrap(), &self.volume_id)
}
_ => self.volume_id_mac,
};
let mut out = Vec::with_capacity(36);
out.extend_from_slice(&[0x00, 0x22, 0x00, 0x00]);
out.extend_from_slice(&self.volume_id);
out.extend_from_slice(&mac);
Ok(ScsiResponse::good(out))
}
FORMAT_AACS_MEDIA_SERIAL => {
let mac: [u8; ID_MAC_LEN] = match &self.auth {
Some(a) if a.bus_key.is_some() => crate::aes::aes_128_cmac(
&a.bus_key.unwrap(),
&self.media_serial_number,
),
_ => self.media_serial_mac,
};
let mut out = Vec::with_capacity(36);
out.extend_from_slice(&[0x00, 0x22, 0x00, 0x00]);
out.extend_from_slice(&self.media_serial_number);
out.extend_from_slice(&mac);
Ok(ScsiResponse::good(out))
}
FORMAT_AACS_MEDIA_ID => {
let mac: [u8; ID_MAC_LEN] = match &self.auth {
Some(a) if a.bus_key.is_some() => crate::aes::aes_128_cmac(
&a.bus_key.unwrap(),
&self.media_identifier,
),
_ => self.media_id_mac,
};
let mut out = Vec::with_capacity(36);
out.extend_from_slice(&[0x00, 0x22, 0x00, 0x00]);
out.extend_from_slice(&self.media_identifier);
out.extend_from_slice(&mac);
Ok(ScsiResponse::good(out))
}
other => Err(AacsError::InvalidValue {
what: "MockDrive READ_DISC_STRUCTURE Format",
value: other as u64,
}),
}
}
other => Err(AacsError::InvalidValue {
what: "MockDrive unsupported opcode",
value: other as u64,
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn report_key_cdb_layout_matches_mmc6_table_513() {
let rk = ReportKey::aacs_drive_cert_challenge(2);
let cdb = rk.cdb();
assert_eq!(cdb[0], 0xA4, "opcode must be 0xA4");
assert_eq!(cdb[7], 0x02, "Key Class AACS");
assert_eq!(cdb[8], 0x00);
assert_eq!(cdb[9], 0x74);
assert_eq!(cdb[10], 0x81);
assert_eq!(cdb[11], 0x00, "default Control byte");
let parsed = ReportKey::parse_cdb(&cdb).unwrap();
assert_eq!(parsed, rk);
}
#[test]
fn send_key_cdb_layout_matches_mmc6_table_599() {
let sk = SendKey::aacs_host_cert_challenge(3);
let cdb = sk.cdb();
assert_eq!(cdb[0], 0xA3);
assert_eq!(cdb[7], 0x02);
assert_eq!(cdb[8], 0x00);
assert_eq!(cdb[9], 0x74);
assert_eq!(cdb[10], 0xC1);
let parsed = SendKey::parse_cdb(&cdb).unwrap();
assert_eq!(parsed, sk);
}
#[test]
fn read_disc_structure_cdb_layout_matches_mmc6_table_381() {
let rds = ReadDiscStructure::aacs_volume_id(1);
let cdb = rds.cdb();
assert_eq!(cdb[0], 0xAD);
assert_eq!(cdb[1] & 0x0F, 0x01);
assert_eq!(cdb[7], 0x80);
assert_eq!(cdb[8], 0x00);
assert_eq!(cdb[9], 0x24);
assert_eq!(cdb[10], 0x40);
let parsed = ReadDiscStructure::parse_cdb(&cdb).unwrap();
assert_eq!(parsed, rds);
}
#[test]
fn rejects_wrong_opcode_in_parse_cdb() {
let mut cdb = [0u8; MMC_CDB_LEN];
cdb[0] = 0xFF;
assert!(ReportKey::parse_cdb(&cdb).is_err());
assert!(SendKey::parse_cdb(&cdb).is_err());
assert!(ReadDiscStructure::parse_cdb(&cdb).is_err());
}
#[test]
fn agid_field_packing_round_trip() {
for agid in 0..=3u8 {
let rk = ReportKey {
key_class: KEY_CLASS_AACS,
key_format: KF_REPORT_AACS_DRIVE_KEY,
agid,
lba_or_starting_offset: 0,
block_count_function: 0,
allocation_length: 84,
control: 0,
};
let cdb = rk.cdb();
assert_eq!(cdb[10] >> 6, agid);
let parsed = ReportKey::parse_cdb(&cdb).unwrap();
assert_eq!(parsed.agid, agid);
}
}
#[test]
fn media_serial_cdb_uses_format_0x81() {
let rds = ReadDiscStructure::aacs_media_serial(2);
let cdb = rds.cdb();
assert_eq!(cdb[0], READ_DISC_STRUCTURE_OPCODE);
assert_eq!(cdb[7], FORMAT_AACS_MEDIA_SERIAL);
assert_eq!(cdb[8], 0x00);
assert_eq!(cdb[9], 0x24);
assert_eq!(cdb[10] >> 6, 2);
}
#[test]
fn media_id_cdb_uses_format_0x82() {
let rds = ReadDiscStructure::aacs_media_id(3);
let cdb = rds.cdb();
assert_eq!(cdb[0], READ_DISC_STRUCTURE_OPCODE);
assert_eq!(cdb[7], FORMAT_AACS_MEDIA_ID);
assert_eq!(cdb[8], 0x00);
assert_eq!(cdb[9], 0x24);
assert_eq!(cdb[10] >> 6, 3);
}
#[test]
fn media_serial_response_parser_round_trip() {
let pmsn = [0xAA; VOLUME_ID_LEN];
let mac = [0x55; ID_MAC_LEN];
let mut wire = Vec::with_capacity(36);
wire.extend_from_slice(&[0x00, 0x22, 0x00, 0x00]);
wire.extend_from_slice(&pmsn);
wire.extend_from_slice(&mac);
let parsed = parse_media_serial_response(&wire).unwrap();
assert_eq!(parsed.pmsn, pmsn);
assert_eq!(parsed.mac, mac);
}
#[test]
fn media_id_response_parser_round_trip() {
let mid = [0x33; VOLUME_ID_LEN];
let mac = [0xCC; ID_MAC_LEN];
let mut wire = Vec::with_capacity(36);
wire.extend_from_slice(&[0x00, 0x22, 0x00, 0x00]);
wire.extend_from_slice(&mid);
wire.extend_from_slice(&mac);
let parsed = parse_media_id_response(&wire).unwrap();
assert_eq!(parsed.media_id, mid);
assert_eq!(parsed.mac, mac);
}
#[test]
fn media_serial_parser_rejects_wrong_length_field() {
let mut wire = vec![0x00, 0x10, 0x00, 0x00];
wire.resize(36, 0);
assert!(parse_media_serial_response(&wire).is_err());
}
#[test]
fn media_id_parser_rejects_truncated_payload() {
let wire = [0x00, 0x22, 0x00, 0x00, 0xAA, 0xBB];
assert!(parse_media_id_response(&wire).is_err());
}
#[test]
fn mkb_pack_response_parser_round_trip() {
let pack_data: Vec<u8> = (0..32u8).collect();
let total_packs = 5u8;
let length: u16 = (2 + pack_data.len()) as u16;
let mut wire = vec![
(length >> 8) as u8,
(length & 0xFF) as u8,
0x00, total_packs,
];
wire.extend_from_slice(&pack_data);
let parsed = parse_mkb_pack_response(&wire).unwrap();
assert_eq!(parsed.total_packs, total_packs);
assert_eq!(parsed.pack_data, pack_data);
}
#[test]
fn mkb_pack_parser_rejects_truncated_payload() {
let wire = [0x00, 0x66, 0x00, 0x01];
assert!(parse_mkb_pack_response(&wire).is_err());
}
}