diff --git a/Cargo.toml b/Cargo.toml index d2ffc2e..9d24273 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ rand = "0.8.3" [dev-dependencies] hex-literal = "0.3.1" +mockall = "0.10.2" diff --git a/src/desfire/desfire.rs b/src/desfire/desfire.rs index 53a3974..f4fdf5d 100644 --- a/src/desfire/desfire.rs +++ b/src/desfire/desfire.rs @@ -6,15 +6,17 @@ use crate::crypto::cipher::aes::AES; use crate::crypto::cipher::Cipher; use crate::crypto::util; use crate::error::{Result, Error}; -use crate::error::Error::{InvalidApplicationID, InvalidKeyID, InvalidFileID}; +use crate::error::Error::{InvalidApplicationID, InvalidKeyID, InvalidFileID, NumKeys}; use crate::crypto::util::expand_to_blocksize; -use crate::desfire::FileCommunication; +use crate::desfire::{FileCommunication, ChangeMasterKeySettings, CreateDeleteFile, FileDirectoryAccess, ChangeMasterKey, ChangeApplicationKey, CryptoOperationsType, FileIdentifiers}; use crate::iso7816_4::apduresponse::APDUResponse; +use num_traits::FromPrimitive; +use std::fs::read_to_string; pub struct Desfire { card: Box, - session_key: Vec, - cbc_iv: Vec, + session_key: Option>, + cbc_iv: Option>, } impl Desfire { @@ -68,7 +70,7 @@ impl Desfire { Err(e) => { return Err(e); } } - let rnd_b_enc = response.body.as_slice(); + let rnd_b_enc = response.body.unwrap().as_slice(); let mut rnd_b = Tdes::decrypt(rnd_b_enc, key, vec![0 as u8; 8].as_slice()).unwrap(); @@ -100,7 +102,7 @@ impl Desfire { Err(e) => { return Err(e); } } - let mut rnd_a_rot_from_card = Tdes::decrypt(response.body.as_slice(), key, rnd_b.as_slice()).unwrap(); + let mut rnd_a_rot_from_card = Tdes::decrypt(response.body.unwrap().as_slice(), key, rnd_b.as_slice()).unwrap(); rnd_a_rot_from_card.rotate_right(1); let rnd_a_from_card = rnd_a_rot_from_card.as_slice(); @@ -108,9 +110,85 @@ impl Desfire { return Err(Error::InvalidPICCChallenge); } - self.session_key = generate_session_key_des(&rnd_a, rnd_b.as_slice()).unwrap(); + self.session_key = Some(generate_session_key_des(&rnd_a, rnd_b.as_slice()).unwrap()); - self.cbc_iv = vec![0 as u8; 8]; + self.cbc_iv = Some(vec![0 as u8; 8]); + Ok(()) + } + + /// + /// Authenticate to PICC, with ISO Authenticate for AES Key + /// + /// 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<()> { + if key_id > 0x0E { + return Err(InvalidKeyID); + } + + let auth_iv: Vec; + + let cmd_challenge_request = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::AUTHENTICATE_AES as u8, + data: Option::from(vec!(key_id)), + ..Default::default() + }; + + let response = self.card.transmit(cmd_challenge_request).unwrap(); + + match response.check() { + Ok(_) => {} + Err(e) => { return Err(e); } + } + + let rnd_b_enc = response.body.unwrap().as_slice(); + + let mut rnd_b = AES::decrypt(rnd_b_enc, key, vec![0 as u8; 16].as_slice()).unwrap(); + + auth_iv = rnd_b.clone(); + + rnd_b.rotate_left(1); + + let rnd_b_rl = rnd_b.as_slice(); + + let rnd_a = match rnd_a { + None => { rand::random() } + Some(i) => { i } + }; + + let rnd_ab = [&rnd_a, rnd_b_rl].concat(); + + let rnd_ab_enc = AES::encrypt(rnd_ab.as_slice(), key, auth_iv.as_slice()).unwrap(); + + let cmd_challenge_response = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::CONTINUE as u8, + data: Some(rnd_ab_enc), + ..Default::default() + }; + + let response = self.card.transmit(cmd_challenge_response).unwrap(); + + match response.check() { + Ok(_) => {} + Err(e) => { return Err(e); } + } + + let mut rnd_a_rot_from_card = AES::decrypt(response.body.unwrap().as_slice(), key, auth_iv.as_slice()).unwrap(); + rnd_a_rot_from_card.rotate_right(1); + let rnd_a_from_card = rnd_a_rot_from_card.as_slice(); + + if rnd_a != rnd_a_from_card { + return Err(Error::InvalidPICCChallenge); + } + + self.session_key = Some(generate_session_key_aes(&rnd_a, rnd_b.as_slice()).unwrap()); + + self.cbc_iv = Some(vec![0 as u8; 16]); Ok(()) } @@ -132,9 +210,9 @@ impl Desfire { } /// - /// Create Application for ApplicationID - /// - /// 3 Byte ID + /// Create Application for ApplicationID + /// + /// 3 Byte ID fn create_application(&self, aid: u32, keysetting1: u8, keysetting2: u8) -> Result<()> { if aid > 0xFFFFFF { return Err(InvalidApplicationID); @@ -182,9 +260,9 @@ impl Desfire { let plaintext_pad = expand_to_blocksize(plaintext.as_mut_slice(), 16)?; - let cryptogram = AES::encrypt(plaintext_pad, self.session_key.as_slice(), self.cbc_iv.as_slice())?; + let cryptogram = AES::encrypt(plaintext_pad, self.session_key.as_ref().unwrap(), self.cbc_iv.as_ref().unwrap())?; - self.cbc_iv = util::extract_last_block(cryptogram.as_slice(), 16)?.to_vec(); + self.cbc_iv = Some(util::extract_last_block(cryptogram.as_slice(), 16)?.to_vec()); let mut data: Vec = vec![key_id]; data.extend(cryptogram); @@ -203,12 +281,12 @@ impl Desfire { } /// - /// Change AES key, other than Authenticated - /// - /// 0x01 - 0x0D - /// Array of 16 Bytes - /// Array of 16 Bytes - /// Version of Key(min. 0x10) + /// Change AES key, other than Authenticated + /// + /// 0x01 - 0x0D + /// Array of 16 Bytes + /// Array of 16 Bytes + /// Version of Key(min. 0x10) fn change_other_key_aes(&mut self, key_id: u8, new_key: &[u8], old_key: &[u8], key_version: u8) -> Result<()> { if key_id >= 0x0E { return Err(InvalidKeyID); @@ -216,7 +294,7 @@ impl Desfire { let header = vec![0xC4, key_id]; - let key_xor : Vec = new_key.iter().zip(old_key.iter()).map(|(&x1, &x2)| x1 ^ x2).collect(); + let key_xor: Vec = new_key.iter().zip(old_key.iter()).map(|(&x1, &x2)| x1 ^ x2).collect(); let key_and_version: Vec = [key_xor, vec![key_version]].concat(); let mut command = vec![]; @@ -232,9 +310,9 @@ impl Desfire { let plaintext_pad = expand_to_blocksize(plaintext.as_mut_slice(), 16)?; - let cryptogram = AES::encrypt(plaintext_pad, self.session_key.as_slice(), self.cbc_iv.as_slice())?; + let cryptogram = AES::encrypt(plaintext_pad, self.session_key.as_ref().unwrap(), self.cbc_iv.as_ref().unwrap())?; - self.cbc_iv = util::extract_last_block(cryptogram.as_slice(), 16)?.to_vec(); + self.cbc_iv = Some(util::extract_last_block(cryptogram.as_slice(), 16)?.to_vec()); let mut data: Vec = vec![key_id]; data.extend(cryptogram); @@ -252,9 +330,9 @@ impl Desfire { response.check() } - fn create_file_standard(&self, file_id: u8, communication: FileCommunication, access_rights: u16, size: u32) -> Result<()>{ + fn create_file_standard(&self, file_id: u8, communication: FileCommunication, access_rights: u16, size: u32) -> Result<()> { if file_id >= 0x20 { - return Err(InvalidFileID) + return Err(InvalidFileID); } let access_rights = access_rights.to_le_bytes().to_vec(); @@ -277,20 +355,20 @@ impl Desfire { } /// - /// Write Data to File - /// - /// ID of File (0x00 - 0x20) - /// Offset for File - /// Data to write + /// Write Data to File + /// + /// ID of File (0x00 - 0x20) + /// Offset for File + /// Data to write fn write_data(&self, file_id: u8, offset: u32, data: &[u8]) -> Result<()> { if file_id >= 0x20 { - return Err(InvalidFileID) + return Err(InvalidFileID); } const MAX_BYTES_PER_TRANSACTION: usize = 47; let mut bytes_writen: usize = 0; let length: usize = data.len(); - + let mut ret: Result<()> = Err(error::Error::Simple(simple_error::simple_error!("Error while writing data to card"))); //TODO: Replace with better error while bytes_writen != data.len() { @@ -317,19 +395,19 @@ impl Desfire { ret = response.check(); }; - - return ret + + return ret; } /// - /// Read Data from File - /// - /// ID of File (0x00 - 0x20) - /// Offset for File - /// Lenght of Data + /// Read Data from File + /// + /// ID of File (0x00 - 0x20) + /// Offset for File + /// Lenght of Data fn read_data(&self, file_id: u8, offset: u32, length: usize) -> Result> { if file_id >= 0x20 { - return Err(InvalidFileID) + return Err(InvalidFileID); } const MAX_BYTES_PER_TRANSACTION: usize = 47; @@ -360,29 +438,201 @@ impl Desfire { response.check().or_else(|e| return Err(e)); - read_buffer.append(&mut response.body()[..bytes_toread].to_vec()); + read_buffer.append(&mut response.body.unwrap()[..bytes_toread].to_vec()); }; - return Ok(read_buffer) + return Ok(read_buffer); } } - /// - /// Generates SessionKey for DES Authentification - /// - /// 8Byte SessionKey +/// Generates SessionKey for DES Authentification +/// +/// 8Byte SessionKey fn generate_session_key_des(rnd_a: &[u8], rnd_b: &[u8]) -> Option> { - Some([&rnd_a[..4], &rnd_b[5..8]].concat()) + let sessionkey = [&rnd_a[..4], &rnd_b[..4]].concat(); + Some([sessionkey.as_slice(), sessionkey.as_slice()].concat()) } /// - /// Generates SessionKey for AES Authentification - /// - /// 16Byte SessionKey +/// Generates SessionKey for AES Authentification +/// +/// 16Byte SessionKey fn generate_session_key_aes(rnd_a: &[u8], rnd_b: &[u8]) -> Option> { //FIXME: Check math - Some([&rnd_a[..4], &rnd_b[5..8], &rnd_a[5..8], &rnd_b[..4]].concat()) + Some([&rnd_a[..4], &rnd_b[..4], &rnd_a[12..], &rnd_b[12..]].concat()) +} + +/// +/// Generate KeySetting1 for Application Settings or PICC Setting +/// +/// ID of Key for changing Application Keys +/// generated keysettings +fn generate_keysetting1(change_key: u8, change_masterkey_settings: ChangeMasterKeySettings, create_delete_file: CreateDeleteFile, file_directory_access: FileDirectoryAccess, change_master_key: ChangeMasterKey) -> Result { + return match FromPrimitive::from_u8(change_key) { + Some(ChangeApplicationKey::MASTERKEY) | Some(ChangeApplicationKey::SAMEKEY) | Some(ChangeApplicationKey::ALLKEYS) => Ok((change_key << 4) | change_masterkey_settings as u8 | create_delete_file as u8 | file_directory_access as u8 | change_master_key as u8), + None => { + if change_key < 0x01 || change_key >= 0x08 { + Err(InvalidKeyID) + } else { + Ok((change_key << 4) | change_masterkey_settings as u8 | create_delete_file as u8 | file_directory_access as u8 | change_master_key as u8) + } + } + }; +} + +/// +/// Generate KeySetting2 for Application Creation +/// +/// Number of keys that can be stored within the application (0x01-0x0D) +/// generated keysettings +fn generate_keysetting2(crypto_operations: CryptoOperationsType, file_identifier: FileIdentifiers, num_keys: u8) -> Result { + return if num_keys < 0x01 || num_keys >= 0x0D { + Err(NumKeys) + } else { + Ok(crypto_operations as u8 | file_identifier as u8 | num_keys) + }; +} + +/// +/// Generate FileAccess Rights for File Settings +/// Use enum AccesRights for Free or Never Option +/// +/// KeyID for Read Access +/// KeyID for Write Access +/// KeyID for Read and Write Access +/// KeyID for Configuration Access +fn generate_file_access_rights(read: u8, write: u8, read_write: u8, configure: u8) -> Result { + return if read > 0x0F || write >= 0x0F || read_write >= 0x0F || configure >= 0x0F { + Err(InvalidKeyID) + } else { + Ok((((read as u16) << 12) | ((write as u16) << 8) | ((read_write as u16) << 4) | configure as u16) as u16) + }; +} + + +#[cfg(test)] +mod tests { + use hex_literal::hex; + use mockall::{mock, predicate}; + use crate::{APDUResponse, APDUCommand, Card}; + use crate::error::Result; + 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; + + mock! { + pub VirtualCard {} + impl Card for VirtualCard { + fn connect(&self); + fn disconnect(&self); + fn transmit(&self, apdu_cmd: APDUCommand) -> Result; + } + } + + #[test] + fn generate_sessionkey_des() { + let rndA = hex!("a541a9dc9138df07"); + let rndB = hex!("cbe55aa893b2da25"); + + let expected_sessionkey = hex!("a541a9dccbe55aa8a541a9dccbe55aa8"); + + let sessionkey = generate_session_key_des(&rndA, &rndB).unwrap(); + + println!("expected sessionkey: {:X?}", expected_sessionkey); + println!("actual sessionkey: {:X?}", sessionkey.as_slice()); + + assert_eq!(expected_sessionkey, sessionkey.as_slice()); + } + + #[test] + fn generate_sessionkey_aes() { + let rndA = hex!("bc14dfde20074617e45a8822f06fdd91"); + let rndB = hex!("482ddc54426e6dee560413b8d95471f5"); + + let expected_sessionkey = hex!("bc14dfde482ddc54f06fdd91d95471f5"); + + let sessionkey = generate_session_key_aes(&rndA, &rndB).unwrap(); + + println!("expected sessionkey: {:X?}", expected_sessionkey); + println!("actual sessionkey: {:X?}", sessionkey.as_slice()); + + assert_eq!(expected_sessionkey, sessionkey.as_slice()); + } + + #[test] + fn generate_key_setting1() { + assert_eq!(0x0B, + generate_keysetting1(ChangeApplicationKey::MASTERKEY as u8, + ChangeMasterKeySettings::WITHMASTERKEY, + CreateDeleteFile::ONLYMASTERKEY, + FileDirectoryAccess::NOKEY, + ChangeMasterKey::CHANGEABLE).unwrap() + ) + } + + #[test] + fn generate_key_setting1_changekey() { + assert_eq!(0x1B, + generate_keysetting1(0x01, + ChangeMasterKeySettings::WITHMASTERKEY, + CreateDeleteFile::ONLYMASTERKEY, + FileDirectoryAccess::NOKEY, + ChangeMasterKey::CHANGEABLE).unwrap() + ) + } + + #[test] + #[should_panic(expected= "InvalidKeyID")] + fn generate_key_setting1_invalid_keyid() { + generate_keysetting1(0x10, + ChangeMasterKeySettings::WITHMASTERKEY, + CreateDeleteFile::ONLYMASTERKEY, + FileDirectoryAccess::NOKEY, + ChangeMasterKey::CHANGEABLE).unwrap(); + } + + #[test] + fn generate_key_setting2() { + assert_eq!(0x82, generate_keysetting2(CryptoOperationsType::AES, FileIdentifiers::NOTUSED, 0x02).unwrap()); + } + + #[test] + #[should_panic(expected= "NumKeys")] + fn generate_key_setting2_wrong_num_keys() { + generate_keysetting2(CryptoOperationsType::AES, FileIdentifiers::NOTUSED, 0x10).unwrap(); + } + + #[test] + fn generate_file_accessrights() { + assert_eq!(0x1234, generate_file_access_rights(0x01, 0x02, 0x03, 0x04).unwrap()) + } + + #[test] + #[should_panic(expected = "InvalidKeyID")] + fn generate_file_accessrights_outofrange() { + generate_file_access_rights(0x10, 0x00, 0x00, 0x00).unwrap(); + } + + #[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{ + body: None, + sw1: 0x91, + sw2: 0x00 + })); + + let desfire = Desfire{ + card: Box::new(mock), + cbc_iv: None, + session_key: None + }; + + assert!(desfire.select_application(0x112233).is_ok()) + + + } } \ No newline at end of file diff --git a/src/desfire/mod.rs b/src/desfire/mod.rs index bcaae7f..ad69250 100644 --- a/src/desfire/mod.rs +++ b/src/desfire/mod.rs @@ -1,7 +1,11 @@ +use num_derive::FromPrimitive; + + /// /// hold the Access Rights for changing application keys (Change Key command) /// #[repr(u8)] +#[derive(Clone, Copy, FromPrimitive)] pub enum ChangeApplicationKey { /// /// Application master key authentication is necessary to change any key (default) @@ -21,6 +25,7 @@ pub enum ChangeApplicationKey { /// codes whether the application master key is changeable /// #[repr(u8)] +#[derive(Clone, Copy)] pub enum ChangeMasterKey { /// /// Application master key is not changeable anymore (frozen) @@ -37,6 +42,7 @@ pub enum ChangeMasterKey { /// codes whether a change of the application master key settings is allowed /// #[repr(u8)] +#[derive(Clone, Copy)] pub enum ChangeMasterKeySettings { /// @@ -54,6 +60,7 @@ WITHMASTERKEY = 0x08 /// codes whether application master key authentication is needed before “Create File” / “Delete File” /// #[repr(u8)] +#[derive(Clone, Copy)] pub enum CreateDeleteFile { /// /// “Create File”/ “Delete File”is permitted only with application master key authentication @@ -70,6 +77,7 @@ NOKEY = 0x04, /// Crypto method of the application /// #[repr(u8)] +#[derive(Clone, Copy)] pub enum CryptoOperationsType { TDES = 0x00, TKTDES = 0x40, @@ -77,12 +85,14 @@ pub enum CryptoOperationsType { } #[repr(u8)] +#[derive(Clone, Copy)] pub enum FileAccessRights { FREE = 0x0E, NEVER = 0x0F } #[repr(u8)] +#[derive(Clone, Copy)] pub enum FileCommunication { /// /// "Plain communication" @@ -104,6 +114,7 @@ pub enum FileCommunication { /// codes whether application master key authentication is needed for file directory access /// #[repr(u8)] +#[derive(Clone, Copy)] pub enum FileDirectoryAccess { /// /// Successful application master key authentication is required for executing the “Get FID List”, “Get File Settings”and “Get Key Settings”commands @@ -120,12 +131,14 @@ pub enum FileDirectoryAccess { /// Indicates use of 2 byte ISO/IEC 7816-4 File Identifies for files within the Application /// #[repr(u8)] -pub enum FileIdentifies { +#[derive(Clone, Copy)] +pub enum FileIdentifiers { NOTUSED = 0x00, USED = 0x20 } #[repr(u8)] +#[derive(Clone, Copy)] pub enum FileTypes { /// /// Standard Data File diff --git a/src/error.rs b/src/error.rs index e0fa1e6..1c63d12 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,9 +25,11 @@ pub enum Error { FileNotFound, InvalidApplicationID, InvalidAPDUResponse, + InvalidIsoCase, InvalidKeyID, InvalidPICCChallenge, InvalidFileID, + NumKeys, } impl fmt::Display for Error { @@ -99,6 +101,9 @@ impl fmt::Display for Error { Error::InvalidAPDUResponse => { write!(f, "Got malformed APDUResponse") } + Error::InvalidIsoCase => { + write!(f, "The specified IsoCase is not supported") + } Error::InvalidKeyID => { write!(f, "Invalid KeyID: Was larger then 0x0E") } @@ -108,6 +113,9 @@ impl fmt::Display for Error { Error::InvalidFileID => { write!(f, "Invalid FileID: Was larger then 0x20") } + Error::NumKeys => { + write!(f, "Number of Keys was larger then 0x0D") + } } } } diff --git a/src/iso7816_4/apducommand.rs b/src/iso7816_4/apducommand.rs index 0c1f861..23f750f 100644 --- a/src/iso7816_4/apducommand.rs +++ b/src/iso7816_4/apducommand.rs @@ -1,5 +1,7 @@ use std::fmt; use std::fmt::Formatter; +use std::convert::TryFrom; +use crate::error::{Result, Error}; #[repr(u8)] #[derive(Eq, PartialEq, Hash, Debug)] @@ -119,6 +121,48 @@ pub struct APDUCommand { pub lc: i32, pub le: i32, } + +impl TryFrom for Vec { + type Error = Error; + + fn try_from(cmd: APDUCommand) -> Result { + let mut v: Vec = vec![]; + //build header + v.push(cmd.cla); + v.push(cmd.ins); + v.push(cmd.p1); + v.push(cmd.p2); + + //build body according to case + match cmd.case { + IsoCase::Case1 => { + if cmd.protocol == SCardProtocol::T0 { + v.push(0x00); + } + } + IsoCase::Case2Short => { + v.push(cmd.le as u8); + } + IsoCase::Case3Short => { + v.push(cmd.lc as u8); + v.extend(cmd.data.unwrap()); + } + IsoCase::Case4Short => { + v.push(cmd.lc as u8); + v.extend(cmd.data.unwrap()); + if cmd.protocol == SCardProtocol::T0 { + v.push(0x00); + } else { + v.push(cmd.le as u8); + } + } + _ => { + return Err(Error::InvalidIsoCase) + } + } + Ok(v) + } +} //FIXME: Do we want pub fields or constructor? // We want pub fields! + constructor? diff --git a/src/iso7816_4/apduresponse.rs b/src/iso7816_4/apduresponse.rs index 38cbc37..9e212b6 100644 --- a/src/iso7816_4/apduresponse.rs +++ b/src/iso7816_4/apduresponse.rs @@ -4,15 +4,15 @@ 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)] +#[derive(Eq, PartialEq, Hash, Debug, Default)] pub struct APDUResponse { - pub body: Vec, + pub body: Option>, pub sw1: u8, pub sw2: u8, } impl APDUResponse { - pub fn body(&self) -> &Vec { + pub fn body(&self) -> &Option> { &self.body } } @@ -20,7 +20,10 @@ impl APDUResponse { impl From for Vec { fn from(resp: APDUResponse) -> Self { let mut v: Vec = vec![]; - v.extend(resp.body); + match resp.body { + None => {} + Some(body) => {v.extend(body);} + } v.push(resp.sw1); v.push(resp.sw2); v @@ -29,12 +32,12 @@ impl From for Vec { impl fmt::Display for APDUResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.body.is_empty() { - true => { + match self.body { + None => { 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) + Some(body) => { + write!(f, "SW1: {:#X} | SW2: {:#X} | Body: {:#X?}", self.sw1, self.sw2, body) } } } @@ -43,7 +46,7 @@ impl fmt::Display for APDUResponse { impl APDUResponse { pub fn new(raw: &[u8]) -> Self { APDUResponse { - body: raw[..raw.len() - 1].to_vec(), + body: Some(raw[..raw.len() - 1].to_vec()), sw1: raw[raw.len() - 2], sw2: raw[raw.len() - 3] }