use crate::ake::{
aacs_la_pub_point, bus_key_from_point, AACS_LA_PUB_X, AACS_LA_PUB_Y, BUS_KEY_LEN,
};
#[cfg(any(test, feature = "test-util"))]
use crate::ake::{
build_signed_certificate, host_authenticate, DriveAuthState, HostCredentials, CERT_TYPE_DRIVE,
CERT_TYPE_HOST,
};
use crate::ec::{scalar_add, scalar_inv, scalar_mul, Point, N, U160};
use crate::error::AacsError;
#[cfg(any(test, feature = "test-util"))]
use crate::mmc::MockDrive;
const fn small_scalar(v: u32) -> U160 {
U160 {
limbs: [v, 0, 0, 0, 0],
}
}
pub fn curve_self_check() -> Result<(), AacsError> {
let g = Point::generator();
if !g.is_on_curve() {
return Err(AacsError::SelfCheckFailed {
what: "generator G not on AACS curve",
});
}
if !g.mul_scalar(&N).is_infinity() {
return Err(AacsError::SelfCheckFailed {
what: "n·G != point at infinity",
});
}
let double = g.double();
let add_self = g.add(&g);
if double != add_self {
return Err(AacsError::SelfCheckFailed {
what: "G.double() != G + G",
});
}
let a = small_scalar(0x0024_6801);
let b = small_scalar(0x0013_5790);
let lhs = g.mul_scalar(&scalar_add(&a, &b));
let rhs = g.mul_scalar(&a).add(&g.mul_scalar(&b));
if lhs != rhs {
return Err(AacsError::SelfCheckFailed {
what: "(a+b)·G != a·G + b·G",
});
}
let s = small_scalar(0x00ab_cdef);
let s_inv = scalar_inv(&s);
if scalar_mul(&s, &s_inv) != U160::ONE {
return Err(AacsError::SelfCheckFailed {
what: "scalar a · a⁻¹ != 1 (mod n)",
});
}
let bytes = g.to_bytes();
let mut x = [0u8; 20];
let mut y = [0u8; 20];
x.copy_from_slice(&bytes[..20]);
y.copy_from_slice(&bytes[20..]);
let recovered = Point::from_coords(&x, &y).ok_or(AacsError::SelfCheckFailed {
what: "Point::from_coords rejected the encoding of G",
})?;
if recovered != g {
return Err(AacsError::SelfCheckFailed {
what: "Point byte round-trip lost identity",
});
}
Ok(())
}
pub fn aacs_la_pub_self_check() -> Result<(), AacsError> {
let p =
Point::from_coords(&AACS_LA_PUB_X, &AACS_LA_PUB_Y).ok_or(AacsError::SelfCheckFailed {
what: "bundled AACS_LA_PUB coordinates not on curve",
})?;
if !p.is_on_curve() {
return Err(AacsError::SelfCheckFailed {
what: "AACS_LA_PUB Point::is_on_curve() returned false",
});
}
let helper = aacs_la_pub_point();
if helper != p {
return Err(AacsError::SelfCheckFailed {
what: "aacs_la_pub_point() disagrees with from_coords(AACS_LA_PUB_*)",
});
}
Ok(())
}
pub fn ake_ecdh_self_check() -> Result<(), AacsError> {
let dk = small_scalar(0x0013_5790);
let hk = small_scalar(0x0024_6801);
let dv = Point::generator().mul_scalar(&dk);
let hv = Point::generator().mul_scalar(&hk);
if dv.is_infinity() || hv.is_infinity() {
return Err(AacsError::SelfCheckFailed {
what: "ECDH self-check produced point at infinity",
});
}
let host_bk = bus_key_from_point(&dv.mul_scalar(&hk));
let drive_bk = bus_key_from_point(&hv.mul_scalar(&dk));
if host_bk != drive_bk {
return Err(AacsError::SelfCheckFailed {
what: "ECDH bus keys disagree",
});
}
if host_bk == [0u8; BUS_KEY_LEN] {
return Err(AacsError::SelfCheckFailed {
what: "ECDH bus key is all-zero (degenerate)",
});
}
Ok(())
}
#[cfg(any(test, feature = "test-util"))]
pub fn ake_full_self_check() -> Result<(), AacsError> {
let la_priv = small_scalar(0x0abc_def1);
let la_pub = Point::generator().mul_scalar(&la_priv);
let drive_priv = small_scalar(0x0011_2233);
let drive_pub = Point::generator().mul_scalar(&drive_priv);
let drive_cert = build_signed_certificate(
CERT_TYPE_DRIVE,
0x00,
&[0xD0, 0x01, 0x02, 0x03, 0x04, 0x05],
&drive_pub,
&la_priv,
);
let host_priv = small_scalar(0x0044_5566);
let host_pub = Point::generator().mul_scalar(&host_priv);
let host_cert = build_signed_certificate(
CERT_TYPE_HOST,
0x00,
&[0xA0, 0x06, 0x07, 0x08, 0x09, 0x0A],
&host_pub,
&la_priv,
);
let dk = small_scalar(0x0013_5790);
let mut drive_nonce = [0u8; 20];
for (i, slot) in drive_nonce.iter_mut().enumerate() {
*slot = 0xD0 ^ (i as u8);
}
let mut drive = MockDrive::with_test_fixture();
drive.agid_to_return = 1;
drive.auth = Some(DriveAuthState::new(
drive_cert,
drive_priv,
dk,
drive_nonce,
la_pub,
));
let creds = HostCredentials {
host_cert,
host_priv,
aacs_la_pub: la_pub,
};
let hk = small_scalar(0x0024_6801);
let mut host_nonce = [0u8; 20];
for (i, slot) in host_nonce.iter_mut().enumerate() {
*slot = 0x50 ^ (i as u8);
}
let result = host_authenticate(&mut drive, &creds, &host_nonce, &hk).map_err(|_| {
AacsError::SelfCheckFailed {
what: "host_authenticate failed on synthetic Phase C fixture",
}
})?;
let drive_bk =
drive
.auth
.as_ref()
.and_then(|a| a.bus_key)
.ok_or(AacsError::SelfCheckFailed {
what: "drive side derived no Bus Key after Hsig verify",
})?;
if result.bus_key != drive_bk {
return Err(AacsError::SelfCheckFailed {
what: "host and drive Bus Keys disagree after full §4.3 AKE",
});
}
if result.bus_key == [0u8; BUS_KEY_LEN] {
return Err(AacsError::SelfCheckFailed {
what: "negotiated Bus Key is all-zero (degenerate)",
});
}
if result.agid != 1 {
return Err(AacsError::SelfCheckFailed {
what: "negotiated AGID drifted from the synthetic fixture value",
});
}
Ok(())
}
#[cfg(any(test, feature = "test-util"))]
pub fn all_self_checks() -> Result<(), AacsError> {
curve_self_check()?;
aacs_la_pub_self_check()?;
ake_ecdh_self_check()?;
ake_full_self_check()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn curve_self_check_passes_on_clean_build() {
curve_self_check().expect("AACS curve constants must round-trip");
}
#[test]
fn aacs_la_pub_self_check_passes_on_clean_build() {
aacs_la_pub_self_check().expect("AACS LA public key constants must be on-curve");
}
#[test]
fn ake_ecdh_self_check_passes_on_clean_build() {
ake_ecdh_self_check().expect("ECDH agreement self-check must pass");
}
#[test]
fn ake_full_self_check_passes_on_clean_build() {
ake_full_self_check().expect("full §4.3 AKE self-check must pass");
}
#[test]
fn all_self_checks_pass_on_clean_build() {
all_self_checks().expect("all AKE self-checks must pass");
}
}