Add extended APDU parser
This commit is contained in:
143
src/ctap/apdu.rs
143
src/ctap/apdu.rs
@@ -1,4 +1,5 @@
|
||||
use alloc::vec::Vec;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use core::convert::TryFrom;
|
||||
|
||||
type ByteArray = &'static [u8];
|
||||
@@ -73,11 +74,14 @@ impl From<&[u8; 4]> for ApduHeader {
|
||||
#[derive(PartialEq)]
|
||||
/// The APDU cases
|
||||
pub enum Case {
|
||||
Le,
|
||||
LcData,
|
||||
LcDataLe,
|
||||
// TODO: More cases to add as extended length APDUs
|
||||
// Le could be 2 or 3 Bytes
|
||||
Le1,
|
||||
Lc1Data,
|
||||
Lc1DataLe1,
|
||||
Lc3Data,
|
||||
Lc3DataLe1,
|
||||
Lc3DataLe2,
|
||||
Lc3DataLe3,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(Clone, Debug))]
|
||||
@@ -127,13 +131,16 @@ impl TryFrom<&[u8]> for APDU {
|
||||
case_type: ApduType::default(),
|
||||
};
|
||||
|
||||
// case 1
|
||||
if payload.is_empty() {
|
||||
// This branch is for Lc = 0
|
||||
apdu.case_type = ApduType::Instruction;
|
||||
} else {
|
||||
// Lc is not equal to zero, let's figure out how long it is
|
||||
let byte_0 = payload[0];
|
||||
if payload.len() == 1 {
|
||||
apdu.case_type = ApduType::Short(Case::Le);
|
||||
// There is only one byte in the payload, that byte cannot be Lc because that would
|
||||
// entail at *least* one another byte in the payload (for the command data)
|
||||
apdu.case_type = ApduType::Short(Case::Le1);
|
||||
apdu.le = if byte_0 == 0x00 {
|
||||
// Ne = 256
|
||||
0x100
|
||||
@@ -142,12 +149,16 @@ impl TryFrom<&[u8]> for APDU {
|
||||
}
|
||||
}
|
||||
if payload.len() == (1 + byte_0) as usize && byte_0 != 0 {
|
||||
apdu.case_type = ApduType::Short(Case::LcData);
|
||||
// Lc is one-byte long and since the size specified by Lc covers the rest of the
|
||||
// payload there's no Le at the end
|
||||
apdu.case_type = ApduType::Short(Case::Lc1Data);
|
||||
apdu.lc = byte_0.into();
|
||||
apdu.data = payload[1..].to_vec();
|
||||
}
|
||||
if payload.len() == (1 + byte_0 + 1) as usize && byte_0 != 0 {
|
||||
apdu.case_type = ApduType::Short(Case::LcDataLe);
|
||||
// Lc is one-byte long and since the size specified by Lc covers the rest of the
|
||||
// payload with ONE additional byte that byte must be Le
|
||||
apdu.case_type = ApduType::Short(Case::Lc1DataLe1);
|
||||
apdu.lc = byte_0.into();
|
||||
apdu.data = payload[1..(payload.len() - 1)].to_vec();
|
||||
apdu.le = (*payload.last().unwrap()).into();
|
||||
@@ -155,8 +166,38 @@ impl TryFrom<&[u8]> for APDU {
|
||||
apdu.le = 0x100;
|
||||
}
|
||||
}
|
||||
if payload.len() > 2 {
|
||||
// Lc is possibly three-bytes long
|
||||
let extended_apdu_lc: usize = BigEndian::read_u16(&payload[1..]) as usize;
|
||||
let extended_apdu_le_len: usize = if payload.len() > extended_apdu_lc {
|
||||
payload.len() - extended_apdu_lc - 3
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if byte_0 == 0 && extended_apdu_le_len <= 3 {
|
||||
// If first byte is zero AND the next two bytes can be parsed as a big-endian
|
||||
// length that covers the rest of the block (plus few additional bytes for Le), we
|
||||
// have an extended-length APDU
|
||||
apdu.case_type = ApduType::Extended(match extended_apdu_le_len {
|
||||
0 => Case::Lc3Data,
|
||||
1 => Case::Lc3DataLe1,
|
||||
2 => Case::Lc3DataLe2,
|
||||
3 => Case::Lc3DataLe3,
|
||||
_ => Case::Unknown,
|
||||
});
|
||||
apdu.data = payload[3..(payload.len() - extended_apdu_le_len)].to_vec();
|
||||
apdu.lc = extended_apdu_lc as u16;
|
||||
apdu.le = match extended_apdu_le_len {
|
||||
0 => 0,
|
||||
1 => (*payload.last().unwrap()).into(),
|
||||
2 => BigEndian::read_u16(&payload[payload.len() - extended_apdu_le_len..])
|
||||
as u32,
|
||||
3 => BigEndian::read_u32(&payload[payload.len() - extended_apdu_le_len..]),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Add extended length cases
|
||||
if apdu.case_type == ApduType::default() {
|
||||
return Err(ApduStatusCode::SW_COND_USE_NOT_SATISFIED);
|
||||
}
|
||||
@@ -206,7 +247,7 @@ mod test {
|
||||
lc: 0x00,
|
||||
data: Vec::new(),
|
||||
le: 0x0f,
|
||||
case_type: ApduType::Short(Case::Le),
|
||||
case_type: ApduType::Short(Case::Le1),
|
||||
};
|
||||
assert_eq!(Ok(expected), response);
|
||||
}
|
||||
@@ -225,7 +266,7 @@ mod test {
|
||||
lc: 0x00,
|
||||
data: Vec::new(),
|
||||
le: 0x100,
|
||||
case_type: ApduType::Short(Case::Le),
|
||||
case_type: ApduType::Short(Case::Le1),
|
||||
};
|
||||
assert_eq!(Ok(expected), response);
|
||||
}
|
||||
@@ -245,7 +286,7 @@ mod test {
|
||||
lc: 0x02,
|
||||
data: payload.to_vec(),
|
||||
le: 0x00,
|
||||
case_type: ApduType::Short(Case::LcData),
|
||||
case_type: ApduType::Short(Case::Lc1Data),
|
||||
};
|
||||
assert_eq!(Ok(expected), response);
|
||||
}
|
||||
@@ -267,7 +308,7 @@ mod test {
|
||||
lc: 0x07,
|
||||
data: payload.to_vec(),
|
||||
le: 0xff,
|
||||
case_type: ApduType::Short(Case::LcDataLe),
|
||||
case_type: ApduType::Short(Case::Lc1DataLe1),
|
||||
};
|
||||
assert_eq!(Ok(expected), response);
|
||||
}
|
||||
@@ -289,7 +330,7 @@ mod test {
|
||||
lc: 0x07,
|
||||
data: payload.to_vec(),
|
||||
le: 0x100,
|
||||
case_type: ApduType::Short(Case::LcDataLe),
|
||||
case_type: ApduType::Short(Case::Lc1DataLe1),
|
||||
};
|
||||
assert_eq!(Ok(expected), response);
|
||||
}
|
||||
@@ -302,7 +343,56 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsupported_case_type() {
|
||||
fn test_extended_length_apdu() {
|
||||
let frame: [u8; 186] = [
|
||||
0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0xb1, 0x60, 0xc5, 0xb3, 0x42, 0x58, 0x6b, 0x49,
|
||||
0xdb, 0x3e, 0x72, 0xd8, 0x24, 0x4b, 0xa5, 0x6c, 0x8d, 0x79, 0x2b, 0x65, 0x08, 0xe8,
|
||||
0xda, 0x9b, 0x0e, 0x2b, 0xc1, 0x63, 0x0d, 0xbc, 0xf3, 0x6d, 0x66, 0xa5, 0x46, 0x72,
|
||||
0xb2, 0x22, 0xc4, 0xcf, 0x95, 0xe1, 0x51, 0xed, 0x8d, 0x4d, 0x3c, 0x76, 0x7a, 0x6c,
|
||||
0xc3, 0x49, 0x43, 0x59, 0x43, 0x79, 0x4e, 0x88, 0x4f, 0x3d, 0x02, 0x3a, 0x82, 0x29,
|
||||
0xfd, 0x70, 0x3f, 0x8b, 0xd4, 0xff, 0xe0, 0xa8, 0x93, 0xdf, 0x1a, 0x58, 0x34, 0x16,
|
||||
0xb0, 0x1b, 0x8e, 0xbc, 0xf0, 0x2d, 0xc9, 0x99, 0x8d, 0x6f, 0xe4, 0x8a, 0xb2, 0x70,
|
||||
0x9a, 0x70, 0x3a, 0x27, 0x71, 0x88, 0x3c, 0x75, 0x30, 0x16, 0xfb, 0x02, 0x11, 0x4d,
|
||||
0x30, 0x54, 0x6c, 0x4e, 0x8c, 0x76, 0xb2, 0xf0, 0xa8, 0x4e, 0xd6, 0x90, 0xe4, 0x40,
|
||||
0x25, 0x6a, 0xdd, 0x64, 0x63, 0x3e, 0x83, 0x4f, 0x8b, 0x25, 0xcf, 0x88, 0x68, 0x80,
|
||||
0x01, 0x07, 0xdb, 0xc8, 0x64, 0xf7, 0xca, 0x4f, 0xd1, 0xc7, 0x95, 0x7c, 0xe8, 0x45,
|
||||
0xbc, 0xda, 0xd4, 0xef, 0x45, 0x63, 0x5a, 0x7a, 0x65, 0x3f, 0xaa, 0x22, 0x67, 0xe7,
|
||||
0x8a, 0xf2, 0x5f, 0xe8, 0x59, 0x2e, 0x0b, 0xc6, 0x85, 0xc6, 0xf7, 0x0e, 0x9e, 0xdb,
|
||||
0xb6, 0x2b, 0x00, 0x00,
|
||||
];
|
||||
let payload = [
|
||||
0x60, 0xc5, 0xb3, 0x42, 0x58, 0x6b, 0x49, 0xdb, 0x3e, 0x72, 0xd8, 0x24, 0x4b, 0xa5,
|
||||
0x6c, 0x8d, 0x79, 0x2b, 0x65, 0x08, 0xe8, 0xda, 0x9b, 0x0e, 0x2b, 0xc1, 0x63, 0x0d,
|
||||
0xbc, 0xf3, 0x6d, 0x66, 0xa5, 0x46, 0x72, 0xb2, 0x22, 0xc4, 0xcf, 0x95, 0xe1, 0x51,
|
||||
0xed, 0x8d, 0x4d, 0x3c, 0x76, 0x7a, 0x6c, 0xc3, 0x49, 0x43, 0x59, 0x43, 0x79, 0x4e,
|
||||
0x88, 0x4f, 0x3d, 0x02, 0x3a, 0x82, 0x29, 0xfd, 0x70, 0x3f, 0x8b, 0xd4, 0xff, 0xe0,
|
||||
0xa8, 0x93, 0xdf, 0x1a, 0x58, 0x34, 0x16, 0xb0, 0x1b, 0x8e, 0xbc, 0xf0, 0x2d, 0xc9,
|
||||
0x99, 0x8d, 0x6f, 0xe4, 0x8a, 0xb2, 0x70, 0x9a, 0x70, 0x3a, 0x27, 0x71, 0x88, 0x3c,
|
||||
0x75, 0x30, 0x16, 0xfb, 0x02, 0x11, 0x4d, 0x30, 0x54, 0x6c, 0x4e, 0x8c, 0x76, 0xb2,
|
||||
0xf0, 0xa8, 0x4e, 0xd6, 0x90, 0xe4, 0x40, 0x25, 0x6a, 0xdd, 0x64, 0x63, 0x3e, 0x83,
|
||||
0x4f, 0x8b, 0x25, 0xcf, 0x88, 0x68, 0x80, 0x01, 0x07, 0xdb, 0xc8, 0x64, 0xf7, 0xca,
|
||||
0x4f, 0xd1, 0xc7, 0x95, 0x7c, 0xe8, 0x45, 0xbc, 0xda, 0xd4, 0xef, 0x45, 0x63, 0x5a,
|
||||
0x7a, 0x65, 0x3f, 0xaa, 0x22, 0x67, 0xe7, 0x8a, 0xf2, 0x5f, 0xe8, 0x59, 0x2e, 0x0b,
|
||||
0xc6, 0x85, 0xc6, 0xf7, 0x0e, 0x9e, 0xdb, 0xb6, 0x2b,
|
||||
];
|
||||
let response = pass_frame(&frame);
|
||||
let expected = APDU {
|
||||
header: ApduHeader {
|
||||
cla: 0x00,
|
||||
ins: 0x02,
|
||||
p1: 0x03,
|
||||
p2: 0x00,
|
||||
},
|
||||
lc: 0xb1,
|
||||
data: payload.to_vec(),
|
||||
le: 0x00,
|
||||
case_type: ApduType::Extended(Case::Lc3DataLe2),
|
||||
};
|
||||
assert_eq!(Ok(expected), response);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_previously_unsupported_case_type() {
|
||||
let frame: [u8; 73] = [
|
||||
0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x40, 0xe3, 0x8f, 0xde, 0x51, 0x3d, 0xac, 0x9d,
|
||||
0x1c, 0x6e, 0x86, 0x76, 0x31, 0x40, 0x25, 0x96, 0x86, 0x4d, 0x29, 0xe8, 0x07, 0xb3,
|
||||
@@ -311,7 +401,26 @@ mod test {
|
||||
0xe8, 0xb5, 0x83, 0xfb, 0xe0, 0x66, 0x98, 0x4d, 0x98, 0x81, 0xf7, 0xb5, 0x49, 0x4d,
|
||||
0xcb, 0x00, 0x00,
|
||||
];
|
||||
let payload = [
|
||||
0xe3, 0x8f, 0xde, 0x51, 0x3d, 0xac, 0x9d, 0x1c, 0x6e, 0x86, 0x76, 0x31, 0x40, 0x25,
|
||||
0x96, 0x86, 0x4d, 0x29, 0xe8, 0x07, 0xb3, 0x56, 0x19, 0xdf, 0x4a, 0x00, 0x02, 0xae,
|
||||
0x2a, 0x8c, 0x9d, 0x5a, 0xab, 0xc3, 0x4b, 0x4e, 0xb9, 0x78, 0xb9, 0x11, 0xe5, 0x52,
|
||||
0x40, 0xf3, 0x45, 0x64, 0x9c, 0xd3, 0xd7, 0xe8, 0xb5, 0x83, 0xfb, 0xe0, 0x66, 0x98,
|
||||
0x4d, 0x98, 0x81, 0xf7, 0xb5, 0x49, 0x4d, 0xcb,
|
||||
];
|
||||
let response = pass_frame(&frame);
|
||||
assert_eq!(Err(ApduStatusCode::SW_COND_USE_NOT_SATISFIED), response);
|
||||
let expected = APDU {
|
||||
header: ApduHeader {
|
||||
cla: 0x00,
|
||||
ins: 0x01,
|
||||
p1: 0x03,
|
||||
p2: 0x00,
|
||||
},
|
||||
lc: 0x40,
|
||||
data: payload.to_vec(),
|
||||
le: 0x00,
|
||||
case_type: ApduType::Extended(Case::Lc3DataLe2),
|
||||
};
|
||||
assert_eq!(Ok(expected), response);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user