use crate::{transaction::Transaction, Buffer, Result};
use log::trace;
use zeroize::{Zeroize, Zeroizing};
const APDU_DATA_MAX: usize = 0xFF;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Apdu {
cla: u8,
ins: Ins,
p1: u8,
p2: u8,
data: Vec<u8>,
}
impl Apdu {
pub fn new(ins: impl Into<Ins>) -> Self {
Self {
cla: 0,
ins: ins.into(),
p1: 0,
p2: 0,
data: vec![],
}
}
pub fn cla(&mut self, value: u8) -> &mut Self {
self.cla = value;
self
}
pub fn p1(&mut self, value: u8) -> &mut Self {
self.p1 = value;
self
}
pub fn params(&mut self, p1: u8, p2: u8) -> &mut Self {
self.p1 = p1;
self.p2 = p2;
self
}
pub fn data(&mut self, bytes: impl AsRef<[u8]>) -> &mut Self {
assert!(self.data.is_empty(), "APDU command already set!");
let bytes = bytes.as_ref();
assert!(
bytes.len() <= APDU_DATA_MAX,
"APDU command data too long: {} (max: {})",
bytes.len(),
APDU_DATA_MAX
);
self.data.extend_from_slice(bytes);
self
}
pub fn transmit(&self, txn: &Transaction<'_>, recv_len: usize) -> Result<Response> {
trace!(">>> {:?}", self);
let response = Response::from(txn.transmit(&self.to_bytes(), recv_len)?);
trace!("<<< {:?}", &response);
Ok(response)
}
pub fn to_bytes(&self) -> Buffer {
let mut bytes = Vec::with_capacity(5 + self.data.len());
bytes.push(self.cla);
bytes.push(self.ins.code());
bytes.push(self.p1);
bytes.push(self.p2);
bytes.push(self.data.len() as u8);
bytes.extend_from_slice(self.data.as_ref());
Zeroizing::new(bytes)
}
}
impl Drop for Apdu {
fn drop(&mut self) {
self.zeroize();
}
}
impl Zeroize for Apdu {
fn zeroize(&mut self) {
self.data.zeroize();
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Ins {
Verify,
ChangeReference,
ResetRetry,
GenerateAsymmetric,
Authenticate,
GetData,
PutData,
SelectApplication,
GetResponseApdu,
SetMgmKey,
ImportKey,
GetVersion,
Reset,
SetPinRetries,
Attest,
GetSerial,
GetMetadata,
Other(u8),
}
impl Ins {
pub fn code(self) -> u8 {
match self {
Ins::Verify => 0x20,
Ins::ChangeReference => 0x24,
Ins::ResetRetry => 0x2c,
Ins::GenerateAsymmetric => 0x47,
Ins::Authenticate => 0x87,
Ins::GetData => 0xcb,
Ins::PutData => 0xdb,
Ins::SelectApplication => 0xa4,
Ins::GetResponseApdu => 0xc0,
Ins::SetMgmKey => 0xff,
Ins::ImportKey => 0xfe,
Ins::GetVersion => 0xfd,
Ins::Reset => 0xfb,
Ins::SetPinRetries => 0xfa,
Ins::Attest => 0xf9,
Ins::GetSerial => 0xf8,
Ins::GetMetadata => 0xf7,
Ins::Other(code) => code,
}
}
}
impl From<u8> for Ins {
fn from(code: u8) -> Self {
match code {
0x20 => Ins::Verify,
0x24 => Ins::ChangeReference,
0x2c => Ins::ResetRetry,
0x47 => Ins::GenerateAsymmetric,
0x87 => Ins::Authenticate,
0xcb => Ins::GetData,
0xdb => Ins::PutData,
0xa4 => Ins::SelectApplication,
0xc0 => Ins::GetResponseApdu,
0xff => Ins::SetMgmKey,
0xfe => Ins::ImportKey,
0xfd => Ins::GetVersion,
0xfb => Ins::Reset,
0xfa => Ins::SetPinRetries,
0xf9 => Ins::Attest,
0xf8 => Ins::GetSerial,
0xf7 => Ins::GetMetadata,
code => Ins::Other(code),
}
}
}
impl From<Ins> for u8 {
fn from(ins: Ins) -> u8 {
ins.code()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Response {
status_words: StatusWords,
data: Vec<u8>,
}
impl Response {
pub fn new(status_words: StatusWords, data: Vec<u8>) -> Response {
Response { status_words, data }
}
pub fn status_words(&self) -> StatusWords {
self.status_words
}
pub fn code(&self) -> u16 {
self.status_words.code()
}
pub fn is_success(&self) -> bool {
self.status_words.is_success()
}
pub fn data(&self) -> &[u8] {
self.data.as_ref()
}
}
impl AsRef<[u8]> for Response {
fn as_ref(&self) -> &[u8] {
self.data()
}
}
impl Drop for Response {
fn drop(&mut self) {
self.zeroize();
}
}
impl From<Vec<u8>> for Response {
fn from(mut bytes: Vec<u8>) -> Self {
if bytes.len() < 2 {
return Response {
status_words: StatusWords::None,
data: bytes,
};
}
let sw = StatusWords::from(
(bytes[bytes.len() - 2] as u16) << 8 | (bytes[bytes.len() - 1] as u16),
);
let len = bytes.len() - 2;
bytes.truncate(len);
Response {
status_words: sw,
data: bytes,
}
}
}
impl Zeroize for Response {
fn zeroize(&mut self) {
self.data.zeroize();
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum StatusWords {
None,
Success,
NoInputDataError,
VerifyFailError {
tries: u8,
},
WrongLengthError,
SecurityStatusError,
AuthBlockedError,
DataInvalidError,
ConditionsNotSatisfiedError,
CommandNotAllowedError,
IncorrectParamError,
NotFoundError,
NoSpaceError,
IncorrectSlotError,
NotSupportedError,
CommandAbortedError,
Other(u16),
}
impl StatusWords {
pub fn code(self) -> u16 {
match self {
StatusWords::None => 0,
StatusWords::NoInputDataError => 0x6285,
StatusWords::VerifyFailError { tries } => 0x63c0 & tries as u16,
StatusWords::WrongLengthError => 0x6700,
StatusWords::SecurityStatusError => 0x6982,
StatusWords::AuthBlockedError => 0x6983,
StatusWords::DataInvalidError => 0x6984,
StatusWords::ConditionsNotSatisfiedError => 0x6985,
StatusWords::CommandNotAllowedError => 0x6986,
StatusWords::IncorrectParamError => 0x6a80,
StatusWords::NotFoundError => 0x6a82,
StatusWords::NoSpaceError => 0x6a84,
StatusWords::IncorrectSlotError => 0x6b00,
StatusWords::NotSupportedError => 0x6d00,
StatusWords::CommandAbortedError => 0x6f00,
StatusWords::Success => 0x9000,
StatusWords::Other(n) => n,
}
}
pub fn is_success(self) -> bool {
self == StatusWords::Success
}
}
impl From<u16> for StatusWords {
fn from(sw: u16) -> Self {
match sw {
0x0000 => StatusWords::None,
0x6285 => StatusWords::NoInputDataError,
sw if sw & 0xfff0 == 0x63c0 => StatusWords::VerifyFailError {
tries: (sw & 0x000f) as u8,
},
0x6700 => StatusWords::WrongLengthError,
0x6982 => StatusWords::SecurityStatusError,
0x6983 => StatusWords::AuthBlockedError,
0x6984 => StatusWords::DataInvalidError,
0x6985 => StatusWords::ConditionsNotSatisfiedError,
0x6986 => StatusWords::CommandNotAllowedError,
0x6a80 => StatusWords::IncorrectParamError,
0x6a82 => StatusWords::NotFoundError,
0x6a84 => StatusWords::NoSpaceError,
0x6b00 => StatusWords::IncorrectSlotError,
0x6d00 => StatusWords::NotSupportedError,
0x6f00 => StatusWords::CommandAbortedError,
0x9000 => StatusWords::Success,
_ => StatusWords::Other(sw),
}
}
}
impl From<StatusWords> for u16 {
fn from(sw: StatusWords) -> u16 {
sw.code()
}
}