aacs: preserve transport-failure errors through the auth handshake
A SEND KEY / REPORT KEY step in the bus-auth handshake mapped every SCSI error to a cert/key-specific code (AacsCertRejected, AacsCertRead, AacsKeyRead, AacsKeyRejected, etc.) via map_err(|_| ...). That discarded the underlying SCSI error, so a transport-layer wedge (bridge crash / USB disconnect) mid-handshake was reported as 'drive rejected your host cert', sending operators down a keydb/host-cert dead end for what is really a replug/power-cycle situation. Add a handshake_err() helper that keeps the original error when it is a transport failure (is_scsi_transport_failure) and only substitutes the handshake-specific code for genuine SCSI rejections. Apply it at every SEND KEY / REPORT KEY / REPORT DISC STRUCTURE step in both the AACS 1.0 and AACS 2.0 paths. Add a regression test covering both branches.
This commit is contained in:
+73
-12
@@ -25,6 +25,25 @@ use num_bigint::BigUint;
|
|||||||
use num_traits::{One, Zero};
|
use num_traits::{One, Zero};
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
|
|
||||||
|
/// Map a SCSI-layer error from a handshake step onto a cert/key-specific
|
||||||
|
/// code — but only when the failure is *not* a transport-layer wedge.
|
||||||
|
///
|
||||||
|
/// A SEND KEY / REPORT KEY step can fail because the drive genuinely
|
||||||
|
/// rejected the host certificate or key (a real `Aacs*` condition), or
|
||||||
|
/// because the transport died mid-handshake (bridge wedge / USB
|
||||||
|
/// disconnect). Collapsing the latter into a cert/key code tells the
|
||||||
|
/// operator the drive rejected their credentials, sending them down a
|
||||||
|
/// keydb/host-cert rabbit hole for what is actually a replug/power-cycle
|
||||||
|
/// situation. Preserve the transport error so the true root cause is
|
||||||
|
/// surfaced; otherwise substitute the handshake-specific code.
|
||||||
|
fn handshake_err(err: Error, fallback: Error) -> Error {
|
||||||
|
if err.is_scsi_transport_failure() {
|
||||||
|
err
|
||||||
|
} else {
|
||||||
|
fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute a SCSI command that reads data from the device.
|
/// Execute a SCSI command that reads data from the device.
|
||||||
fn scsi_read(session: &mut Drive, cdb: &[u8], len: usize) -> Result<Vec<u8>> {
|
fn scsi_read(session: &mut Drive, cdb: &[u8], len: usize) -> Result<Vec<u8>> {
|
||||||
let mut buf = vec![0u8; len];
|
let mut buf = vec![0u8; len];
|
||||||
@@ -878,7 +897,8 @@ pub fn aacs_authenticate(
|
|||||||
|
|
||||||
// Step 2: Allocate AGID
|
// Step 2: Allocate AGID
|
||||||
let cdb = cdb_report_key(0, 0x00, 8);
|
let cdb = cdb_report_key(0, 0x00, 8);
|
||||||
let response = scsi_read(session, &cdb, 8).map_err(|_| Error::AacsAgidAlloc)?;
|
let response =
|
||||||
|
scsi_read(session, &cdb, 8).map_err(|e| handshake_err(e, Error::AacsAgidAlloc))?;
|
||||||
let agid = (response[7] >> 6) & 0x03;
|
let agid = (response[7] >> 6) & 0x03;
|
||||||
|
|
||||||
// Step 3: Generate host nonce and ephemeral key pair
|
// Step 3: Generate host nonce and ephemeral key pair
|
||||||
@@ -894,11 +914,12 @@ pub fn aacs_authenticate(
|
|||||||
send_buf[24..116].copy_from_slice(&host_cert[..92]);
|
send_buf[24..116].copy_from_slice(&host_cert[..92]);
|
||||||
|
|
||||||
let cdb = cdb_send_key(agid, 0x01, 116);
|
let cdb = cdb_send_key(agid, 0x01, 116);
|
||||||
scsi_write(session, &cdb, &send_buf).map_err(|_| Error::AacsCertRejected)?;
|
scsi_write(session, &cdb, &send_buf).map_err(|e| handshake_err(e, Error::AacsCertRejected))?;
|
||||||
|
|
||||||
// Step 5: Read drive certificate + nonce (REPORT KEY format 0x01)
|
// Step 5: Read drive certificate + nonce (REPORT KEY format 0x01)
|
||||||
let cdb = cdb_report_key(agid, 0x01, 116);
|
let cdb = cdb_report_key(agid, 0x01, 116);
|
||||||
let response = scsi_read(session, &cdb, 116).map_err(|_| Error::AacsCertRead)?;
|
let response =
|
||||||
|
scsi_read(session, &cdb, 116).map_err(|e| handshake_err(e, Error::AacsCertRead))?;
|
||||||
|
|
||||||
let mut drive_nonce = [0u8; 20];
|
let mut drive_nonce = [0u8; 20];
|
||||||
let mut drive_cert = [0u8; 92];
|
let mut drive_cert = [0u8; 92];
|
||||||
@@ -926,7 +947,8 @@ pub fn aacs_authenticate(
|
|||||||
|
|
||||||
// Step 6: Read drive key point + signature (REPORT KEY format 0x02)
|
// Step 6: Read drive key point + signature (REPORT KEY format 0x02)
|
||||||
let cdb = cdb_report_key(agid, 0x02, 84);
|
let cdb = cdb_report_key(agid, 0x02, 84);
|
||||||
let response = scsi_read(session, &cdb, 84).map_err(|_| Error::AacsKeyRead)?;
|
let response =
|
||||||
|
scsi_read(session, &cdb, 84).map_err(|e| handshake_err(e, Error::AacsKeyRead))?;
|
||||||
|
|
||||||
let mut drive_key_point = [0u8; 40]; // x(20) + y(20)
|
let mut drive_key_point = [0u8; 40]; // x(20) + y(20)
|
||||||
let mut drive_key_sig = [0u8; 40]; // r(20) + s(20)
|
let mut drive_key_sig = [0u8; 40]; // r(20) + s(20)
|
||||||
@@ -971,7 +993,7 @@ pub fn aacs_authenticate(
|
|||||||
send_buf[64..84].copy_from_slice(&host_sig_s);
|
send_buf[64..84].copy_from_slice(&host_sig_s);
|
||||||
|
|
||||||
let cdb = cdb_send_key(agid, 0x02, 84);
|
let cdb = cdb_send_key(agid, 0x02, 84);
|
||||||
scsi_write(session, &cdb, &send_buf).map_err(|_| Error::AacsKeyRejected)?;
|
scsi_write(session, &cdb, &send_buf).map_err(|e| handshake_err(e, Error::AacsKeyRejected))?;
|
||||||
|
|
||||||
// Step 9: Compute bus key via ECDH
|
// Step 9: Compute bus key via ECDH
|
||||||
let mut dkp_x = [0u8; 20];
|
let mut dkp_x = [0u8; 20];
|
||||||
@@ -1038,7 +1060,8 @@ fn aacs2_authenticate_p256(
|
|||||||
|
|
||||||
// Step 2: Allocate AGID
|
// Step 2: Allocate AGID
|
||||||
let cdb = cdb_report_key(0, 0x00, 8);
|
let cdb = cdb_report_key(0, 0x00, 8);
|
||||||
let response = scsi_read(session, &cdb, 8).map_err(|_| Error::AacsAgidAlloc)?;
|
let response =
|
||||||
|
scsi_read(session, &cdb, 8).map_err(|e| handshake_err(e, Error::AacsAgidAlloc))?;
|
||||||
let agid = (response[7] >> 6) & 0x03;
|
let agid = (response[7] >> 6) & 0x03;
|
||||||
|
|
||||||
// Step 3: Generate host nonce + P-256 ephemeral key pair
|
// Step 3: Generate host nonce + P-256 ephemeral key pair
|
||||||
@@ -1055,12 +1078,13 @@ fn aacs2_authenticate_p256(
|
|||||||
send_buf[24..156].copy_from_slice(&host_cert[..132]);
|
send_buf[24..156].copy_from_slice(&host_cert[..132]);
|
||||||
|
|
||||||
let cdb = cdb_send_key(agid, 0x01, 156);
|
let cdb = cdb_send_key(agid, 0x01, 156);
|
||||||
scsi_write(session, &cdb, &send_buf).map_err(|_| Error::AacsCertRejected)?;
|
scsi_write(session, &cdb, &send_buf).map_err(|e| handshake_err(e, Error::AacsCertRejected))?;
|
||||||
|
|
||||||
// Step 5: Read drive certificate + nonce
|
// Step 5: Read drive certificate + nonce
|
||||||
// AACS 2.0 drive cert is also 132 bytes
|
// AACS 2.0 drive cert is also 132 bytes
|
||||||
let cdb = cdb_report_key(agid, 0x01, 156);
|
let cdb = cdb_report_key(agid, 0x01, 156);
|
||||||
let response = scsi_read(session, &cdb, 156).map_err(|_| Error::AacsCertRead)?;
|
let response =
|
||||||
|
scsi_read(session, &cdb, 156).map_err(|e| handshake_err(e, Error::AacsCertRead))?;
|
||||||
|
|
||||||
let mut drive_nonce = [0u8; 20];
|
let mut drive_nonce = [0u8; 20];
|
||||||
drive_nonce.copy_from_slice(&response[4..24]);
|
drive_nonce.copy_from_slice(&response[4..24]);
|
||||||
@@ -1083,7 +1107,8 @@ fn aacs2_authenticate_p256(
|
|||||||
|
|
||||||
// Step 6: Read drive key point + signature (P-256: 64+64 = 128 bytes)
|
// Step 6: Read drive key point + signature (P-256: 64+64 = 128 bytes)
|
||||||
let cdb = cdb_report_key(agid, 0x02, 132);
|
let cdb = cdb_report_key(agid, 0x02, 132);
|
||||||
let response = scsi_read(session, &cdb, 132).map_err(|_| Error::AacsKeyRead)?;
|
let response =
|
||||||
|
scsi_read(session, &cdb, 132).map_err(|e| handshake_err(e, Error::AacsKeyRead))?;
|
||||||
|
|
||||||
let drive_key_x = &response[4..36];
|
let drive_key_x = &response[4..36];
|
||||||
let drive_key_y = &response[36..68];
|
let drive_key_y = &response[36..68];
|
||||||
@@ -1124,7 +1149,7 @@ fn aacs2_authenticate_p256(
|
|||||||
send_buf[100..132].copy_from_slice(&host_sig_s);
|
send_buf[100..132].copy_from_slice(&host_sig_s);
|
||||||
|
|
||||||
let cdb = cdb_send_key(agid, 0x02, 132);
|
let cdb = cdb_send_key(agid, 0x02, 132);
|
||||||
scsi_write(session, &cdb, &send_buf).map_err(|_| Error::AacsKeyRejected)?;
|
scsi_write(session, &cdb, &send_buf).map_err(|e| handshake_err(e, Error::AacsKeyRejected))?;
|
||||||
|
|
||||||
// Step 9: Compute bus key via P-256 ECDH
|
// Step 9: Compute bus key via P-256 ECDH
|
||||||
let bus_key = compute_bus_key_p256(&host_eph_key, drive_key_x, drive_key_y)
|
let bus_key = compute_bus_key_p256(&host_eph_key, drive_key_x, drive_key_y)
|
||||||
@@ -1147,7 +1172,8 @@ fn aacs2_authenticate_p256(
|
|||||||
pub fn read_volume_id(session: &mut Drive, auth: &mut AacsAuth) -> Result<[u8; 16]> {
|
pub fn read_volume_id(session: &mut Drive, auth: &mut AacsAuth) -> Result<[u8; 16]> {
|
||||||
// REPORT DISC STRUCTURE format 0x80
|
// REPORT DISC STRUCTURE format 0x80
|
||||||
let cdb = cdb_report_disc_structure(auth.agid, 0x80, 36);
|
let cdb = cdb_report_disc_structure(auth.agid, 0x80, 36);
|
||||||
let response = scsi_read(session, &cdb, 36).map_err(|_| Error::AacsVidRead)?;
|
let response =
|
||||||
|
scsi_read(session, &cdb, 36).map_err(|e| handshake_err(e, Error::AacsVidRead))?;
|
||||||
|
|
||||||
let mut vid = [0u8; 16];
|
let mut vid = [0u8; 16];
|
||||||
let mut mac = [0u8; 16];
|
let mut mac = [0u8; 16];
|
||||||
@@ -1168,7 +1194,8 @@ pub fn read_volume_id(session: &mut Drive, auth: &mut AacsAuth) -> Result<[u8; 1
|
|||||||
pub fn read_data_keys(session: &mut Drive, auth: &mut AacsAuth) -> Result<([u8; 16], [u8; 16])> {
|
pub fn read_data_keys(session: &mut Drive, auth: &mut AacsAuth) -> Result<([u8; 16], [u8; 16])> {
|
||||||
// REPORT DISC STRUCTURE format 0x84
|
// REPORT DISC STRUCTURE format 0x84
|
||||||
let cdb = cdb_report_disc_structure(auth.agid, 0x84, 36);
|
let cdb = cdb_report_disc_structure(auth.agid, 0x84, 36);
|
||||||
let response = scsi_read(session, &cdb, 36).map_err(|_| Error::AacsDataKey)?;
|
let response =
|
||||||
|
scsi_read(session, &cdb, 36).map_err(|e| handshake_err(e, Error::AacsDataKey))?;
|
||||||
|
|
||||||
let mut enc_rdk = [0u8; 16];
|
let mut enc_rdk = [0u8; 16];
|
||||||
let mut enc_wdk = [0u8; 16];
|
let mut enc_wdk = [0u8; 16];
|
||||||
@@ -1189,6 +1216,40 @@ pub fn read_data_keys(session: &mut Drive, auth: &mut AacsAuth) -> Result<([u8;
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn handshake_err_preserves_transport_failure() {
|
||||||
|
use crate::scsi::{SCSI_STATUS_CHECK_CONDITION, SCSI_STATUS_TRANSPORT_FAILURE};
|
||||||
|
|
||||||
|
// A transport wedge mid-handshake must NOT be reported as a cert/key
|
||||||
|
// rejection — the operator needs to see the real (replug) cause, not
|
||||||
|
// be sent down a keydb/host-cert rabbit hole.
|
||||||
|
let transport = Error::ScsiError {
|
||||||
|
opcode: 0xA3, // SEND KEY
|
||||||
|
status: SCSI_STATUS_TRANSPORT_FAILURE,
|
||||||
|
sense: None,
|
||||||
|
};
|
||||||
|
let mapped = handshake_err(transport, Error::AacsCertRejected);
|
||||||
|
assert!(
|
||||||
|
mapped.is_scsi_transport_failure(),
|
||||||
|
"transport failure must be preserved, not collapsed to a cert code"
|
||||||
|
);
|
||||||
|
|
||||||
|
// A genuine SCSI rejection (CHECK CONDITION) IS the drive saying no, so
|
||||||
|
// it maps to the handshake-specific code as before.
|
||||||
|
let rejected = Error::ScsiError {
|
||||||
|
opcode: 0xA3,
|
||||||
|
status: SCSI_STATUS_CHECK_CONDITION,
|
||||||
|
sense: Some(crate::scsi::ScsiSense {
|
||||||
|
sense_key: 0x05, // ILLEGAL REQUEST
|
||||||
|
asc: 0x24,
|
||||||
|
ascq: 0x00,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let mapped = handshake_err(rejected, Error::AacsCertRejected);
|
||||||
|
assert!(matches!(mapped, Error::AacsCertRejected));
|
||||||
|
assert!(!mapped.is_scsi_transport_failure());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ec_curve_generator_on_curve() {
|
fn test_ec_curve_generator_on_curve() {
|
||||||
// Verify G is on the curve: y² = x³ + ax + b (mod p)
|
// Verify G is on the curve: y² = x³ + ax + b (mod p)
|
||||||
|
|||||||
Reference in New Issue
Block a user