use zeroize::Zeroizing;
use crate::Transport;
use crate::commands::authenticate::AuthResult;
use crate::crypto::ct_eq_16;
use crate::crypto::suite::{Direction, LrpSuite, SessionSuite};
use crate::session::SessionError;
use crate::types::{KeyNumber, ResponseCode, ResponseStatus};
const PCDCAP2: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00];
const AUTH_MODE_LRP: u8 = 0x01;
pub(crate) async fn authenticate_ev2_non_first<T: Transport>(
transport: &mut T,
key_no: KeyNumber,
key: &[u8; 16],
rnd_a: [u8; 16],
) -> Result<LrpSuite, SessionError<T::Error>> {
let part1_apdu = [0x90, 0x77, 0x00, 0x00, 0x01, key_no.as_byte(), 0x00];
let r1 = transport.transmit(&part1_apdu).await?;
let code = ResponseCode::desfire(r1.sw1, r1.sw2);
if !matches!(code.status(), ResponseStatus::AdditionalFrame) {
return Err(SessionError::ErrorResponse(code.status()));
}
let data1 = r1.data.as_ref();
if data1.len() != 17 {
return Err(SessionError::UnexpectedLength {
got: data1.len(),
expected: 17,
});
}
if data1[0] != AUTH_MODE_LRP {
return Err(SessionError::AuthenticationMismatch);
}
let rnd_b: Zeroizing<[u8; 16]> = Zeroizing::new(data1[1..17].try_into().unwrap());
let suite = LrpSuite::derive(key, &rnd_a, &rnd_b);
let mut mac_input: Zeroizing<[u8; 32]> = Zeroizing::new([0u8; 32]);
mac_input[..16].copy_from_slice(&rnd_a);
mac_input[16..].copy_from_slice(&*rnd_b);
let pcd_response = suite.mac_full(&*mac_input);
let part2_apdu = build_part2_apdu(&rnd_a, &pcd_response);
let r2 = transport.transmit(&part2_apdu).await?;
let code = ResponseCode::desfire(r2.sw1, r2.sw2);
if !code.ok() {
return Err(SessionError::ErrorResponse(code.status()));
}
let picc_response: [u8; 16] =
r2.data
.as_ref()
.try_into()
.map_err(|_| SessionError::UnexpectedLength {
got: r2.data.as_ref().len(),
expected: 16,
})?;
let mut verify_input: Zeroizing<[u8; 32]> = Zeroizing::new([0u8; 32]);
verify_input[..16].copy_from_slice(&*rnd_b);
verify_input[16..].copy_from_slice(&rnd_a);
if !ct_eq_16(&suite.mac_full(&*verify_input), &picc_response) {
return Err(SessionError::AuthenticationMismatch);
}
Ok(suite)
}
pub(crate) async fn authenticate_ev2_first<T: Transport>(
transport: &mut T,
key_no: KeyNumber,
key: &[u8; 16],
rnd_a: [u8; 16],
) -> Result<AuthResult<LrpSuite>, SessionError<T::Error>> {
let part1_apdu = [
0x90,
0x71,
0x00,
0x00,
0x08,
key_no.as_byte(),
0x06,
PCDCAP2[0],
PCDCAP2[1],
PCDCAP2[2],
PCDCAP2[3],
PCDCAP2[4],
PCDCAP2[5],
0x00, ];
let r1 = transport.transmit(&part1_apdu).await?;
let code = ResponseCode::desfire(r1.sw1, r1.sw2);
if !matches!(code.status(), ResponseStatus::AdditionalFrame) {
return Err(SessionError::ErrorResponse(code.status()));
}
let data1 = r1.data.as_ref();
if data1.len() != 17 {
return Err(SessionError::UnexpectedLength {
got: data1.len(),
expected: 17,
});
}
if data1[0] != AUTH_MODE_LRP {
return Err(SessionError::AuthenticationMismatch);
}
let rnd_b: Zeroizing<[u8; 16]> = Zeroizing::new(data1[1..17].try_into().unwrap());
let suite = LrpSuite::derive(key, &rnd_a, &rnd_b);
let mut mac_input: Zeroizing<[u8; 32]> = Zeroizing::new([0u8; 32]);
mac_input[..16].copy_from_slice(&rnd_a);
mac_input[16..].copy_from_slice(&*rnd_b);
let pcd_response = suite.mac_full(&*mac_input);
let part2_apdu = build_part2_apdu(&rnd_a, &pcd_response);
let r2 = transport.transmit(&part2_apdu).await?;
let code = ResponseCode::desfire(r2.sw1, r2.sw2);
if !code.ok() {
return Err(SessionError::ErrorResponse(code.status()));
}
let data2: [u8; 32] =
r2.data
.as_ref()
.try_into()
.map_err(|_| SessionError::UnexpectedLength {
got: r2.data.as_ref().len(),
expected: 32,
})?;
let picc_data: [u8; 16] = data2[..16].try_into().unwrap();
let picc_response: [u8; 16] = data2[16..].try_into().unwrap();
verify_and_extract_auth_result(suite, &rnd_a, &rnd_b, &picc_data, &picc_response)
}
fn build_part2_apdu(rnd_a: &[u8; 16], pcd_response: &[u8; 16]) -> [u8; 38] {
let mut apdu = [0u8; 38];
apdu[0] = 0x90;
apdu[1] = 0xAF;
apdu[4] = 0x20;
apdu[5..21].copy_from_slice(rnd_a);
apdu[21..37].copy_from_slice(pcd_response);
apdu
}
fn verify_and_extract_auth_result<E: core::error::Error + core::fmt::Debug>(
mut suite: LrpSuite,
rnd_a: &[u8; 16],
rnd_b: &[u8; 16],
picc_data: &[u8; 16],
picc_response: &[u8; 16],
) -> Result<AuthResult<LrpSuite>, SessionError<E>> {
let mut verify_input: Zeroizing<[u8; 48]> = Zeroizing::new([0u8; 48]);
verify_input[..16].copy_from_slice(rnd_b);
verify_input[16..32].copy_from_slice(rnd_a);
verify_input[32..].copy_from_slice(picc_data);
if !ct_eq_16(&suite.mac_full(&*verify_input), picc_response) {
return Err(SessionError::AuthenticationMismatch);
}
let mut plain: Zeroizing<[u8; 16]> = Zeroizing::new(*picc_data);
suite.decrypt(Direction::Response, &[0; 4], 0, &mut *plain);
if plain[10..16] != PCDCAP2 {
return Err(SessionError::AuthenticationMismatch);
}
let mut ti = [0u8; 4];
ti.copy_from_slice(&plain[..4]);
let mut pd_cap2 = [0u8; 6];
pd_cap2.copy_from_slice(&plain[4..10]);
let mut pcd_cap2 = [0u8; 6];
pcd_cap2.copy_from_slice(&plain[10..16]);
Ok(AuthResult {
suite,
ti,
pd_cap2,
pcd_cap2,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::hex_array;
#[derive(Debug)]
struct NeverError;
impl core::fmt::Display for NeverError {
fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Ok(())
}
}
impl core::error::Error for NeverError {}
#[test]
fn part2_apdu_and_verify_an12321_key3() {
let key = [0u8; 16];
let rnd_a: [u8; 16] = hex_array("74D7DF6A2CEC0B72B412DE0D2B1117E6");
let rnd_b: [u8; 16] = hex_array("56109A31977C855319CD4618C9D2AED2");
let pcd_response: [u8; 16] = hex_array("189B59DCEDC31A3D3F38EF8D4810B3B4");
let part2 = build_part2_apdu(&rnd_a, &pcd_response);
assert_eq!(
part2,
hex_array::<38>(
"90AF00002074D7DF6A2CEC0B72B412DE0D2B1117E6189B59DCEDC31A3D3F38EF8D4810B3B400"
),
);
let picc_data: [u8; 16] = hex_array("F4FC209D9D60623588B299FA5D6B2D71");
let picc_response: [u8; 16] = hex_array("0125F8547D9FB8D572C90D2C2A14E235");
let suite = LrpSuite::derive(&key, &rnd_a, &rnd_b);
let auth_result = verify_and_extract_auth_result::<NeverError>(
suite,
&rnd_a,
&rnd_b,
&picc_data,
&picc_response,
)
.expect("valid transcript should verify");
assert_eq!(auth_result.ti, hex_array::<4>("58EE9424"));
assert_eq!(auth_result.pd_cap2, PCDCAP2);
assert_eq!(auth_result.pcd_cap2, PCDCAP2);
assert_eq!(auth_result.suite.enc_ctr(), 1);
}
#[test]
fn part2_apdu_and_verify_hw_key0() {
let key = [0u8; 16];
let rnd_a: [u8; 16] = hex_array("D1D85ACB0A57299BFEED443D832DAD0C");
let rnd_b: [u8; 16] = hex_array("B40643A537D6B0ACD8E7816168CD85C1");
let pcd_response: [u8; 16] = hex_array("23A13B80F26E481E4FAD3F3D75B14B7B");
let part2 = build_part2_apdu(&rnd_a, &pcd_response);
assert_eq!(
part2,
hex_array::<38>(
"90AF000020D1D85ACB0A57299BFEED443D832DAD0C23A13B80F26E481E4FAD3F3D75B14B7B00"
),
);
let picc_data: [u8; 16] = hex_array("1C8EE9654067C50B188BD7652CEA8ABF");
let picc_response: [u8; 16] = hex_array("4DCAF2776C80ABACEC992D6DF2D6E4EE");
let suite = LrpSuite::derive(&key, &rnd_a, &rnd_b);
let mut mac_input = [0u8; 32];
mac_input[..16].copy_from_slice(&rnd_a);
mac_input[16..].copy_from_slice(&rnd_b);
assert_eq!(suite.mac_full(&mac_input), pcd_response);
let auth_result = verify_and_extract_auth_result::<NeverError>(
suite,
&rnd_a,
&rnd_b,
&picc_data,
&picc_response,
)
.expect("valid hardware transcript should verify");
assert_eq!(auth_result.ti, hex_array::<4>("9D96C13C"));
assert_eq!(auth_result.pd_cap2, PCDCAP2);
assert_eq!(auth_result.pcd_cap2, PCDCAP2);
assert_eq!(auth_result.suite.enc_ctr(), 1);
}
#[test]
fn verify_detects_bad_picc_response() {
let key = [0u8; 16];
let rnd_a: [u8; 16] = hex_array("74D7DF6A2CEC0B72B412DE0D2B1117E6");
let rnd_b: [u8; 16] = hex_array("56109A31977C855319CD4618C9D2AED2");
let picc_data: [u8; 16] = hex_array("F4FC209D9D60623588B299FA5D6B2D71");
let mut picc_response: [u8; 16] = hex_array("0125F8547D9FB8D572C90D2C2A14E235");
picc_response[0] ^= 0x01;
let suite = LrpSuite::derive(&key, &rnd_a, &rnd_b);
match verify_and_extract_auth_result::<NeverError>(
suite,
&rnd_a,
&rnd_b,
&picc_data,
&picc_response,
) {
Err(SessionError::AuthenticationMismatch) => (),
Ok(_) => panic!("verify accepted a corrupted PICCResponse"),
Err(e) => panic!("unexpected error: {e:?}"),
}
}
#[test]
fn verify_detects_bad_pcdcap2_echo() {
let key = [0u8; 16];
let rnd_a: [u8; 16] = hex_array("74D7DF6A2CEC0B72B412DE0D2B1117E6");
let rnd_b: [u8; 16] = hex_array("56109A31977C855319CD4618C9D2AED2");
let mut forge = LrpSuite::derive(&key, &rnd_a, &rnd_b);
let mut plain = [0u8; 16];
plain[..4].copy_from_slice(&hex_array::<4>("58EE9424"));
forge.encrypt(Direction::Response, &[0; 4], 0, &mut plain);
let picc_data = plain;
let mut mac_input = [0u8; 48];
mac_input[..16].copy_from_slice(&rnd_b);
mac_input[16..32].copy_from_slice(&rnd_a);
mac_input[32..].copy_from_slice(&picc_data);
let picc_response = forge.mac_full(&mac_input);
let suite = LrpSuite::derive(&key, &rnd_a, &rnd_b);
match verify_and_extract_auth_result::<NeverError>(
suite,
&rnd_a,
&rnd_b,
&picc_data,
&picc_response,
) {
Err(SessionError::AuthenticationMismatch) => (),
Ok(auth_result) => {
panic!(
"verify accepted wrong PCDCap2 echo, got TI={:x?}",
auth_result.ti
)
}
Err(e) => panic!("unexpected error: {e:?}"),
}
}
}