From 58b5e4d8fab3d72443a58ea2aaabd624bc5df46a Mon Sep 17 00:00:00 2001 From: Mirna Date: Fri, 13 Nov 2020 09:32:59 +0200 Subject: [PATCH 1/2] Add short APDUs parser --- src/ctap/apdu.rs | 303 +++++++++++++++++++++++++++++++++++++++++++++++ src/ctap/mod.rs | 1 + 2 files changed, 304 insertions(+) create mode 100644 src/ctap/apdu.rs diff --git a/src/ctap/apdu.rs b/src/ctap/apdu.rs new file mode 100644 index 0000000..f324a03 --- /dev/null +++ b/src/ctap/apdu.rs @@ -0,0 +1,303 @@ +use alloc::vec::Vec; +use core::convert::TryFrom; + +type ByteArray = &'static [u8]; + +#[cfg_attr(test, derive(Clone, Debug))] +#[allow(non_camel_case_types)] +#[derive(PartialEq)] +pub enum ApduStatusCode { + SW_SUCCESS, + /// Command successfully executed; 'XX' bytes of data are + /// available and can be requested using GET RESPONSE. + SW_GET_RESPONSE, + SW_WRONG_DATA, + SW_WRONG_LENGTH, + SW_COND_USE_NOT_SATISFIED, + SW_FILE_NOT_FOUND, + SW_INCORRECT_P1P2, + /// Instruction code not supported or invalid + SW_INS_INVALID, + SW_CLA_INVALID, + SW_INTERNAL_EXCEPTION, +} + +impl From for ByteArray { + fn from(status_code: ApduStatusCode) -> ByteArray { + match status_code { + ApduStatusCode::SW_SUCCESS => b"\x90\x00", + ApduStatusCode::SW_GET_RESPONSE => b"\x61\x00", + ApduStatusCode::SW_WRONG_DATA => b"\x6A\x80", + ApduStatusCode::SW_WRONG_LENGTH => b"\x67\x00", + ApduStatusCode::SW_COND_USE_NOT_SATISFIED => b"\x69\x85", + ApduStatusCode::SW_FILE_NOT_FOUND => b"\x6a\x82", + ApduStatusCode::SW_INCORRECT_P1P2 => b"\x6a\x86", + ApduStatusCode::SW_INS_INVALID => b"\x6d\x00", + ApduStatusCode::SW_CLA_INVALID => b"\x6e\x00", + ApduStatusCode::SW_INTERNAL_EXCEPTION => b"\x6f\x00", + } + } +} + +#[allow(non_camel_case_types, dead_code)] +pub enum ApduIns { + SELECT = 0xA4, + READ_BINARY = 0xB0, + GET_RESPONSE = 0xC0, +} + +#[cfg_attr(test, derive(Clone, Debug))] +#[allow(dead_code)] +#[derive(Default, PartialEq)] +pub struct ApduHeader { + cla: u8, + ins: u8, + p1: u8, + p2: u8, +} + +impl From<&[u8]> for ApduHeader { + fn from(header: &[u8]) -> Self { + ApduHeader { + cla: header[0], + ins: header[1], + p1: header[2], + p2: header[3], + } + } +} + +#[cfg_attr(test, derive(Clone, Debug))] +#[allow(dead_code)] +#[derive(Default, PartialEq)] +pub struct APDU { + header: ApduHeader, + lc: u16, + data: Vec, + le: u32, + case_type: u8, +} + +const APDU_HEADER_LEN: u8 = 4; + +impl TryFrom<&[u8]> for APDU { + type Error = ApduStatusCode; + + fn try_from(frame: &[u8]) -> Result { + if frame.len() < APDU_HEADER_LEN as usize { + return Err(ApduStatusCode::SW_WRONG_DATA); + } + // +-----+-----+----+----+ + // header | CLA | INS | P1 | P2 | + // +-----+-----+----+----+ + let (header, payload) = frame.split_at(APDU_HEADER_LEN as usize); + + let mut apdu = APDU { + header: header.into(), + lc: 0x00, + data: Vec::new(), + le: 0x00, + case_type: 0x00, + }; + + // case 1 + if payload.is_empty() { + apdu.case_type = 0x01; + } else { + let byte_0 = payload[0]; + // case 2S (Le) + if payload.len() == 1 { + apdu.case_type = 0x02; + apdu.le = if byte_0 == 0x00 { + // Ne = 256 + 0x100 + } else { + byte_0.into() + } + } + // case 3S (Lc + data) + if payload.len() == (1 + byte_0) as usize && byte_0 != 0 { + apdu.case_type = 0x03; + apdu.lc = byte_0.into(); + apdu.data = payload[1..].to_vec(); + } + // case 4S (Lc + data + Le) + if payload.len() == (1 + byte_0 + 1) as usize && byte_0 != 0 { + apdu.case_type = 0x04; + 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; + } + } + } + // TODO: Add extended length cases + if apdu.case_type == 0x00 { + return Err(ApduStatusCode::SW_COND_USE_NOT_SATISFIED); + } + Ok(apdu) + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn pass_frame(frame: &[u8]) -> Result { + APDU::try_from(frame) + } + + #[test] + fn test_case_type_1() { + let frame: [u8; 4] = [0x00, 0x12, 0x00, 0x80]; + let response = pass_frame(&frame); + assert!(response.is_ok()); + let expected = APDU { + header: ApduHeader { + cla: 0x00, + ins: 0x12, + p1: 0x00, + p2: 0x80, + }, + lc: 0x00, + data: Vec::new(), + le: 0x00, + case_type: 0x01, + }; + assert_eq!(expected, response.unwrap()); + } + + #[test] + fn test_case_type_2_short() { + let frame: [u8; 5] = [0x00, 0xb0, 0x00, 0x00, 0x0f]; + let response = pass_frame(&frame); + assert!(response.is_ok()); + let expected = APDU { + header: ApduHeader { + cla: 0x00, + ins: 0xb0, + p1: 0x00, + p2: 0x00, + }, + lc: 0x00, + data: Vec::new(), + le: 0x0f, + case_type: 0x02, + }; + assert_eq!(expected, response.unwrap()); + } + + #[test] + fn test_case_type_2_short_le() { + let frame: [u8; 5] = [0x00, 0xb0, 0x00, 0x00, 0x00]; + let response = pass_frame(&frame); + assert!(response.is_ok()); + let expected = APDU { + header: ApduHeader { + cla: 0x00, + ins: 0xb0, + p1: 0x00, + p2: 0x00, + }, + lc: 0x00, + data: Vec::new(), + le: 0x100, + case_type: 0x02, + }; + assert_eq!(expected, response.unwrap()); + } + + #[test] + fn test_case_type_3_short() { + let frame: [u8; 7] = [0x00, 0xa4, 0x00, 0x0c, 0x02, 0xe1, 0x04]; + let payload = [0xe1, 0x04]; + let response = pass_frame(&frame); + assert!(response.is_ok()); + let expected = APDU { + header: ApduHeader { + cla: 0x00, + ins: 0xa4, + p1: 0x00, + p2: 0x0c, + }, + lc: 0x02, + data: payload.to_vec(), + le: 0x00, + case_type: 0x03, + }; + assert_eq!(expected, response.unwrap()); + } + + #[test] + fn test_case_type_4_short() { + let frame: [u8; 13] = [ + 0x00, 0xa4, 0x04, 0x00, 0x07, 0xd2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0xff, + ]; + let payload = [0xd2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01]; + let response = pass_frame(&frame); + assert!(response.is_ok()); + let expected = APDU { + header: ApduHeader { + cla: 0x00, + ins: 0xa4, + p1: 0x04, + p2: 0x00, + }, + lc: 0x07, + data: payload.to_vec(), + le: 0xff, + case_type: 0x04, + }; + assert_eq!(expected, response.unwrap()); + } + + #[test] + fn test_case_type_4_short_le() { + let frame: [u8; 13] = [ + 0x00, 0xa4, 0x04, 0x00, 0x07, 0xd2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00, + ]; + let payload = [0xd2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01]; + let response = pass_frame(&frame); + assert!(response.is_ok()); + let expected = APDU { + header: ApduHeader { + cla: 0x00, + ins: 0xa4, + p1: 0x04, + p2: 0x00, + }, + lc: 0x07, + data: payload.to_vec(), + le: 0x100, + case_type: 0x04, + }; + assert_eq!(expected, response.unwrap()); + } + + #[test] + fn test_invalid_apdu_header_length() { + let frame: [u8; 3] = [0x00, 0x12, 0x00]; + let response = pass_frame(&frame); + assert!(response.is_err()); + assert_eq!(Some(ApduStatusCode::SW_WRONG_DATA), response.err()); + } + + #[test] + fn test_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, + 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, 0x00, 0x00, + ]; + let response = pass_frame(&frame); + assert!(response.is_err()); + assert_eq!( + Some(ApduStatusCode::SW_COND_USE_NOT_SATISFIED), + response.err() + ); + } +} diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index 1a98ce5..2551e7b 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod apdu; pub mod command; #[cfg(feature = "with_ctap1")] mod ctap1; From fce91744c68abe1f41761500b563b4e10c612e80 Mon Sep 17 00:00:00 2001 From: Mirna Date: Fri, 13 Nov 2020 22:06:27 +0200 Subject: [PATCH 2/2] Addressing some of the requested changes --- src/ctap/apdu.rs | 98 +++++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/src/ctap/apdu.rs b/src/ctap/apdu.rs index f324a03..431b18e 100644 --- a/src/ctap/apdu.rs +++ b/src/ctap/apdu.rs @@ -3,6 +3,8 @@ use core::convert::TryFrom; type ByteArray = &'static [u8]; +const APDU_HEADER_LEN: usize = 4; + #[cfg_attr(test, derive(Clone, Debug))] #[allow(non_camel_case_types)] #[derive(PartialEq)] @@ -39,11 +41,11 @@ impl From for ByteArray { } } -#[allow(non_camel_case_types, dead_code)] -pub enum ApduIns { - SELECT = 0xA4, - READ_BINARY = 0xB0, - GET_RESPONSE = 0xC0, +#[allow(dead_code)] +pub enum ApduInstructions { + Select = 0xA4, + ReadBinary = 0xB0, + GetResponse = 0xC0, } #[cfg_attr(test, derive(Clone, Debug))] @@ -67,6 +69,33 @@ impl From<&[u8]> for ApduHeader { } } +#[cfg_attr(test, derive(Clone, Debug))] +#[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 +} + +#[cfg_attr(test, derive(Clone, Debug))] +#[allow(dead_code)] +#[derive(PartialEq)] +pub enum ApduType { + Instruction, + Short(Case), + Extended(Case), + Unknown, +} + +impl Default for ApduType { + fn default() -> ApduType { + ApduType::Unknown + } +} + #[cfg_attr(test, derive(Clone, Debug))] #[allow(dead_code)] #[derive(Default, PartialEq)] @@ -75,11 +104,9 @@ pub struct APDU { lc: u16, data: Vec, le: u32, - case_type: u8, + case_type: ApduType, } -const APDU_HEADER_LEN: u8 = 4; - impl TryFrom<&[u8]> for APDU { type Error = ApduStatusCode; @@ -90,24 +117,23 @@ impl TryFrom<&[u8]> for APDU { // +-----+-----+----+----+ // header | CLA | INS | P1 | P2 | // +-----+-----+----+----+ - let (header, payload) = frame.split_at(APDU_HEADER_LEN as usize); + let (header, payload) = frame.split_at(APDU_HEADER_LEN); let mut apdu = APDU { header: header.into(), lc: 0x00, data: Vec::new(), le: 0x00, - case_type: 0x00, + case_type: ApduType::default(), }; // case 1 if payload.is_empty() { - apdu.case_type = 0x01; + apdu.case_type = ApduType::Instruction; } else { let byte_0 = payload[0]; - // case 2S (Le) if payload.len() == 1 { - apdu.case_type = 0x02; + apdu.case_type = ApduType::Short(Case::Le); apdu.le = if byte_0 == 0x00 { // Ne = 256 0x100 @@ -115,15 +141,13 @@ impl TryFrom<&[u8]> for APDU { byte_0.into() } } - // case 3S (Lc + data) if payload.len() == (1 + byte_0) as usize && byte_0 != 0 { - apdu.case_type = 0x03; + apdu.case_type = ApduType::Short(Case::LcData); apdu.lc = byte_0.into(); apdu.data = payload[1..].to_vec(); } - // case 4S (Lc + data + Le) if payload.len() == (1 + byte_0 + 1) as usize && byte_0 != 0 { - apdu.case_type = 0x04; + 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(); @@ -133,7 +157,7 @@ impl TryFrom<&[u8]> for APDU { } } // TODO: Add extended length cases - if apdu.case_type == 0x00 { + if apdu.case_type == ApduType::default() { return Err(ApduStatusCode::SW_COND_USE_NOT_SATISFIED); } Ok(apdu) @@ -163,16 +187,15 @@ mod test { lc: 0x00, data: Vec::new(), le: 0x00, - case_type: 0x01, + case_type: ApduType::Instruction, }; - assert_eq!(expected, response.unwrap()); + assert_eq!(Ok(expected), response); } #[test] fn test_case_type_2_short() { let frame: [u8; 5] = [0x00, 0xb0, 0x00, 0x00, 0x0f]; let response = pass_frame(&frame); - assert!(response.is_ok()); let expected = APDU { header: ApduHeader { cla: 0x00, @@ -183,16 +206,15 @@ mod test { lc: 0x00, data: Vec::new(), le: 0x0f, - case_type: 0x02, + case_type: ApduType::Short(Case::Le), }; - assert_eq!(expected, response.unwrap()); + assert_eq!(Ok(expected), response); } #[test] fn test_case_type_2_short_le() { let frame: [u8; 5] = [0x00, 0xb0, 0x00, 0x00, 0x00]; let response = pass_frame(&frame); - assert!(response.is_ok()); let expected = APDU { header: ApduHeader { cla: 0x00, @@ -203,9 +225,9 @@ mod test { lc: 0x00, data: Vec::new(), le: 0x100, - case_type: 0x02, + case_type: ApduType::Short(Case::Le), }; - assert_eq!(expected, response.unwrap()); + assert_eq!(Ok(expected), response); } #[test] @@ -213,7 +235,6 @@ mod test { let frame: [u8; 7] = [0x00, 0xa4, 0x00, 0x0c, 0x02, 0xe1, 0x04]; let payload = [0xe1, 0x04]; let response = pass_frame(&frame); - assert!(response.is_ok()); let expected = APDU { header: ApduHeader { cla: 0x00, @@ -224,9 +245,9 @@ mod test { lc: 0x02, data: payload.to_vec(), le: 0x00, - case_type: 0x03, + case_type: ApduType::Short(Case::LcData), }; - assert_eq!(expected, response.unwrap()); + assert_eq!(Ok(expected), response); } #[test] @@ -236,7 +257,6 @@ mod test { ]; let payload = [0xd2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01]; let response = pass_frame(&frame); - assert!(response.is_ok()); let expected = APDU { header: ApduHeader { cla: 0x00, @@ -247,9 +267,9 @@ mod test { lc: 0x07, data: payload.to_vec(), le: 0xff, - case_type: 0x04, + case_type: ApduType::Short(Case::LcDataLe), }; - assert_eq!(expected, response.unwrap()); + assert_eq!(Ok(expected), response); } #[test] @@ -259,7 +279,6 @@ mod test { ]; let payload = [0xd2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01]; let response = pass_frame(&frame); - assert!(response.is_ok()); let expected = APDU { header: ApduHeader { cla: 0x00, @@ -270,17 +289,16 @@ mod test { lc: 0x07, data: payload.to_vec(), le: 0x100, - case_type: 0x04, + case_type: ApduType::Short(Case::LcDataLe), }; - assert_eq!(expected, response.unwrap()); + assert_eq!(Ok(expected), response); } #[test] fn test_invalid_apdu_header_length() { let frame: [u8; 3] = [0x00, 0x12, 0x00]; let response = pass_frame(&frame); - assert!(response.is_err()); - assert_eq!(Some(ApduStatusCode::SW_WRONG_DATA), response.err()); + assert_eq!(Err(ApduStatusCode::SW_WRONG_DATA), response); } #[test] @@ -294,10 +312,6 @@ mod test { 0xcb, 0x00, 0x00, ]; let response = pass_frame(&frame); - assert!(response.is_err()); - assert_eq!( - Some(ApduStatusCode::SW_COND_USE_NOT_SATISFIED), - response.err() - ); + assert_eq!(Err(ApduStatusCode::SW_COND_USE_NOT_SATISFIED), response); } }