added basic desfire support

This commit is contained in:
Kai Kriegel 2021-04-27 00:38:15 +02:00
parent 8c112d519d
commit ad3437c263
4 changed files with 655 additions and 0 deletions

View File

@ -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,
}

View File

@ -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,
}

388
src/desfire/desfire.rs Normal file
View File

@ -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<dyn Card>,
session_key: Vec<u8>,
cbc_iv: Vec<u8>,
}
impl Desfire {
// Desfire commands
/// <summary>
/// Select Application by ApplicationID (AID)
/// </summary>
/// <param name="aid">3 Byte AID</param>
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)
}
/// <summary>
/// Authenticate to PICC, with ISO Authenticate for DES Key
/// </summary>
/// <param name="key_id">0x01 - 0x0D</param>
/// <param name="key">Array of 8/16 Bytes</param>
/// <param name="rndA">!!! WARNING For Testing only !!!</param>
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(())
}
/// <summary>
/// Format PICC
/// Need Authentication for PICC / Application 0x000000
/// </summary>
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()
}
/// <summary>
/// Create Application for ApplicationID
/// </summary>
/// <param name="aid">3 Byte ID</param>
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()
}
/// <summary>
/// Change AES key, the same as Authenticated
/// </summary>
/// <param name="key_id">0x01 - 0x0D</param>
/// <param name="new_key">Array of 16 Bytes</param>
/// <param name="key_version">Version of Key(min. 0x10)</param>
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<u8> = [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<u8> = 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<u8> = 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()
}
/// <summary>
/// Change AES key, other than Authenticated
/// </summary>
/// <param name="key_id">0x01 - 0x0D</param>
/// <param name="new_key">Array of 16 Bytes</param>
/// <param name="old_key">Array of 16 Bytes</param>
/// <param name="key_version">Version of Key(min. 0x10)</param>
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<u8> = new_key.iter().zip(old_key.iter()).map(|(&x1, &x2)| x1 ^ x2).collect();
let key_and_version: Vec<u8> = [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<u8> = 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<u8> = 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()
}
/// <summary>
/// Write Data to File
/// </summary>
/// <param name="file_id">ID of File (0x00 - 0x20)</param>
/// <param name="offset">Offset for File</param>
/// <param name="data">Data to write</param>
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
}
/// <summary>
/// Read Data from File
/// </summary>
/// <param name="file_id">ID of File (0x00 - 0x20)</param>
/// <param name="offset">Offset for File</param>
/// <param name="length">Lenght of Data</param>
fn read_data(&self, file_id: u8, offset: u32, length: usize) -> Result<Vec<u8>> {
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<u8> = 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)
}
}
/// <summary>
/// Generates SessionKey for DES Authentification
/// </summary>
/// <returns>8Byte SessionKey</returns>
fn generate_session_key_des(rnd_a: &[u8], rnd_b: &[u8]) -> Option<Vec<u8>> {
Some([&rnd_a[..4], &rnd_b[5..8]].concat())
}
/// <summary>
/// Generates SessionKey for AES Authentification
/// </summary>
/// <returns>16Byte SessionKey</returns>
fn generate_session_key_aes(rnd_a: &[u8], rnd_b: &[u8]) -> Option<Vec<u8>> {
//FIXME: Check math
Some([&rnd_a[..4], &rnd_b[5..8], &rnd_a[5..8], &rnd_b[..4]].concat())
}

View File

@ -0,0 +1,158 @@
/// <summary>
/// hold the Access Rights for changing application keys (Change Key command)
/// </summary>
#[repr(u8)]
pub enum ChangeApplicationKey {
/// <summary>
/// Application master key authentication is necessary to change any key (default)
/// </summary>
MASTERKEY = 0x00,
/// <summary>
/// Authentication with the key to be changed (same Key#) is necessary to change a key
/// </summary>
SAMEKEY = 0x0E,
/// <summary>
/// All keys (except application master key, see Bit 0) within this application are frozen
/// </summary>
ALLKEYS = 0x0F,
}
/// <summary>
/// codes whether the application master key is changeable
/// </summary>
#[repr(u8)]
pub enum ChangeMasterKey {
/// <summary>
/// Application master key is not changeable anymore (frozen)
/// </summary>
FROZEN = 0x00,
/// <summary>
/// Application master key is changeable (authentication with the current application master key necessary, default)
/// </summary>
CHANGEABLE = 0x01,
}
/// <summary>
/// codes whether a change of the application master key settings is allowed
/// </summary>
#[repr(u8)]
pub enum ChangeMasterKeySettings {
/// <summary>
/// configuration not changeable anymore (frozen)
/// </summary>
FROZEN = 0x00,
/// <summary>
/// this configuration is changeable if authenticated with the application master key (default)
/// </summary>
WITHMASTERKEY = 0x08
}
/// <summary>
/// codes whether application master key authentication is needed before “Create File” / “Delete File”
/// </summary>
#[repr(u8)]
pub enum CreateDeleteFile {
/// <summary>
/// “Create File”/ “Delete File”is permitted only with application master key authentication
/// </summary>
ONLYMASTERKEY = 0x00,
/// <summary>
/// “Create File”/ “Delete File”is permitted also without application master key authentication (default)
/// </summary>
NOKEY = 0x04,
}
/// <summary>
/// Crypto method of the application
/// </summary>
#[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 {
/// <summary>
/// "Plain communication"
/// </summary>
PLAIN = 0x00,
/// <summary>
/// Plain communication secured by DES/3DES MACing
/// </summary>
MAC = 0x01,
/// <summary>
/// Fully DES/3DES enciphered communication
/// </summary>
ENCRYPT = 0x03
}
/// <summary>
/// codes whether application master key authentication is needed for file directory access
/// </summary>
#[repr(u8)]
pub enum FileDirectoryAccess {
/// <summary>
/// Successful application master key authentication is required for executing the “Get FID List”, “Get File Settings”and “Get Key Settings”commands
/// </summary>
ONLYMASTERKEY = 0x00,
/// <summary>
/// “Get FID List”, “Get File Settings” and “Get Key Settings” commands succeed independentlyof a preceding application master key authentication (default)
/// </summary>
NOKEY = 0x02,
}
/// <summary>
/// Indicates use of 2 byte ISO/IEC 7816-4 File Identifies for files within the Application
/// </summary>
#[repr(u8)]
pub enum FileIdentifies {
NOTUSED = 0x00,
USED = 0x20
}
#[repr(u8)]
pub enum FileTypes {
/// <summary>
/// Standard Data File
/// </summary>
STANDARD = 0x00,
/// <summary>
/// Backup Data Files
/// </summary>
BACKUP = 0x01,
/// <summary>
/// Value Files with Backup
/// </summary>
VALUE = 0x02,
/// <summary>
/// Linear Record Files with Backup
/// </summary>
LINEARRECORD = 0x03,
/// <summary>
/// Cyclic Record Files with Backup
/// </summary>
CYCLICRECORD = 0x04
}
mod apduinstructions;
mod apdustatuscodes;
mod desfire;