use anyhow::{bail, Result};
pub mod ins {
pub const SELECT: u8 = 0xA4;
pub const READ_BINARY: u8 = 0xB0;
pub const READ_RECORD: u8 = 0xB2;
pub const UPDATE_BINARY: u8 = 0xD6;
pub const UPDATE_RECORD: u8 = 0xDC;
pub const VERIFY: u8 = 0x20;
pub const GET_RESPONSE: u8 = 0xC0;
pub const GET_DATA: u8 = 0xCA;
pub const PUT_DATA: u8 = 0xDA;
pub const ENVELOPE: u8 = 0xC2;
pub const INTERNAL_AUTHENTICATE: u8 = 0x88;
pub const EXTERNAL_AUTHENTICATE: u8 = 0x82;
pub const GET_CHALLENGE: u8 = 0x84;
}
pub const SELECT_P1_BY_AID: u8 = 0x04;
pub const SW_NO_PRECISE_DIAGNOSIS: (u8, u8) = (0x6F, 0x00);
pub const SW_WRONG_LENGTH: (u8, u8) = (0x67, 0x00);
pub const SW_EXECUTION_ERROR_STATE_UNCHANGED: (u8, u8) = (0x64, 0x00);
#[derive(Debug, Clone)]
pub struct ApduCommand {
pub cla: u8,
pub ins: u8,
pub p1: u8,
pub p2: u8,
pub data: Vec<u8>,
pub le: Option<u32>,
}
impl ApduCommand {
pub fn parse(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 4 {
bail!("APDU too short: {} byte(s), need at least 4", bytes.len());
}
let cla = bytes[0];
let ins = bytes[1];
let p1 = bytes[2];
let p2 = bytes[3];
let body = &bytes[4..];
let (data, le) = match body.len() {
0 => (Vec::new(), None),
1 => (Vec::new(), Some(expand_le_short(body[0]))),
_ if body[0] != 0 => {
let lc = body[0] as usize;
let data_end = 1 + lc;
if body.len() < data_end {
bail!(
"APDU truncated: Lc={} but only {} byte(s) follow",
lc,
body.len() - 1
);
}
let data = body[1..data_end].to_vec();
match body.len() - data_end {
0 => (data, None),
1 => (data, Some(expand_le_short(body[data_end]))),
extra => bail!("APDU has {} trailing byte(s) after Lc/Data/Le", extra),
}
}
_ => {
if body.len() < 3 {
bail!(
"APDU truncated: extended-length marker needs 3 byte(s), got {}",
body.len()
);
}
let extended = u16::from_be_bytes([body[1], body[2]]);
if body.len() == 3 {
return Ok(Self {
cla,
ins,
p1,
p2,
data: Vec::new(),
le: Some(expand_le_extended(extended)),
});
}
let lc = extended as usize;
if lc == 0 {
bail!("APDU has an extended-length marker with Lc=0 and trailing bytes");
}
let data_end = 3 + lc;
if body.len() < data_end {
bail!(
"APDU truncated: extended Lc={} but only {} byte(s) follow",
lc,
body.len() - 3
);
}
let data = body[3..data_end].to_vec();
match body.len() - data_end {
0 => (data, None),
2 => (
data,
Some(expand_le_extended(u16::from_be_bytes([
body[data_end],
body[data_end + 1],
]))),
),
extra => bail!(
"APDU has {} trailing byte(s) after extended Lc/Data/Le",
extra
),
}
}
};
Ok(Self {
cla,
ins,
p1,
p2,
data,
le,
})
}
pub fn ins_name(&self) -> &'static str {
match self.ins {
ins::SELECT => "SELECT",
ins::READ_BINARY => "READ_BINARY",
ins::READ_RECORD => "READ_RECORD",
ins::UPDATE_BINARY => "UPDATE_BINARY",
ins::UPDATE_RECORD => "UPDATE_RECORD",
ins::VERIFY => "VERIFY",
ins::GET_RESPONSE => "GET_RESPONSE",
ins::GET_DATA => "GET_DATA",
ins::PUT_DATA => "PUT_DATA",
ins::ENVELOPE => "ENVELOPE",
ins::INTERNAL_AUTHENTICATE => "INTERNAL_AUTHENTICATE",
ins::EXTERNAL_AUTHENTICATE => "EXTERNAL_AUTHENTICATE",
ins::GET_CHALLENGE => "GET_CHALLENGE",
_ => "UNKNOWN",
}
}
pub fn is_select_by_aid(&self) -> bool {
self.ins == ins::SELECT && self.p1 == SELECT_P1_BY_AID && !self.data.is_empty()
}
}
fn expand_le_short(value: u8) -> u32 {
if value == 0 {
256
} else {
value as u32
}
}
fn expand_le_extended(value: u16) -> u32 {
if value == 0 {
65536
} else {
value as u32
}
}
#[derive(Debug, Clone)]
pub struct ApduResponse {
pub data: Vec<u8>,
pub sw1: u8,
pub sw2: u8,
}
impl ApduResponse {
pub fn new(data: Vec<u8>, sw1: u8, sw2: u8) -> Self {
Self { data, sw1, sw2 }
}
pub fn card_error() -> Self {
Self::new(
Vec::new(),
SW_NO_PRECISE_DIAGNOSIS.0,
SW_NO_PRECISE_DIAGNOSIS.1,
)
}
pub fn for_wire_failure(failure: crate::utils::WireFailure) -> Self {
let (sw1, sw2) = match failure {
crate::utils::WireFailure::Overloaded => SW_EXECUTION_ERROR_STATE_UNCHANGED,
crate::utils::WireFailure::Unavailable => SW_NO_PRECISE_DIAGNOSIS,
};
Self::new(Vec::new(), sw1, sw2)
}
pub fn status_word(&self) -> String {
format!("{:02X}{:02X}", self.sw1, self.sw2)
}
pub fn into_bytes(self) -> Vec<u8> {
let mut bytes = self.data;
bytes.push(self.sw1);
bytes.push(self.sw2);
bytes
}
}