mirror of
https://gitlab.com/fabinfra/fabaccess/nfc_rs.git
synced 2025-05-10 03:23:26 +02:00
added first test on real hardware
This commit is contained in:
parent
f5a11d256a
commit
cc836af2c4
@ -20,3 +20,4 @@ rand = "0.8.3"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.3.1"
|
hex-literal = "0.3.1"
|
||||||
mockall = "0.10.2"
|
mockall = "0.10.2"
|
||||||
|
pcsc = "2.6.0"
|
||||||
|
@ -7,9 +7,9 @@ use crate::error::Result;
|
|||||||
use simple_error::simple_error;
|
use simple_error::simple_error;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct CipherKey {
|
pub struct CipherKey {
|
||||||
/// Key as Array
|
/// Key as Array
|
||||||
key: Box<[u8]>,
|
pub key: Box<[u8]>, //TODO: Decide if this should be pub
|
||||||
|
|
||||||
/// CipherType of Key
|
/// CipherType of Key
|
||||||
cipher: CipherType,
|
cipher: CipherType,
|
||||||
|
@ -70,28 +70,34 @@ impl Desfire {
|
|||||||
Err(e) => { return Err(e); }
|
Err(e) => { return Err(e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
let rnd_b_enc = response.body.unwrap().as_slice();
|
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 mut rnd_b = Tdes::decrypt(rnd_b_enc, key, vec![0 as u8; 8].as_slice()).unwrap();
|
let rnd_b = Tdes::decrypt(rnd_b_enc, key, vec![0 as u8; 8].as_slice()).unwrap();
|
||||||
|
println!("RND_B: {:x?}", rnd_b);
|
||||||
|
|
||||||
rnd_b.rotate_left(1);
|
let mut rnd_b_rl = rnd_b.clone();
|
||||||
|
rnd_b_rl.rotate_left(1);
|
||||||
let rnd_b_rl = rnd_b.as_slice();
|
println!("RND_B_RL: {:x?}", rnd_b_rl);
|
||||||
|
|
||||||
let rnd_a = match rnd_a {
|
let rnd_a = match rnd_a {
|
||||||
None => { rand::random() }
|
None => { rand::random() }
|
||||||
Some(i) => { i }
|
Some(i) => { i }
|
||||||
};
|
};
|
||||||
|
println!("RND_A: {:x?}", rnd_a);
|
||||||
|
|
||||||
let rnd_ab = [&rnd_a, rnd_b_rl].concat();
|
let rnd_ab = [&rnd_a, rnd_b_rl.as_slice()].concat();
|
||||||
|
println!("RND_AB: {:x?}", rnd_ab);
|
||||||
|
|
||||||
let rnd_ab_enc = Tdes::encrypt(rnd_ab.as_slice(), key, rnd_b_enc).unwrap();
|
let rnd_ab_enc = Tdes::encrypt(rnd_ab.as_slice(), key, rnd_b_enc).unwrap();
|
||||||
|
println!("RND_AB_ENC: {:x?}", rnd_ab_enc);
|
||||||
|
|
||||||
let cmd_challenge_response = APDUCommand {
|
let cmd_challenge_response = APDUCommand {
|
||||||
case: IsoCase::Case4Short,
|
case: IsoCase::Case4Short,
|
||||||
cla: 0x90,
|
cla: 0x90,
|
||||||
ins: APDUInstructions::CONTINUE as u8,
|
ins: APDUInstructions::CONTINUE as u8,
|
||||||
data: Some(rnd_ab_enc),
|
data: Some(rnd_ab_enc.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,17 +108,24 @@ impl Desfire {
|
|||||||
Err(e) => { return Err(e); }
|
Err(e) => { return Err(e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rnd_a_rot_from_card = Tdes::decrypt(response.body.unwrap().as_slice(), key, rnd_b.as_slice()).unwrap();
|
let iv: &[u8] = util::extract_last_block(rnd_ab_enc.as_slice(), 8).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 = Tdes::decrypt(rnd_a_enc_from_card.as_slice(), key, iv).unwrap();
|
||||||
|
println!("RND_A_ROT_FROM_CARD: {:x?}", rnd_a_rot_from_card);
|
||||||
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();
|
||||||
|
println!("RND_A_FROM_CARD: {:x?}", rnd_a_from_card);
|
||||||
|
|
||||||
if rnd_a != rnd_a_from_card {
|
if rnd_a != rnd_a_from_card {
|
||||||
return Err(Error::InvalidPICCChallenge);
|
return Err(Error::InvalidPICCChallenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.session_key = Some(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());
|
||||||
|
println!("SESSION_KEY: {:x?}", self.session_key.as_ref().unwrap());
|
||||||
|
|
||||||
self.cbc_iv = Some(vec![0 as u8; 8]);
|
self.cbc_iv = Some(vec![0 as u8; 8]);
|
||||||
|
println!("CBC_IV: {:x?}", self.cbc_iv.as_ref().unwrap());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,12 +135,12 @@ impl Desfire {
|
|||||||
/// <param name="key_id">0x01 - 0x0D</param>
|
/// <param name="key_id">0x01 - 0x0D</param>
|
||||||
/// <param name="key">Array of 8/16 Bytes</param>
|
/// <param name="key">Array of 8/16 Bytes</param>
|
||||||
/// <param name="rndA">!!! WARNING For Testing only !!!</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<()> {
|
pub fn authenticate_iso_aes(&mut self, key_id: u8, key: &[u8], rnd_a: Option<[u8; 16]>) -> Result<()> {
|
||||||
if key_id > 0x0E {
|
if key_id > 0x0E {
|
||||||
return Err(InvalidKeyID);
|
return Err(InvalidKeyID);
|
||||||
}
|
}
|
||||||
|
|
||||||
let auth_iv: Vec<u8>;
|
// let auth_iv: Vec<u8>;
|
||||||
|
|
||||||
let cmd_challenge_request = APDUCommand {
|
let cmd_challenge_request = APDUCommand {
|
||||||
case: IsoCase::Case4Short,
|
case: IsoCase::Case4Short,
|
||||||
@ -144,30 +157,35 @@ impl Desfire {
|
|||||||
Err(e) => { return Err(e); }
|
Err(e) => { return Err(e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
let rnd_b_enc = response.body.unwrap().as_slice();
|
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 mut rnd_b = AES::decrypt(rnd_b_enc, key, vec![0 as u8; 16].as_slice()).unwrap();
|
let mut 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();
|
// auth_iv = rnd_b.clone();
|
||||||
|
let rnd_b_rl = rnd_b.clone();
|
||||||
rnd_b.rotate_left(1);
|
rnd_b.rotate_left(1);
|
||||||
|
println!("RND_B_RL: {:x?}", rnd_b_rl);
|
||||||
let rnd_b_rl = rnd_b.as_slice();
|
|
||||||
|
|
||||||
let rnd_a = match rnd_a {
|
let rnd_a = match rnd_a {
|
||||||
None => { rand::random() }
|
None => { rand::random() }
|
||||||
Some(i) => { i }
|
Some(i) => { i }
|
||||||
};
|
};
|
||||||
|
println!("RND_A: {:x?}", rnd_a);
|
||||||
|
|
||||||
let rnd_ab = [&rnd_a, rnd_b_rl].concat();
|
let rnd_ab = [&rnd_a, rnd_b_rl].concat();
|
||||||
|
println!("RND_AB: {:x?}", rnd_ab);
|
||||||
|
|
||||||
let rnd_ab_enc = AES::encrypt(rnd_ab.as_slice(), key, auth_iv.as_slice()).unwrap();
|
let rnd_ab_enc = AES::encrypt(rnd_ab.as_slice(), key, auth_iv.as_slice()).unwrap();
|
||||||
|
println!("RND_AB_ENC: {:x?}", rnd_ab_enc);
|
||||||
|
|
||||||
let cmd_challenge_response = APDUCommand {
|
let cmd_challenge_response = APDUCommand {
|
||||||
case: IsoCase::Case4Short,
|
case: IsoCase::Case4Short,
|
||||||
cla: 0x90,
|
cla: 0x90,
|
||||||
ins: APDUInstructions::CONTINUE as u8,
|
ins: APDUInstructions::CONTINUE as u8,
|
||||||
data: Some(rnd_ab_enc),
|
data: Some(rnd_ab_enc.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -515,23 +533,71 @@ fn generate_file_access_rights(read: u8, write: u8, read_write: u8, configure: u
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::fs::read;
|
||||||
|
use std::ops::Deref;
|
||||||
use hex_literal::hex;
|
use hex_literal::hex;
|
||||||
use mockall::{mock, predicate};
|
use mockall::{mock, predicate};
|
||||||
use crate::{APDUResponse, APDUCommand, Card};
|
use pcsc::{Context, MAX_BUFFER_SIZE, Protocols, Scope, ShareMode, Card, Error, Disposition};
|
||||||
|
use crate::{APDUResponse, APDUCommand, Card as CardTrait};
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
use crate::crypto::cipher_key::CipherKey;
|
||||||
|
use crate::crypto::cipher_type::CipherType;
|
||||||
use crate::desfire::desfire::{generate_session_key_des, generate_session_key_aes, generate_keysetting1, generate_keysetting2, generate_file_access_rights, Desfire};
|
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::desfire::{ChangeApplicationKey, ChangeMasterKeySettings, CreateDeleteFile, FileDirectoryAccess, ChangeMasterKey, CryptoOperationsType, FileIdentifiers};
|
||||||
use crate::error::Error::InvalidKeyID;
|
use crate::desfire::CryptoOperationsType::TDES;
|
||||||
|
use crate::error::Error::{InvalidKeyID, CardError};
|
||||||
|
use crate::iso7816_4::apdustatuswords::APDUStatusWord::OK;
|
||||||
|
|
||||||
mock! {
|
mock! {
|
||||||
pub VirtualCard {}
|
pub VirtualCard {}
|
||||||
impl Card for VirtualCard {
|
impl CardTrait for VirtualCard {
|
||||||
fn connect(&self);
|
fn connect(&mut self) -> Result<()>;
|
||||||
fn disconnect(&self);
|
fn disconnect(&mut self) -> Result<()>;
|
||||||
fn transmit(&self, apdu_cmd: APDUCommand) -> Result<APDUResponse>;
|
fn transmit(&self, apdu_cmd: APDUCommand) -> Result<APDUResponse>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PCSCCard {
|
||||||
|
ctx: Context,
|
||||||
|
reader: CString,
|
||||||
|
card: Option<Card>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CardTrait for PCSCCard {
|
||||||
|
fn connect(&mut self) -> Result<()> {
|
||||||
|
self.card = match self.ctx.connect(&self.reader, ShareMode::Shared, Protocols::ANY) {
|
||||||
|
Ok(card) => Some(card),
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to connect to card: {}", err);
|
||||||
|
return Err(CardError)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disconnect(&mut self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transmit(&self, apdu_cmd: APDUCommand) -> Result<APDUResponse> {
|
||||||
|
println!("{}", apdu_cmd);
|
||||||
|
let apdu = Vec::<u8>::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) {
|
||||||
|
Ok(rapdu) => rapdu,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to transmit APDU command to card: {}", err);
|
||||||
|
return Err(CardError)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
println!("APDU response: {:x?}", rapdu);
|
||||||
|
return Ok(APDUResponse::new(rapdu))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generate_sessionkey_des() {
|
fn generate_sessionkey_des() {
|
||||||
let rndA = hex!("a541a9dc9138df07");
|
let rndA = hex!("a541a9dc9138df07");
|
||||||
@ -619,7 +685,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn select_application() {
|
fn select_application() {
|
||||||
let mut mock = MockVirtualCard::new();
|
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{
|
mock.expect_transmit().withf(|x: &APDUCommand| (Vec::<u8>::try_from(x.clone()).unwrap()) == vec![0x90, 0x5a, 0x00, 0x00, 0x03, 0x33, 0x22, 0x11, 0x00]).return_once(move |_| Ok(APDUResponse{
|
||||||
body: None,
|
body: None,
|
||||||
sw1: 0x91,
|
sw1: 0x91,
|
||||||
sw2: 0x00
|
sw2: 0x00
|
||||||
@ -632,7 +698,180 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
assert!(desfire.select_application(0x112233).is_ok())
|
assert!(desfire.select_application(0x112233).is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn select_application_hardware() {
|
||||||
|
// Establish a PC/SC context.
|
||||||
|
let ctx = match Context::establish(Scope::User) {
|
||||||
|
Ok(ctx) => ctx,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to establish context: {}", err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// List available readers.
|
||||||
|
let mut readers_buf = [0; 2048];
|
||||||
|
let mut readers = match ctx.list_readers(&mut readers_buf) {
|
||||||
|
Ok(readers) => readers,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to list readers: {}", err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the first reader.
|
||||||
|
let reader = match readers.next() {
|
||||||
|
Some(reader) => reader,
|
||||||
|
None => {
|
||||||
|
println!("No readers are connected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut card = PCSCCard {
|
||||||
|
ctx,
|
||||||
|
reader: CString::from(reader),
|
||||||
|
card: None
|
||||||
|
};
|
||||||
|
|
||||||
|
card.connect();
|
||||||
|
|
||||||
|
let desfire = Desfire{
|
||||||
|
card: Box::new(card),
|
||||||
|
cbc_iv: None,
|
||||||
|
session_key: None
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(desfire.select_application(0x000000).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authenticate_des() {
|
||||||
|
let mut mock = MockVirtualCard::new();
|
||||||
|
mock.expect_transmit().withf(|x: &APDUCommand| (Vec::<u8>::try_from(x.clone()).unwrap()) == vec![0x90, 0x1a, 0x00, 0x00, 0x01, 0x00, 0x00]).return_once(move |_| Ok(APDUResponse{
|
||||||
|
sw1: 0x91,
|
||||||
|
sw2: 0xAF,
|
||||||
|
body: Some(vec![0x2b, 0xf9, 0xa9, 0x38, 0xec, 0xca, 0x02, 0xe2])
|
||||||
|
}));
|
||||||
|
|
||||||
|
mock.expect_transmit().withf(|x: &APDUCommand| (Vec::<u8>::try_from(x.clone()).unwrap()) == hex!("90af000010f8cdb2eaa42a3167dfcb53852ce267fd00")).return_once(move |_| Ok(APDUResponse{
|
||||||
|
sw1: 0x91,
|
||||||
|
sw2: 0x00,
|
||||||
|
body: Some(vec![0x07, 0xd8, 0x25, 0x60, 0x7a, 0x55, 0x2e, 0x2e])
|
||||||
|
}));
|
||||||
|
|
||||||
|
let rndA = hex!("5f7d1dd12d979173");
|
||||||
|
let mut key = CipherKey::new_empty(CipherType::TDES).unwrap();
|
||||||
|
println!("{:x?}", key.key.deref());
|
||||||
|
|
||||||
|
let mut desfire = Desfire{
|
||||||
|
card: Box::new(mock),
|
||||||
|
cbc_iv: None,
|
||||||
|
session_key: None
|
||||||
|
};
|
||||||
|
|
||||||
|
desfire.authenticate_iso_des(0x00, key.key.as_ref(), Some(rndA)).unwrap();
|
||||||
|
|
||||||
|
let sessionkey_expected = hex!("5f7d1dd1f449db5c5f7d1dd1f449db5c");
|
||||||
|
let iv_expected = hex!("0000000000000000");
|
||||||
|
|
||||||
|
assert_eq!(desfire.session_key.unwrap(), sessionkey_expected);
|
||||||
|
assert_eq!(desfire.cbc_iv.unwrap(), iv_expected);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn authenticate_des_hardware() {
|
||||||
|
// Establish a PC/SC context.
|
||||||
|
let ctx = match Context::establish(Scope::User) {
|
||||||
|
Ok(ctx) => ctx,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to establish context: {}", err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// List available readers.
|
||||||
|
let mut readers_buf = [0; 2048];
|
||||||
|
let mut readers = match ctx.list_readers(&mut readers_buf) {
|
||||||
|
Ok(readers) => readers,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to list readers: {}", err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the first reader.
|
||||||
|
let reader = match readers.next() {
|
||||||
|
Some(reader) => reader,
|
||||||
|
None => {
|
||||||
|
println!("No readers are connected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut card = PCSCCard {
|
||||||
|
ctx,
|
||||||
|
reader: CString::from(reader),
|
||||||
|
card: None
|
||||||
|
};
|
||||||
|
|
||||||
|
card.connect();
|
||||||
|
|
||||||
|
let mut desfire = Desfire{
|
||||||
|
card: Box::new(card),
|
||||||
|
cbc_iv: None,
|
||||||
|
session_key: None
|
||||||
|
};
|
||||||
|
|
||||||
|
desfire.select_application(0x000000);
|
||||||
|
|
||||||
|
let mut key = CipherKey::new_empty(CipherType::TDES).unwrap();
|
||||||
|
match desfire.authenticate_iso_des(0x00, key.key.as_ref(), None) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to authenticate: {}", err);
|
||||||
|
panic!("Failed to authenticate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authenticate_aes() {
|
||||||
|
let mut mock = MockVirtualCard::new();
|
||||||
|
mock.expect_transmit().withf(|x: &APDUCommand| (Vec::<u8>::try_from(x.clone()).unwrap()) == hex!("90aa0000010000")).return_once(move |_| Ok(APDUResponse{
|
||||||
|
sw1: 0x91,
|
||||||
|
sw2: 0xAF,
|
||||||
|
body: Some(hex!("a33856932308775cf464610c2b17a558").to_vec())
|
||||||
|
}));
|
||||||
|
|
||||||
|
mock.expect_transmit().withf(|x: &APDUCommand| (Vec::<u8>::try_from(x.clone()).unwrap()) == hex!("90af000020cbe9726faf54bc76b2055d0b9700e7dc97ecad5627f1d1702a16e8408d2a0ada00")).return_once(move |_| Ok(APDUResponse{
|
||||||
|
sw1: 0x91,
|
||||||
|
sw2: 0x00,
|
||||||
|
body: Some(hex!("8fdc476f6bac44fe9150e285abd68d48").to_vec())
|
||||||
|
}));
|
||||||
|
|
||||||
|
let rndA = hex!("2176770e7a6eb4bef00d5e4b201d1e57");
|
||||||
|
let mut key = CipherKey::new_empty(CipherType::AES).unwrap();
|
||||||
|
println!("{:x?}", key.key.deref());
|
||||||
|
|
||||||
|
let mut desfire = Desfire{
|
||||||
|
card: Box::new(mock),
|
||||||
|
cbc_iv: None,
|
||||||
|
session_key: None
|
||||||
|
};
|
||||||
|
|
||||||
|
desfire.authenticate_iso_aes(0x00, key.key.as_ref(), Some(rndA)).unwrap();
|
||||||
|
|
||||||
|
let sessionkey_expected = hex!("5f7d1dd1f449db5c5f7d1dd1f449db5c");
|
||||||
|
let iv_expected = hex!("0000000000000000");
|
||||||
|
|
||||||
|
assert_eq!(desfire.session_key.unwrap(), sessionkey_expected);
|
||||||
|
assert_eq!(desfire.cbc_iv.unwrap(), iv_expected);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,12 +24,14 @@ pub enum Error {
|
|||||||
DuplicateError,
|
DuplicateError,
|
||||||
FileNotFound,
|
FileNotFound,
|
||||||
InvalidApplicationID,
|
InvalidApplicationID,
|
||||||
|
ApplicationNotFound,
|
||||||
InvalidAPDUResponse,
|
InvalidAPDUResponse,
|
||||||
InvalidIsoCase,
|
InvalidIsoCase,
|
||||||
InvalidKeyID,
|
InvalidKeyID,
|
||||||
InvalidPICCChallenge,
|
InvalidPICCChallenge,
|
||||||
InvalidFileID,
|
InvalidFileID,
|
||||||
NumKeys,
|
NumKeys,
|
||||||
|
CardError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
@ -116,6 +118,12 @@ impl fmt::Display for Error {
|
|||||||
Error::NumKeys => {
|
Error::NumKeys => {
|
||||||
write!(f, "Number of Keys was larger then 0x0D")
|
write!(f, "Number of Keys was larger then 0x0D")
|
||||||
}
|
}
|
||||||
|
Error::CardError => {
|
||||||
|
write!(f, "An error occurred during communication with the card.")
|
||||||
|
}
|
||||||
|
Error::ApplicationNotFound => {
|
||||||
|
write!(f, "The application was not found on the card.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use std::convert::TryFrom;
|
|||||||
use crate::error::{Result, Error};
|
use crate::error::{Result, Error};
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Eq, PartialEq, Hash, Debug)]
|
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
|
||||||
pub enum IsoCase {
|
pub enum IsoCase {
|
||||||
/// <summary>No command data. No response data.</summary>
|
/// <summary>No command data. No response data.</summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
@ -82,7 +82,7 @@ impl Default for IsoCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Eq, PartialEq, Hash, Debug)]
|
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
|
||||||
pub enum SCardProtocol {
|
pub enum SCardProtocol {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// protocol not defined.
|
/// protocol not defined.
|
||||||
@ -106,10 +106,10 @@ pub enum SCardProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SCardProtocol {
|
impl Default for SCardProtocol {
|
||||||
fn default() -> Self { SCardProtocol::Unset }
|
fn default() -> Self { SCardProtocol::Any }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Debug, Default)]
|
#[derive(Eq, PartialEq, Hash, Debug, Default, Clone)]
|
||||||
pub struct APDUCommand {
|
pub struct APDUCommand {
|
||||||
pub case: IsoCase,
|
pub case: IsoCase,
|
||||||
pub protocol: SCardProtocol,
|
pub protocol: SCardProtocol,
|
||||||
@ -118,8 +118,7 @@ pub struct APDUCommand {
|
|||||||
pub p1: u8,
|
pub p1: u8,
|
||||||
pub p2: u8,
|
pub p2: u8,
|
||||||
pub data: Option<Vec<u8>>,
|
pub data: Option<Vec<u8>>,
|
||||||
pub lc: i32,
|
pub le: usize,
|
||||||
pub le: i32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<APDUCommand> for Vec<u8> {
|
impl TryFrom<APDUCommand> for Vec<u8> {
|
||||||
@ -144,11 +143,11 @@ impl TryFrom<APDUCommand> for Vec<u8> {
|
|||||||
v.push(cmd.le as u8);
|
v.push(cmd.le as u8);
|
||||||
}
|
}
|
||||||
IsoCase::Case3Short => {
|
IsoCase::Case3Short => {
|
||||||
v.push(cmd.lc as u8);
|
v.push(cmd.lc() as u8);
|
||||||
v.extend(cmd.data.unwrap());
|
v.extend(cmd.data.unwrap());
|
||||||
}
|
}
|
||||||
IsoCase::Case4Short => {
|
IsoCase::Case4Short => {
|
||||||
v.push(cmd.lc as u8);
|
v.push(cmd.data.as_ref().unwrap().len() as u8);
|
||||||
v.extend(cmd.data.unwrap());
|
v.extend(cmd.data.unwrap());
|
||||||
if cmd.protocol == SCardProtocol::T0 {
|
if cmd.protocol == SCardProtocol::T0 {
|
||||||
v.push(0x00);
|
v.push(0x00);
|
||||||
@ -157,6 +156,7 @@ impl TryFrom<APDUCommand> for Vec<u8> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
||||||
return Err(Error::InvalidIsoCase)
|
return Err(Error::InvalidIsoCase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,11 +166,15 @@ impl TryFrom<APDUCommand> for Vec<u8> {
|
|||||||
//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?
|
||||||
|
|
||||||
// impl APDUCommand {
|
impl APDUCommand {
|
||||||
// pub fn new(case: IsoCase) -> Self {
|
// pub fn new(case: IsoCase) -> Self {
|
||||||
// APDUCommand { case, protocol: (), cla: (), ins: (), p1: (), p2: (), data: (), lc: (), le: () }
|
// APDUCommand { case, protocol: (), cla: (), ins: (), p1: (), p2: (), data: (), lc: (), le: () }
|
||||||
// }
|
// }
|
||||||
// }
|
|
||||||
|
pub fn lc(&self) -> usize {
|
||||||
|
self.data.as_ref().unwrap().len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for APDUCommand {
|
impl fmt::Display for APDUCommand {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
@ -182,14 +186,14 @@ impl fmt::Display for APDUCommand {
|
|||||||
write!(f, "(CASE: 2) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LE: {:#X}", self.cla, self.ins, self.p1, self.p2, self.le)
|
write!(f, "(CASE: 2) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LE: {:#X}", self.cla, self.ins, self.p1, self.p2, self.le)
|
||||||
}
|
}
|
||||||
IsoCase::Case3Short | IsoCase::Case3Extended => {
|
IsoCase::Case3Short | IsoCase::Case3Extended => {
|
||||||
write!(f, "(CASE: 3) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LC: {:#X} | data: [ ", self.cla, self.ins, self.p1, self.p2, self.lc)?;
|
write!(f, "(CASE: 3) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LC: {:#X} | data: [ ", self.cla, self.ins, self.p1, self.p2, self.lc())?;
|
||||||
for b in self.data.as_ref().unwrap() {
|
for b in self.data.as_ref().unwrap() {
|
||||||
write!(f, "{:#X} ", b)?;
|
write!(f, "{:#X} ", b)?;
|
||||||
}
|
}
|
||||||
write!(f, "]")
|
write!(f, "]")
|
||||||
}
|
}
|
||||||
IsoCase::Case4Short | IsoCase::Case4Extended => {
|
IsoCase::Case4Short | IsoCase::Case4Extended => {
|
||||||
write!(f, "(CASE: 4) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LC: {:#X} | data: [ ", self.cla, self.ins, self.p1, self.p2, self.lc)?;
|
write!(f, "(CASE: 4) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LC: {:#X} | data: [ ", self.cla, self.ins, self.p1, self.p2, self.lc())?;
|
||||||
for b in self.data.as_ref().unwrap() {
|
for b in self.data.as_ref().unwrap() {
|
||||||
write!(f, "{:#X} ", b)?;
|
write!(f, "{:#X} ", b)?;
|
||||||
}
|
}
|
||||||
@ -286,7 +290,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
println!("{}", command1.to_string());
|
println!("{}", command1.to_string());
|
||||||
assert_eq!("(CASE: 3) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LC: 0x0 | data: [ 0x1 0x2 0x3 ]", command1.to_string());
|
assert_eq!("(CASE: 3) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LC: 0x3 | data: [ 0x1 0x2 0x3 ]", command1.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -300,6 +304,6 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
println!("{}", command1.to_string());
|
println!("{}", command1.to_string());
|
||||||
assert_eq!("(CASE: 4) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LC: 0x0 | data: [ 0x1 0x2 0x3 ] | LE: 0x0", command1.to_string());
|
assert_eq!("(CASE: 4) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LC: 0x3 | data: [ 0x1 0x2 0x3 ] | LE: 0x0", command1.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use crate::iso7816_4::apdustatuswords::{APDUStatusWord, APDUStatusWord2};
|
use crate::iso7816_4::apdustatuswords::{APDUStatusWord, APDUStatusWord2};
|
||||||
use crate::error::{Result};
|
use crate::error::{Result};
|
||||||
use num_traits::FromPrimitive;
|
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, ApplicationNotFound};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Debug, Default)]
|
#[derive(Eq, PartialEq, Hash, Debug, Default)]
|
||||||
@ -32,7 +32,7 @@ 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 {
|
match &self.body {
|
||||||
None => {
|
None => {
|
||||||
write!(f, "SW1: {:#X} | SW2: 0x{:#X}", self.sw1, self.sw2)
|
write!(f, "SW1: {:#X} | SW2: 0x{:#X}", self.sw1, self.sw2)
|
||||||
},
|
},
|
||||||
@ -42,13 +42,22 @@ impl fmt::Display for APDUResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl APDUResponse {
|
impl APDUResponse {
|
||||||
pub fn new(raw: &[u8]) -> Self {
|
pub fn new(raw: &[u8]) -> Self {
|
||||||
|
if raw.len() < 2 {
|
||||||
|
panic!("APDU response must be at least 2 bytes long");
|
||||||
|
} else if raw.len() == 2 {
|
||||||
APDUResponse {
|
APDUResponse {
|
||||||
body: Some(raw[..raw.len() - 1].to_vec()),
|
body: None,
|
||||||
|
sw1: raw[0],
|
||||||
|
sw2: raw[1],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
APDUResponse {
|
||||||
|
body: Some(raw[..raw.len() - 2].to_vec()),
|
||||||
sw1: raw[raw.len() - 2],
|
sw1: raw[raw.len() - 2],
|
||||||
sw2: raw[raw.len() - 3]
|
sw2: raw[raw.len() - 1],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +105,7 @@ impl APDUResponse {
|
|||||||
Some(APDUStatusWord2::CommandAborted) => {Err(CommandAborted)}
|
Some(APDUStatusWord2::CommandAborted) => {Err(CommandAborted)}
|
||||||
Some(APDUStatusWord2::DuplicateError) => {Err(DuplicateError)}
|
Some(APDUStatusWord2::DuplicateError) => {Err(DuplicateError)}
|
||||||
Some(APDUStatusWord2::FileNotFound) => {Err(FileNotFound)}
|
Some(APDUStatusWord2::FileNotFound) => {Err(FileNotFound)}
|
||||||
|
Some(APDUStatusWord2::ApplicationNotFound) => {Err(ApplicationNotFound)}
|
||||||
None => { Err(InvalidStatusWord)}
|
None => { Err(InvalidStatusWord)}
|
||||||
}
|
}
|
||||||
} else { Err(InvalidStatusWord) }
|
} else { Err(InvalidStatusWord) }
|
||||||
|
@ -150,4 +150,5 @@ pub enum APDUStatusWord2 {
|
|||||||
CommandAborted = 0xCA,
|
CommandAborted = 0xCA,
|
||||||
DuplicateError = 0xDE,
|
DuplicateError = 0xDE,
|
||||||
FileNotFound = 0xF0,
|
FileNotFound = 0xF0,
|
||||||
|
ApplicationNotFound = 0xA0,
|
||||||
}
|
}
|
@ -11,12 +11,12 @@ pub trait Card {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connect to Smartcard
|
/// Connect to Smartcard
|
||||||
/// </summary>
|
/// </summary>
|
||||||
fn connect(&self);
|
fn connect(&mut self) -> Result<()>;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disconnect from Smartcard
|
/// Disconnect from Smartcard
|
||||||
/// </summary>
|
/// </summary>
|
||||||
fn disconnect(&self);
|
fn disconnect(&mut self) -> Result<()>;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transmit APDU Command to Smartcard
|
/// Transmit APDU Command to Smartcard
|
||||||
|
Loading…
x
Reference in New Issue
Block a user