Rename crypto-service -> trussed. Introduce iso7816 component

This commit is contained in:
Nicolas Stalder
2020-05-14 18:10:23 +02:00
commit 6789cbcbda
7 changed files with 634 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "iso7816"
version = "0.1.0"
authors = ["Nicolas Stalder <n@stalder.io>"]
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" }
+186
View File
@@ -0,0 +1,186 @@
pub mod class;
pub mod instruction;
pub type Data = heapless_bytes::Bytes<crate::MAX_COMMAND_DATA>;
#[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<Self, FromSliceError> {
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<class::InvalidClass> 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<Self, Self::Error> {
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<ParsedLengths, FromSliceError> {
// 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)
}
+168
View File
@@ -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<u8> {
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<u8> for Class {
type Error = InvalidClass;
#[inline]
fn try_from(cla: u8) -> Result<Self, Self::Error> {
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<u8> for Range {
type Error = InvalidClass;
#[inline]
fn try_from(cla: u8) -> Result<Self, Self::Error> {
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)
}
}
+54
View File
@@ -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<u8> 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<u8> for Instruction {
// type Error = UnknownInstruction;
// fn try_from(ins: u8) -> Result<Self, Self::Error> {
// 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)
// }
// }
+15
View File
@@ -0,0 +1,15 @@
// #![cfg_attr(not(test), no_std)]
#![no_std]
#[allow(non_camel_case_types)]
pub type U3076 = <heapless_bytes::consts::U2048 as core::ops::Add<heapless_bytes::consts::U1024>>::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;
+78
View File
@@ -0,0 +1,78 @@
pub mod status;
pub use status::Status;
pub type Data = heapless_bytes::Bytes<crate::MAX_COMMAND_DATA>;
#[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<Data, Status>;
impl From<Result> for Response {
fn from(result: Result) -> Self {
match result {
Ok(data) => Self::Data(data),
Err(status) => Self::Status(status)
}
}
}
impl Into<Result> 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<Result> 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
}
}
+117
View File
@@ -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<u16> 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()
}
}