mirror of
https://gitlab.com/fabinfra/fabaccess/nfc_rs.git
synced 2025-05-10 03:23:26 +02:00
added basic iso7816_4 card support
This commit is contained in:
parent
32cb8e6639
commit
8c112d519d
99
src/error.rs
99
src/error.rs
@ -1,14 +1,33 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use hex::FromHexError;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
IO(io::Error),
|
IO(io::Error),
|
||||||
Boxed(Box<dyn std::error::Error>),
|
Boxed(Box<dyn std::error::Error>),
|
||||||
|
Simple(simple_error::SimpleError),
|
||||||
BlockModeError(block_modes::BlockModeError),
|
BlockModeError(block_modes::BlockModeError),
|
||||||
InvalidKeyIvLength(block_modes::InvalidKeyIvLength),
|
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 {
|
impl fmt::Display for Error {
|
||||||
@ -16,19 +35,79 @@ impl fmt::Display for Error {
|
|||||||
match self {
|
match self {
|
||||||
Error::IO(e) => {
|
Error::IO(e) => {
|
||||||
write!(f, "IO Error: {}", e)
|
write!(f, "IO Error: {}", e)
|
||||||
},
|
}
|
||||||
Error::Boxed(e) => {
|
Error::Boxed(e) => {
|
||||||
write!(f, "{}", e)
|
write!(f, "{}", e)
|
||||||
},
|
}
|
||||||
|
Error::Simple(e) => {
|
||||||
|
write!(f, "Generic Error: {}", e)
|
||||||
|
}
|
||||||
Error::BlockModeError(e) => {
|
Error::BlockModeError(e) => {
|
||||||
write!(f, "CBC Error: {}", e)
|
write!(f, "CBC Error: {}", e)
|
||||||
},
|
}
|
||||||
Error::InvalidKeyIvLength(e) => {
|
Error::InvalidKeyIvLength(e) => {
|
||||||
write!(f, "CBC InvalidKeyIvLength Error: {}", e)
|
write!(f, "CBC InvalidKeyIvLength Error: {}", e)
|
||||||
}
|
}
|
||||||
Error::FromHexError(e) => {
|
Error::FromHexError(e) => {
|
||||||
write!(f, "Hex conversion Error: {}", 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 file’s/record’s 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 {
|
impl From<block_modes::BlockModeError> for Error {
|
||||||
fn from(e: block_modes::BlockModeError) -> Error {
|
fn from(e: block_modes::BlockModeError) -> Error {
|
||||||
Error::BlockModeError(e)
|
Error::BlockModeError(e)
|
||||||
@ -52,7 +135,11 @@ impl From<block_modes::InvalidKeyIvLength> for Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl From<hex::FromHexError> for Error {
|
impl From<hex::FromHexError> for Error {
|
||||||
fn from(e: hex::FromHexError) -> Error { Error::FromHexError(e)}
|
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>;
|
pub(crate) type Result<T> = std::result::Result<T, Error>;
|
||||||
|
261
src/iso7816_4/apducommand.rs
Normal file
261
src/iso7816_4/apducommand.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
100
src/iso7816_4/apduresponse.rs
Normal file
100
src/iso7816_4/apduresponse.rs
Normal 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) }
|
||||||
|
}
|
||||||
|
}
|
153
src/iso7816_4/apdustatuswords.rs
Normal file
153
src/iso7816_4/apdustatuswords.rs
Normal 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,
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
pub mod apducommand;
|
||||||
|
pub mod apduresponse;
|
||||||
|
pub mod apdustatuswords;
|
25
src/lib.rs
25
src/lib.rs
@ -1,4 +1,27 @@
|
|||||||
|
use crate::iso7816_4::apduresponse::APDUResponse;
|
||||||
|
use crate::iso7816_4::apducommand::APDUCommand;
|
||||||
|
use error::Result;
|
||||||
|
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod iso7816_4;
|
pub mod iso7816_4;
|
||||||
pub mod desfire;
|
pub mod desfire;
|
||||||
pub mod error;
|
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>;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user