From 94f548d5c53195c08df5ec59ce26867060050861 Mon Sep 17 00:00:00 2001 From: Kamran Khan Date: Mon, 30 Nov 2020 14:35:01 -0800 Subject: [PATCH] Add extended APDU parser --- src/ctap/apdu.rs | 143 +++++++++++++++++++++++++++++++++++++++++------ src/main.rs | 2 +- 2 files changed, 127 insertions(+), 18 deletions(-) diff --git a/src/ctap/apdu.rs b/src/ctap/apdu.rs index fd03f2a..a761564 100644 --- a/src/ctap/apdu.rs +++ b/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); } } diff --git a/src/main.rs b/src/main.rs index ca10c3a..2ca12a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,9 +18,9 @@ extern crate alloc; #[cfg(feature = "std")] extern crate core; extern crate lang_items; - #[macro_use] extern crate arrayref; +extern crate byteorder; mod ctap; pub mod embedded_flash;