Merge pull request #227 from krkhan/apdu-parser-kk

Add extended APDU parser
This commit is contained in:
Jean-Michel Picod
2020-12-03 17:55:39 +01:00
committed by GitHub
4 changed files with 179 additions and 57 deletions

2
.gitmodules vendored
View File

@@ -1,6 +1,8 @@
[submodule "third_party/libtock-rs"] [submodule "third_party/libtock-rs"]
path = third_party/libtock-rs path = third_party/libtock-rs
url = https://github.com/tock/libtock-rs url = https://github.com/tock/libtock-rs
ignore = dirty
[submodule "third_party/tock"] [submodule "third_party/tock"]
path = third_party/tock path = third_party/tock
url = https://github.com/tock/tock url = https://github.com/tock/tock
ignore = dirty

View File

@@ -1,4 +1,5 @@
use alloc::vec::Vec; use alloc::vec::Vec;
use byteorder::{BigEndian, ByteOrder};
use core::convert::TryFrom; use core::convert::TryFrom;
type ByteArray = &'static [u8]; type ByteArray = &'static [u8];
@@ -58,8 +59,8 @@ pub struct ApduHeader {
p2: u8, p2: u8,
} }
impl From<&[u8]> for ApduHeader { impl From<&[u8; APDU_HEADER_LEN]> for ApduHeader {
fn from(header: &[u8]) -> Self { fn from(header: &[u8; APDU_HEADER_LEN]) -> Self {
ApduHeader { ApduHeader {
cla: header[0], cla: header[0],
ins: header[1], ins: header[1],
@@ -73,11 +74,13 @@ impl From<&[u8]> for ApduHeader {
#[derive(PartialEq)] #[derive(PartialEq)]
/// The APDU cases /// The APDU cases
pub enum Case { pub enum Case {
Le, Le1,
LcData, Lc1Data,
LcDataLe, Lc1DataLe1,
// TODO: More cases to add as extended length APDUs Lc3Data,
// Le could be 2 or 3 Bytes Lc3DataLe1,
Lc3DataLe2,
Le3,
} }
#[cfg_attr(test, derive(Clone, Debug))] #[cfg_attr(test, derive(Clone, Debug))]
@@ -87,18 +90,11 @@ pub enum ApduType {
Instruction, Instruction,
Short(Case), Short(Case),
Extended(Case), Extended(Case),
Unknown,
}
impl Default for ApduType {
fn default() -> ApduType {
ApduType::Unknown
}
} }
#[cfg_attr(test, derive(Clone, Debug))] #[cfg_attr(test, derive(Clone, Debug))]
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Default, PartialEq)] #[derive(PartialEq)]
pub struct APDU { pub struct APDU {
header: ApduHeader, header: ApduHeader,
lc: u16, lc: u16,
@@ -119,48 +115,118 @@ impl TryFrom<&[u8]> for APDU {
// +-----+-----+----+----+ // +-----+-----+----+----+
let (header, payload) = frame.split_at(APDU_HEADER_LEN); let (header, payload) = frame.split_at(APDU_HEADER_LEN);
let mut apdu = APDU { if payload.is_empty() {
header: header.into(), // Lc is zero-bytes in length
return Ok(APDU {
header: array_ref!(header, 0, APDU_HEADER_LEN).into(),
lc: 0x00, lc: 0x00,
data: Vec::new(), data: Vec::new(),
le: 0x00, le: 0x00,
case_type: ApduType::default(), case_type: ApduType::Instruction,
}; });
}
// case 1 // Lc is not zero-bytes in length, let's figure out how long it is
if payload.is_empty() {
apdu.case_type = ApduType::Instruction;
} else {
let byte_0 = payload[0]; let byte_0 = payload[0];
if payload.len() == 1 { 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
apdu.le = if byte_0 == 0x00 { // entail at *least* one another byte in the payload (for the command data)
return Ok(APDU {
header: array_ref!(header, 0, APDU_HEADER_LEN).into(),
lc: 0x00,
data: Vec::new(),
le: if byte_0 == 0x00 {
// Ne = 256 // Ne = 256
0x100 0x100
} else { } else {
byte_0.into() byte_0.into()
},
case_type: ApduType::Short(Case::Le1),
});
}
if payload.len() == 1 + (byte_0 as usize) && byte_0 != 0 {
// 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
return Ok(APDU {
header: array_ref!(header, 0, APDU_HEADER_LEN).into(),
lc: byte_0.into(),
data: payload[1..].to_vec(),
case_type: ApduType::Short(Case::Lc1Data),
le: 0,
});
}
if payload.len() == 2 + (byte_0 as usize) && byte_0 != 0 {
// 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
let last_byte: u32 = (*payload.last().unwrap()).into();
return Ok(APDU {
header: array_ref!(header, 0, APDU_HEADER_LEN).into(),
lc: byte_0.into(),
data: payload[1..(payload.len() - 1)].to_vec(),
le: if last_byte == 0x00 { 0x100 } else { last_byte },
case_type: ApduType::Short(Case::Lc1DataLe1),
});
}
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
let last_byte: u32 = (*payload.last().unwrap()).into();
return Ok(APDU {
header: array_ref!(header, 0, APDU_HEADER_LEN).into(),
lc: extended_apdu_lc as u16,
data: payload[3..(payload.len() - extended_apdu_le_len)].to_vec(),
le: match extended_apdu_le_len {
0 => 0,
1 => {
if last_byte == 0x00 {
0x100
} else {
last_byte
} }
} }
if payload.len() == (1 + byte_0) as usize && byte_0 != 0 { 2 => {
apdu.case_type = ApduType::Short(Case::LcData); let le_parsed = BigEndian::read_u16(&payload[payload.len() - 2..]);
apdu.lc = byte_0.into(); if le_parsed == 0x00 {
apdu.data = payload[1..].to_vec(); 0x10000
} } else {
if payload.len() == (1 + byte_0 + 1) as usize && byte_0 != 0 { le_parsed as u32
apdu.case_type = ApduType::Short(Case::LcDataLe);
apdu.lc = byte_0.into();
apdu.data = payload[1..(payload.len() - 1)].to_vec();
apdu.le = (*payload.last().unwrap()).into();
if apdu.le == 0x00 {
apdu.le = 0x100;
} }
} }
3 => {
let le_first_byte: u32 =
(*payload.get(payload.len() - 3).unwrap()).into();
if le_first_byte != 0x00 {
return Err(ApduStatusCode::SW_INTERNAL_EXCEPTION);
} }
// TODO: Add extended length cases let le_parsed = BigEndian::read_u16(&payload[payload.len() - 2..]);
if apdu.case_type == ApduType::default() { if le_parsed == 0x00 {
return Err(ApduStatusCode::SW_COND_USE_NOT_SATISFIED); 0x10000
} else {
le_parsed as u32
} }
Ok(apdu) }
_ => return Err(ApduStatusCode::SW_INTERNAL_EXCEPTION),
},
case_type: ApduType::Extended(match extended_apdu_le_len {
0 => Case::Lc3Data,
1 => Case::Lc3DataLe1,
2 => Case::Lc3DataLe2,
3 => Case::Le3,
_ => return Err(ApduStatusCode::SW_INTERNAL_EXCEPTION),
}),
});
}
}
return Err(ApduStatusCode::SW_INTERNAL_EXCEPTION);
} }
} }
@@ -206,7 +272,7 @@ mod test {
lc: 0x00, lc: 0x00,
data: Vec::new(), data: Vec::new(),
le: 0x0f, le: 0x0f,
case_type: ApduType::Short(Case::Le), case_type: ApduType::Short(Case::Le1),
}; };
assert_eq!(Ok(expected), response); assert_eq!(Ok(expected), response);
} }
@@ -225,7 +291,7 @@ mod test {
lc: 0x00, lc: 0x00,
data: Vec::new(), data: Vec::new(),
le: 0x100, le: 0x100,
case_type: ApduType::Short(Case::Le), case_type: ApduType::Short(Case::Le1),
}; };
assert_eq!(Ok(expected), response); assert_eq!(Ok(expected), response);
} }
@@ -245,7 +311,7 @@ mod test {
lc: 0x02, lc: 0x02,
data: payload.to_vec(), data: payload.to_vec(),
le: 0x00, le: 0x00,
case_type: ApduType::Short(Case::LcData), case_type: ApduType::Short(Case::Lc1Data),
}; };
assert_eq!(Ok(expected), response); assert_eq!(Ok(expected), response);
} }
@@ -267,7 +333,7 @@ mod test {
lc: 0x07, lc: 0x07,
data: payload.to_vec(), data: payload.to_vec(),
le: 0xff, le: 0xff,
case_type: ApduType::Short(Case::LcDataLe), case_type: ApduType::Short(Case::Lc1DataLe1),
}; };
assert_eq!(Ok(expected), response); assert_eq!(Ok(expected), response);
} }
@@ -289,7 +355,7 @@ mod test {
lc: 0x07, lc: 0x07,
data: payload.to_vec(), data: payload.to_vec(),
le: 0x100, le: 0x100,
case_type: ApduType::Short(Case::LcDataLe), case_type: ApduType::Short(Case::Lc1DataLe1),
}; };
assert_eq!(Ok(expected), response); assert_eq!(Ok(expected), response);
} }
@@ -302,7 +368,42 @@ mod test {
} }
#[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: &[u8] = &frame[7..frame.len() - 2];
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: 0x10000,
case_type: ApduType::Extended(Case::Lc3DataLe2),
};
assert_eq!(Ok(expected), response);
}
#[test]
fn test_previously_unsupported_case_type() {
let frame: [u8; 73] = [ let frame: [u8; 73] = [
0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x40, 0xe3, 0x8f, 0xde, 0x51, 0x3d, 0xac, 0x9d, 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, 0x1c, 0x6e, 0x86, 0x76, 0x31, 0x40, 0x25, 0x96, 0x86, 0x4d, 0x29, 0xe8, 0x07, 0xb3,
@@ -311,7 +412,20 @@ mod test {
0xe8, 0xb5, 0x83, 0xfb, 0xe0, 0x66, 0x98, 0x4d, 0x98, 0x81, 0xf7, 0xb5, 0x49, 0x4d, 0xe8, 0xb5, 0x83, 0xfb, 0xe0, 0x66, 0x98, 0x4d, 0x98, 0x81, 0xf7, 0xb5, 0x49, 0x4d,
0xcb, 0x00, 0x00, 0xcb, 0x00, 0x00,
]; ];
let payload: &[u8] = &frame[7..frame.len() - 2];
let response = pass_frame(&frame); 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: 0x10000,
case_type: ApduType::Extended(Case::Lc3DataLe2),
};
assert_eq!(Ok(expected), response);
} }
} }

View File

@@ -18,3 +18,6 @@ extern crate alloc;
pub mod ctap; pub mod ctap;
pub mod embedded_flash; pub mod embedded_flash;
#[macro_use]
extern crate arrayref;

View File

@@ -18,6 +18,9 @@ extern crate alloc;
#[cfg(feature = "std")] #[cfg(feature = "std")]
extern crate core; extern crate core;
extern crate lang_items; extern crate lang_items;
#[macro_use]
extern crate arrayref;
extern crate byteorder;
mod ctap; mod ctap;
pub mod embedded_flash; pub mod embedded_flash;