From 6789cbcbda61ba23c7459cebf2bcbe5397562ccd Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Thu, 14 May 2020 18:10:23 +0200 Subject: [PATCH] Rename crypto-service -> trussed. Introduce iso7816 component --- Cargo.toml | 16 ++++ src/command.rs | 186 +++++++++++++++++++++++++++++++++++++ src/command/class.rs | 168 +++++++++++++++++++++++++++++++++ src/command/instruction.rs | 54 +++++++++++ src/lib.rs | 15 +++ src/response.rs | 78 ++++++++++++++++ src/response/status.rs | 117 +++++++++++++++++++++++ 7 files changed, 634 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/command.rs create mode 100644 src/command/class.rs create mode 100644 src/command/instruction.rs create mode 100644 src/lib.rs create mode 100644 src/response.rs create mode 100644 src/response/status.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..599e41f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "iso7816" +version = "0.1.0" +authors = ["Nicolas Stalder "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +heapless = "0.5.5" +heapless-bytes = { git = "https://github.com/ycrypto/heapless-bytes", branch = "main" } + +[patch.crates-io] +ufmt = { git = "https://github.com/nickray/ufmt", branch = "nickray-derive-empty-enums" } +ufmt-macros = { git = "https://github.com/nickray/ufmt", branch = "nickray-derive-empty-enums" } +heapless = { git = "https://github.com/nickray/heapless", branch = "nickray-udebug" } \ No newline at end of file diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..ddd1c89 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,186 @@ +pub mod class; +pub mod instruction; + +pub type Data = heapless_bytes::Bytes; + +#[derive(Clone, Debug, PartialEq)] +pub struct Command { + class: class::Class, + instruction: instruction::Instruction, + + pub p1: u8, + pub p2: u8, + + /// The main reason this is modeled as ByteBuf and not + /// a fixed array is for serde purposes. + data: Data, + + le: usize, + pub extended: bool, +} + +impl Command { + pub fn try_from(apdu: &[u8]) -> Result { + use core::convert::TryInto; + apdu.try_into() + } + + pub fn class(&self) -> class::Class { + self.class + } + + pub fn instruction(&self) -> instruction::Instruction { + self.instruction + } + + pub fn data(&self) -> &Data { + &self.data + } + + pub fn expected(&self) -> usize { + self.le + } + + // pub fn instruction(&self) -> class::Class { + // } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum FromSliceError { + TooShort, + InvalidClass, + InvalidFirstBodyByteForExtended, + CanThisReallyOccur, +} + +impl From for FromSliceError { + fn from(_: class::InvalidClass) -> Self { + Self::InvalidClass + } +} + +impl core::convert::TryFrom<&[u8]> for Command { + type Error = FromSliceError; + fn try_from(apdu: &[u8]) -> core::result::Result { + if apdu.len() < 4 { + return Err(FromSliceError::TooShort); + } + let (header, body) = apdu.split_at(4); + let class = class::Class::try_from(header[0])?; + let instruction = instruction::Instruction::from(header[1]); + let parsed = parse_lengths(body)?; + let data_slice = &body[parsed.offset..][..parsed.lc]; + + Ok(Self { + class, + instruction, + p1: header[2], + p2: header[3], + le: parsed.le, + data: Data::try_from_slice(data_slice).unwrap(), + extended: parsed.extended, + }) + } +} + +// cf. ISO 7816-3, 12.1.3: Decoding conventions for command APDUs +// freely available version: +// http://www.ttfn.net/techno/smartcards/iso7816_4.html#table5 + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +struct ParsedLengths { + lc: usize, + le: usize, + offset: usize, + extended: bool, +} + +#[inline(always)] +fn replace_zero(value: usize, replacement: usize) -> usize { + if value == 0 { + replacement + } else { + value + } +} +#[inline] +fn parse_lengths(body: &[u8]) -> Result { + + // Encoding rules: + // - Lc or Le = 0 => leave out + // - short + extended length fields shall not be combined + // - for extended, if Lc > 0, then Le has no leading zero byte + + let l = body.len(); + + let mut parsed: ParsedLengths = Default::default(); + + // Case 1 + if l == 0 { + return Ok(parsed); + } + + // the reference starts indexing at 1 + let b1 = body[0] as usize; + + // Case 2S + if l == 1 { + parsed.lc = 0; + parsed.le = replace_zero(b1, 256); + return Ok(parsed) + } + + // Case 3S + if l == 1 + b1 && b1 != 0 { + // B1 encodes Lc valued from 1 to 255 + parsed.lc = b1; + parsed.le = 0; + return Ok(parsed); + } + + // Case 4S + if l == 2 + b1 && b1 != 0 { + // B1 encodes Lc valued from 1 to 255 + // Bl encodes Le from 1 to 256 + parsed.lc = b1; + parsed.le = replace_zero(body[l - 1] as usize + 1, 256); + parsed.offset = 1; + return Ok(parsed); + } + + parsed.extended = true; + + // only extended cases left now + if b1 != 0 { + return Err(FromSliceError::InvalidFirstBodyByteForExtended); + }; + + // Case 2E (no data) + if l == 3 && b1 == 0 { + parsed.lc = 0; + parsed.le = replace_zero( + u16::from_be_bytes([body[1], body[2]]) as usize, + 65_536); + return Ok(parsed); + } + + parsed.lc = u16::from_be_bytes([body[1], body[2]]) as usize; + + // Case 3E + if l == 3 + parsed.lc { + parsed.le = 0; + parsed.offset = 3; + return Ok(parsed); + } + + // Case 4E + if l == 5 + parsed.lc { + parsed.le = replace_zero( + u16::from_be_bytes([body[l - 2], body[l - 1]]) as usize, + 65_536); + parsed.offset = 3; + return Ok(parsed); + } + + Err(FromSliceError::CanThisReallyOccur) +} diff --git a/src/command/class.rs b/src/command/class.rs new file mode 100644 index 0000000..8c7bfc2 --- /dev/null +++ b/src/command/class.rs @@ -0,0 +1,168 @@ +// There are four ranges: +// First Interindustry 0b000x_xxxx +// Further Interindustry 0b01xx_xxxx +// Reserved 0b001x_xxxx +// Proprietary 0b1xxx_xxxx +// +// For the interindustry ranges, class contains: +// - chaining (continues/last) +// - secure messaging indication (none, two standard, proprietary) +// - logical channel number + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Class { + cla: u8, + range: Range, + // secure_messaging: SecureMessaging, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum SecureMessaging { + None = 0, + Proprietary = 1, + Standard = 2, + Authenticated = 3, + Unknown, +} + +impl SecureMessaging { + pub fn none(&self) -> bool { + *self == SecureMessaging::None + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Chain { + LastOrOnly, + NotTheLast, + Unknown, +} + +impl Chain { + #[inline] + pub fn last_or_only(&self) -> bool { + *self == Chain::LastOrOnly + } +} + +impl Class { + #[inline] + pub fn into_inner(self) -> u8 { + self.cla + } + + #[inline] + pub fn range(&self) -> Range { + self.range + } + + #[inline] + pub fn secure_messaging(&self) -> SecureMessaging { + match self.range { + Range::Interindustry(which) => match which { + Interindustry::First => { + match (self.cla >> 2) & 0b11 { + 0b00 => SecureMessaging::None, + 0b01 => SecureMessaging::Proprietary, + 0b10 => SecureMessaging::Standard, + 0b11 => SecureMessaging::Authenticated, + _ => unreachable!(), + } + }, + Interindustry::Further => { + match (self.cla >> 5) != 0 { + true => SecureMessaging::Standard, + false => SecureMessaging::None, + } + } + Interindustry::Reserved => SecureMessaging::Unknown, + } + _ => SecureMessaging::Unknown, + } + } + + #[inline] + pub fn chain(&self) -> Chain { + match self.range { + Range::Interindustry(which) => match which { + Interindustry::First | Interindustry::Further => { + if self.cla & (1 << 4) != 0 { + Chain::NotTheLast + } else { + Chain::LastOrOnly + } + } + _ => Chain::Unknown, + } + _ => Chain::Unknown, + } + } + + #[inline] + pub fn channel(&self) -> Option { + Some(match self.range() { + Range::Interindustry(Interindustry::First) => { + self.cla & 0b11 + } + Range::Interindustry(Interindustry::Further) => { + 4 + self.cla & 0b111 + } + _ => return None + }) + } + + +} + +impl core::convert::TryFrom for Class { + type Error = InvalidClass; + + #[inline] + fn try_from(cla: u8) -> Result { + let range = Range::try_from(cla)?; + Ok(Self { cla, range }) + } +} + +// impl core::ops::Deref for Class { +// type Target = u8; +// fn deref(&self) -> &Self::Target { +// &self.cla +// } +// } + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Range { + Interindustry(Interindustry), + Proprietary, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Interindustry { + First, + Further, + Reserved, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct InvalidClass {} + +impl core::convert::TryFrom for Range { + type Error = InvalidClass; + + #[inline] + fn try_from(cla: u8) -> Result { + if cla == 0xff { + return Err(InvalidClass {}) + } + + let range = match cla >> 5 { + 0b000 => Range::Interindustry(Interindustry::First), + 0b010 | 0b011 => Range::Interindustry(Interindustry::Further), + 0b001 => Range::Interindustry(Interindustry::Reserved), + _ => Range::Proprietary, + }; + + Ok(range) + } +} diff --git a/src/command/instruction.rs b/src/command/instruction.rs new file mode 100644 index 0000000..7869713 --- /dev/null +++ b/src/command/instruction.rs @@ -0,0 +1,54 @@ +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Instruction { + Select, + GetData, + Verify, + ChangeReferenceData, + ResetRetryCounter, + GeneralAuthenticate, + PutData, + GenerateAsymmetricKeyPair, + GetResponse, + Unknown(u8), +} + +pub struct UnknownInstruction {} + +impl core::convert::From for Instruction { + fn from(ins: u8) -> Self { + match ins { + 0x20 => Instruction::Verify, + 0x24 => Instruction::ChangeReferenceData, + 0x2c => Instruction::ResetRetryCounter, + 0x47 => Instruction::GenerateAsymmetricKeyPair, + 0x87 => Instruction::GeneralAuthenticate, + 0xa4 => Instruction::Select, + 0xc0 => Instruction::GetResponse, + 0xcb => Instruction::GetData, + 0xdb => Instruction::PutData, + ins => Instruction::Unknown(ins), + } + } +} + +// impl core::convert::TryFrom for Instruction { +// type Error = UnknownInstruction; + +// fn try_from(ins: u8) -> Result { +// let instruction = match ins { +// 0x20 => Instruction::Verify, +// 0x24 => Instruction::ChangeReferenceData, +// 0x2c => Instruction::ResetRetryCounter, +// 0x47 => Instruction::GenerateAsymmetricKeyPair, +// 0x87 => Instruction::GeneralAuthenticate, +// 0xa4 => Instruction::Select, +// 0xc0 => Instruction::GetResponse, +// 0xcb => Instruction::GetData, +// 0xdb => Instruction::PutData, +// _ => return Instruction::Unknown(ins), +// Err(UnknownInstruction {}) +// }; + +// Ok(instruction) +// } +// } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6b2abe3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,15 @@ +// #![cfg_attr(not(test), no_std)] +#![no_std] + +#[allow(non_camel_case_types)] +pub type U3076 = >::Output; +#[allow(non_camel_case_types)] +pub type MAX_COMMAND_DATA = U3076; + +pub mod command; +pub mod response; + +pub use command::Command; +pub use command::instruction::Instruction; +pub use response::Response; +pub use response::status::Status; \ No newline at end of file diff --git a/src/response.rs b/src/response.rs new file mode 100644 index 0000000..cb36cac --- /dev/null +++ b/src/response.rs @@ -0,0 +1,78 @@ +pub mod status; +pub use status::Status; + +pub type Data = heapless_bytes::Bytes; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Response { + Data(Data), + Status(Status), +} + +impl Default for Response { + fn default() -> Self { + Self::Status(Default::default()) + } +} + +pub type Result = core::result::Result; + +impl From for Response { + fn from(result: Result) -> Self { + match result { + Ok(data) => Self::Data(data), + Err(status) => Self::Status(status) + } + } +} + +impl Into for Response { + fn into(self) -> Result { + match self { + Self::Data(data) => Ok(data), + Self::Status(status) => Err(status), + } + } +} + +// #[derive(Clone, Debug, Default, Eq, PartialEq)] +// pub struct Response { +// pub status: Status, +// pub data: Data, +// } + +// impl From for Response { +// fn from(result: Result) -> Self { +// match result { +// Ok(data) => { +// Response { +// status: Default::default(), +// data, +// } +// } +// Err(status) => { +// Response { +// status, +// data: Default::default(), +// } +// } +// } +// } +// } + +impl Response { + pub fn into_message(&self) -> Data { + let mut message = Data::new(); + let status = match self { + Self::Data(data) => { + message.extend_from_slice(&data).unwrap(); + Status::default() + } + Self::Status(status) => *status, + }; + + let status_bytes: [u8; 2] = status.into(); + message.extend_from_slice(&status_bytes).unwrap(); + message + } +} diff --git a/src/response/status.rs b/src/response/status.rs new file mode 100644 index 0000000..4e89341 --- /dev/null +++ b/src/response/status.rs @@ -0,0 +1,117 @@ +impl Default for Status { + fn default() -> Self { + Status::Success + } +} + +// ISO/IEC 7816-4, 5.1.3 "Status bytes" +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Status { + +////////////////////////////// +// Normal processing (90, 61) +////////////////////////////// + + /// 9000 + Success, + + /// 61XX + MoreAvailable(u8), + +/////////////////////////////// +// Warning processing (62, 63) +/////////////////////////////// + + // 62XX: state of non-volatile memory unchanged (cf. SW2) + + // 63XX: state of non-volatile memory changed (cf. SW2) + VerificationFailed, + FailedRetries(u8), + +//////////////////////////////// +// Execution error (64, 65, 66) +//////////////////////////////// + + // 64XX: persistent memory unchanged (cf. SW2) + // 65XX: persistent memory changed (cf. SW2) + // 66XX: security related issues + +/////////////////////////////// +// Checking error (67 - 6F) +/////////////////////////////// + + // 6700: wrong length, no further indication + + // 68XX: functions in CLA not supported (cf. SW2) + LogicalChannelNotSupported, + SecureMessagingNotSupported, + CommandChainingNotSupported, + + // 69xx: command not allowed (cf. SW2) + SecurityStatusNotSatisfied, + OperationBlocked, + + // 6Axx: wrong parameters P1-P2 (cf. SW2) + IncorrectDataParameter, + FunctionNotSupported, + NotFound, + NotEnoughMemory, + IncorrectP1OrP2Parameter, + KeyReferenceNotFound, + + // 6BXX: wrong parameters P1-P2 + + // 6CXX: wrong Le field, SW2 encodes available bytes + + // 6D00: instruction code not supported or invalid + InstructionNotSupportedOrInvalid, + + // 6E00: class not supported + ClassNotSupported, + + // 6F00: no precise diagnosis + UnspecifiedCheckingError, +} + +impl Into for Status { + #[inline] + fn into(self) -> u16 { + match self { + Self::VerificationFailed => 0x6300, + Self::FailedRetries(x) => { + assert!(x < 16); + u16::from_be_bytes([0x63, 0xc0 + x]) + } + + Self::LogicalChannelNotSupported => 0x6881, + Self::SecureMessagingNotSupported => 0x6882, + Self::CommandChainingNotSupported => 0x6884, + + Self::SecurityStatusNotSatisfied => 0x6982, + Self::OperationBlocked => 0x6983, + + Self::IncorrectDataParameter => 0x6a80, + Self::FunctionNotSupported => 0x6a81, + Self::NotFound => 0x6a82, + Self::NotEnoughMemory => 0x6a84, + Self::IncorrectP1OrP2Parameter => 0x6a86, + Self::KeyReferenceNotFound => 0x6a88, + + Self::InstructionNotSupportedOrInvalid => 0x6d00, + Self::ClassNotSupported => 0x6e00, + Self::UnspecifiedCheckingError => 0x6f00, + + Self::Success => 0x9000, + Self::MoreAvailable(x) => u16::from_be_bytes([0x61, x]), + } + } +} + +impl Into<[u8; 2]> for Status { + #[inline] + fn into(self) -> [u8; 2] { + let sw: u16 = self.into(); + sw.to_be_bytes() + } +} +