diff --git a/src/desfire/apduinstructions.rs b/src/desfire/apduinstructions.rs new file mode 100644 index 0000000..3ed2b2d --- /dev/null +++ b/src/desfire/apduinstructions.rs @@ -0,0 +1,41 @@ +#[repr(u8)] +pub enum APDUInstructions { + AUTHENTICATE_ISO = 0x1A, + AUTHENTICATE_AES = 0xAA, + CHANGE_KEY_SETTINGS = 0x54, + SET_CONFIGURATION = 0x5C, + CHANGE_KEY = 0xC4, + GET_KEY_VERSION = 0x64, + CREATE_APPLICATION = 0xCA, + DELETE_APPLICATION = 0xDA, + GET_APPLICATION_IDS = 0x6A, + FREE_MEMORY = 0x6E, + GET_DF_NAMES = 0x6D, + GET_KEY_SETTINGS = 0x45, + SELECT_APPLICATION = 0x5A, + FORMAT_PICC = 0xFC, + GET_VERSION = 0x60, + GET_CARD_UID = 0x51, + GET_FILE_IDS = 0x6F, + GET_FILE_SETTINGS = 0xF5, + CHANGE_FILE_SETTINGS = 0x5F, + CREATE_STDDATAFILE = 0xCD, + CREATE_BACKUPDATAFILE = 0xCB, + CREATE_VALUE_FILE = 0xCC, + CREATE_LINEAR_RECORD_FILE = 0xC1, + CREATE_CYCLIC_RECORD_FILE = 0xC0, + DELETE_FILE = 0xDF, + GET_ISO_FILE_IDS = 0x61, + READ_DATA = 0xBD, + WRITE_DATA = 0x3D, + GET_VALUE = 0x6C, + CREDIT = 0x0C, + DEBIT = 0xDC, + LIMITED_CREDIT = 0x1C, + WRITE_RECORD = 0x3B, + READ_RECORDS = 0xBB, + CLEAR_RECORD_FILE = 0xEB, + COMMIT_TRANSACTION = 0xC7, + ABORT_TRANSACTION = 0xA7, + CONTINUE = 0xAF, +} diff --git a/src/desfire/apdustatuscodes.rs b/src/desfire/apdustatuscodes.rs new file mode 100644 index 0000000..bd0eb8f --- /dev/null +++ b/src/desfire/apdustatuscodes.rs @@ -0,0 +1,68 @@ +#[repr(u16)] +pub enum APDUStatusCodes { + /// Successful operation + OPERATION_OK = 0x9000, + + /// No changes done to backup files, CommitTransaction / AbortTransaction not necessary + NO_CHANGES = 0x900C, + + /// Insufficient NV-Memory to complete command + OUT_OF_EEPROM_ERROR = 0x900E, + + /// Command code not supported + ILLEGAL_COMMAND_CODE = 0x901C, + + /// CRC or MAC does not match data Padding bytes not valid + INTEGRITY_ERROR = 0x901E, + + /// Invalid key number specified + NO_SUCH_KEY = 0x9040, + + /// Invalid key number specified + LENGTH_ERROR = 0x907E, + + /// Current configuration / status does not allow the requested command + PERMISSION_DENIED = 0x909D, + + /// Value of the parameter(s) invalid + PARAMETER_ERROR = 0x909E, + + /// Requested AID not present on PICC + APPLICATION_NOT_FOUND = 0x90A0, + + /// Unrecoverable error within application, application will be disabled + APPL_INTEGRITY_ERROR = 0x90A1, + + /// Current authentication status does not allow the requested command + AUTHENTICATION_ERROR = 0x90AE, + + /// Additional data frame is expected to be sent + ADDITIONAL_FRAME = 0x90AF, + + /// Attempt to read/write data from/to beyond the file\'s/record\'s limits. Attempt to exceed the limits of a value file. + BOUNDARY_ERROR = 0x90BE, + + /// Unrecoverable error within PICC, PICC will be disabled + PICC_INTEGRITY_ERROR = 0x90C1, + + /// Previous Command was not fully completed Not all Frames were requested or provided by the PCD + COMMAND_ABORTED = 0x90CA, + + /// PICC was disabled by an unrecoverable error + PICC_DISABLED_ERROR = 0x90CD, + + /// Number of Applications limited to 28, no additional CreateApplication possible + COUNT_ERROR = 0x90CE, + + /// Creation of file/application failed because file/application with same number already exists + DUPLICATE_ERROR = 0x90DE, + + /// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated + EEPROM_ERROR = 0x90EE, + + /// Specified file number does not exist + FILE_NOT_FOUND = 0x90F0, + + /// Unrecoverable error within file, file will be disabled + FILE_INTEGRITY_ERROR = 0x90F1, +} diff --git a/src/desfire/desfire.rs b/src/desfire/desfire.rs new file mode 100644 index 0000000..53a3974 --- /dev/null +++ b/src/desfire/desfire.rs @@ -0,0 +1,388 @@ +use crate::{Card, error}; +use crate::iso7816_4::apducommand::{APDUCommand, IsoCase}; +use crate::desfire::apduinstructions::APDUInstructions; +use crate::crypto::cipher::tdes::Tdes; +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::crypto::util::expand_to_blocksize; +use crate::desfire::FileCommunication; +use crate::iso7816_4::apduresponse::APDUResponse; + +pub struct Desfire { + card: Box, + session_key: Vec, + cbc_iv: Vec, +} + +impl Desfire { + // Desfire commands + + /// + /// 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 = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::SELECT_APPLICATION as u8, + data: Option::from(aid.to_le_bytes()[..3].to_vec()), //FIXME: Which byteorder? + ..Default::default() + }; + + let response = self.card.transmit(cmd_select_app).unwrap(); + + response.check().map_err(|e| e) + } + + /// + /// Authenticate to PICC, with ISO Authenticate for DES Key + /// + /// 0x01 - 0x0D + /// Array of 8/16 Bytes + /// !!! WARNING For Testing only !!! + pub fn authenticate_iso_des(&mut self, key_id: u8, key: &[u8], rnd_a: Option<[u8; 8]>) -> Result<()> { + if key_id > 0x0E { + return Err(InvalidKeyID); + } + + let cmd_challenge_request = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::AUTHENTICATE_ISO 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.as_slice(); + + let mut rnd_b = Tdes::decrypt(rnd_b_enc, key, vec![0 as u8; 8].as_slice()).unwrap(); + + 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 = Tdes::encrypt(rnd_ab.as_slice(), key, rnd_b_enc).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 = Tdes::decrypt(response.body.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(); + + if rnd_a != rnd_a_from_card { + return Err(Error::InvalidPICCChallenge); + } + + self.session_key = generate_session_key_des(&rnd_a, rnd_b.as_slice()).unwrap(); + + self.cbc_iv = vec![0 as u8; 8]; + Ok(()) + } + + /// + /// Format PICC + /// Need Authentication for PICC / Application 0x000000 + /// + fn format_picc(&self) -> Result<()> { + let cmd_format = APDUCommand { + case: IsoCase::Case2Short, + cla: 0x90, + ins: APDUInstructions::FORMAT_PICC as u8, + ..Default::default() + }; + + let response = self.card.transmit(cmd_format).unwrap(); + + response.check() + } + + /// + /// Create Application for ApplicationID + /// + /// 3 Byte ID + fn create_application(&self, aid: u32, keysetting1: u8, keysetting2: u8) -> Result<()> { + if aid > 0xFFFFFF { + return Err(InvalidApplicationID); + } + + let mut data = aid.to_le_bytes()[..3].to_vec(); + data.push(keysetting1); + data.push(keysetting2); + let cmd_create_application = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::CREATE_APPLICATION as u8, + data: Option::from(data), //FIXME: Which byteorder? + ..Default::default() + }; + + let response = self.card.transmit(cmd_create_application).unwrap(); + + response.check() + } + + /// + /// Change AES key, the same as Authenticated + /// + /// 0x01 - 0x0D + /// Array of 16 Bytes + /// Version of Key(min. 0x10) + fn change_key_aes(&mut self, key_id: u8, new_key: &[u8], key_version: u8) -> Result<()> { + if key_id >= 0x0E { + return Err(InvalidKeyID); + } + + let header = vec![0xC4, key_id]; + let key_and_version: Vec = [new_key, &[key_version]].concat(); + let mut command = vec![]; + + command.extend(&header); + command.extend(&key_and_version); + + let crc = crate::crypto::crc::crc32::calculate(command.as_slice()); + + let mut plaintext: Vec = vec![]; + plaintext.extend(key_and_version); + plaintext.extend(crc); + + 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())?; + + self.cbc_iv = util::extract_last_block(cryptogram.as_slice(), 16)?.to_vec(); + + let mut data: Vec = vec![key_id]; + data.extend(cryptogram); + + let cmd_change_key = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::CHANGE_KEY as u8, + data: Option::from(data), //FIXME: Which byteorder? + ..Default::default() + }; + + let response = self.card.transmit(cmd_change_key).unwrap(); + + response.check() + } + + /// + /// 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); + } + + 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_and_version: Vec = [key_xor, vec![key_version]].concat(); + let mut command = vec![]; + + command.extend(&header); + command.extend(&key_and_version); + + let crc = crate::crypto::crc::crc32::calculate(command.as_slice()); + + let mut plaintext: Vec = vec![]; + plaintext.extend(key_and_version); + plaintext.extend(crc); + + 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())?; + + self.cbc_iv = util::extract_last_block(cryptogram.as_slice(), 16)?.to_vec(); + + let mut data: Vec = vec![key_id]; + data.extend(cryptogram); + + let cmd_change_key = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::CHANGE_KEY as u8, + data: Option::from(data), //FIXME: Which byteorder? + ..Default::default() + }; + + let response = self.card.transmit(cmd_change_key).unwrap(); + + response.check() + } + + fn create_file_standard(&self, file_id: u8, communication: FileCommunication, access_rights: u16, size: u32) -> Result<()>{ + if file_id >= 0x20 { + return Err(InvalidFileID) + } + + let access_rights = access_rights.to_le_bytes().to_vec(); + let size = size.to_le_bytes()[..3].to_vec(); + let mut data = vec![file_id, communication as u8]; + data.extend(&access_rights); + data.extend(&size); + + let cmd_create_file_standard = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::CREATE_STDDATAFILE as u8, + data: Option::from(data), + ..Default::default() + }; + + let response = self.card.transmit(cmd_create_file_standard).unwrap(); + + response.check() + } + + /// + /// 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) + } + + 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() { + let bytes_towrite = match length - bytes_writen < MAX_BYTES_PER_TRANSACTION { + true => { length - bytes_writen } + false => { MAX_BYTES_PER_TRANSACTION } + }; + + let mut write_buffer = vec![file_id]; + write_buffer.append(&mut offset.to_le_bytes()[..3].to_vec()); + write_buffer.append(&mut bytes_towrite.to_le_bytes()[..3].to_vec()); + write_buffer.append(&mut data[bytes_writen..bytes_writen + bytes_towrite].to_vec()); + bytes_writen += bytes_towrite; + + let cmd_write_data = APDUCommand { + case: IsoCase::Case4Short, + cla: 0x90, + ins: APDUInstructions::WRITE_DATA as u8, + data: Option::from(write_buffer), + ..Default::default() + }; + + let response = self.card.transmit(cmd_write_data).unwrap(); + + ret = response.check(); + }; + + return ret + } + + /// + /// 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) + } + + const MAX_BYTES_PER_TRANSACTION: usize = 47; + let mut bytes_read: usize = 0; + + let mut read_buffer: Vec = vec![]; + + while bytes_read != length { + let bytes_toread = match length - bytes_read < MAX_BYTES_PER_TRANSACTION { + true => { length - bytes_read } + false => { MAX_BYTES_PER_TRANSACTION } + }; + + let mut send_buffer = vec![file_id]; + send_buffer.append(&mut offset.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::READ_DATA as u8, + data: Option::from(send_buffer), + ..Default::default() + }; + + let response = self.card.transmit(cmd_read_data).unwrap(); + + response.check().or_else(|e| return Err(e)); + + read_buffer.append(&mut response.body()[..bytes_toread].to_vec()); + }; + + return Ok(read_buffer) + + } +} + + + +/// + /// 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()) +} + +/// + /// 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()) +} \ No newline at end of file diff --git a/src/desfire/mod.rs b/src/desfire/mod.rs index e69de29..bcaae7f 100644 --- a/src/desfire/mod.rs +++ b/src/desfire/mod.rs @@ -0,0 +1,158 @@ +/// +/// hold the Access Rights for changing application keys (Change Key command) +/// +#[repr(u8)] +pub enum ChangeApplicationKey { + /// + /// Application master key authentication is necessary to change any key (default) + /// + MASTERKEY = 0x00, + /// + /// Authentication with the key to be changed (same Key#) is necessary to change a key + /// + SAMEKEY = 0x0E, + /// + /// All keys (except application master key, see Bit 0) within this application are frozen + /// + ALLKEYS = 0x0F, +} + +/// +/// codes whether the application master key is changeable +/// +#[repr(u8)] +pub enum ChangeMasterKey { + /// +/// Application master key is not changeable anymore (frozen) +/// + FROZEN = 0x00, + + /// +/// Application master key is changeable (authentication with the current application master key necessary, default) +/// + CHANGEABLE = 0x01, +} + +/// +/// codes whether a change of the application master key settings is allowed +/// +#[repr(u8)] +pub enum ChangeMasterKeySettings { + +/// +/// configuration not changeable anymore (frozen) +/// +FROZEN = 0x00, + +/// +/// this configuration is changeable if authenticated with the application master key (default) +/// +WITHMASTERKEY = 0x08 +} + +/// +/// codes whether application master key authentication is needed before “Create File” / “Delete File” +/// +#[repr(u8)] +pub enum CreateDeleteFile { +/// +/// “Create File”/ “Delete File”is permitted only with application master key authentication +/// +ONLYMASTERKEY = 0x00, + +/// +/// “Create File”/ “Delete File”is permitted also without application master key authentication (default) +/// +NOKEY = 0x04, +} + +/// +/// Crypto method of the application +/// +#[repr(u8)] +pub enum CryptoOperationsType { + TDES = 0x00, + TKTDES = 0x40, + AES = 0x80, +} + +#[repr(u8)] +pub enum FileAccessRights { + FREE = 0x0E, + NEVER = 0x0F +} + +#[repr(u8)] +pub enum FileCommunication { + /// + /// "Plain communication" + /// + PLAIN = 0x00, + + /// + /// Plain communication secured by DES/3DES MACing + /// + MAC = 0x01, + + /// + /// Fully DES/3DES enciphered communication + /// + ENCRYPT = 0x03 +} + +/// +/// codes whether application master key authentication is needed for file directory access +/// +#[repr(u8)] +pub enum FileDirectoryAccess { + /// + /// Successful application master key authentication is required for executing the “Get FID List”, “Get File Settings”and “Get Key Settings”commands + /// + ONLYMASTERKEY = 0x00, + + /// + /// “Get FID List”, “Get File Settings” and “Get Key Settings” commands succeed independentlyof a preceding application master key authentication (default) + /// + NOKEY = 0x02, +} + +/// +/// Indicates use of 2 byte ISO/IEC 7816-4 File Identifies for files within the Application +/// +#[repr(u8)] +pub enum FileIdentifies { + NOTUSED = 0x00, + USED = 0x20 +} + +#[repr(u8)] +pub enum FileTypes { + /// + /// Standard Data File + /// + STANDARD = 0x00, + + /// + /// Backup Data Files + /// + BACKUP = 0x01, + + /// + /// Value Files with Backup + /// + VALUE = 0x02, + + /// + /// Linear Record Files with Backup + /// + LINEARRECORD = 0x03, + + /// + /// Cyclic Record Files with Backup + /// + CYCLICRECORD = 0x04 +} + +mod apduinstructions; +mod apdustatuscodes; +mod desfire; \ No newline at end of file