diff --git a/src/crypto/cipher_key.rs b/src/crypto/cipher_key.rs index c78dab3..d703926 100644 --- a/src/crypto/cipher_key.rs +++ b/src/crypto/cipher_key.rs @@ -13,11 +13,11 @@ pub struct CipherKey { /// CipherType of Key #[allow(dead_code)] - cipher: CipherType, + pub cipher: CipherType, /// KeyVersion of Key #[allow(dead_code)] - key_version: u8, + pub key_version: u8, } impl CipherKey { diff --git a/src/desfire/desfire.rs b/src/desfire/desfire.rs index c0ca999..ec8a1d6 100644 --- a/src/desfire/desfire.rs +++ b/src/desfire/desfire.rs @@ -1,3 +1,5 @@ +use std::fs::read; +use std::usize::MAX; use crate::{Card, error}; use crate::iso7816_4::apducommand::{APDUCommand, IsoCase}; use crate::desfire::apduinstructions::APDUInstructions; @@ -6,13 +8,15 @@ 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, NumKeys, InvalidKeyVersion}; +use crate::error::Error::{InvalidApplicationID, InvalidKeyID, InvalidFileID, NumKeys, InvalidKeyVersion, InvalidLength}; use crate::crypto::util::expand_to_blocksize; use crate::desfire::{FileCommunication, ChangeMasterKeySettings, CreateDeleteFile, FileDirectoryAccess, ChangeMasterKey, ChangeApplicationKey, CryptoOperationsType, FileIdentifiers}; use num_traits::FromPrimitive; +pub const MAX_BYTES_PER_TRANSACTION: usize = 47; + pub struct Desfire { - pub card: Box, + pub card: Option>, pub session_key: Option>, pub cbc_iv: Option>, } @@ -20,26 +24,31 @@ pub struct Desfire { impl Desfire { // Desfire commands + + pub fn select_application_cmd(&self, aid: u32) -> Result { + if aid > 0xFFFFFF { + return Err(InvalidApplicationID); + } + + Ok(APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::SelectApplication as u8, + data: Option::from(aid.to_le_bytes()[..3].to_vec()), + ..Default::default() + }) + } + /// /// Select Application by ApplicationID (AID) /// /// 3 Byte AID pub fn select_application(&self, aid: u32) -> Result<()> { - if aid > 0xFFFFFF { - return Err(InvalidApplicationID); - } + let cmd_select_app = self.select_application_cmd(aid)?; - let cmd_select_app = APDUCommand { - case: IsoCase::Case4Short, - cla: 0x90, - ins: APDUInstructions::SelectApplication as u8, - data: Option::from(aid.to_le_bytes()[..3].to_vec()), //FIXME: Which byteorder? - ..Default::default() - }; + let response = self.card.as_ref().unwrap().transmit(cmd_select_app)?; - let response = self.card.transmit(cmd_select_app).unwrap(); - - response.check().map_err(|e| e) + response.check() } /// @@ -61,7 +70,7 @@ impl Desfire { ..Default::default() }; - let response = self.card.transmit(cmd_challenge_request).unwrap(); + let response = self.card.as_ref().unwrap().transmit(cmd_challenge_request).unwrap(); match response.check() { Ok(_) => {} @@ -101,7 +110,7 @@ impl Desfire { ..Default::default() }; - let response = self.card.transmit(cmd_challenge_response).unwrap(); + let response = self.card.as_ref().unwrap().transmit(cmd_challenge_response).unwrap(); match response.check() { Ok(_) => {} @@ -129,13 +138,8 @@ impl Desfire { 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; 16]>) -> Result<()> { + /// Request challenge for aes auth from card + pub fn authenticate_iso_aes_challenge_cmd(&mut self, key_id: u8) -> Result { if key_id > 0x0E { return Err(InvalidKeyID); } @@ -147,9 +151,73 @@ impl Desfire { data: Option::from(vec!(key_id)), ..Default::default() }; + + Ok(cmd_challenge_request) + } + + /// Generate response for aes auth challenge + pub fn authenticate_iso_aes_response_cmd(&mut self, challenge: &[u8], key: &[u8], rnd_a: &[u8]) -> Result<(APDUCommand, Vec, Vec)> { + println!("RND_B_ENC: {:x?}", challenge); + + let rnd_b = AES::decrypt(challenge, key, vec![0 as u8; 16].as_slice()).unwrap(); + println!("RND_B: {:x?}", rnd_b); + + // auth_iv = rnd_b.clone(); + let mut rnd_b_rl = rnd_b.clone(); + rnd_b_rl.rotate_left(1); + println!("RND_B_RL: {:x?}", rnd_b_rl); + + let rnd_ab = [&rnd_a, rnd_b_rl.as_slice()].concat(); + println!("RND_AB: {:x?}", rnd_ab); + + let rnd_ab_enc = AES::encrypt(rnd_ab.as_slice(), key, challenge).unwrap(); + println!("RND_AB_ENC: {:x?}", rnd_ab_enc); + + let iv: &[u8] = util::extract_last_block(rnd_ab_enc.as_slice(), 16)?; + + let cmd_challenge_response = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::Continue as u8, + data: Some(rnd_ab_enc.clone()), + ..Default::default() + }; + + Ok((cmd_challenge_response, rnd_b, iv.to_vec())) + } + + /// Verify response from card + pub fn authenticate_iso_aes_verify(&mut self, response: &[u8], expected_response: &[u8], challenge: &[u8], key: &[u8], iv: &[u8]) -> Result<()> { + let mut rnd_a_rot_from_card = AES::decrypt(response, key, iv)?; + rnd_a_rot_from_card.rotate_right(1); + println!("RND_A_ROT_FROM_CARD: {:x?}", rnd_a_rot_from_card); + let rnd_a_from_card = rnd_a_rot_from_card.as_slice(); + println!("RND_A_FROM_CARD: {:x?}", rnd_a_from_card); + + if expected_response != rnd_a_from_card { + return Err(Error::InvalidPICCChallenge); + } + + self.session_key = Some(generate_session_key_aes(expected_response, challenge).unwrap()); + println!("SESSION_KEY: {:x?}", self.session_key.as_ref().unwrap()); + + self.cbc_iv = Some(vec![0 as u8; 16]); //FIXME: this should be a random value + println!("CBC_IV: {:x?}", self.cbc_iv.as_ref().unwrap()); + + 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; 16]>) -> Result<()> { + let cmd_challenge_request = self.authenticate_iso_aes_challenge_cmd(key_id)?; println!("CMD_CHALLENGE_REQUEST: {}", cmd_challenge_request); - let response = self.card.transmit(cmd_challenge_request).unwrap(); + let response = self.card.as_ref().unwrap().transmit(cmd_challenge_request)?; match response.check() { Ok(_) => {} @@ -159,15 +227,6 @@ impl Desfire { 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 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(); - let mut rnd_b_rl = rnd_b.clone(); - rnd_b_rl.rotate_left(1); - println!("RND_B_RL: {:x?}", rnd_b_rl); //FIXME: This is ugly, we should find a better way to make the function testable //TODO: Check if we need a CSPRNG here @@ -177,22 +236,9 @@ impl Desfire { }; println!("RND_A: {:x?}", rnd_a); - let rnd_ab = [&rnd_a, rnd_b_rl.as_slice()].concat(); - println!("RND_AB: {:x?}", rnd_ab); + let (cmd_challenge_response, rnd_b, iv) = self.authenticate_iso_aes_response_cmd(rnd_b_enc, key, &rnd_a)?; - let rnd_ab_enc = AES::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.clone()), - ..Default::default() - }; - println!("CMD_CHALLENGE_RESPONSE: {}", cmd_challenge_response); - - let response = self.card.transmit(cmd_challenge_response).unwrap(); + let response = self.card.as_ref().unwrap().transmit(cmd_challenge_response)?; match response.check() { Ok(_) => {} @@ -200,26 +246,9 @@ impl Desfire { } println!("RESPONSE: {}", response); - let iv: &[u8] = util::extract_last_block(rnd_ab_enc.as_slice(), 16).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 = AES::decrypt(rnd_a_enc_from_card.as_slice(), key, iv).unwrap(); - rnd_a_rot_from_card.rotate_right(1); - println!("RND_A_ROT_FROM_CARD: {:x?}", rnd_a_rot_from_card); - 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_aes(&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; 16]); - println!("CBC_IV: {:x?}", self.cbc_iv.as_ref().unwrap()); - - Ok(()) + self.authenticate_iso_aes_verify(rnd_a_enc_from_card.as_slice(), rnd_a.as_slice(), rnd_b.as_slice(), key, iv.as_slice()) } /// @@ -234,7 +263,7 @@ impl Desfire { ..Default::default() }; - let response = self.card.transmit(cmd_format).unwrap(); + let response = self.card.as_ref().unwrap().transmit(cmd_format).unwrap(); response.check() } @@ -259,7 +288,7 @@ impl Desfire { ..Default::default() }; - let response = self.card.transmit(cmd_create_application).unwrap(); + let response = self.card.as_ref().unwrap().transmit(cmd_create_application).unwrap(); response.check() } @@ -317,7 +346,7 @@ impl Desfire { }; println!("CMD_CHANGE_KEY: {}", cmd_change_key); - let response = self.card.transmit(cmd_change_key).unwrap(); + let response = self.card.as_ref().unwrap().transmit(cmd_change_key).unwrap(); println!("RESPONSE: {}", response); response.check() @@ -386,7 +415,7 @@ impl Desfire { }; println!("CMD_CHANGE_KEY: {}", cmd_change_key); - let response = self.card.transmit(cmd_change_key).unwrap(); + let response = self.card.as_ref().unwrap().transmit(cmd_change_key).unwrap(); println!("RESPONSE: {}", response); response.check() @@ -410,8 +439,9 @@ impl Desfire { data: Option::from(data), ..Default::default() }; + println!("CMD_CREATE_FILE_STANDARD: {}", cmd_create_file_standard); - let response = self.card.transmit(cmd_create_file_standard).unwrap(); + let response = self.card.as_ref().unwrap().transmit(cmd_create_file_standard).unwrap(); println!("RESPONSE: {}", response); response.check() @@ -430,7 +460,7 @@ impl Desfire { println!("Writing data to file {}", file_id); - const MAX_BYTES_PER_TRANSACTION: usize = 47; + let mut bytes_writen: usize = 0; let length: usize = data.len(); @@ -460,7 +490,7 @@ impl Desfire { }; println!("CMD_WRITE_DATA: {}", cmd_write_data); - let response = self.card.transmit(cmd_write_data).unwrap(); + let response = self.card.as_ref().unwrap().transmit(cmd_write_data).unwrap(); println!("RESPONSE: {}", response); ret = response.check(); @@ -469,6 +499,30 @@ impl Desfire { return ret; } + pub fn read_data_chunk_cmd(&self, file_id: u8, offset: u32, length: usize) -> Result { + if file_id >= 0x20 { + return Err(InvalidFileID); + } + + if length > MAX_BYTES_PER_TRANSACTION { + return Err(InvalidLength); + } + + let mut sendbuf = vec![file_id]; + sendbuf.append(&mut offset.to_le_bytes()[..3].to_vec()); + sendbuf.append(&mut length.to_le_bytes()[..3].to_vec()); + + let cmd_read_data_chunk = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::ReadData as u8, + data: Option::from(sendbuf), + ..Default::default() + }; + + return Ok(cmd_read_data_chunk); + } + /// /// Read Data from File /// @@ -480,7 +534,6 @@ impl Desfire { return Err(InvalidFileID); } - const MAX_BYTES_PER_TRANSACTION: usize = 47; let mut bytes_read: usize = 0; let mut read_buffer: Vec = vec![]; @@ -491,21 +544,12 @@ impl Desfire { false => { MAX_BYTES_PER_TRANSACTION } }; - let mut send_buffer = vec![file_id]; - send_buffer.append(&mut (offset as usize + bytes_read).to_le_bytes()[..3].to_vec()); - send_buffer.append(&mut bytes_toread.to_le_bytes()[..3].to_vec()); - bytes_read += bytes_toread; - - let cmd_read_data = APDUCommand { - case: IsoCase::Case4Short, - cla: 0x90, - ins: APDUInstructions::ReadData as u8, - data: Option::from(send_buffer), - ..Default::default() - }; + let cmd_read_data = self.read_data_chunk_cmd(file_id, (offset as usize + bytes_read) as u32, bytes_toread).unwrap(); println!("CMD_READ_DATA: {}", cmd_read_data); - let response = self.card.transmit(cmd_read_data).unwrap(); + bytes_read += bytes_toread; + + let response = self.card.as_ref().unwrap().transmit(cmd_read_data).unwrap(); println!("RESPONSE: {}", response); response.check().or_else(|e| return Err(e))?; @@ -641,7 +685,7 @@ mod tests { 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) { + let rapdu = match self.card.as_ref().as_ref().unwrap().transmit(apdu.as_slice(), &mut rapdu_buf) { Ok(rapdu) => rapdu, Err(err) => { eprintln!("Failed to transmit APDU command to card: {}", err); @@ -746,7 +790,7 @@ mod tests { })); let desfire = Desfire{ - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -760,7 +804,7 @@ mod tests { let mock = MockVirtualCard::new(); let mut desfire = Desfire{ - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -808,7 +852,7 @@ mod tests { card.connect(); let desfire = Desfire{ - card: Box::new(card), + card: Some(Box::new(card)), cbc_iv: None, session_key: None }; @@ -836,7 +880,7 @@ mod tests { println!("{:x?}", key.key.deref()); let mut desfire = Desfire{ - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -857,7 +901,7 @@ mod tests { let mock = MockVirtualCard::new(); let mut desfire = Desfire{ - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -905,7 +949,7 @@ mod tests { card.connect(); let mut desfire = Desfire{ - card: Box::new(card), + card: Some(Box::new(card)), cbc_iv: None, session_key: None }; @@ -942,7 +986,7 @@ mod tests { println!("{:x?}", key.key.deref()); let mut desfire = Desfire{ - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -963,7 +1007,7 @@ mod tests { let mock = MockVirtualCard::new(); let mut desfire = Desfire{ - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -981,7 +1025,7 @@ mod tests { })); let mut desfire = Desfire{ - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1002,7 +1046,7 @@ mod tests { })); let mut desfire = Desfire{ - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1017,7 +1061,7 @@ mod tests { let mut mock = MockVirtualCard::new(); let mut desfire = Desfire{ - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1037,7 +1081,7 @@ mod tests { let new_key = hex!("25432a462d4a614e645267556b587032"); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: Some(hex!("00000000000000000000000000000000").to_vec()), session_key: Some(hex!("a8514dd0350f3dfbc86e80744bcc9b57").to_vec()) }; @@ -1051,7 +1095,7 @@ mod tests { let mut mock = MockVirtualCard::new(); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1072,7 +1116,7 @@ mod tests { let new_key = hex!("25432a462d4a614e645267556b587032"); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: Some(hex!("00000000000000000000000000000000").to_vec()), session_key: Some(hex!("1677623e1e158a62dc3d128db55f947d").to_vec()) }; @@ -1086,7 +1130,7 @@ mod tests { let mut mock = MockVirtualCard::new(); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1104,7 +1148,7 @@ mod tests { })); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1119,7 +1163,7 @@ mod tests { let mut mock = MockVirtualCard::new(); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1138,7 +1182,7 @@ mod tests { })); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1169,7 +1213,7 @@ mod tests { })); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1184,7 +1228,7 @@ mod tests { let mut mock = MockVirtualCard::new(); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1202,7 +1246,7 @@ mod tests { })); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1221,7 +1265,7 @@ mod tests { })); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1252,7 +1296,7 @@ mod tests { })); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; @@ -1267,7 +1311,7 @@ mod tests { let mut mock = MockVirtualCard::new(); let mut desfire = Desfire { - card: Box::new(mock), + card: Some(Box::new(mock)), cbc_iv: None, session_key: None }; diff --git a/src/desfire/mod.rs b/src/desfire/mod.rs index b37928e..448dde7 100644 --- a/src/desfire/mod.rs +++ b/src/desfire/mod.rs @@ -167,3 +167,4 @@ pub enum FileTypes { mod apduinstructions; mod apdustatuscodes; pub mod desfire; +pub use desfire::Desfire; \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index c57b05e..e50026f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -31,6 +31,7 @@ pub enum Error { InvalidPICCChallenge, InvalidFileID, InvalidKeyVersion, + InvalidLength, NumKeys, CardError, } @@ -137,6 +138,47 @@ impl fmt::Display for Error { Error::ApplicationNotFound => { write!(f, "The application was not found on the card.") } + Error::InvalidLength => { + write!(f, "More data was requested than can be handled in a single transaction.") + } + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::IO(e) => Some(e), + Error::FromHexError(e) => Some(e), + Error::PadError(_) => None, + Error::InvalidStatusWord => None, + Error::IllegalCommandCode => None, + Error::IntegrityError => None, + Error::NoSuchKey => None, + Error::LengthError => None, + Error::PermissionDenied => None, + Error::ParameterError => None, + Error::AuthenticationDelay => None, + Error::AuthenticationError => None, + Error::BoundaryError => None, + Error::CommandAborted => None, + Error::DuplicateError => None, + Error::FileNotFound => None, + Error::InvalidApplicationID => None, + Error::InvalidAPDUResponse => None, + Error::InvalidIsoCase => None, + Error::InvalidKeyID => None, + Error::InvalidKeyVersion => None, + Error::InvalidPICCChallenge => None, + Error::InvalidFileID => None, + Error::NumKeys => None, + Error::CardError => None, + Error::ApplicationNotFound => None, + Error::InvalidLength => None, + Error::Boxed(e) => e.source(), + Error::Simple(e) => Some(e), + Error::BlockModeError(e) => Some(e), + Error::InvalidKeyIvLength(e) => Some(e), } } } diff --git a/src/iso7816_4/apduresponse.rs b/src/iso7816_4/apduresponse.rs index 1240cfa..393832e 100644 --- a/src/iso7816_4/apduresponse.rs +++ b/src/iso7816_4/apduresponse.rs @@ -40,7 +40,7 @@ impl fmt::Display for APDUResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.body { None => { - write!(f, "SW1: {:#X} | SW2: 0x{:#X}", self.sw1, self.sw2) + write!(f, "SW1: {:#X} | SW2: {:#X}", self.sw1, self.sw2) } Some(body) => { write!(