diff --git a/Cargo.toml b/Cargo.toml index 5bacea4..eb178aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iso7816" -version = "0.1.0-alpha.0" +version = "0.1.0-alpha.1" authors = ["Nicolas Stalder "] edition = "2018" repository = "https://github.com/ycrypto/iso7816" @@ -12,5 +12,7 @@ documentation = "https://docs.rs/iso7816" [dependencies] delog = "0.1.2" -heapless = "0.6" -heapless-bytes = "0.2.0" +heapless = "0.7" + +[dev-dependencies] +hex-literal = "0.3.1" diff --git a/src/aid.rs b/src/aid.rs index 9e110de..a9b5225 100644 --- a/src/aid.rs +++ b/src/aid.rs @@ -1,7 +1,4 @@ -// use core::convert::TryInto; - -// // 7816-4, 8.2.1.2 -// pub type Aid = crate::Bytes; +// use crate::{Command, Interface, Response, Result}; /// Constant panicking assertion. // TODO(tarcieri): use const panic when stable. @@ -16,7 +13,9 @@ macro_rules! const_assert { /// ISO 7816-4 Application identifier pub struct Aid { /// Array containing the AID (padded with zeros) - bytes: [u8; Self::max_len()], + /// + /// Does not use heapless as its Vec is not `Copy`. + bytes: [u8; Self::MAX_LEN], /// Length in bytes len: u8, @@ -25,6 +24,20 @@ pub struct Aid { truncated_len: u8, } +#[derive(Copy, Clone, Eq, Hash, PartialEq)] +pub enum Category { + /// International registration of application providers according to ISO/IEC 7816-5 + International, + /// National (ISO 3166-1) registration of application providers according to ISO/IEC 7816-5 + National, + /// Identification of a standard by an object identifier according to ISO/IEC 8825-1 + Standard, + /// No registration of application providers + Proprietary, + /// 0-9 are reserved for backwards compatibility, B-C are RFU. + Other, +} + impl core::fmt::Debug for Aid { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { if self.len <= self.truncated_len { @@ -40,13 +53,22 @@ impl core::fmt::Debug for Aid { } } -#[allow(non_snake_case)] -pub const fn Aid(aid: &[u8], truncated_len: usize) -> Aid { - Aid::new(aid, truncated_len) -} +/// According to ISO 7816-4, "Application selection using AID as DF name": +/// A multi-application card shall support the SELECT command with P1='04', P2='00' and a data field +/// containing 5 to 16 bytes with the AID of an application that may reside on the card. +/// The command shall complete successfully if the AID of an application the card holds matches the data field. +/// +/// It is also specified that: +/// In a multi-application card an application in the card shall be identified by +///  a single AID in the proprietary, national or international category, and/or +///  one or more AIDs in the standard category. pub trait App { + // using an associated constant here would make the trait object unsafe fn aid(&self) -> Aid; +// fn select_via_aid(&mut self, interface: Interface, aid: Aid) -> Result<()>; +// fn deselect(&mut self) -> Result<()>; +// fn call(&mut self, interface: Interface, command: &Command, response: &mut Response) -> Result<()>; } impl core::ops::Deref for Aid { @@ -57,10 +79,7 @@ impl core::ops::Deref for Aid { } impl Aid { - /// Maximum length of an AID. - pub const fn max_len() -> usize { - 16 - } + const MAX_LEN: usize = 16; pub fn as_bytes(&self) -> &[u8] { &self.bytes[..self.len as usize] @@ -74,25 +93,21 @@ impl Aid { aid.starts_with(self.truncated()) } - pub const fn new(aid: &[u8], truncated_len: usize) -> Self { - const_assert!(!aid.is_empty(), "AID needs at least a category identifier"); - const_assert!(aid.len() <= Self::max_len(), "AID too long"); - const_assert!(truncated_len <= aid.len(), "truncated length too long"); - let mut s = Self { bytes: [0u8; Self::max_len()], len: aid.len() as u8, truncated_len: truncated_len as u8 }; - s = s.fill(aid, 0); - const_assert!(!s.national() || aid.len() >= 5, "National RID must have length 5"); - const_assert!(!s.international() || aid.len() >= 5, "International RID must have length 5"); - s + pub const fn new(aid: &[u8]) -> Self { + Self::new_truncatable(aid, aid.len()) } // pub fn try_new(aid: &[u8], truncated_len: u8) -> Result { - // if aid.len() > Self::max_len() { - // return Err(()); - // } - // if truncated_len > aid.len() { - // return Err(()); - // } - // } + pub const fn new_truncatable(aid: &[u8], truncated_len: usize) -> Self { + const_assert!(!aid.is_empty(), "AID needs at least a category identifier"); + const_assert!(aid.len() <= Self::MAX_LEN, "AID too long"); + const_assert!(truncated_len <= aid.len(), "truncated length too long"); + let mut s = Self { bytes: [0u8; Self::MAX_LEN], len: aid.len() as u8, truncated_len: truncated_len as u8 }; + s = s.fill(aid, 0); + const_assert!(!s.is_national() || aid.len() >= 5, "National RID must have length 5"); + const_assert!(!s.is_international() || aid.len() >= 5, "International RID must have length 5"); + s + } // workaround to copy in the aid while remaining "const" // maybe there is a better way? @@ -106,41 +121,58 @@ impl Aid { } } - pub const fn international(&self) -> bool { - (self.bytes[0] >> 4) == b'A' + pub const fn category(&self) -> Category { + match self.bytes[0] >> 4 { + b'A' => Category::International, + b'D' => Category::National, + b'E' => Category::Standard, + b'F' => Category::Proprietary, + _ => Category::Other, + } + } + pub const fn is_international(&self) -> bool { + // This is not "const" yet. + // self.category() == Category::International + match self.category() { + Category::International => true, + _ => false, + } } - pub const fn national(&self) -> bool { - (self.bytes[0] >> 4) == b'D' + pub const fn is_national(&self) -> bool { + match self.category() { + Category::National => true, + _ => false, + } } - pub const fn standard(&self) -> bool { - (self.bytes[0] >> 4) == b'E' + pub const fn is_standard(&self) -> bool { + match self.category() { + Category::Standard => true, + _ => false, + } } - pub const fn proprietary(&self) -> bool { - (self.bytes[0] >> 4) == b'F' + pub const fn is_proprietary(&self) -> bool { + match self.category() { + Category::Proprietary => true, + _ => false, + } + } + + const fn has_rid_pix(&self) -> bool { + self.is_national() || self.is_international() } // pub fn rid(&self) -> &[u8; 5] { /// International or national registered application provider identifier, 5 bytes. pub fn rid(&self) -> Option<&[u8]> { - if self.national() || self.international() { - Some(&self.bytes[..5]) - } else { - // "RID not defined" - None - } + self.has_rid_pix().then(|| &self.bytes[..5]) } /// Proprietary application identifier extension, up to 11 bytes. pub fn pix(&self) -> Option<&[u8]> { - if self.national() || self.international() { - Some(&self.bytes[5..]) - } else { - // "PIX not defined" - None - } + self.has_rid_pix().then(|| &self.bytes[5..]) } } @@ -148,12 +180,15 @@ impl Aid { #[cfg(test)] mod test { use super::Aid; + use hex_literal::hex; #[allow(dead_code)] - const PIV_AID: Aid = Aid::new(&hex_literal::hex!("A000000308 00001000 0100"), 11); + const PIV_AID: Aid = Aid::new_truncatable(&hex!("A000000308 00001000 0100"), 9); #[test] - fn non_const_aid() { - let aid = Aid::new(&hex_literal::hex!("A000000308 00001000 0100"), 11); + fn aid() { + let piv_aid = Aid::new(&hex!("A000000308 00001000 0100")); + assert!(piv_aid.matches(&*PIV_AID)); + assert!(PIV_AID.matches(&*piv_aid)); // panics // let aid = Aid::new(&hex_literal::hex!("A000000308 00001000 01001232323333333333333332")); } diff --git a/src/command.rs b/src/command.rs index 2a62e20..f00c614 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,30 +1,29 @@ -use heapless_bytes::ArrayLength; +use core::convert::TryFrom; + +use crate::Data; pub mod class; pub mod instruction; - -use crate::Bytes; +pub use instruction::Instruction; #[derive(Clone, Debug, PartialEq, Eq)] -pub struct Command -where SIZE: ArrayLength +pub struct Command { class: class::Class, - instruction: instruction::Instruction, + instruction: Instruction, pub p1: u8, pub p2: u8, /// The main reason this is modeled as Bytes and not /// a fixed array is for serde purposes. - data: Bytes, + data: Data, le: usize, pub extended: bool, } -impl Command -where SIZE: ArrayLength +impl Command { pub fn try_from(apdu: &[u8]) -> Result { use core::convert::TryInto; @@ -35,15 +34,15 @@ where SIZE: ArrayLength self.class } - pub fn instruction(&self) -> instruction::Instruction { + pub fn instruction(&self) -> Instruction { self.instruction } - pub fn data(&self) -> &Bytes { + pub fn data(&self) -> &Data { &self.data } - pub fn data_mut(&mut self) -> &mut Bytes{ + pub fn data_mut(&mut self) -> &mut Data { &mut self.data } @@ -54,7 +53,7 @@ where SIZE: ArrayLength /// This can be use for APDU chaining to convert /// multiple APDU's into one. /// * Global Platform GPC_SPE_055 3.10 - pub fn extend_from_command(&mut self, command: &Command>) -> core::result::Result<(),()> { + pub fn extend_from_command(&mut self, command: &Command) -> core::result::Result<(), ()> { // Always take the header from the last command; self.class = command.class(); @@ -72,6 +71,7 @@ where SIZE: ArrayLength #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum FromSliceError { TooShort, + TooLong, InvalidClass, InvalidFirstBodyByteForExtended, InvalidSliceLength, @@ -83,8 +83,7 @@ impl From for FromSliceError { } } -impl core::convert::TryFrom<&[u8]> for Command -where SIZE: ArrayLength +impl TryFrom<&[u8]> for Command { type Error = FromSliceError; fn try_from(apdu: &[u8]) -> core::result::Result { @@ -95,7 +94,7 @@ where SIZE: ArrayLength println!("{}", apdu.len()); let (header, body) = apdu.split_at(4); let class = class::Class::try_from(header[0])?; - let instruction = instruction::Instruction::from(header[1]); + let instruction = Instruction::from(header[1]); let p1 = header[2]; let p2 = header[3]; let parsed = parse_lengths(body)?; @@ -107,7 +106,8 @@ where SIZE: ArrayLength // maximum expected response length le: parsed.le, // payload - data: Bytes::try_from_slice(data_slice).unwrap(), + data: Data::from_slice(data_slice) + .map_err(|_| Self::Error::TooLong)?, extended: parsed.extended, }) } @@ -248,8 +248,7 @@ mod test { 0xbe, 0x1e, 0x2c, 0x69, 0x1d, 0xc3, 0x53, 0x4c, 0x89, 0x14, 0xa3, 0x12, 0x30, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, ]; - // let apdu = b"\x10\xdb?\xff\xff\\\x03_\xc1\x05S\x82\x01Zp\x82\x01Q0\x82\x01M0\x81\xf4\xa0\x03\x02\x01\x02\x02\x10\x19\x185\xd2i\xcb\x0b\xf9\xcc\x07)*\xb5QLq0\n\x06\x08*\x86"; - let command = Command::try_from(apdu).unwrap(); + let command = Command::<256>::try_from(apdu).unwrap(); } } diff --git a/src/lib.rs b/src/lib.rs index c72bec4..8139d0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,8 @@ #![cfg_attr(not(test), no_std)] -// #![no_std] #[macro_use] extern crate delog; -generate_macros!(); - -pub use heapless_bytes::Bytes as Bytes; - -// // 7816-4, 8.2.1.2 -// pub type Aid = Bytes; +// generate_macros!(); #[derive(Copy, Clone, PartialEq)] pub enum Interface { @@ -16,14 +10,13 @@ pub enum Interface { Contactless, } -pub type Result = core::result::Result; +pub type Data = heapless::Vec; +pub type Result = core::result::Result; pub mod aid; pub mod command; pub mod response; pub use aid::{Aid, App}; -pub use command::Command; -pub use command::instruction::Instruction; -pub use response::{Data, Response}; -pub use response::status::Status; +pub use command::{Command, Instruction}; +pub use response::{Response, Status}; diff --git a/src/response.rs b/src/response.rs index a43a79d..c7da6ef 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,20 +1,15 @@ -use heapless_bytes::ArrayLength; - -pub mod status; +mod status; pub use status::Status; -pub type Data = crate::Bytes; +use crate::Data; #[derive(Clone, Debug, Eq, PartialEq)] -pub enum Response -where SIZE: ArrayLength -{ - Data(Data), +pub enum Response { + Data(Data), Status(Status), } -impl Default for Response -where SIZE: ArrayLength { +impl Default for Response { fn default() -> Self { Self::Status(Default::default()) } diff --git a/src/response/status.rs b/src/response/status.rs index 44d412a..153ae4f 100644 --- a/src/response/status.rs +++ b/src/response/status.rs @@ -1,4 +1,5 @@ use core::convert::TryFrom; +use crate::Data; impl Default for Status { fn default() -> Self { @@ -170,13 +171,12 @@ impl Into<[u8; 2]> for Status { } } -impl Into> for Status -where S: heapless_bytes::ArrayLength +impl Into> for Status { #[inline] - fn into(self) -> heapless_bytes::Bytes { + fn into(self) -> Data { let arr: [u8; 2] = self.into(); - heapless_bytes::Bytes::try_from_slice(&arr).unwrap() + Data::from_slice(&arr).unwrap() } }