added basic iso7816_4 card support

This commit is contained in:
Kai Kriegel 2021-04-27 00:37:58 +02:00
parent 32cb8e6639
commit 8c112d519d
6 changed files with 634 additions and 7 deletions

View File

@ -1,14 +1,33 @@
use std::io;
use std::fmt;
use hex::FromHexError;
#[derive(Debug)]
pub enum Error {
IO(io::Error),
Boxed(Box<dyn std::error::Error>),
Simple(simple_error::SimpleError),
BlockModeError(block_modes::BlockModeError),
InvalidKeyIvLength(block_modes::InvalidKeyIvLength),
FromHexError(hex::FromHexError)
FromHexError(hex::FromHexError),
PadError(block_modes::block_padding::PadError),
InvalidStatusWord,
IllegalCommandCode,
IntegrityError,
NoSuchKey,
LengthError,
PermissionDenied,
ParameterError,
AuthenticationDelay,
AuthenticationError,
BoundaryError,
CommandAborted,
DuplicateError,
FileNotFound,
InvalidApplicationID,
InvalidAPDUResponse,
InvalidKeyID,
InvalidPICCChallenge,
InvalidFileID,
}
impl fmt::Display for Error {
@ -16,19 +35,79 @@ impl fmt::Display for Error {
match self {
Error::IO(e) => {
write!(f, "IO Error: {}", e)
},
}
Error::Boxed(e) => {
write!(f, "{}", e)
},
}
Error::Simple(e) => {
write!(f, "Generic Error: {}", e)
}
Error::BlockModeError(e) => {
write!(f, "CBC Error: {}", e)
},
}
Error::InvalidKeyIvLength(e) => {
write!(f, "CBC InvalidKeyIvLength Error: {}", e)
}
Error::FromHexError(e) => {
write!(f, "Hex conversion Error: {}", e)
}
Error::PadError(e) => {
write!(f, "Padding to blocksize failed: {:#?}", e)
}
Error::InvalidStatusWord => {
write!(f, "Invalid APDU Statusword")
}
Error::IllegalCommandCode => {
write!(f, "SW2: Command code not supported.")
}
Error::IntegrityError => {
write!(f, "SW2: CRC or MAC does not match data. Paddingbytes not valid.")
}
Error::NoSuchKey => {
write!(f, "SW2: Invalid key number specified.")
}
Error::LengthError => {
write!(f, "SW2: Length of command string invalid.")
}
Error::PermissionDenied => {
write!(f, "SW2: Current configuration / status does not allow the requested command.")
}
Error::ParameterError => {
write!(f, "SW2: Value of the parameter(s) invalid.")
}
Error::AuthenticationDelay => {
write!(f, "SW2: Currently not allowed to authenticate. Keeptrying until full delay is spent.")
}
Error::AuthenticationError => {
write!(f, "SW2: Current authentication status does not allow the requested command.")
}
Error::BoundaryError => {
write!(f, "SW2: Attempt to read/write data from/to beyond the files/records limits. Attempt to exceed the limits of a value file.")
}
Error::CommandAborted => {
write!(f, "SW2: Previous Command was not fully completed.Not all Frames were requested or provided by the PCD.")
}
Error::DuplicateError => {
write!(f, "SW2: Creation of file/application failed because file/application with same number already exists")
}
Error::FileNotFound => {
write!(f, "SW2: Specified file number does not exist.")
}
Error::InvalidApplicationID => {
write!(f, "Application ID was larger then 24 bit")
}
Error::InvalidAPDUResponse => {
write!(f, "Got malformed APDUResponse")
}
Error::InvalidKeyID => {
write!(f, "Invalid KeyID: Was larger then 0x0E")
}
Error::InvalidPICCChallenge => {
write!(f, "Authentication failed, PICC Challenge is invalid.")
}
Error::InvalidFileID => {
write!(f, "Invalid FileID: Was larger then 0x20")
}
}
}
}
@ -39,6 +118,10 @@ impl From<io::Error> for Error {
}
}
impl From<simple_error::SimpleError> for Error {
fn from(e: simple_error::SimpleError) -> Error { Error::Simple(e) }
}
impl From<block_modes::BlockModeError> for Error {
fn from(e: block_modes::BlockModeError) -> Error {
Error::BlockModeError(e)
@ -55,4 +138,8 @@ impl From<hex::FromHexError> for Error {
fn from(e: hex::FromHexError) -> Error { Error::FromHexError(e) }
}
impl From<block_modes::block_padding::PadError> for Error {
fn from(e: block_modes::block_padding::PadError) -> Error { Error::PadError(e) }
}
pub(crate) type Result<T> = std::result::Result<T, Error>;

View File

@ -0,0 +1,261 @@
use std::fmt;
use std::fmt::Formatter;
#[repr(u8)]
#[derive(Eq, PartialEq, Hash, Debug)]
pub enum IsoCase {
/// <summary>No command data. No response data.</summary>
/// <remarks>
/// <list type="bullet">
/// <item><description>lc is valued to 0.</description></item>
/// <item><description>le is valued to 0.</description></item>
/// <item><description>No data byte is present.</description></item>
/// </list>
/// </remarks>
Case1 = 0,
/// <summary>No command data. Expected response data.</summary>
/// <remarks>
/// <list type="bullet">
/// <item><description>lc is valued to 0.</description></item>
/// <item><description>le is valued from 1 to 256.</description></item>
/// <item><description>No data byte is present.</description></item>
/// </list>
/// </remarks>
Case2Short = 1,
/// <summary>Command data. No response data.</summary>
/// <remarks>
/// <list type="bullet">
/// <item><description>lc is valued from 1 to 255.</description></item>
/// <item><description>le is valued to 0.</description></item>
/// <item><description>lc data bytes are present.</description></item>
/// </list>
/// </remarks>
Case3Short = 2,
/// <summary>Command data. Expected response data.</summary>
/// <remarks>
/// <list type="bullet">
/// <item><description>lc is valued from 1 to 255.</description></item>
/// <item><description>le is valued from 1 to 256.</description></item>
/// <item><description>lc data bytes are present.</description></item>
/// </list>
/// </remarks>
Case4Short = 3,
/// <summary>No command data. Expected response data.</summary>
/// <remarks>
/// <list type="bullet">
/// <item><description>lc is valued to 0.</description></item>
/// <item><description>le is valued from 1 to 65536.</description></item>
/// <item><description>No data byte is present.</description></item>
/// </list>
/// </remarks>
Case2Extended = 4,
/// <summary>Command data. No response data.</summary>
/// <remarks>
/// <list type="bullet">
/// <item><description>lc is valued from 1 to 65536.</description></item>
/// <item><description>le is valued to 0.</description></item>
/// <item><description>lc data bytes are present.</description></item>
/// </list>
/// </remarks>
Case3Extended = 5,
/// <summary>Command data. Expected response data.</summary>
/// <remarks>
/// <list type="bullet">
/// <item><description>lc is valued from 1 to 65535.</description></item>
/// <item><description>le is valued from 1 to 65536.</description></item>
/// <item><description>lc data bytes are present.</description></item>
/// </list>
/// </remarks>
Case4Extended = 6,
}
impl Default for IsoCase {
fn default() -> Self { IsoCase::Case1 }
}
#[repr(u8)]
#[derive(Eq, PartialEq, Hash, Debug)]
pub enum SCardProtocol {
/// <summary>
/// protocol not defined.
/// </summary>
Unset = 0x0000,
/// <summary>T=0 active protocol.</summary>
T0 = 0x0001,
/// <summary>T=1 active protocol.</summary>
T1 = 0x0002,
/// <summary>Raw active protocol. Use with memory type cards.</summary>
Raw = 0x0004,
/// <summary>T=15 protocol.</summary>
T15 = 0x0008,
/// <summary>(<see cref="SCardProtocol.T0" /> | <see cref="SCardProtocol.T1" />). IFD (Interface device) determines protocol.</summary>
Any = (0x0001 | 0x0002),
}
impl Default for SCardProtocol {
fn default() -> Self { SCardProtocol::Unset }
}
#[derive(Eq, PartialEq, Hash, Debug, Default)]
pub struct APDUCommand {
pub case: IsoCase,
pub protocol: SCardProtocol,
pub cla: u8,
pub ins: u8,
pub p1: u8,
pub p2: u8,
pub data: Option<Vec<u8>>,
pub lc: i32,
pub le: i32,
}
//FIXME: Do we want pub fields or constructor?
// We want pub fields! + constructor?
// impl APDUCommand {
// pub fn new(case: IsoCase) -> Self {
// APDUCommand { case, protocol: (), cla: (), ins: (), p1: (), p2: (), data: (), lc: (), le: () }
// }
// }
impl fmt::Display for APDUCommand {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.case {
IsoCase::Case1 => {
write!(f, "(CASE: 1) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X}", self.cla, self.ins, self.p1, self.p2)
}
IsoCase::Case2Short | IsoCase::Case2Extended => {
write!(f, "(CASE: 2) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LE: {:#X}", self.cla, self.ins, self.p1, self.p2, self.le)
}
IsoCase::Case3Short | IsoCase::Case3Extended => {
write!(f, "(CASE: 3) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LC: {:#X} | data: [ ", self.cla, self.ins, self.p1, self.p2, self.lc)?;
for b in self.data.as_ref().unwrap() {
write!(f, "{:#X} ", b)?;
}
write!(f, "]")
}
IsoCase::Case4Short | IsoCase::Case4Extended => {
write!(f, "(CASE: 4) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LC: {:#X} | data: [ ", self.cla, self.ins, self.p1, self.p2, self.lc)?;
for b in self.data.as_ref().unwrap() {
write!(f, "{:#X} ", b)?;
}
write!(f, "] | LE: {:#X}", self.le)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::iso7816_4::apducommand::{APDUCommand, SCardProtocol, IsoCase};
#[test]
fn compare() {
let command1 = APDUCommand{
case: IsoCase::Case4Short,
protocol: SCardProtocol::Unset,
cla: 0x90,
ins: 0xAA,
data: Some(vec![0x01, 0x02, 0x03]),
..Default::default()
};
let command2 = APDUCommand{
case: IsoCase::Case4Short,
protocol: SCardProtocol::Unset,
cla: 0x90,
ins: 0xAA,
data: Some(vec![0x01, 0x02, 0x03]),
..Default::default()
};
assert_eq!(command1, command2);
}
#[test]
fn compare_diff() {
let command1 = APDUCommand{
case: IsoCase::Case4Short,
protocol: SCardProtocol::Unset,
cla: 0x90,
ins: 0xAA,
data: Some(vec![0x01, 0x02, 0x03]),
..Default::default()
};
let command2 = APDUCommand{
case: IsoCase::Case4Short,
protocol: SCardProtocol::Unset,
cla: 0x90,
ins: 0x1A,
data: Some(vec![0x01, 0x02, 0x03]),
..Default::default()
};
assert_ne!(command1, command2);
}
#[test]
fn to_string_case1() {
let command1 = APDUCommand{
case: IsoCase::Case1,
cla: 0x90,
ins: 0x1A,
..Default::default()
};
println!("{}", command1.to_string());
assert_eq!("(CASE: 1) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0", command1.to_string());
}
#[test]
fn to_string_case2() {
let command1 = APDUCommand{
case: IsoCase::Case2Short,
cla: 0x90,
ins: 0x1A,
..Default::default()
};
println!("{}", command1.to_string());
assert_eq!("(CASE: 2) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LE: 0x0", command1.to_string());
}
#[test]
fn to_string_case3() {
let command1 = APDUCommand{
case: IsoCase::Case3Short,
cla: 0x90,
ins: 0x1A,
data: Some(vec![0x01, 0x02, 0x03]),
..Default::default()
};
println!("{}", command1.to_string());
assert_eq!("(CASE: 3) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LC: 0x0 | data: [ 0x1 0x2 0x3 ]", command1.to_string());
}
#[test]
fn to_string_case4() {
let command1 = APDUCommand{
case: IsoCase::Case4Short,
cla: 0x90,
ins: 0x1A,
data: Some(vec![0x01, 0x02, 0x03]),
..Default::default()
};
println!("{}", command1.to_string());
assert_eq!("(CASE: 4) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LC: 0x0 | data: [ 0x1 0x2 0x3 ] | LE: 0x0", command1.to_string());
}
}

View File

@ -0,0 +1,100 @@
use crate::iso7816_4::apdustatuswords::{APDUStatusWord, APDUStatusWord2};
use crate::error::{Result};
use num_traits::FromPrimitive;
use crate::error::Error::{InvalidStatusWord, IllegalCommandCode, IntegrityError, NoSuchKey, LengthError, PermissionDenied, ParameterError, AuthenticationDelay, AuthenticationError, BoundaryError, CommandAborted, DuplicateError, FileNotFound};
use std::fmt;
#[derive(Eq, PartialEq, Hash, Debug)]
pub struct APDUResponse {
pub body: Vec<u8>,
pub sw1: u8,
pub sw2: u8,
}
impl APDUResponse {
pub fn body(&self) -> &Vec<u8> {
&self.body
}
}
impl From<APDUResponse> for Vec<u8> {
fn from(resp: APDUResponse) -> Self {
let mut v: Vec<u8> = vec![];
v.extend(resp.body);
v.push(resp.sw1);
v.push(resp.sw2);
v
}
}
impl fmt::Display for APDUResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.body.is_empty() {
true => {
write!(f, "SW1: {:#X} | SW2: 0x{:#X}", self.sw1, self.sw2)
},
false => {
write!(f, "SW1: {:#X} | SW2: {:#X} | Body: {:#X?}", self.sw1, self.sw2, self.body)
}
}
}
}
impl APDUResponse {
pub fn new(raw: &[u8]) -> Self {
APDUResponse {
body: raw[..raw.len() - 1].to_vec(),
sw1: raw[raw.len() - 2],
sw2: raw[raw.len() - 3]
}
}
pub fn get_statusword(&self) -> Result<APDUStatusWord> {
match self.sw1 {
0x61 => Ok(APDUStatusWord::DataReady),
0x62 => Ok(APDUStatusWord::StorageNotChanged),
0x63 => {
if (self.sw2 & 0xF0) == 0xC0 { Ok(APDUStatusWord::CounterReached) } else { Ok(APDUStatusWord::StorageChanged) }
},
0x64 => Ok(APDUStatusWord::ExecutionErrorWithoutChange),
0x65 => Ok(APDUStatusWord::ExecutionErrorWithChange),
0x6C => Ok(APDUStatusWord::InvalidLe),
_ => FromPrimitive::from_u16(((self.sw1 as u16) << 8) | self.sw2 as u16).ok_or(InvalidStatusWord)
}
}
pub fn get_data_length(&self) -> u8 {
self.sw2
}
pub fn get_counter(&self) -> u8 {
self.sw2 & 0x0F
}
pub fn get_correct_le(&self) -> u8 {
self.sw2
}
pub fn check(&self) -> Result<()> {
if self.sw1 == 0x91 {
return match FromPrimitive::from_u8(self.sw2) {
Some(APDUStatusWord2::OperationOk) => { Ok(()) }
Some(APDUStatusWord2::NoChanges) => { Ok(()) }
Some(APDUStatusWord2::IllegalCommandCode) => {Err(IllegalCommandCode)}
Some(APDUStatusWord2::IntegrityError) => {Err(IntegrityError)}
Some(APDUStatusWord2::NoSuchKey) => {Err(NoSuchKey)}
Some(APDUStatusWord2::LengthError) => {Err(LengthError)}
Some(APDUStatusWord2::PermissionDenied) => {Err(PermissionDenied)}
Some(APDUStatusWord2::ParameterError) => {Err(ParameterError)}
Some(APDUStatusWord2::AuthenticationDelay) => {Err(AuthenticationDelay)}
Some(APDUStatusWord2::AuthenticationError) => {Err(AuthenticationError)}
Some(APDUStatusWord2::AdditionalFrame) => {Ok(())}
Some(APDUStatusWord2::BoundaryError) => {Err(BoundaryError)}
Some(APDUStatusWord2::CommandAborted) => {Err(CommandAborted)}
Some(APDUStatusWord2::DuplicateError) => {Err(DuplicateError)}
Some(APDUStatusWord2::FileNotFound) => {Err(FileNotFound)}
None => { Err(InvalidStatusWord)}
}
} else { Err(InvalidStatusWord) }
}
}

View File

@ -0,0 +1,153 @@
use num_derive::FromPrimitive;
#[derive(FromPrimitive)]
#[repr(u16)]
pub enum APDUStatusWord {
/// Kommando erfolgreich ausgefhrt. xx Datenbytes knnen mit dem GET RESPONSE-Kommando abgeholt werden. Statuswort zur Steuerung des T=0-Protokolls
DataReady = 0x6100,
/// Die zurckgegebenen Daten knnen fehlerhaft sein.
FaultyData = 0x6281,
/// Da das Dateiende vorher erreicht wurde, konnten nur weniger als Le Bytes gelesen werden.
UnexpectedEndOfFile = 0x6282,
/// Die ausgewhlte Datei ist gesperrt (englisch invalidated, wrtlich ungltig).
InvalidatedFile = 0x6283,
/// Die File Control Information (FCI) ist inkonform zu ISO 7816-4.
FciNotConform = 0x6284,
/// Warnung; Zustand des nichtflchtigen Speichers nicht verndert
StorageNotChanged = 0x6200,
/// Zhler hat den Wert x erreicht (die genaue Bedeutung ist vom Kommando abhngig)
CounterReached = 0x63C0,
/// Warnung; Zustand des nichtflchtigen Speichers verndert
StorageChanged = 0x6300,
/// Ausfhrungsfehler; Zustand des nichtflchtigen Speichers nicht verndert
ExecutionErrorWithoutChange = 0x6400,
/// Speicherfehler
MemoryError = 0x6581,
/// Ausfhrungsfehler; Zustand des nichtflchtigen Speichers verndert
ExecutionErrorWithChange = 0x6500,
/// Befehlslnge (Lc) oder erwartete Antwortlnge (Le) falsch
InvalidLcLe = 0x6700,
/// Funktionen im Class-Byte werden nicht untersttzt
ClassFeatureNotSupported = 0x6800,
/// Logische Kanle werden nicht untersttzt
LogicChannelNotSupported = 0x6881,
/// Secure Messaging wird nicht untersttzt
SecureMessagingNotSupported = 0x6882,
/// Kommando nicht erlaubt
CommandNotAllowed = 0x6900,
/// Kommando inkompatibel zur Dateistruktur
CommandIncompatible = 0x6981,
/// Sicherheitszustand nicht erfllt
SafetyStatusNotFulfilled = 0x6982,
/// Authentisierungsmethode ist gesperrt
AuthenticationMethodLocked = 0x6983,
/// Referenzierte Daten sind gesperrt
ReferencedFileLocked = 0x6984,
/// Nutzungsbedingungen sind nicht erfllt
TermsOfServiceNotFulfilled = 0x6985,
/// Kommando nicht erlaubt (kein EF selektiert)
CommandNotAllowedNoEfSelected = 0x6986,
/// Erwartete Secure-Messaging-Objekte nicht gefunden
ExpectedSecureMessagingObjectsNotFound = 0x6987,
/// Secure-Messaging-Datenobjekte sind inkorrekt
InvalidSecureMessagingObjects = 0x6988,
/// Falsche Parameter P1/P2
WrongParameters = 0x6A00,
/// Falsche Daten
WrongData = 0x6A80,
/// Funktion wird nicht untersttzt
FeatureNotSupported = 0x6A81,
/// Datei wurde nicht gefunden
FileNotFound = 0x6A82,
/// Datensatz (engl. record) der Datei nicht gefunden
RecordNotFound = 0x6A83,
/// Nicht gengend Speicherplatz in der Datei
InsufficientSpace = 0x6A84,
/// Lc nicht konsistent mit der TLV-Struktur
LcTlvInconsistent = 0x6A85,
/// Inkorrekte Parameter P1/P2
IncorrectParameters = 0x6A86,
/// Lc inkonsistent mit P1/P2
LcParametersInconsistent = 0x6A87,
/// Referenzierte Daten nicht gefunden
ReferencedFileNotFound = 0x6A88,
/// Parameter P1/P2 falsch
WrongParameters2 = 0x6B00,
/// Falsche Lnge Le; xx gibt die korrekte Lnge an Statuswort zur Steuerung des T=0-Protokolls
InvalidLe = 0x6C00,
/// Das Kommando (INS) wird nicht untersttzt
InstructionNotSupported = 0x6D00,
/// Die Kommandoklasse (CLA) wird nicht untersttzt
ClassNotSupported = 0x6E00,
/// Kommando wurde mit unbekanntem Fehler abgebrochen
UnknownError = 0x6F00,
/// Kommando erfolgreich ausgefhrt
SUCCESS = 0x9000,
/// OK
OK = 0x9100,
}
#[derive(FromPrimitive)]
#[repr(u8)]
pub enum APDUStatusWord2 {
OperationOk = 0x00,
NoChanges = 0x0C,
IllegalCommandCode = 0x1C,
IntegrityError = 0x1E,
NoSuchKey = 0x40,
LengthError = 0x7E,
PermissionDenied = 0x9D,
ParameterError = 0x9E,
AuthenticationDelay = 0xAD,
AuthenticationError = 0xAE,
AdditionalFrame = 0xAF,
BoundaryError = 0xBE,
CommandAborted = 0xCA,
DuplicateError = 0xDE,
FileNotFound = 0xF0,
}

View File

@ -0,0 +1,3 @@
pub mod apducommand;
pub mod apduresponse;
pub mod apdustatuswords;

View File

@ -1,4 +1,27 @@
use crate::iso7816_4::apduresponse::APDUResponse;
use crate::iso7816_4::apducommand::APDUCommand;
use error::Result;
pub mod crypto;
pub mod iso7816_4;
pub mod desfire;
pub mod error;
pub trait Card {
/// <summary>
/// Connect to Smartcard
/// </summary>
fn connect(&self);
/// <summary>
/// Disconnect from Smartcard
/// </summary>
fn disconnect(&self);
/// <summary>
/// Transmit APDU Command to Smartcard
/// </summary>
/// <param name="apdu_cmd">Application Protocol Data Unit Command - ISO 7816</param>
/// <returns>Application Protocol Data Unit Response - ISO 7816</returns>
fn transmit(&self, apdu_cmd: APDUCommand) -> Result<APDUResponse>;
}