mirror of
https://github.com/trussed-dev/iso7816.git
synced 2026-06-20 04:16:14 -07:00
Rename crypto-service -> trussed. Introduce iso7816 component
This commit is contained in:
+16
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user