diff --git a/Cargo.toml b/Cargo.toml index 9d24273..1a7cf8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ rand = "0.8.3" [dev-dependencies] hex-literal = "0.3.1" mockall = "0.10.2" +pcsc = "2.6.0" diff --git a/src/crypto/cipher_key.rs b/src/crypto/cipher_key.rs index 2029f3b..e0b8bb9 100644 --- a/src/crypto/cipher_key.rs +++ b/src/crypto/cipher_key.rs @@ -7,9 +7,9 @@ use crate::error::Result; use simple_error::simple_error; #[derive(Debug, Clone)] -struct CipherKey { +pub struct CipherKey { /// Key as Array - key: Box<[u8]>, + pub key: Box<[u8]>, //TODO: Decide if this should be pub /// CipherType of Key cipher: CipherType, diff --git a/src/desfire/desfire.rs b/src/desfire/desfire.rs index f4fdf5d..b7fa2d8 100644 --- a/src/desfire/desfire.rs +++ b/src/desfire/desfire.rs @@ -70,28 +70,34 @@ impl Desfire { Err(e) => { return Err(e); } } - let rnd_b_enc = response.body.unwrap().as_slice(); + let rnd_b_response_body = response.body.unwrap(); + let rnd_b_enc = rnd_b_response_body.as_slice(); + println!("RND_B_ENC: {:x?}", rnd_b_enc); - let mut rnd_b = Tdes::decrypt(rnd_b_enc, key, vec![0 as u8; 8].as_slice()).unwrap(); + let rnd_b = Tdes::decrypt(rnd_b_enc, key, vec![0 as u8; 8].as_slice()).unwrap(); + println!("RND_B: {:x?}", rnd_b); - rnd_b.rotate_left(1); - - let rnd_b_rl = rnd_b.as_slice(); + let mut rnd_b_rl = rnd_b.clone(); + rnd_b_rl.rotate_left(1); + println!("RND_B_RL: {:x?}", rnd_b_rl); let rnd_a = match rnd_a { None => { rand::random() } Some(i) => { i } }; + println!("RND_A: {:x?}", rnd_a); - let rnd_ab = [&rnd_a, rnd_b_rl].concat(); + let rnd_ab = [&rnd_a, rnd_b_rl.as_slice()].concat(); + println!("RND_AB: {:x?}", rnd_ab); let rnd_ab_enc = Tdes::encrypt(rnd_ab.as_slice(), key, rnd_b_enc).unwrap(); + println!("RND_AB_ENC: {:x?}", rnd_ab_enc); let cmd_challenge_response = APDUCommand { case: IsoCase::Case4Short, cla: 0x90, ins: APDUInstructions::CONTINUE as u8, - data: Some(rnd_ab_enc), + data: Some(rnd_ab_enc.clone()), ..Default::default() }; @@ -102,17 +108,24 @@ impl Desfire { Err(e) => { return Err(e); } } - let mut rnd_a_rot_from_card = Tdes::decrypt(response.body.unwrap().as_slice(), key, rnd_b.as_slice()).unwrap(); + let iv: &[u8] = util::extract_last_block(rnd_ab_enc.as_slice(), 8).unwrap(); + let rnd_a_enc_from_card = response.body.unwrap(); + println!("RND_A_ENC_FROM_CARD: {:x?}", rnd_a_enc_from_card.as_slice()); + let mut rnd_a_rot_from_card = Tdes::decrypt(rnd_a_enc_from_card.as_slice(), key, iv).unwrap(); + println!("RND_A_ROT_FROM_CARD: {:x?}", rnd_a_rot_from_card); rnd_a_rot_from_card.rotate_right(1); let rnd_a_from_card = rnd_a_rot_from_card.as_slice(); + println!("RND_A_FROM_CARD: {:x?}", rnd_a_from_card); if rnd_a != rnd_a_from_card { return Err(Error::InvalidPICCChallenge); } self.session_key = Some(generate_session_key_des(&rnd_a, rnd_b.as_slice()).unwrap()); + println!("SESSION_KEY: {:x?}", self.session_key.as_ref().unwrap()); self.cbc_iv = Some(vec![0 as u8; 8]); + println!("CBC_IV: {:x?}", self.cbc_iv.as_ref().unwrap()); Ok(()) } @@ -122,12 +135,12 @@ impl Desfire { /// 0x01 - 0x0D /// Array of 8/16 Bytes /// !!! WARNING For Testing only !!! - pub fn authenticate_iso_aes(&mut self, key_id: u8, key: &[u8], rnd_a: Option<[u8; 8]>) -> Result<()> { + pub fn authenticate_iso_aes(&mut self, key_id: u8, key: &[u8], rnd_a: Option<[u8; 16]>) -> Result<()> { if key_id > 0x0E { return Err(InvalidKeyID); } - let auth_iv: Vec; + // let auth_iv: Vec; let cmd_challenge_request = APDUCommand { case: IsoCase::Case4Short, @@ -144,30 +157,35 @@ impl Desfire { Err(e) => { return Err(e); } } - let rnd_b_enc = response.body.unwrap().as_slice(); + let rnd_b_response_body = response.body.unwrap(); + let rnd_b_enc = rnd_b_response_body.as_slice(); + println!("RND_B_ENC: {:x?}", rnd_b_enc); let mut rnd_b = AES::decrypt(rnd_b_enc, key, vec![0 as u8; 16].as_slice()).unwrap(); + println!("RND_B: {:x?}", rnd_b); - auth_iv = rnd_b.clone(); - + // auth_iv = rnd_b.clone(); + let rnd_b_rl = rnd_b.clone(); rnd_b.rotate_left(1); - - let rnd_b_rl = rnd_b.as_slice(); + println!("RND_B_RL: {:x?}", rnd_b_rl); let rnd_a = match rnd_a { None => { rand::random() } Some(i) => { i } }; + println!("RND_A: {:x?}", rnd_a); let rnd_ab = [&rnd_a, rnd_b_rl].concat(); + println!("RND_AB: {:x?}", rnd_ab); let rnd_ab_enc = AES::encrypt(rnd_ab.as_slice(), key, auth_iv.as_slice()).unwrap(); + println!("RND_AB_ENC: {:x?}", rnd_ab_enc); let cmd_challenge_response = APDUCommand { case: IsoCase::Case4Short, cla: 0x90, ins: APDUInstructions::CONTINUE as u8, - data: Some(rnd_ab_enc), + data: Some(rnd_ab_enc.clone()), ..Default::default() }; @@ -515,23 +533,71 @@ fn generate_file_access_rights(read: u8, write: u8, read_write: u8, configure: u #[cfg(test)] mod tests { + use std::convert::TryFrom; + use std::ffi::{CStr, CString}; + use std::fs::read; + use std::ops::Deref; use hex_literal::hex; use mockall::{mock, predicate}; - use crate::{APDUResponse, APDUCommand, Card}; + use pcsc::{Context, MAX_BUFFER_SIZE, Protocols, Scope, ShareMode, Card, Error, Disposition}; + use crate::{APDUResponse, APDUCommand, Card as CardTrait}; use crate::error::Result; + use crate::crypto::cipher_key::CipherKey; + use crate::crypto::cipher_type::CipherType; use crate::desfire::desfire::{generate_session_key_des, generate_session_key_aes, generate_keysetting1, generate_keysetting2, generate_file_access_rights, Desfire}; use crate::desfire::{ChangeApplicationKey, ChangeMasterKeySettings, CreateDeleteFile, FileDirectoryAccess, ChangeMasterKey, CryptoOperationsType, FileIdentifiers}; - use crate::error::Error::InvalidKeyID; + use crate::desfire::CryptoOperationsType::TDES; + use crate::error::Error::{InvalidKeyID, CardError}; + use crate::iso7816_4::apdustatuswords::APDUStatusWord::OK; mock! { pub VirtualCard {} - impl Card for VirtualCard { - fn connect(&self); - fn disconnect(&self); + impl CardTrait for VirtualCard { + fn connect(&mut self) -> Result<()>; + fn disconnect(&mut self) -> Result<()>; fn transmit(&self, apdu_cmd: APDUCommand) -> Result; } } + pub struct PCSCCard { + ctx: Context, + reader: CString, + card: Option, + } + + impl CardTrait for PCSCCard { + fn connect(&mut self) -> Result<()> { + self.card = match self.ctx.connect(&self.reader, ShareMode::Shared, Protocols::ANY) { + Ok(card) => Some(card), + Err(err) => { + eprintln!("Failed to connect to card: {}", err); + return Err(CardError) + } + }; + + return Ok(()) + } + + fn disconnect(&mut self) -> Result<()> { + Ok(()) + } + + fn transmit(&self, apdu_cmd: APDUCommand) -> Result { + println!("{}", apdu_cmd); + let apdu = Vec::::try_from(apdu_cmd).unwrap(); + let mut rapdu_buf = [0; MAX_BUFFER_SIZE]; + let rapdu = match self.card.as_ref().unwrap().transmit(apdu.as_slice(), &mut rapdu_buf) { + Ok(rapdu) => rapdu, + Err(err) => { + eprintln!("Failed to transmit APDU command to card: {}", err); + return Err(CardError) + } + }; + println!("APDU response: {:x?}", rapdu); + return Ok(APDUResponse::new(rapdu)) + } + } + #[test] fn generate_sessionkey_des() { let rndA = hex!("a541a9dc9138df07"); @@ -619,7 +685,7 @@ mod tests { #[test] fn select_application() { let mut mock = MockVirtualCard::new(); - mock.expect_transmit().withf(|x: APDUCommand| (x as Vec) == vec![0x90, 0x5a, 0x00, 0x00, 0x03, 0x33, 0x22, 0x11, 0x00]).return_const(Ok(APDUResponse{ + mock.expect_transmit().withf(|x: &APDUCommand| (Vec::::try_from(x.clone()).unwrap()) == vec![0x90, 0x5a, 0x00, 0x00, 0x03, 0x33, 0x22, 0x11, 0x00]).return_once(move |_| Ok(APDUResponse{ body: None, sw1: 0x91, sw2: 0x00 @@ -632,7 +698,180 @@ mod tests { }; assert!(desfire.select_application(0x112233).is_ok()) + } + #[test] + #[ignore] + fn select_application_hardware() { + // Establish a PC/SC context. + let ctx = match Context::establish(Scope::User) { + Ok(ctx) => ctx, + Err(err) => { + eprintln!("Failed to establish context: {}", err); + std::process::exit(1); + } + }; + + // List available readers. + let mut readers_buf = [0; 2048]; + let mut readers = match ctx.list_readers(&mut readers_buf) { + Ok(readers) => readers, + Err(err) => { + eprintln!("Failed to list readers: {}", err); + std::process::exit(1); + } + }; + + // Use the first reader. + let reader = match readers.next() { + Some(reader) => reader, + None => { + println!("No readers are connected."); + return; + } + }; + + let mut card = PCSCCard { + ctx, + reader: CString::from(reader), + card: None + }; + + card.connect(); + + let desfire = Desfire{ + card: Box::new(card), + cbc_iv: None, + session_key: None + }; + + assert!(desfire.select_application(0x000000).is_ok()); + } + + #[test] + fn authenticate_des() { + let mut mock = MockVirtualCard::new(); + mock.expect_transmit().withf(|x: &APDUCommand| (Vec::::try_from(x.clone()).unwrap()) == vec![0x90, 0x1a, 0x00, 0x00, 0x01, 0x00, 0x00]).return_once(move |_| Ok(APDUResponse{ + sw1: 0x91, + sw2: 0xAF, + body: Some(vec![0x2b, 0xf9, 0xa9, 0x38, 0xec, 0xca, 0x02, 0xe2]) + })); + + mock.expect_transmit().withf(|x: &APDUCommand| (Vec::::try_from(x.clone()).unwrap()) == hex!("90af000010f8cdb2eaa42a3167dfcb53852ce267fd00")).return_once(move |_| Ok(APDUResponse{ + sw1: 0x91, + sw2: 0x00, + body: Some(vec![0x07, 0xd8, 0x25, 0x60, 0x7a, 0x55, 0x2e, 0x2e]) + })); + + let rndA = hex!("5f7d1dd12d979173"); + let mut key = CipherKey::new_empty(CipherType::TDES).unwrap(); + println!("{:x?}", key.key.deref()); + + let mut desfire = Desfire{ + card: Box::new(mock), + cbc_iv: None, + session_key: None + }; + + desfire.authenticate_iso_des(0x00, key.key.as_ref(), Some(rndA)).unwrap(); + + let sessionkey_expected = hex!("5f7d1dd1f449db5c5f7d1dd1f449db5c"); + let iv_expected = hex!("0000000000000000"); + + assert_eq!(desfire.session_key.unwrap(), sessionkey_expected); + assert_eq!(desfire.cbc_iv.unwrap(), iv_expected); + + } + + #[test] + #[ignore] + fn authenticate_des_hardware() { + // Establish a PC/SC context. + let ctx = match Context::establish(Scope::User) { + Ok(ctx) => ctx, + Err(err) => { + eprintln!("Failed to establish context: {}", err); + std::process::exit(1); + } + }; + + // List available readers. + let mut readers_buf = [0; 2048]; + let mut readers = match ctx.list_readers(&mut readers_buf) { + Ok(readers) => readers, + Err(err) => { + eprintln!("Failed to list readers: {}", err); + std::process::exit(1); + } + }; + + // Use the first reader. + let reader = match readers.next() { + Some(reader) => reader, + None => { + println!("No readers are connected."); + return; + } + }; + + let mut card = PCSCCard { + ctx, + reader: CString::from(reader), + card: None + }; + + card.connect(); + + let mut desfire = Desfire{ + card: Box::new(card), + cbc_iv: None, + session_key: None + }; + + desfire.select_application(0x000000); + + let mut key = CipherKey::new_empty(CipherType::TDES).unwrap(); + match desfire.authenticate_iso_des(0x00, key.key.as_ref(), None) { + Ok(_) => {}, + Err(err) => { + eprintln!("Failed to authenticate: {}", err); + panic!("Failed to authenticate"); + } + } + } + + #[test] + fn authenticate_aes() { + let mut mock = MockVirtualCard::new(); + mock.expect_transmit().withf(|x: &APDUCommand| (Vec::::try_from(x.clone()).unwrap()) == hex!("90aa0000010000")).return_once(move |_| Ok(APDUResponse{ + sw1: 0x91, + sw2: 0xAF, + body: Some(hex!("a33856932308775cf464610c2b17a558").to_vec()) + })); + + mock.expect_transmit().withf(|x: &APDUCommand| (Vec::::try_from(x.clone()).unwrap()) == hex!("90af000020cbe9726faf54bc76b2055d0b9700e7dc97ecad5627f1d1702a16e8408d2a0ada00")).return_once(move |_| Ok(APDUResponse{ + sw1: 0x91, + sw2: 0x00, + body: Some(hex!("8fdc476f6bac44fe9150e285abd68d48").to_vec()) + })); + + let rndA = hex!("2176770e7a6eb4bef00d5e4b201d1e57"); + let mut key = CipherKey::new_empty(CipherType::AES).unwrap(); + println!("{:x?}", key.key.deref()); + + let mut desfire = Desfire{ + card: Box::new(mock), + cbc_iv: None, + session_key: None + }; + + desfire.authenticate_iso_aes(0x00, key.key.as_ref(), Some(rndA)).unwrap(); + + let sessionkey_expected = hex!("5f7d1dd1f449db5c5f7d1dd1f449db5c"); + let iv_expected = hex!("0000000000000000"); + + assert_eq!(desfire.session_key.unwrap(), sessionkey_expected); + assert_eq!(desfire.cbc_iv.unwrap(), iv_expected); } } \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 1c63d12..9bf9424 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,12 +24,14 @@ pub enum Error { DuplicateError, FileNotFound, InvalidApplicationID, + ApplicationNotFound, InvalidAPDUResponse, InvalidIsoCase, InvalidKeyID, InvalidPICCChallenge, InvalidFileID, NumKeys, + CardError, } impl fmt::Display for Error { @@ -116,6 +118,12 @@ impl fmt::Display for Error { Error::NumKeys => { write!(f, "Number of Keys was larger then 0x0D") } + Error::CardError => { + write!(f, "An error occurred during communication with the card.") + } + Error::ApplicationNotFound => { + write!(f, "The application was not found on the card.") + } } } } diff --git a/src/iso7816_4/apducommand.rs b/src/iso7816_4/apducommand.rs index 23f750f..398a374 100644 --- a/src/iso7816_4/apducommand.rs +++ b/src/iso7816_4/apducommand.rs @@ -4,7 +4,7 @@ use std::convert::TryFrom; use crate::error::{Result, Error}; #[repr(u8)] -#[derive(Eq, PartialEq, Hash, Debug)] +#[derive(Eq, PartialEq, Hash, Debug, Clone)] pub enum IsoCase { /// No command data. No response data. /// @@ -82,7 +82,7 @@ impl Default for IsoCase { } #[repr(u8)] -#[derive(Eq, PartialEq, Hash, Debug)] +#[derive(Eq, PartialEq, Hash, Debug, Clone)] pub enum SCardProtocol { /// /// protocol not defined. @@ -106,10 +106,10 @@ pub enum SCardProtocol { } impl Default for SCardProtocol { - fn default() -> Self { SCardProtocol::Unset } + fn default() -> Self { SCardProtocol::Any } } -#[derive(Eq, PartialEq, Hash, Debug, Default)] +#[derive(Eq, PartialEq, Hash, Debug, Default, Clone)] pub struct APDUCommand { pub case: IsoCase, pub protocol: SCardProtocol, @@ -118,8 +118,7 @@ pub struct APDUCommand { pub p1: u8, pub p2: u8, pub data: Option>, - pub lc: i32, - pub le: i32, + pub le: usize, } impl TryFrom for Vec { @@ -144,11 +143,11 @@ impl TryFrom for Vec { v.push(cmd.le as u8); } IsoCase::Case3Short => { - v.push(cmd.lc as u8); + v.push(cmd.lc() as u8); v.extend(cmd.data.unwrap()); } IsoCase::Case4Short => { - v.push(cmd.lc as u8); + v.push(cmd.data.as_ref().unwrap().len() as u8); v.extend(cmd.data.unwrap()); if cmd.protocol == SCardProtocol::T0 { v.push(0x00); @@ -157,6 +156,7 @@ impl TryFrom for Vec { } } _ => { + return Err(Error::InvalidIsoCase) } } @@ -166,11 +166,15 @@ impl TryFrom for Vec { //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 APDUCommand { + // pub fn new(case: IsoCase) -> Self { + // APDUCommand { case, protocol: (), cla: (), ins: (), p1: (), p2: (), data: (), lc: (), le: () } + // } + + pub fn lc(&self) -> usize { + self.data.as_ref().unwrap().len() + } +} impl fmt::Display for APDUCommand { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { @@ -182,14 +186,14 @@ impl fmt::Display for APDUCommand { 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)?; + 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)?; + 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)?; } @@ -286,7 +290,7 @@ mod tests { }; 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()); + assert_eq!("(CASE: 3) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LC: 0x3 | data: [ 0x1 0x2 0x3 ]", command1.to_string()); } #[test] @@ -300,6 +304,6 @@ mod tests { }; 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()); + assert_eq!("(CASE: 4) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LC: 0x3 | data: [ 0x1 0x2 0x3 ] | LE: 0x0", command1.to_string()); } } \ No newline at end of file diff --git a/src/iso7816_4/apduresponse.rs b/src/iso7816_4/apduresponse.rs index 9e212b6..515fac7 100644 --- a/src/iso7816_4/apduresponse.rs +++ b/src/iso7816_4/apduresponse.rs @@ -1,7 +1,7 @@ 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 crate::error::Error::{InvalidStatusWord, IllegalCommandCode, IntegrityError, NoSuchKey, LengthError, PermissionDenied, ParameterError, AuthenticationDelay, AuthenticationError, BoundaryError, CommandAborted, DuplicateError, FileNotFound, ApplicationNotFound}; use std::fmt; #[derive(Eq, PartialEq, Hash, Debug, Default)] @@ -32,7 +32,7 @@ impl From for Vec { impl fmt::Display for APDUResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.body { + match &self.body { None => { write!(f, "SW1: {:#X} | SW2: 0x{:#X}", self.sw1, self.sw2) }, @@ -42,13 +42,22 @@ impl fmt::Display for APDUResponse { } } } - impl APDUResponse { pub fn new(raw: &[u8]) -> Self { - APDUResponse { - body: Some(raw[..raw.len() - 1].to_vec()), - sw1: raw[raw.len() - 2], - sw2: raw[raw.len() - 3] + if raw.len() < 2 { + panic!("APDU response must be at least 2 bytes long"); + } else if raw.len() == 2 { + APDUResponse { + body: None, + sw1: raw[0], + sw2: raw[1], + } + } else { + APDUResponse { + body: Some(raw[..raw.len() - 2].to_vec()), + sw1: raw[raw.len() - 2], + sw2: raw[raw.len() - 1], + } } } @@ -96,6 +105,7 @@ impl APDUResponse { Some(APDUStatusWord2::CommandAborted) => {Err(CommandAborted)} Some(APDUStatusWord2::DuplicateError) => {Err(DuplicateError)} Some(APDUStatusWord2::FileNotFound) => {Err(FileNotFound)} + Some(APDUStatusWord2::ApplicationNotFound) => {Err(ApplicationNotFound)} None => { Err(InvalidStatusWord)} } } else { Err(InvalidStatusWord) } diff --git a/src/iso7816_4/apdustatuswords.rs b/src/iso7816_4/apdustatuswords.rs index d2c627f..8442134 100644 --- a/src/iso7816_4/apdustatuswords.rs +++ b/src/iso7816_4/apdustatuswords.rs @@ -150,4 +150,5 @@ pub enum APDUStatusWord2 { CommandAborted = 0xCA, DuplicateError = 0xDE, FileNotFound = 0xF0, + ApplicationNotFound = 0xA0, } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index d5daeb3..1c4671c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,16 +7,16 @@ pub mod iso7816_4; pub mod desfire; pub mod error; -pub trait Card { +pub trait Card { /// /// Connect to Smartcard /// -fn connect(&self); +fn connect(&mut self) -> Result<()>; /// /// Disconnect from Smartcard /// -fn disconnect(&self); +fn disconnect(&mut self) -> Result<()>; /// /// Transmit APDU Command to Smartcard