use crate::apdu::{APDU, Response, SW};
use crate::tlv::{TLVBuilder, TLVEncoder};
use crate::crypto::rsa::RsaOperations;
use crate::crypto::ecc_nist::{EccNistOperations, EccCurve};
use crate::crypto::tdes::TDesOperations;
use super::data_objects::{PIVDataObjects, PIVKeySlot, PIVAlgorithm, PIVKeyData};
use super::security_state::PIVSecurityState;
use log::{debug, info, warn};
pub const PIV_AID: &[u8] = &[0xA0, 0x00, 0x00, 0x03, 0x08];
pub mod key_ref {
pub const PIV_PIN: u8 = 0x80;
pub const PIV_PUK: u8 = 0x81;
pub const MGMT_KEY: u8 = 0x9B;
}
pub struct PIVApplet {
data_objects: PIVDataObjects,
security_state: PIVSecurityState,
response_buffer: Vec<u8>,
response_offset: usize,
current_challenge: Option<Vec<u8>>,
version: (u8, u8, u8),
serial: u32,
command_chain_buffer: Vec<u8>,
command_chain_ins: Option<u8>,
command_chain_p1: u8,
command_chain_p2: u8,
}
impl PIVApplet {
pub fn new() -> Self {
let mut serial_bytes = [0u8; 4];
use rand::RngCore;
rand::rngs::OsRng.fill_bytes(&mut serial_bytes);
let serial = u32::from_be_bytes(serial_bytes);
Self {
data_objects: PIVDataObjects::new(),
security_state: PIVSecurityState::new(),
response_buffer: Vec::new(),
response_offset: 0,
current_challenge: None,
version: (1, 0, 0),
serial,
command_chain_buffer: Vec::new(),
command_chain_ins: None,
command_chain_p1: 0,
command_chain_p2: 0,
}
}
pub fn process_apdu(&mut self, cmd: &APDU) -> Response {
debug!("PIV APDU: CLA={:02X} INS={:02X} P1={:02X} P2={:02X} data_len={}",
cmd.cla, cmd.ins, cmd.p1, cmd.p2, cmd.data.len());
let is_chaining = (cmd.cla & 0x10) != 0;
if is_chaining || self.command_chain_ins.is_some() {
if self.command_chain_ins.is_none() {
self.command_chain_ins = Some(cmd.ins);
self.command_chain_p1 = cmd.p1;
self.command_chain_p2 = cmd.p2;
self.command_chain_buffer.clear();
}
if self.command_chain_ins != Some(cmd.ins) {
self.command_chain_ins = None;
self.command_chain_buffer.clear();
return Response::error(SW::CONDITIONS_NOT_SATISFIED);
}
self.command_chain_buffer.extend_from_slice(&cmd.data);
debug!("Command chaining: buffered {} bytes, total {}",
cmd.data.len(), self.command_chain_buffer.len());
if is_chaining {
return Response::ok();
} else {
let complete_cmd = APDU {
cla: cmd.cla & 0xEF, ins: self.command_chain_ins.unwrap(),
p1: self.command_chain_p1,
p2: self.command_chain_p2,
data: std::mem::take(&mut self.command_chain_buffer),
le: cmd.le,
};
self.command_chain_ins = None;
debug!("Command chain complete: {} bytes total", complete_cmd.data.len());
return match complete_cmd.ins {
0xDB => self.handle_put_data(&complete_cmd),
_ => {
warn!("Command chaining not supported for INS {:02X}", complete_cmd.ins);
Response::error(SW::INS_NOT_SUPPORTED)
}
};
}
}
match cmd.ins {
0xA4 => self.handle_select(cmd),
0x20 => self.handle_verify(cmd),
0x24 => self.handle_change_reference_data(cmd),
0x2C => self.handle_reset_retry_counter(cmd),
0xCB => self.handle_get_data(cmd),
0xDB => self.handle_put_data(cmd),
0x47 => self.handle_generate_key(cmd),
0x87 => self.handle_general_authenticate(cmd),
0xC0 => self.handle_get_response(cmd),
0xFD => self.handle_get_version(cmd),
0xF8 => self.handle_get_serial(cmd),
_ => {
warn!("Unknown PIV instruction: {:02X}", cmd.ins);
Response::error(SW::INS_NOT_SUPPORTED)
}
}
}
pub fn get_data_objects(&self) -> &PIVDataObjects {
&self.data_objects
}
pub fn get_data_objects_mut(&mut self) -> &mut PIVDataObjects {
&mut self.data_objects
}
pub fn reset(&mut self) {
self.security_state.clear_all();
self.response_buffer.clear();
self.response_offset = 0;
self.current_challenge = None;
}
fn handle_select(&mut self, cmd: &APDU) -> Response {
if cmd.p1 != 0x04 {
return Response::error(SW::WRONG_P1_P2);
}
if !cmd.data.starts_with(PIV_AID) {
return Response::error(SW::FILE_NOT_FOUND);
}
self.security_state.clear_all();
self.current_challenge = None;
let response_data = [
0x4F, 0x06, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x79, 0x07, 0x4F, 0x05, ].iter().chain(PIV_AID.iter()).copied().collect::<Vec<u8>>();
info!("PIV application selected");
Response::success(response_data)
}
fn handle_verify(&mut self, cmd: &APDU) -> Response {
match cmd.p2 {
key_ref::PIV_PIN => {
if cmd.data.is_empty() {
return Response::counter_warning(self.data_objects.pin_retries);
}
let pin: Vec<u8> = cmd.data.iter()
.take_while(|&&b| b != 0xFF)
.copied()
.collect();
if self.data_objects.pin_retries == 0 {
return Response::error(SW::AUTH_METHOD_BLOCKED);
}
if pin == self.data_objects.pin {
self.security_state.set_pin_verified(true);
self.data_objects.pin_retries = 3; info!("PIV PIN verified successfully");
Response::ok()
} else {
self.data_objects.pin_retries = self.data_objects.pin_retries.saturating_sub(1);
warn!("PIV PIN verification failed, {} retries remaining", self.data_objects.pin_retries);
if self.data_objects.pin_retries == 0 {
Response::error(SW::AUTH_METHOD_BLOCKED)
} else {
Response::counter_warning(self.data_objects.pin_retries)
}
}
}
key_ref::PIV_PUK => {
if cmd.data.is_empty() {
return Response::counter_warning(self.data_objects.puk_retries);
}
if self.data_objects.puk_retries == 0 {
return Response::error(SW::AUTH_METHOD_BLOCKED);
}
if cmd.data == self.data_objects.puk {
self.data_objects.puk_retries = 3;
Response::ok()
} else {
self.data_objects.puk_retries = self.data_objects.puk_retries.saturating_sub(1);
if self.data_objects.puk_retries == 0 {
Response::error(SW::AUTH_METHOD_BLOCKED)
} else {
Response::counter_warning(self.data_objects.puk_retries)
}
}
}
_ => Response::error(SW::WRONG_P1_P2),
}
}
fn handle_change_reference_data(&mut self, cmd: &APDU) -> Response {
if cmd.data.len() != 16 {
return Response::error(SW::WRONG_LENGTH);
}
let old_value: Vec<u8> = cmd.data[..8].iter()
.take_while(|&&b| b != 0xFF)
.copied()
.collect();
let new_value: Vec<u8> = cmd.data[8..].iter()
.take_while(|&&b| b != 0xFF)
.copied()
.collect();
match cmd.p2 {
key_ref::PIV_PIN => {
if self.data_objects.pin_retries == 0 {
return Response::error(SW::AUTH_METHOD_BLOCKED);
}
if old_value == self.data_objects.pin {
self.data_objects.pin = new_value;
self.data_objects.pin_retries = 3;
info!("PIV PIN changed successfully");
Response::ok()
} else {
self.data_objects.pin_retries = self.data_objects.pin_retries.saturating_sub(1);
if self.data_objects.pin_retries == 0 {
Response::error(SW::AUTH_METHOD_BLOCKED)
} else {
Response::counter_warning(self.data_objects.pin_retries)
}
}
}
key_ref::PIV_PUK => {
if self.data_objects.puk_retries == 0 {
return Response::error(SW::AUTH_METHOD_BLOCKED);
}
if old_value == self.data_objects.puk {
self.data_objects.puk = new_value;
self.data_objects.puk_retries = 3;
info!("PIV PUK changed successfully");
Response::ok()
} else {
self.data_objects.puk_retries = self.data_objects.puk_retries.saturating_sub(1);
if self.data_objects.puk_retries == 0 {
Response::error(SW::AUTH_METHOD_BLOCKED)
} else {
Response::counter_warning(self.data_objects.puk_retries)
}
}
}
_ => Response::error(SW::WRONG_P1_P2),
}
}
fn handle_reset_retry_counter(&mut self, cmd: &APDU) -> Response {
if cmd.p2 != key_ref::PIV_PIN {
return Response::error(SW::WRONG_P1_P2);
}
if cmd.data.len() != 16 {
return Response::error(SW::WRONG_LENGTH);
}
let puk = &cmd.data[..8];
let new_pin: Vec<u8> = cmd.data[8..].iter()
.take_while(|&&b| b != 0xFF)
.copied()
.collect();
if self.data_objects.puk_retries == 0 {
return Response::error(SW::AUTH_METHOD_BLOCKED);
}
if puk == self.data_objects.puk.as_slice() {
self.data_objects.pin = new_pin;
self.data_objects.pin_retries = 3;
self.data_objects.puk_retries = 3;
info!("PIV PIN reset with PUK successfully");
Response::ok()
} else {
self.data_objects.puk_retries = self.data_objects.puk_retries.saturating_sub(1);
if self.data_objects.puk_retries == 0 {
Response::error(SW::AUTH_METHOD_BLOCKED)
} else {
Response::counter_warning(self.data_objects.puk_retries)
}
}
}
fn handle_get_data(&self, cmd: &APDU) -> Response {
if cmd.p1 != 0x3F || cmd.p2 != 0xFF {
return Response::error(SW::WRONG_P1_P2);
}
if cmd.data.len() < 2 || cmd.data[0] != 0x5C {
return Response::error(SW::WRONG_DATA);
}
let tag_len = cmd.data[1] as usize;
if cmd.data.len() < 2 + tag_len {
return Response::error(SW::WRONG_DATA);
}
let object_id = &cmd.data[2..2 + tag_len];
let data = match object_id {
[0x5F, 0xC1, 0x02] => {
if self.data_objects.chuid.is_empty() {
return Response::error(SW::FILE_NOT_FOUND);
}
&self.data_objects.chuid
}
[0x5F, 0xC1, 0x07] => {
if self.data_objects.ccc.is_empty() {
return Response::error(SW::FILE_NOT_FOUND);
}
&self.data_objects.ccc
}
[0x5F, 0xC1, 0x05] => {
if self.data_objects.key_9a.certificate.is_empty() {
return Response::error(SW::FILE_NOT_FOUND);
}
&self.data_objects.key_9a.certificate
}
[0x5F, 0xC1, 0x0A] => {
if self.data_objects.key_9c.certificate.is_empty() {
return Response::error(SW::FILE_NOT_FOUND);
}
&self.data_objects.key_9c.certificate
}
[0x5F, 0xC1, 0x0B] => {
if self.data_objects.key_9d.certificate.is_empty() {
return Response::error(SW::FILE_NOT_FOUND);
}
&self.data_objects.key_9d.certificate
}
[0x5F, 0xC1, 0x01] => {
if self.data_objects.key_9e.certificate.is_empty() {
return Response::error(SW::FILE_NOT_FOUND);
}
&self.data_objects.key_9e.certificate
}
[0x7E] => {
if self.data_objects.discovery.is_empty() {
return Response::error(SW::FILE_NOT_FOUND);
}
&self.data_objects.discovery
}
_ => {
debug!("Unknown PIV data object: {:02X?}", object_id);
return Response::error(SW::FILE_NOT_FOUND);
}
};
let response = TLVEncoder::encode(0x53, data);
debug!("GET DATA for object {:02X?}: {} bytes", object_id, response.len());
Response::success(response)
}
fn handle_put_data(&mut self, cmd: &APDU) -> Response {
if !self.security_state.is_management_key_authenticated() {
return Response::error(SW::SECURITY_STATUS_NOT_SATISFIED);
}
if cmd.p1 != 0x3F || cmd.p2 != 0xFF {
return Response::error(SW::WRONG_P1_P2);
}
if cmd.data.len() < 2 || cmd.data[0] != 0x5C {
return Response::error(SW::WRONG_DATA);
}
let tag_len = cmd.data[1] as usize;
if cmd.data.len() < 2 + tag_len {
return Response::error(SW::WRONG_DATA);
}
let object_id = &cmd.data[2..2 + tag_len];
let remaining = &cmd.data[2 + tag_len..];
if remaining.is_empty() || remaining[0] != 0x53 {
return Response::error(SW::WRONG_DATA);
}
let (data_len, data_offset) = if remaining.len() < 2 {
return Response::error(SW::WRONG_DATA);
} else if remaining[1] == 0x82 {
if remaining.len() < 4 {
return Response::error(SW::WRONG_DATA);
}
let len = ((remaining[2] as usize) << 8) | (remaining[3] as usize);
(len, 4)
} else if remaining[1] == 0x81 {
if remaining.len() < 3 {
return Response::error(SW::WRONG_DATA);
}
(remaining[2] as usize, 3)
} else {
(remaining[1] as usize, 2)
};
if remaining.len() < data_offset + data_len {
return Response::error(SW::WRONG_DATA);
}
let data = remaining[data_offset..data_offset + data_len].to_vec();
match object_id {
[0x5F, 0xC1, 0x02] => self.data_objects.chuid = data,
[0x5F, 0xC1, 0x07] => self.data_objects.ccc = data,
[0x5F, 0xC1, 0x05] => self.data_objects.key_9a.certificate = data,
[0x5F, 0xC1, 0x0A] => self.data_objects.key_9c.certificate = data,
[0x5F, 0xC1, 0x0B] => self.data_objects.key_9d.certificate = data,
[0x5F, 0xC1, 0x01] => self.data_objects.key_9e.certificate = data,
[0x7E] => self.data_objects.discovery = data,
_ => {
debug!("Unknown PIV data object for PUT: {:02X?}", object_id);
return Response::error(SW::FILE_NOT_FOUND);
}
}
info!("PUT DATA for object {:02X?}: {} bytes", object_id, data_len);
Response::ok()
}
fn handle_generate_key(&mut self, cmd: &APDU) -> Response {
if !self.security_state.is_management_key_authenticated() {
return Response::error(SW::SECURITY_STATUS_NOT_SATISFIED);
}
let slot = match PIVKeySlot::from_byte(cmd.p2) {
Some(s) => s,
None => return Response::error(SW::WRONG_P1_P2),
};
if cmd.data.len() < 5 || cmd.data[0] != 0xAC {
return Response::error(SW::WRONG_DATA);
}
let mut idx = 2;
let mut algorithm_byte = None;
while idx + 2 <= cmd.data.len() {
let tag = cmd.data[idx];
let length = cmd.data[idx + 1] as usize;
if tag == 0x80 && length == 1 && idx + 2 < cmd.data.len() {
algorithm_byte = Some(cmd.data[idx + 2]);
break;
}
idx += 2 + length;
}
let algorithm = match algorithm_byte.and_then(PIVAlgorithm::from_byte) {
Some(a) => a,
None => return Response::error(SW::WRONG_DATA),
};
info!("Generating {:?} key in slot {:?}", algorithm, slot);
match algorithm {
PIVAlgorithm::RSA2048 => self.generate_rsa_key(slot, 2048),
PIVAlgorithm::ECCP256 => self.generate_ecc_key(slot, algorithm),
PIVAlgorithm::ECCP384 => self.generate_ecc_key(slot, algorithm),
PIVAlgorithm::TDES => Response::error(SW::FUNCTION_NOT_SUPPORTED),
}
}
fn generate_rsa_key(&mut self, slot: PIVKeySlot, bits: usize) -> Response {
match RsaOperations::generate_keypair(bits) {
Ok((priv_key, pub_key_data)) => {
let n = RsaOperations::get_modulus(&pub_key_data).unwrap_or_default();
let e = RsaOperations::get_exponent(&pub_key_data).unwrap_or_default();
let pub_tlv = TLVBuilder::new()
.add(0x81, &n)
.add(0x82, &e)
.wrap(0x7F49)
.build();
if let Some(key_data) = self.data_objects.get_key_mut(slot) {
key_data.algorithm = PIVAlgorithm::RSA2048 as u8;
key_data.private_key = priv_key;
key_data.public_key = pub_tlv.clone();
}
info!("Generated RSA-{} key in slot {:?}, modulus {} bytes", bits, slot, n.len());
Response::success(pub_tlv)
}
Err(_) => Response::error(SW::EXEC_ERROR),
}
}
fn generate_ecc_key(&mut self, slot: PIVKeySlot, algorithm: PIVAlgorithm) -> Response {
let curve = if algorithm == PIVAlgorithm::ECCP256 {
EccCurve::P256
} else {
EccCurve::P384
};
match EccNistOperations::generate_keypair(curve) {
Ok((priv_key, pub_point)) => {
let pub_tlv = TLVBuilder::new()
.add(0x86, &pub_point)
.wrap(0x7F49)
.build();
if let Some(key_data) = self.data_objects.get_key_mut(slot) {
key_data.algorithm = algorithm as u8;
key_data.private_key = priv_key;
key_data.public_key = pub_tlv.clone();
}
info!("Generated ECC {:?} key in slot {:?}", algorithm, slot);
Response::success(pub_tlv)
}
Err(_) => Response::error(SW::EXEC_ERROR),
}
}
fn handle_general_authenticate(&mut self, cmd: &APDU) -> Response {
let _algo = cmd.p1;
let key_ref = cmd.p2;
if cmd.data.is_empty() || cmd.data[0] != 0x7C {
return Response::error(SW::WRONG_DATA);
}
let contents = if cmd.data.len() > 2 { &cmd.data[2..] } else { &[] };
let mut tags: std::collections::HashMap<u8, Vec<u8>> = std::collections::HashMap::new();
let mut idx = 0;
while idx + 1 < contents.len() {
let tag = contents[idx];
let length = contents[idx + 1] as usize;
let value = if idx + 2 + length <= contents.len() {
contents[idx + 2..idx + 2 + length].to_vec()
} else {
Vec::new()
};
tags.insert(tag, value);
idx += 2 + length;
}
if key_ref == key_ref::MGMT_KEY {
return self.authenticate_mgmt_key(&tags);
}
let slot = match PIVKeySlot::from_byte(key_ref) {
Some(s) => s,
None => return Response::error(SW::WRONG_P1_P2),
};
if slot != PIVKeySlot::CardAuthentication && !self.security_state.is_pin_verified() {
return Response::error(SW::SECURITY_STATUS_NOT_SATISFIED);
}
let key_data = match self.data_objects.get_key(slot) {
Some(k) if !k.private_key.is_empty() => k.clone(),
_ => return Response::error(SW::REFERENCED_DATA_NOT_FOUND),
};
if let Some(empty_82) = tags.get(&0x82) {
if empty_82.is_empty() {
if let Some(challenge) = tags.get(&0x81) {
return self.sign_data(&key_data, challenge);
}
return Response::error(SW::WRONG_DATA);
}
}
if let Some(cipher_text) = tags.get(&0x85) {
return self.decrypt_data(&key_data, cipher_text);
}
Response::error(SW::WRONG_DATA)
}
fn authenticate_mgmt_key(&mut self, tags: &std::collections::HashMap<u8, Vec<u8>>) -> Response {
if let Some(witness_tag) = tags.get(&0x80) {
if witness_tag.is_empty() && !tags.contains_key(&0x81) {
let mut witness = vec![0u8; 8];
use rand::RngCore;
rand::rngs::OsRng.fill_bytes(&mut witness);
self.current_challenge = Some(witness.clone());
match TDesOperations::encrypt_ecb(&self.data_objects.management_key, &witness) {
Ok(encrypted_witness) => {
let mut response = vec![0x7C, 0x0A, 0x80, 0x08];
response.extend_from_slice(&encrypted_witness);
debug!("Management key auth: witness {:02X?}, encrypted {:02X?}", witness, encrypted_witness);
return Response::success(response);
}
Err(_) => return Response::error(SW::EXEC_ERROR),
}
}
}
if let (Some(host_witness), Some(host_challenge)) = (tags.get(&0x80), tags.get(&0x81)) {
let original_challenge = match &self.current_challenge {
Some(c) => c.clone(),
None => return Response::error(SW::CONDITIONS_NOT_SATISFIED),
};
debug!("Original witness: {:02X?}", original_challenge);
debug!("Host witness (plaintext): {:02X?}", host_witness);
if host_witness.as_slice() != original_challenge.as_slice() {
warn!("Management key authentication failed");
self.current_challenge = None;
return Response::error(SW::SECURITY_STATUS_NOT_SATISFIED);
}
match TDesOperations::encrypt_ecb(&self.data_objects.management_key, host_challenge) {
Ok(our_response) => {
self.security_state.set_management_key_authenticated(true);
self.current_challenge = None;
let mut response = vec![0x7C, 0x0A, 0x82, 0x08];
response.extend_from_slice(&our_response);
info!("Management key authenticated successfully");
return Response::success(response);
}
Err(_) => {
self.current_challenge = None;
return Response::error(SW::EXEC_ERROR);
}
}
}
Response::error(SW::WRONG_DATA)
}
fn sign_data(&self, key_data: &PIVKeyData, data: &[u8]) -> Response {
let algorithm = PIVAlgorithm::from_byte(key_data.algorithm);
let signature = match algorithm {
Some(PIVAlgorithm::RSA2048) => {
let n = match RsaOperations::get_modulus(&key_data.public_key) {
Some(n) => n,
None => return Response::error(SW::EXEC_ERROR),
};
let rsa_key = match RsaOperations::decode_private_key(&key_data.private_key, &n) {
Ok(key) => key,
Err(_) => return Response::error(SW::EXEC_ERROR),
};
match RsaOperations::raw_sign(&rsa_key, data) {
Ok(sig) => sig,
Err(_) => return Response::error(SW::EXEC_ERROR),
}
}
Some(PIVAlgorithm::ECCP256) => {
match EccNistOperations::sign(EccCurve::P256, &key_data.private_key, data) {
Ok(sig) => sig,
Err(_) => return Response::error(SW::EXEC_ERROR),
}
}
Some(PIVAlgorithm::ECCP384) => {
match EccNistOperations::sign(EccCurve::P384, &key_data.private_key, data) {
Ok(sig) => sig,
Err(_) => return Response::error(SW::EXEC_ERROR),
}
}
_ => return Response::error(SW::FUNCTION_NOT_SUPPORTED),
};
let response = if signature.len() <= 127 {
let mut resp = vec![0x7C, (signature.len() + 2) as u8, 0x82, signature.len() as u8];
resp.extend_from_slice(&signature);
resp
} else {
let mut resp = vec![0x7C, 0x81, (signature.len() + 3) as u8, 0x82, 0x81, signature.len() as u8];
resp.extend_from_slice(&signature);
resp
};
debug!("Signed {} bytes, signature {} bytes", data.len(), signature.len());
Response::success(response)
}
fn decrypt_data(&self, key_data: &PIVKeyData, cipher_text: &[u8]) -> Response {
let algorithm = PIVAlgorithm::from_byte(key_data.algorithm);
let plaintext = match algorithm {
Some(PIVAlgorithm::RSA2048) => {
let n = match RsaOperations::get_modulus(&key_data.public_key) {
Some(n) => n,
None => return Response::error(SW::EXEC_ERROR),
};
let rsa_key = match RsaOperations::decode_private_key(&key_data.private_key, &n) {
Ok(key) => key,
Err(_) => return Response::error(SW::EXEC_ERROR),
};
match RsaOperations::decrypt(&rsa_key, cipher_text) {
Ok(pt) => pt,
Err(_) => return Response::error(SW::EXEC_ERROR),
}
}
Some(PIVAlgorithm::ECCP256) | Some(PIVAlgorithm::ECCP384) => {
return Response::error(SW::FUNCTION_NOT_SUPPORTED);
}
_ => return Response::error(SW::FUNCTION_NOT_SUPPORTED),
};
let response = if plaintext.len() <= 127 {
let mut resp = vec![0x7C, (plaintext.len() + 2) as u8, 0x82, plaintext.len() as u8];
resp.extend_from_slice(&plaintext);
resp
} else {
let mut resp = vec![0x7C, 0x81, (plaintext.len() + 3) as u8, 0x82, 0x81, plaintext.len() as u8];
resp.extend_from_slice(&plaintext);
resp
};
debug!("Decrypted/ECDH result: {} bytes", plaintext.len());
Response::success(response)
}
fn handle_get_response(&mut self, cmd: &APDU) -> Response {
if self.response_buffer.is_empty() {
return Response::error(SW::CONDITIONS_NOT_SATISFIED);
}
let le = cmd.le.unwrap_or(256) as usize;
let remaining = self.response_buffer.len() - self.response_offset;
let to_send = std::cmp::min(le, remaining);
let data = self.response_buffer[self.response_offset..self.response_offset + to_send].to_vec();
self.response_offset += to_send;
if self.response_offset >= self.response_buffer.len() {
self.response_buffer.clear();
self.response_offset = 0;
Response::success(data)
} else {
let still_remaining = self.response_buffer.len() - self.response_offset;
Response::more_data(data, std::cmp::min(still_remaining, 255) as u8)
}
}
fn handle_get_version(&self, _cmd: &APDU) -> Response {
let data = vec![self.version.0, self.version.1, self.version.2];
Response::success(data)
}
fn handle_get_serial(&self, _cmd: &APDU) -> Response {
let data = self.serial.to_be_bytes().to_vec();
Response::success(data)
}
}
impl Default for PIVApplet {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_select_piv() {
let mut applet = PIVApplet::new();
let cmd = APDU {
cla: 0x00,
ins: 0xA4,
p1: 0x04,
p2: 0x00,
data: PIV_AID.to_vec(),
le: None,
};
let response = applet.process_apdu(&cmd);
assert!(response.is_okay());
}
#[test]
fn test_verify_pin() {
let mut applet = PIVApplet::new();
let select_cmd = APDU {
cla: 0x00,
ins: 0xA4,
p1: 0x04,
p2: 0x00,
data: PIV_AID.to_vec(),
le: None,
};
applet.process_apdu(&select_cmd);
let mut pin_data = b"123456".to_vec();
pin_data.extend(vec![0xFF; 2]); let verify_cmd = APDU {
cla: 0x00,
ins: 0x20,
p1: 0x00,
p2: 0x80,
data: pin_data,
le: None,
};
let response = applet.process_apdu(&verify_cmd);
assert!(response.is_okay());
}
#[test]
fn test_get_version() {
let mut applet = PIVApplet::new();
let cmd = APDU {
cla: 0x00,
ins: 0xFD,
p1: 0x00,
p2: 0x00,
data: Vec::new(),
le: None,
};
let response = applet.process_apdu(&cmd);
assert!(response.is_okay());
assert_eq!(response.data.len(), 3);
assert_eq!(response.data[0], 1); }
#[test]
fn test_get_serial() {
let mut applet = PIVApplet::new();
let cmd = APDU {
cla: 0x00,
ins: 0xF8,
p1: 0x00,
p2: 0x00,
data: Vec::new(),
le: None,
};
let response = applet.process_apdu(&cmd);
assert!(response.is_okay());
assert_eq!(response.data.len(), 4);
}
}