use core::error::Error;
use crate::{
Transport,
commands::SecureChannel,
crypto::suite::SessionSuite,
session::SessionError,
types::{ResponseCode, ResponseStatus, Version},
};
pub(crate) async fn get_version<T: Transport>(
transport: &mut T,
) -> Result<Version, SessionError<T::Error>> {
let (part1, part2, last) = drive_chain(transport, &[0x90, 0x60, 0x00, 0x00, 0x00]).await?;
let part3 = extract_part3(last.as_ref())?;
Ok(Version {
part1,
part2,
part3,
})
}
pub(crate) async fn get_version_mac<T: Transport, S: SessionSuite>(
transport: &mut T,
channel: &mut SecureChannel<'_, S>,
) -> Result<Version, SessionError<T::Error>> {
let cmd_mac = channel.compute_cmd_mac(0x60, &[], &[]);
let mut head = [0u8; 5 + 8 + 1];
head[..5].copy_from_slice(&[0x90, 0x60, 0x00, 0x00, 0x08]);
head[5..13].copy_from_slice(&cmd_mac);
let (part1, part2, last) = drive_chain(transport, &head).await?;
let last = last.as_ref();
if last.len() != 14 + 8 {
return Err(SessionError::UnexpectedLength {
got: last.len(),
expected: 22,
});
}
let mut body = [0u8; 7 + 7 + 14 + 8];
body[0..7].copy_from_slice(&part1);
body[7..14].copy_from_slice(&part2);
body[14..14 + last.len()].copy_from_slice(last);
let verified = channel.verify_response_mac_and_advance(0x00, &body)?;
let part3: [u8; 14] =
verified[14..28]
.try_into()
.map_err(|_| SessionError::UnexpectedLength {
got: verified.len(),
expected: 28,
})?;
Ok(Version {
part1,
part2,
part3,
})
}
async fn drive_chain<T: Transport>(
transport: &mut T,
head: &[u8],
) -> Result<([u8; 7], [u8; 7], T::Data), SessionError<T::Error>> {
let part1 = request_intermediate_part(transport, head).await?;
let part2 = request_intermediate_part(transport, &[0x90, 0xAF, 0x00, 0x00, 0x00]).await?;
let r3 = transport.transmit(&[0x90, 0xAF, 0x00, 0x00, 0x00]).await?;
let code = ResponseCode::desfire(r3.sw1, r3.sw2);
if !code.ok() {
return Err(SessionError::ErrorResponse(code.status()));
}
Ok((part1, part2, r3.data))
}
async fn request_intermediate_part<T: Transport>(
transport: &mut T,
apdu: &[u8],
) -> Result<[u8; 7], SessionError<T::Error>> {
let resp = transport.transmit(apdu).await?;
let code = ResponseCode::desfire(resp.sw1, resp.sw2);
if !matches!(code.status(), ResponseStatus::AdditionalFrame) {
return Err(SessionError::ErrorResponse(code.status()));
}
resp.data
.as_ref()
.try_into()
.map_err(|_| SessionError::UnexpectedLength {
got: resp.data.as_ref().len(),
expected: 7,
})
}
fn extract_part3<E: Error + core::fmt::Debug>(data: &[u8]) -> Result<[u8; 14], SessionError<E>> {
data.get(..14)
.ok_or(SessionError::UnexpectedLength {
got: data.len(),
expected: 14,
})?
.try_into()
.map_err(|_| SessionError::UnexpectedLength {
got: data.len(),
expected: 14,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::suite::{AesSuite, LrpSuite, aes_cbc_decrypt};
use crate::session::Authenticated;
use crate::testing::{Exchange, TestTransport, block_on, hex_array, hex_bytes};
use alloc::vec::Vec;
fn head_apdu(suite: &AesSuite, ti: [u8; 4], ctr: u16) -> Vec<u8> {
let mut mac_input = Vec::new();
mac_input.push(0x60u8); mac_input.extend_from_slice(&ctr.to_le_bytes());
mac_input.extend_from_slice(&ti);
let cmd_mac = suite.mac(&mac_input);
let mut apdu = Vec::from([0x90u8, 0x60, 0x00, 0x00, 0x08]);
apdu.extend_from_slice(&cmd_mac);
apdu.push(0x00); apdu
}
#[test]
fn get_version_plain_roundtrip() {
let part1 = [0x04u8, 0x04, 0x08, 0x30, 0x00, 0x11, 0x05];
let part2 = [0x04u8, 0x04, 0x02, 0x01, 0x02, 0x11, 0x05];
let part3 = hex_array::<14>("04984C7A0B1090CF5D9045104621");
let mut transport = TestTransport::new([
Exchange::new(&[0x90, 0x60, 0x00, 0x00, 0x00], &part1, 0x91, 0xAF),
Exchange::new(&[0x90, 0xAF, 0x00, 0x00, 0x00], &part2, 0x91, 0xAF),
Exchange::new(&[0x90, 0xAF, 0x00, 0x00, 0x00], &part3, 0x91, 0x00),
]);
let version = block_on(get_version(&mut transport)).expect("plain GetVersion must succeed");
assert_eq!(version.part1, part1);
assert_eq!(version.part2, part2);
assert_eq!(version.part3, part3);
assert_eq!(*version.uid(), [0x04, 0x98, 0x4C, 0x7A, 0x0B, 0x10, 0x90]);
assert_eq!(transport.remaining(), 0);
}
#[test]
fn get_version_mac_roundtrip() {
let mac_key = hex_array("4C6626F5E72EA694202139295C7A7FC7");
let enc_key = hex_array("1309C877509E5A215007FF0ED19CA564");
let ti = [0x9D, 0x00, 0xC4, 0xDF];
let part1 = hex_bytes("0404083000110591AF"); let part2 = hex_bytes("0404020101110591AF"); let part3_data = hex_bytes("04968CAA5C5E80CD65935D402118"); assert_eq!(part3_data.len(), 14);
let suite = AesSuite::from_keys(enc_key, mac_key);
let head = head_apdu(&suite, ti, 0);
let mut mac_input = Vec::new();
mac_input.push(0x00); mac_input.extend_from_slice(&1u16.to_le_bytes()); mac_input.extend_from_slice(&ti);
mac_input.extend_from_slice(&part1[..7]);
mac_input.extend_from_slice(&part2[..7]);
mac_input.extend_from_slice(&part3_data);
let expected_mac = suite.mac(&mac_input);
let mut part3_full = part3_data.clone();
part3_full.extend_from_slice(&expected_mac);
let mut transport = TestTransport::new([
Exchange::new(&head, &part1[..7], 0x91, 0xAF),
Exchange::new(&[0x90, 0xAF, 0x00, 0x00, 0x00], &part2[..7], 0x91, 0xAF),
Exchange::new(&[0x90, 0xAF, 0x00, 0x00, 0x00], &part3_full, 0x91, 0x00),
]);
let mut state = Authenticated::new(
AesSuite::from_keys(enc_key, mac_key),
ti,
crate::KeyNumber::Key0,
);
let version = block_on(async {
let mut ch = SecureChannel::new(&mut state);
get_version_mac(&mut transport, &mut ch).await
})
.expect("authenticated GetVersion must succeed");
assert_eq!(version.part1, part1[..7]);
assert_eq!(version.part2, part2[..7]);
assert_eq!(version.part3.as_slice(), part3_data.as_slice());
assert_eq!(state.counter(), 1);
assert_eq!(transport.remaining(), 0);
}
#[test]
fn get_version_mac_rejects_bad_trailer() {
let mac_key = hex_array("4C6626F5E72EA694202139295C7A7FC7");
let enc_key = hex_array("1309C877509E5A215007FF0ED19CA564");
let ti = [0x9D, 0x00, 0xC4, 0xDF];
let part1 = hex_bytes("0404083000110591AF");
let part2 = hex_bytes("0404020101110591AF");
let part3_data = hex_bytes("04968CAA5C5E80CD65935D402118");
let suite = AesSuite::from_keys(enc_key, mac_key);
let mut mac_input = Vec::new();
mac_input.push(0x00);
mac_input.extend_from_slice(&1u16.to_le_bytes());
mac_input.extend_from_slice(&ti);
mac_input.extend_from_slice(&part1[..7]);
mac_input.extend_from_slice(&part2[..7]);
mac_input.extend_from_slice(&part3_data);
let mut bad_mac = suite.mac(&mac_input);
bad_mac[0] ^= 0x01;
let mut part3_full = part3_data.clone();
part3_full.extend_from_slice(&bad_mac);
let head = head_apdu(&suite, ti, 0);
let mut transport = TestTransport::new([
Exchange::new(&head, &part1[..7], 0x91, 0xAF),
Exchange::new(&[0x90, 0xAF, 0x00, 0x00, 0x00], &part2[..7], 0x91, 0xAF),
Exchange::new(&[0x90, 0xAF, 0x00, 0x00, 0x00], &part3_full, 0x91, 0x00),
]);
let mut state = Authenticated::new(
AesSuite::from_keys(enc_key, mac_key),
ti,
crate::KeyNumber::Key0,
);
let result = block_on(async {
let mut ch = SecureChannel::new(&mut state);
get_version_mac(&mut transport, &mut ch).await
});
match result {
Err(SessionError::ResponseMacMismatch) => (),
other => panic!("expected ResponseMacMismatch, got {other:?}"),
}
assert_eq!(state.counter(), 0);
}
fn aes_key0_suite_085bc941() -> (AesSuite, [u8; 4]) {
let key = [0u8; 16];
let rnd_a = hex_array::<16>("C4028B41E6F497099C7087768E78A191");
let mut rnd_b = hex_array::<16>("7858A0B9DBC468F0FF1B2F773D6DF9FC");
aes_cbc_decrypt(&key, &[0u8; 16], &mut rnd_b).unwrap();
(
AesSuite::derive(&key, &rnd_a, &rnd_b),
hex_array("085BC941"),
)
}
fn lrp_key0_suite_bbe12900() -> (LrpSuite, [u8; 4]) {
let key = [0u8; 16];
let rnd_a = hex_array::<16>("0272F1390C4B8EC7D3E43308D4B41EC3");
let rnd_b = hex_array::<16>("57E5BF7AF415C4C8B330442EC1F265E9");
(
LrpSuite::derive(&key, &rnd_a, &rnd_b).with_enc_ctr(1),
hex_array("BBE12900"),
)
}
#[test]
fn get_version_mac_hw_aes() {
let (suite, ti) = aes_key0_suite_085bc941();
let mut state = Authenticated::new(suite, ti, crate::KeyNumber::Key0);
let mut transport = TestTransport::new([
Exchange::new(
&hex_bytes("90600000087EB6309891B11B2400"),
&hex_bytes("04040830001105"),
0x91,
0xAF,
),
Exchange::new(
&hex_bytes("90AF000000"),
&hex_bytes("04040201021105"),
0x91,
0xAF,
),
Exchange::new(
&hex_bytes("90AF000000"),
&hex_bytes("04A9707A0B1090CF5D9045104621EB9482FFE8BB7761"),
0x91,
0x00,
),
]);
let version = block_on(async {
let mut ch = SecureChannel::new(&mut state);
get_version_mac(&mut transport, &mut ch).await
})
.expect("hw AES GetVersion must succeed");
assert_eq!(*version.uid(), [0x04, 0xA9, 0x70, 0x7A, 0x0B, 0x10, 0x90]);
assert_eq!(state.counter(), 1);
assert_eq!(transport.remaining(), 0);
}
#[test]
fn get_version_mac_hw_lrp() {
let (suite, ti) = lrp_key0_suite_bbe12900();
let mut state = Authenticated::new(suite, ti, crate::KeyNumber::Key0);
let mut transport = TestTransport::new([
Exchange::new(
&hex_bytes("906000000855C76087DF2A8F9000"),
&hex_bytes("04040830001105"),
0x91,
0xAF,
),
Exchange::new(
&hex_bytes("90AF000000"),
&hex_bytes("04040201021105"),
0x91,
0xAF,
),
Exchange::new(
&hex_bytes("90AF000000"),
&hex_bytes("04407A7A0B1090CF5D90451046210C084938BB57C2CA"),
0x91,
0x00,
),
]);
let version = block_on(async {
let mut ch = SecureChannel::new(&mut state);
get_version_mac(&mut transport, &mut ch).await
})
.expect("hw LRP GetVersion must succeed");
assert_eq!(*version.uid(), [0x04, 0x40, 0x7A, 0x7A, 0x0B, 0x10, 0x90]);
assert_eq!(state.counter(), 1);
assert_eq!(transport.remaining(), 0);
}
}