use crate::error::{Error, Result};
use crate::drive::DriveSession;
use crate::scsi::DataDirection;
use num_bigint::BigUint;
use num_traits::{One, Zero};
use sha1::{Sha1, Digest};
fn scsi_read(session: &mut DriveSession, cdb: &[u8], len: usize) -> Result<Vec<u8>> {
let mut buf = vec![0u8; len];
session.scsi_execute(cdb, DataDirection::FromDevice, &mut buf, 5_000)?;
Ok(buf)
}
fn scsi_write(session: &mut DriveSession, cdb: &[u8], data: &[u8]) -> Result<()> {
let mut buf = data.to_vec();
session.scsi_execute(cdb, DataDirection::ToDevice, &mut buf, 5_000)?;
Ok(())
}
const EC_P: [u8; 20] = [
0x9D, 0xC9, 0xD8, 0x13, 0x55, 0xEC, 0xCE, 0xB5, 0x60, 0xBD,
0xB0, 0x9E, 0xF9, 0xEA, 0xE7, 0xC4, 0x79, 0xA7, 0xD7, 0xDF,
];
const EC_A: [u8; 20] = [
0x9D, 0xC9, 0xD8, 0x13, 0x55, 0xEC, 0xCE, 0xB5, 0x60, 0xBD,
0xB0, 0x9E, 0xF9, 0xEA, 0xE7, 0xC4, 0x79, 0xA7, 0xD7, 0xDC,
];
#[cfg(test)]
const EC_B: [u8; 20] = [
0x40, 0x2D, 0xAD, 0x3E, 0xC1, 0xCB, 0xCD, 0x16, 0x52, 0x48,
0xD6, 0x8E, 0x12, 0x45, 0xE0, 0xC4, 0xDA, 0xAC, 0xB1, 0xD8,
];
const EC_N: [u8; 20] = [
0x9D, 0xC9, 0xD8, 0x13, 0x55, 0xEC, 0xCE, 0xB5, 0x60, 0xBD,
0xC4, 0x4F, 0x54, 0x81, 0x7B, 0x2C, 0x7F, 0x5A, 0xB0, 0x17,
];
const EC_GX: [u8; 20] = [
0x2E, 0x64, 0xFC, 0x22, 0x57, 0x83, 0x51, 0xE6, 0xF4, 0xCC,
0xA7, 0xEB, 0x81, 0xD0, 0xA4, 0xBD, 0xC5, 0x4C, 0xCE, 0xC6,
];
const EC_GY: [u8; 20] = [
0x09, 0x14, 0xA2, 0x5D, 0xD0, 0x54, 0x42, 0x88, 0x9D, 0xB4,
0x55, 0xC7, 0xF2, 0x3C, 0x9A, 0x07, 0x07, 0xF5, 0xCB, 0xB9,
];
const AACS_LA_PUB_X: [u8; 20] = [
0x01, 0xF3, 0x5D, 0xAB, 0xD8, 0xAE, 0x5F, 0x40, 0x56, 0x5E,
0x30, 0xC8, 0x8A, 0x60, 0x42, 0x82, 0x07, 0x61, 0xDF, 0x93,
];
const AACS_LA_PUB_Y: [u8; 20] = [
0x44, 0x87, 0xB5, 0xAC, 0x07, 0x10, 0x8D, 0x10, 0x5B, 0xA5,
0xB9, 0xE3, 0x2F, 0x3B, 0xBB, 0xFC, 0x0C, 0x2C, 0xBC, 0xD1,
];
#[derive(Clone, Debug)]
struct EcPoint {
x: BigUint,
y: BigUint,
infinity: bool,
}
impl EcPoint {
fn infinity() -> Self {
EcPoint { x: BigUint::zero(), y: BigUint::zero(), infinity: true }
}
fn new(x: BigUint, y: BigUint) -> Self {
EcPoint { x, y, infinity: false }
}
fn from_bytes(x_bytes: &[u8], y_bytes: &[u8]) -> Self {
EcPoint::new(BigUint::from_bytes_be(x_bytes), BigUint::from_bytes_be(y_bytes))
}
}
fn mod_inv(a: &BigUint, m: &BigUint) -> Option<BigUint> {
use num_bigint::BigInt;
use num_traits::Signed;
let a = BigInt::from(a.clone());
let m = BigInt::from(m.clone());
let (mut old_r, mut r) = (a, m.clone());
let (mut old_s, mut s) = (BigInt::one(), BigInt::zero());
while !r.is_zero() {
let q = &old_r / &r;
let temp_r = r.clone();
r = old_r - &q * &r;
old_r = temp_r;
let temp_s = s.clone();
s = old_s - &q * &s;
old_s = temp_s;
}
if old_r != BigInt::one() {
return None;
}
if old_s.is_negative() {
old_s += &m;
}
Some(old_s.to_biguint().unwrap())
}
fn ec_add(p1: &EcPoint, p2: &EcPoint, a: &BigUint, p: &BigUint) -> EcPoint {
if p1.infinity { return p2.clone(); }
if p2.infinity { return p1.clone(); }
if p1.x == p2.x {
if p1.y == p2.y && !p1.y.is_zero() {
return ec_double(p1, a, p);
}
return EcPoint::infinity();
}
let dy = if p2.y >= p1.y {
(&p2.y - &p1.y) % p
} else {
(p - (&p1.y - &p2.y) % p) % p
};
let dx = if p2.x >= p1.x {
(&p2.x - &p1.x) % p
} else {
(p - (&p1.x - &p2.x) % p) % p
};
let dx_inv = mod_inv(&dx, p).unwrap();
let lam = (&dy * &dx_inv) % p;
let x3 = {
let lam2 = (&lam * &lam) % p;
let sum = (&p1.x + &p2.x) % p;
if lam2 >= sum {
(lam2 - sum) % p
} else {
(p - (sum - lam2) % p) % p
}
};
let y3 = {
let diff = if p1.x >= x3 {
(&p1.x - &x3) % p
} else {
(p - (&x3 - &p1.x) % p) % p
};
let prod = (&lam * &diff) % p;
if prod >= p1.y {
(prod - &p1.y) % p
} else {
(p - (&p1.y - prod) % p) % p
}
};
EcPoint::new(x3, y3)
}
fn ec_double(pt: &EcPoint, a: &BigUint, p: &BigUint) -> EcPoint {
if pt.infinity || pt.y.is_zero() {
return EcPoint::infinity();
}
let three = BigUint::from(3u32);
let two = BigUint::from(2u32);
let numerator = (&three * &pt.x * &pt.x + a) % p;
let denominator = (&two * &pt.y) % p;
let denom_inv = mod_inv(&denominator, p).unwrap();
let lam = (&numerator * &denom_inv) % p;
let x3 = {
let lam2 = (&lam * &lam) % p;
let two_x = (&two * &pt.x) % p;
if lam2 >= two_x {
(lam2 - two_x) % p
} else {
(p - (two_x - lam2) % p) % p
}
};
let y3 = {
let diff = if pt.x >= x3 {
(&pt.x - &x3) % p
} else {
(p - (&x3 - &pt.x) % p) % p
};
let prod = (&lam * &diff) % p;
if prod >= pt.y {
(prod - &pt.y) % p
} else {
(p - (&pt.y - prod) % p) % p
}
};
EcPoint::new(x3, y3)
}
fn ec_mul(k: &BigUint, pt: &EcPoint, a: &BigUint, p: &BigUint) -> EcPoint {
if k.is_zero() {
return EcPoint::infinity();
}
let mut result = EcPoint::infinity();
let mut base = pt.clone();
let mut scalar = k.clone();
while !scalar.is_zero() {
if scalar.bit(0) {
result = ec_add(&result, &base, a, p);
}
base = ec_double(&base, a, p);
scalar >>= 1;
}
result
}
fn to_bytes_be_padded(n: &BigUint, len: usize) -> Vec<u8> {
let bytes = n.to_bytes_be();
if bytes.len() >= len {
bytes[bytes.len() - len..].to_vec()
} else {
let mut padded = vec![0u8; len - bytes.len()];
padded.extend_from_slice(&bytes);
padded
}
}
fn ecdsa_sign(priv_key: &[u8; 20], data: &[u8]) -> ([u8; 20], [u8; 20]) {
let p = BigUint::from_bytes_be(&EC_P);
let a = BigUint::from_bytes_be(&EC_A);
let n = BigUint::from_bytes_be(&EC_N);
let g = EcPoint::from_bytes(&EC_GX, &EC_GY);
let d = BigUint::from_bytes_be(priv_key);
let hash = Sha1::digest(data);
let z = BigUint::from_bytes_be(&hash);
loop {
let mut k_bytes = [0u8; 20];
use rand::RngCore;
rand::thread_rng().fill_bytes(&mut k_bytes);
let k = BigUint::from_bytes_be(&k_bytes) % &n;
if k.is_zero() { continue; }
let r_point = ec_mul(&k, &g, &a, &p);
let r = &r_point.x % &n;
if r.is_zero() { continue; }
let k_inv = match mod_inv(&k, &n) {
Some(v) => v,
None => continue,
};
let s = (&k_inv * ((&z + &r * &d) % &n)) % &n;
if s.is_zero() { continue; }
let r_bytes = to_bytes_be_padded(&r, 20);
let s_bytes = to_bytes_be_padded(&s, 20);
let mut r_out = [0u8; 20];
let mut s_out = [0u8; 20];
r_out.copy_from_slice(&r_bytes);
s_out.copy_from_slice(&s_bytes);
return (r_out, s_out);
}
}
fn ecdsa_verify(pub_x: &[u8; 20], pub_y: &[u8; 20], sig_r: &[u8; 20], sig_s: &[u8; 20], data: &[u8]) -> bool {
let p = BigUint::from_bytes_be(&EC_P);
let a = BigUint::from_bytes_be(&EC_A);
let n = BigUint::from_bytes_be(&EC_N);
let g = EcPoint::from_bytes(&EC_GX, &EC_GY);
let q = EcPoint::from_bytes(pub_x, pub_y);
let r = BigUint::from_bytes_be(sig_r);
let s = BigUint::from_bytes_be(sig_s);
if r.is_zero() || r >= n || s.is_zero() || s >= n {
return false;
}
let hash = Sha1::digest(data);
let z = BigUint::from_bytes_be(&hash);
let s_inv = match mod_inv(&s, &n) {
Some(v) => v,
None => return false,
};
let u1 = (&z * &s_inv) % &n;
let u2 = (&r * &s_inv) % &n;
let p1 = ec_mul(&u1, &g, &a, &p);
let p2 = ec_mul(&u2, &q, &a, &p);
let r_point = ec_add(&p1, &p2, &a, &p);
if r_point.infinity {
return false;
}
&r_point.x % &n == r
}
fn verify_cert(cert: &[u8]) -> bool {
if cert.len() < 92 { return false; }
let mut sig_r = [0u8; 20];
let mut sig_s = [0u8; 20];
sig_r.copy_from_slice(&cert[52..72]);
sig_s.copy_from_slice(&cert[72..92]);
ecdsa_verify(&AACS_LA_PUB_X, &AACS_LA_PUB_Y, &sig_r, &sig_s, &cert[..52])
}
fn cert_pub_key(cert: &[u8]) -> ([u8; 20], [u8; 20]) {
let mut x = [0u8; 20];
let mut y = [0u8; 20];
x.copy_from_slice(&cert[12..32]);
y.copy_from_slice(&cert[32..52]);
(x, y)
}
fn compute_bus_key(host_priv: &[u8; 20], drive_key_point_x: &[u8; 20], drive_key_point_y: &[u8; 20]) -> [u8; 16] {
let p = BigUint::from_bytes_be(&EC_P);
let a = BigUint::from_bytes_be(&EC_A);
let d = BigUint::from_bytes_be(host_priv);
let dkp = EcPoint::from_bytes(drive_key_point_x, drive_key_point_y);
let shared = ec_mul(&d, &dkp, &a, &p);
let x_bytes = to_bytes_be_padded(&shared.x, 20);
let mut bus_key = [0u8; 16];
bus_key.copy_from_slice(&x_bytes[4..20]); bus_key
}
fn generate_host_key_pair() -> ([u8; 20], [u8; 20], [u8; 20]) {
let p_mod = BigUint::from_bytes_be(&EC_P);
let a = BigUint::from_bytes_be(&EC_A);
let g = EcPoint::from_bytes(&EC_GX, &EC_GY);
let mut priv_bytes = [0u8; 20];
use rand::RngCore;
rand::thread_rng().fill_bytes(&mut priv_bytes);
let d = BigUint::from_bytes_be(&priv_bytes);
let q = ec_mul(&d, &g, &a, &p_mod);
let qx = to_bytes_be_padded(&q.x, 20);
let qy = to_bytes_be_padded(&q.y, 20);
let mut pub_x = [0u8; 20];
let mut pub_y = [0u8; 20];
pub_x.copy_from_slice(&qx);
pub_y.copy_from_slice(&qy);
(priv_bytes, pub_x, pub_y)
}
fn aes_cmac_16(data: &[u8; 16], key: &[u8; 16]) -> [u8; 16] {
use aes::Aes128;
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
let cipher = Aes128::new(GenericArray::from_slice(key));
let mut l = GenericArray::clone_from_slice(&[0u8; 16]);
cipher.encrypt_block(&mut l);
let mut k1 = [0u8; 16];
let carry = (l[0] >> 7) & 1;
for i in 0..15 {
k1[i] = (l[i] << 1) | (l[i + 1] >> 7);
}
k1[15] = l[15] << 1;
if carry == 1 {
k1[15] ^= 0x87; }
let mut block = [0u8; 16];
for i in 0..16 {
block[i] = data[i] ^ k1[i];
}
let mut ga = GenericArray::clone_from_slice(&block);
cipher.encrypt_block(&mut ga);
let mut mac = [0u8; 16];
mac.copy_from_slice(&ga);
mac
}
fn cdb_report_key(agid: u8, format: u8, len: u16) -> [u8; 12] {
let mut cdb = [0u8; 12];
cdb[0] = crate::scsi::SCSI_REPORT_KEY;
cdb[7] = crate::scsi::AACS_KEY_CLASS;
cdb[8] = (len >> 8) as u8;
cdb[9] = (len & 0xFF) as u8;
cdb[10] = (agid << 6) | (format & 0x3F);
cdb
}
fn cdb_send_key(agid: u8, format: u8, len: u16) -> [u8; 12] {
let mut cdb = [0u8; 12];
cdb[0] = crate::scsi::SCSI_SEND_KEY;
cdb[7] = crate::scsi::AACS_KEY_CLASS;
cdb[8] = (len >> 8) as u8;
cdb[9] = (len & 0xFF) as u8;
cdb[10] = (agid << 6) | (format & 0x3F);
cdb
}
fn cdb_report_disc_structure(agid: u8, format: u8, len: u16) -> [u8; 12] {
let mut cdb = [0u8; 12];
cdb[0] = crate::scsi::SCSI_READ_DISC_STRUCTURE;
cdb[1] = 0x01; cdb[7] = format;
cdb[8] = (len >> 8) as u8;
cdb[9] = (len & 0xFF) as u8;
cdb[10] = agid << 6;
cdb
}
#[derive(Debug)]
pub struct AacsAuth {
pub bus_key: [u8; 16],
pub agid: u8,
pub volume_id: Option<[u8; 16]>,
pub read_data_key: Option<[u8; 16]>,
pub drive_cert: [u8; 92],
}
pub fn aacs_authenticate(
session: &mut DriveSession,
host_priv_key: &[u8; 20],
host_cert: &[u8],
) -> Result<AacsAuth> {
if host_cert.len() < 92 {
return Err(Error::AacsError { detail: "host certificate too short".into() });
}
for agid in 0..4u8 {
let cdb = cdb_report_key(agid, 0x3F, 2);
let _ = scsi_read(session, &cdb, 2);
}
let cdb = cdb_report_key(0, 0x00, 8);
let response = scsi_read(session, &cdb, 8)
.map_err(|e| Error::AacsError { detail: format!("failed to allocate AGID: {}", e) })?;
let agid = (response[7] >> 6) & 0x03;
let mut host_nonce = [0u8; 20];
use rand::RngCore;
rand::thread_rng().fill_bytes(&mut host_nonce);
let (host_key, host_key_point_x, host_key_point_y) = generate_host_key_pair();
let mut send_buf = [0u8; 116];
send_buf[1] = 0x72; send_buf[4..24].copy_from_slice(&host_nonce);
send_buf[24..116].copy_from_slice(&host_cert[..92]);
let cdb = cdb_send_key(agid, 0x01, 116);
scsi_write(session, &cdb, &send_buf)
.map_err(|_| Error::AacsError { detail: "drive rejected host certificate".into() })?;
let cdb = cdb_report_key(agid, 0x01, 116);
let response = scsi_read(session, &cdb, 116)
.map_err(|_| Error::AacsError { detail: "failed to read drive certificate".into() })?;
let mut drive_nonce = [0u8; 20];
let mut drive_cert = [0u8; 92];
drive_nonce.copy_from_slice(&response[4..24]);
drive_cert.copy_from_slice(&response[24..116]);
if drive_cert[0] == 0x11 {
}
if drive_cert[0] == 0x01 && !verify_cert(&drive_cert) {
return Err(Error::AacsError { detail: "drive certificate verification failed".into() });
}
let cdb = cdb_report_key(agid, 0x02, 84);
let response = scsi_read(session, &cdb, 84)
.map_err(|_| Error::AacsError { detail: "failed to read drive key".into() })?;
let mut drive_key_point = [0u8; 40]; let mut drive_key_sig = [0u8; 40]; drive_key_point.copy_from_slice(&response[4..44]);
drive_key_sig.copy_from_slice(&response[44..84]);
let (drive_pub_x, drive_pub_y) = cert_pub_key(&drive_cert);
let mut verify_data = [0u8; 60];
verify_data[..20].copy_from_slice(&host_nonce);
verify_data[20..60].copy_from_slice(&drive_key_point);
let mut sig_r = [0u8; 20];
let mut sig_s = [0u8; 20];
sig_r.copy_from_slice(&drive_key_sig[..20]);
sig_s.copy_from_slice(&drive_key_sig[20..40]);
if !ecdsa_verify(&drive_pub_x, &drive_pub_y, &sig_r, &sig_s, &verify_data) {
return Err(Error::AacsError { detail: "drive key signature verification failed".into() });
}
let mut sign_data = [0u8; 60];
sign_data[..20].copy_from_slice(&drive_nonce);
sign_data[20..40].copy_from_slice(&host_key_point_x);
sign_data[40..60].copy_from_slice(&host_key_point_y);
let (host_sig_r, host_sig_s) = ecdsa_sign(host_priv_key, &sign_data);
let mut send_buf = [0u8; 84];
send_buf[1] = 0x52;
send_buf[4..24].copy_from_slice(&host_key_point_x);
send_buf[24..44].copy_from_slice(&host_key_point_y);
send_buf[44..64].copy_from_slice(&host_sig_r);
send_buf[64..84].copy_from_slice(&host_sig_s);
let cdb = cdb_send_key(agid, 0x02, 84);
scsi_write(session, &cdb, &send_buf)
.map_err(|_| Error::AacsError { detail: "drive rejected host key".into() })?;
let mut dkp_x = [0u8; 20];
let mut dkp_y = [0u8; 20];
dkp_x.copy_from_slice(&drive_key_point[..20]);
dkp_y.copy_from_slice(&drive_key_point[20..40]);
let bus_key = compute_bus_key(&host_key, &dkp_x, &dkp_y);
Ok(AacsAuth {
bus_key,
agid,
volume_id: None,
read_data_key: None,
drive_cert,
})
}
pub fn read_volume_id(session: &mut DriveSession, auth: &mut AacsAuth) -> Result<[u8; 16]> {
let cdb = cdb_report_disc_structure(auth.agid, 0x80, 36);
let response = scsi_read(session, &cdb, 36)
.map_err(|_| Error::AacsError { detail: "failed to read Volume ID".into() })?;
let mut vid = [0u8; 16];
let mut mac = [0u8; 16];
vid.copy_from_slice(&response[4..20]);
mac.copy_from_slice(&response[20..36]);
let calc_mac = aes_cmac_16(&vid, &auth.bus_key);
if calc_mac != mac {
return Err(Error::AacsError { detail: "VID MAC verification failed".into() });
}
auth.volume_id = Some(vid);
Ok(vid)
}
pub fn read_data_keys(session: &mut DriveSession, auth: &mut AacsAuth) -> Result<([u8; 16], [u8; 16])> {
let cdb = cdb_report_disc_structure(auth.agid, 0x84, 36);
let response = scsi_read(session, &cdb, 36)
.map_err(|_| Error::AacsError { detail: "failed to read data keys".into() })?;
let mut enc_rdk = [0u8; 16];
let mut enc_wdk = [0u8; 16];
enc_rdk.copy_from_slice(&response[4..20]);
enc_wdk.copy_from_slice(&response[20..36]);
let read_data_key = super::aes_ecb_decrypt(&auth.bus_key, &enc_rdk);
let write_data_key = super::aes_ecb_decrypt(&auth.bus_key, &enc_wdk);
auth.read_data_key = Some(read_data_key);
Ok((read_data_key, write_data_key))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ec_curve_generator_on_curve() {
let p = BigUint::from_bytes_be(&EC_P);
let a = BigUint::from_bytes_be(&EC_A);
let b = BigUint::from_bytes_be(&EC_B);
let gx = BigUint::from_bytes_be(&EC_GX);
let gy = BigUint::from_bytes_be(&EC_GY);
let lhs = (&gy * &gy) % &p;
let rhs = (&gx * &gx * &gx + &a * &gx + &b) % &p;
assert_eq!(lhs, rhs, "Generator point is not on the curve");
}
#[test]
fn test_ec_mul_identity() {
let p = BigUint::from_bytes_be(&EC_P);
let a = BigUint::from_bytes_be(&EC_A);
let g = EcPoint::from_bytes(&EC_GX, &EC_GY);
let result = ec_mul(&BigUint::one(), &g, &a, &p);
assert_eq!(result.x, g.x);
assert_eq!(result.y, g.y);
}
#[test]
fn test_ec_mul_order() {
let p = BigUint::from_bytes_be(&EC_P);
let a = BigUint::from_bytes_be(&EC_A);
let n = BigUint::from_bytes_be(&EC_N);
let g = EcPoint::from_bytes(&EC_GX, &EC_GY);
let result = ec_mul(&n, &g, &a, &p);
assert!(result.infinity, "n × G should be point at infinity");
}
#[test]
fn test_ecdsa_sign_verify() {
let (priv_key, pub_x, pub_y) = generate_host_key_pair();
let data = b"test data for AACS ECDSA";
let (sig_r, sig_s) = ecdsa_sign(&priv_key, data);
assert!(ecdsa_verify(&pub_x, &pub_y, &sig_r, &sig_s, data),
"ECDSA signature should verify");
assert!(!ecdsa_verify(&pub_x, &pub_y, &sig_r, &sig_s, b"wrong data"),
"ECDSA should fail with wrong data");
}
#[test]
fn test_ecdh_shared_secret() {
let p = BigUint::from_bytes_be(&EC_P);
let a = BigUint::from_bytes_be(&EC_A);
let g = EcPoint::from_bytes(&EC_GX, &EC_GY);
let (priv_a, pub_ax, pub_ay) = generate_host_key_pair();
let (priv_b, pub_bx, pub_by) = generate_host_key_pair();
let shared_a = compute_bus_key(&priv_a, &pub_bx, &pub_by);
let shared_b = compute_bus_key(&priv_b, &pub_ax, &pub_ay);
assert_eq!(shared_a, shared_b, "ECDH shared secrets should match");
}
#[test]
fn test_aes_cmac() {
let key = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c];
let data = [0u8; 16];
let mac1 = aes_cmac_16(&data, &key);
let mac2 = aes_cmac_16(&data, &key);
assert_eq!(mac1, mac2);
assert_ne!(mac1, [0u8; 16]); }
#[test]
fn test_verify_host_cert_from_keydb() {
let keydb_path = match std::env::var("KEYDB_PATH").ok() {
Some(p) => std::path::PathBuf::from(p),
None => return, };
if !keydb_path.exists() { return; }
let db = crate::aacs::KeyDb::load(&keydb_path).unwrap();
if let Some(hc) = &db.host_cert {
let valid = verify_cert(&hc.certificate);
eprintln!("Host cert verification: {}", if valid { "PASS" } else { "FAIL" });
if !valid {
eprintln!(" (cert may use different LA key or format)");
}
}
}
}