mirror of
https://gitlab.com/fabinfra/fabaccess/nfc_rs.git
synced 2025-03-12 14:51:50 +01:00
implemented desfire iso auth + tests
This commit is contained in:
parent
c5f249a936
commit
f5a11d256a
@ -19,3 +19,4 @@ rand = "0.8.3"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.3.1"
|
hex-literal = "0.3.1"
|
||||||
|
mockall = "0.10.2"
|
||||||
|
@ -6,15 +6,17 @@ use crate::crypto::cipher::aes::AES;
|
|||||||
use crate::crypto::cipher::Cipher;
|
use crate::crypto::cipher::Cipher;
|
||||||
use crate::crypto::util;
|
use crate::crypto::util;
|
||||||
use crate::error::{Result, Error};
|
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::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 crate::iso7816_4::apduresponse::APDUResponse;
|
||||||
|
use num_traits::FromPrimitive;
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
pub struct Desfire {
|
pub struct Desfire {
|
||||||
card: Box<dyn Card>,
|
card: Box<dyn Card>,
|
||||||
session_key: Vec<u8>,
|
session_key: Option<Vec<u8>>,
|
||||||
cbc_iv: Vec<u8>,
|
cbc_iv: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Desfire {
|
impl Desfire {
|
||||||
@ -68,7 +70,7 @@ impl Desfire {
|
|||||||
Err(e) => { return Err(e); }
|
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();
|
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); }
|
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);
|
rnd_a_rot_from_card.rotate_right(1);
|
||||||
let rnd_a_from_card = rnd_a_rot_from_card.as_slice();
|
let rnd_a_from_card = rnd_a_rot_from_card.as_slice();
|
||||||
|
|
||||||
@ -108,9 +110,85 @@ impl Desfire {
|
|||||||
return Err(Error::InvalidPICCChallenge);
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Authenticate to PICC, with ISO Authenticate for AES 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_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<u8>;
|
||||||
|
|
||||||
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,9 +260,9 @@ impl Desfire {
|
|||||||
|
|
||||||
let plaintext_pad = expand_to_blocksize(plaintext.as_mut_slice(), 16)?;
|
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<u8> = vec![key_id];
|
let mut data: Vec<u8> = vec![key_id];
|
||||||
data.extend(cryptogram);
|
data.extend(cryptogram);
|
||||||
@ -232,9 +310,9 @@ impl Desfire {
|
|||||||
|
|
||||||
let plaintext_pad = expand_to_blocksize(plaintext.as_mut_slice(), 16)?;
|
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<u8> = vec![key_id];
|
let mut data: Vec<u8> = vec![key_id];
|
||||||
data.extend(cryptogram);
|
data.extend(cryptogram);
|
||||||
@ -254,7 +332,7 @@ impl Desfire {
|
|||||||
|
|
||||||
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 {
|
if file_id >= 0x20 {
|
||||||
return Err(InvalidFileID)
|
return Err(InvalidFileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
let access_rights = access_rights.to_le_bytes().to_vec();
|
let access_rights = access_rights.to_le_bytes().to_vec();
|
||||||
@ -284,7 +362,7 @@ impl Desfire {
|
|||||||
/// <param name="data">Data to write</param>
|
/// <param name="data">Data to write</param>
|
||||||
fn write_data(&self, file_id: u8, offset: u32, data: &[u8]) -> Result<()> {
|
fn write_data(&self, file_id: u8, offset: u32, data: &[u8]) -> Result<()> {
|
||||||
if file_id >= 0x20 {
|
if file_id >= 0x20 {
|
||||||
return Err(InvalidFileID)
|
return Err(InvalidFileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_BYTES_PER_TRANSACTION: usize = 47;
|
const MAX_BYTES_PER_TRANSACTION: usize = 47;
|
||||||
@ -318,7 +396,7 @@ impl Desfire {
|
|||||||
ret = response.check();
|
ret = response.check();
|
||||||
};
|
};
|
||||||
|
|
||||||
return ret
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -329,7 +407,7 @@ impl Desfire {
|
|||||||
/// <param name="length">Lenght of Data</param>
|
/// <param name="length">Lenght of Data</param>
|
||||||
fn read_data(&self, file_id: u8, offset: u32, length: usize) -> Result<Vec<u8>> {
|
fn read_data(&self, file_id: u8, offset: u32, length: usize) -> Result<Vec<u8>> {
|
||||||
if file_id >= 0x20 {
|
if file_id >= 0x20 {
|
||||||
return Err(InvalidFileID)
|
return Err(InvalidFileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_BYTES_PER_TRANSACTION: usize = 47;
|
const MAX_BYTES_PER_TRANSACTION: usize = 47;
|
||||||
@ -360,22 +438,22 @@ impl Desfire {
|
|||||||
|
|
||||||
response.check().or_else(|e| return Err(e));
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates SessionKey for DES Authentification
|
/// Generates SessionKey for DES Authentification
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>8Byte SessionKey</returns>
|
/// <returns>8Byte SessionKey</returns>
|
||||||
fn generate_session_key_des(rnd_a: &[u8], rnd_b: &[u8]) -> Option<Vec<u8>> {
|
fn generate_session_key_des(rnd_a: &[u8], rnd_b: &[u8]) -> Option<Vec<u8>> {
|
||||||
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())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -384,5 +462,177 @@ fn generate_session_key_des(rnd_a: &[u8], rnd_b: &[u8]) -> Option<Vec<u8>> {
|
|||||||
/// <returns>16Byte SessionKey</returns>
|
/// <returns>16Byte SessionKey</returns>
|
||||||
fn generate_session_key_aes(rnd_a: &[u8], rnd_b: &[u8]) -> Option<Vec<u8>> {
|
fn generate_session_key_aes(rnd_a: &[u8], rnd_b: &[u8]) -> Option<Vec<u8>> {
|
||||||
//FIXME: Check math
|
//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())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate KeySetting1 for Application Settings or PICC Setting
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="changeKey">ID of Key for changing Application Keys</param>
|
||||||
|
/// <returns>generated keysettings</returns>
|
||||||
|
fn generate_keysetting1(change_key: u8, change_masterkey_settings: ChangeMasterKeySettings, create_delete_file: CreateDeleteFile, file_directory_access: FileDirectoryAccess, change_master_key: ChangeMasterKey) -> Result<u8> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate KeySetting2 for Application Creation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="numberOfKeys">Number of keys that can be stored within the application (0x01-0x0D)</param>
|
||||||
|
/// <returns>generated keysettings</returns>
|
||||||
|
fn generate_keysetting2(crypto_operations: CryptoOperationsType, file_identifier: FileIdentifiers, num_keys: u8) -> Result<u8> {
|
||||||
|
return if num_keys < 0x01 || num_keys >= 0x0D {
|
||||||
|
Err(NumKeys)
|
||||||
|
} else {
|
||||||
|
Ok(crypto_operations as u8 | file_identifier as u8 | num_keys)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate FileAccess Rights for File Settings
|
||||||
|
/// Use enum AccesRights for Free or Never Option
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="read">KeyID for Read Access</param>
|
||||||
|
/// <param name="write">KeyID for Write Access</param>
|
||||||
|
/// <param name="read_write">KeyID for Read and Write Access</param>
|
||||||
|
/// <param name="configure">KeyID for Configuration Access</param>
|
||||||
|
fn generate_file_access_rights(read: u8, write: u8, read_write: u8, configure: u8) -> Result<u16> {
|
||||||
|
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<APDUResponse>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<u8>) == 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())
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,7 +1,11 @@
|
|||||||
|
use num_derive::FromPrimitive;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// hold the Access Rights for changing application keys (Change Key command)
|
/// hold the Access Rights for changing application keys (Change Key command)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy, FromPrimitive)]
|
||||||
pub enum ChangeApplicationKey {
|
pub enum ChangeApplicationKey {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Application master key authentication is necessary to change any key (default)
|
/// 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
|
/// codes whether the application master key is changeable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum ChangeMasterKey {
|
pub enum ChangeMasterKey {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Application master key is not changeable anymore (frozen)
|
/// 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
|
/// codes whether a change of the application master key settings is allowed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum ChangeMasterKeySettings {
|
pub enum ChangeMasterKeySettings {
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -54,6 +60,7 @@ WITHMASTERKEY = 0x08
|
|||||||
/// codes whether application master key authentication is needed before “Create File” / “Delete File”
|
/// codes whether application master key authentication is needed before “Create File” / “Delete File”
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum CreateDeleteFile {
|
pub enum CreateDeleteFile {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// “Create File”/ “Delete File”is permitted only with application master key authentication
|
/// “Create File”/ “Delete File”is permitted only with application master key authentication
|
||||||
@ -70,6 +77,7 @@ NOKEY = 0x04,
|
|||||||
/// Crypto method of the application
|
/// Crypto method of the application
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum CryptoOperationsType {
|
pub enum CryptoOperationsType {
|
||||||
TDES = 0x00,
|
TDES = 0x00,
|
||||||
TKTDES = 0x40,
|
TKTDES = 0x40,
|
||||||
@ -77,12 +85,14 @@ pub enum CryptoOperationsType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum FileAccessRights {
|
pub enum FileAccessRights {
|
||||||
FREE = 0x0E,
|
FREE = 0x0E,
|
||||||
NEVER = 0x0F
|
NEVER = 0x0F
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum FileCommunication {
|
pub enum FileCommunication {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Plain communication"
|
/// "Plain communication"
|
||||||
@ -104,6 +114,7 @@ pub enum FileCommunication {
|
|||||||
/// codes whether application master key authentication is needed for file directory access
|
/// codes whether application master key authentication is needed for file directory access
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum FileDirectoryAccess {
|
pub enum FileDirectoryAccess {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Successful application master key authentication is required for executing the “Get FID List”, “Get File Settings”and “Get Key Settings”commands
|
/// 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
|
/// Indicates use of 2 byte ISO/IEC 7816-4 File Identifies for files within the Application
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum FileIdentifies {
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum FileIdentifiers {
|
||||||
NOTUSED = 0x00,
|
NOTUSED = 0x00,
|
||||||
USED = 0x20
|
USED = 0x20
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum FileTypes {
|
pub enum FileTypes {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Standard Data File
|
/// Standard Data File
|
||||||
|
@ -25,9 +25,11 @@ pub enum Error {
|
|||||||
FileNotFound,
|
FileNotFound,
|
||||||
InvalidApplicationID,
|
InvalidApplicationID,
|
||||||
InvalidAPDUResponse,
|
InvalidAPDUResponse,
|
||||||
|
InvalidIsoCase,
|
||||||
InvalidKeyID,
|
InvalidKeyID,
|
||||||
InvalidPICCChallenge,
|
InvalidPICCChallenge,
|
||||||
InvalidFileID,
|
InvalidFileID,
|
||||||
|
NumKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
@ -99,6 +101,9 @@ impl fmt::Display for Error {
|
|||||||
Error::InvalidAPDUResponse => {
|
Error::InvalidAPDUResponse => {
|
||||||
write!(f, "Got malformed APDUResponse")
|
write!(f, "Got malformed APDUResponse")
|
||||||
}
|
}
|
||||||
|
Error::InvalidIsoCase => {
|
||||||
|
write!(f, "The specified IsoCase is not supported")
|
||||||
|
}
|
||||||
Error::InvalidKeyID => {
|
Error::InvalidKeyID => {
|
||||||
write!(f, "Invalid KeyID: Was larger then 0x0E")
|
write!(f, "Invalid KeyID: Was larger then 0x0E")
|
||||||
}
|
}
|
||||||
@ -108,6 +113,9 @@ impl fmt::Display for Error {
|
|||||||
Error::InvalidFileID => {
|
Error::InvalidFileID => {
|
||||||
write!(f, "Invalid FileID: Was larger then 0x20")
|
write!(f, "Invalid FileID: Was larger then 0x20")
|
||||||
}
|
}
|
||||||
|
Error::NumKeys => {
|
||||||
|
write!(f, "Number of Keys was larger then 0x0D")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use crate::error::{Result, Error};
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Eq, PartialEq, Hash, Debug)]
|
#[derive(Eq, PartialEq, Hash, Debug)]
|
||||||
@ -119,6 +121,48 @@ pub struct APDUCommand {
|
|||||||
pub lc: i32,
|
pub lc: i32,
|
||||||
pub le: i32,
|
pub le: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<APDUCommand> for Vec<u8> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(cmd: APDUCommand) -> Result<Self> {
|
||||||
|
let mut v: Vec<u8> = 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?
|
//FIXME: Do we want pub fields or constructor?
|
||||||
// We want pub fields! + constructor?
|
// We want pub fields! + constructor?
|
||||||
|
|
||||||
|
@ -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 crate::error::Error::{InvalidStatusWord, IllegalCommandCode, IntegrityError, NoSuchKey, LengthError, PermissionDenied, ParameterError, AuthenticationDelay, AuthenticationError, BoundaryError, CommandAborted, DuplicateError, FileNotFound};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Debug)]
|
#[derive(Eq, PartialEq, Hash, Debug, Default)]
|
||||||
pub struct APDUResponse {
|
pub struct APDUResponse {
|
||||||
pub body: Vec<u8>,
|
pub body: Option<Vec<u8>>,
|
||||||
pub sw1: u8,
|
pub sw1: u8,
|
||||||
pub sw2: u8,
|
pub sw2: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl APDUResponse {
|
impl APDUResponse {
|
||||||
pub fn body(&self) -> &Vec<u8> {
|
pub fn body(&self) -> &Option<Vec<u8>> {
|
||||||
&self.body
|
&self.body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,7 +20,10 @@ impl APDUResponse {
|
|||||||
impl From<APDUResponse> for Vec<u8> {
|
impl From<APDUResponse> for Vec<u8> {
|
||||||
fn from(resp: APDUResponse) -> Self {
|
fn from(resp: APDUResponse) -> Self {
|
||||||
let mut v: Vec<u8> = vec![];
|
let mut v: Vec<u8> = vec![];
|
||||||
v.extend(resp.body);
|
match resp.body {
|
||||||
|
None => {}
|
||||||
|
Some(body) => {v.extend(body);}
|
||||||
|
}
|
||||||
v.push(resp.sw1);
|
v.push(resp.sw1);
|
||||||
v.push(resp.sw2);
|
v.push(resp.sw2);
|
||||||
v
|
v
|
||||||
@ -29,12 +32,12 @@ impl From<APDUResponse> for Vec<u8> {
|
|||||||
|
|
||||||
impl fmt::Display for APDUResponse {
|
impl fmt::Display for APDUResponse {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self.body.is_empty() {
|
match self.body {
|
||||||
true => {
|
None => {
|
||||||
write!(f, "SW1: {:#X} | SW2: 0x{:#X}", self.sw1, self.sw2)
|
write!(f, "SW1: {:#X} | SW2: 0x{:#X}", self.sw1, self.sw2)
|
||||||
},
|
},
|
||||||
false => {
|
Some(body) => {
|
||||||
write!(f, "SW1: {:#X} | SW2: {:#X} | Body: {:#X?}", self.sw1, self.sw2, self.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 {
|
impl APDUResponse {
|
||||||
pub fn new(raw: &[u8]) -> Self {
|
pub fn new(raw: &[u8]) -> Self {
|
||||||
APDUResponse {
|
APDUResponse {
|
||||||
body: raw[..raw.len() - 1].to_vec(),
|
body: Some(raw[..raw.len() - 1].to_vec()),
|
||||||
sw1: raw[raw.len() - 2],
|
sw1: raw[raw.len() - 2],
|
||||||
sw2: raw[raw.len() - 3]
|
sw2: raw[raw.len() - 3]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user