#![allow(dead_code)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
#![allow(clippy::upper_case_acronyms)]
pub mod apdu;
pub mod tlv;
pub mod card;
pub mod crypto;
pub mod openpgp;
pub mod piv;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::ffi::{c_char, c_uchar, c_ulong, CStr};
use std::ptr;
use std::sync::Arc;
use log::{debug, info, error};
use apdu::{parse_apdu, Response};
use card::{atr, CardDataStore};
use openpgp::OpenPGPApplet;
use openpgp::applet::OPENPGP_AID_PREFIX;
use piv::{PIVApplet, applet::PIV_AID};
type DWORD = c_ulong;
type PDWORD = *mut DWORD;
type PUCHAR = *mut c_uchar;
type LPSTR = *const c_char;
type RESPONSECODE = c_ulong;
type UCHAR = c_uchar;
const IFD_SUCCESS: RESPONSECODE = 0;
const IFD_ERROR_TAG: RESPONSECODE = 600;
const IFD_ERROR_NOT_SUPPORTED: RESPONSECODE = 606;
const IFD_COMMUNICATION_ERROR: RESPONSECODE = 612;
const IFD_ICC_PRESENT: RESPONSECODE = 615;
const IFD_ICC_NOT_PRESENT: RESPONSECODE = 614;
const TAG_IFD_ATR: DWORD = 0x0303;
const TAG_IFD_SLOTS_NUMBER: DWORD = 0x0FAE;
const TAG_IFD_THREAD_SAFE: DWORD = 0x0FAD;
const TAG_IFD_SLOT_THREAD_SAFE: DWORD = 0x0FBE;
const IFD_POWER_UP: DWORD = 500;
const IFD_POWER_DOWN: DWORD = 501;
const IFD_RESET: DWORD = 502;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct SCARD_IO_HEADER {
pub protocol: DWORD,
pub length: DWORD,
}
const MAX_ATR_SIZE: usize = 33;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ActiveApplet {
None,
OpenPGP,
PIV,
}
struct VirtualCard {
openpgp: OpenPGPApplet,
piv: PIVApplet,
active_applet: ActiveApplet,
atr: Vec<u8>,
powered: bool,
}
impl VirtualCard {
fn new() -> Self {
let mut store = CardDataStore::new(None);
store.load(); Self {
openpgp: OpenPGPApplet::new(store),
piv: PIVApplet::new(),
active_applet: ActiveApplet::None,
atr: atr::create_openpgp_atr(),
powered: false,
}
}
fn power_on(&mut self) -> Vec<u8> {
self.powered = true;
self.active_applet = ActiveApplet::None;
info!("Virtual card powered on");
self.atr.clone()
}
fn power_off(&mut self) {
self.powered = false;
self.active_applet = ActiveApplet::None;
self.openpgp.reset();
self.piv.reset();
info!("Virtual card powered off");
}
fn reset(&mut self) -> Vec<u8> {
self.openpgp.reset();
self.piv.reset();
self.active_applet = ActiveApplet::None;
self.powered = true;
info!("Virtual card reset");
self.atr.clone()
}
fn process_apdu(&mut self, apdu_bytes: &[u8]) -> Vec<u8> {
if !self.powered {
return vec![0x69, 0x85];
}
let cmd = match parse_apdu(apdu_bytes) {
Ok(apdu) => apdu,
Err(e) => {
error!("Failed to parse APDU: {:?}", e);
return vec![0x67, 0x00];
}
};
debug!("Processing APDU: CLA={:02X} INS={:02X} P1={:02X} P2={:02X}",
cmd.cla, cmd.ins, cmd.p1, cmd.p2);
if cmd.ins == 0xA4 {
if cmd.p1 == 0x04 {
return self.handle_select(&cmd);
} else {
return vec![0x6D, 0x00];
}
}
let response = match self.active_applet {
ActiveApplet::OpenPGP => self.openpgp.process_apdu(&cmd),
ActiveApplet::PIV => self.piv.process_apdu(&cmd),
ActiveApplet::None => {
Response::error(apdu::SW::CONDITIONS_NOT_SATISFIED)
}
};
self.response_to_bytes(&response)
}
fn handle_select(&mut self, cmd: &apdu::APDU) -> Vec<u8> {
if cmd.data.starts_with(OPENPGP_AID_PREFIX) {
self.active_applet = ActiveApplet::OpenPGP;
info!("Selected OpenPGP applet");
let response = self.openpgp.process_apdu(cmd);
return self.response_to_bytes(&response);
}
if cmd.data.starts_with(PIV_AID) {
self.active_applet = ActiveApplet::PIV;
info!("Selected PIV applet");
let response = self.piv.process_apdu(cmd);
return self.response_to_bytes(&response);
}
const YUBIKEY_MGMT_AID: &[u8] = &[0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
if cmd.data == YUBIKEY_MGMT_AID {
info!("Selected Yubikey Management applet (returning version string)");
let mut response: Vec<u8> = b"jcecard - FW version 5.4.3".to_vec();
response.push(0x90);
response.push(0x00);
return response;
}
debug!("Unknown AID: {:02X?}", cmd.data);
vec![0x6A, 0x82]
}
fn response_to_bytes(&self, response: &Response) -> Vec<u8> {
let mut result = response.data.clone();
result.push(response.sw1);
result.push(response.sw2);
result
}
}
struct CardState {
virtual_card: VirtualCard,
}
impl CardState {
fn new() -> Self {
Self {
virtual_card: VirtualCard::new(),
}
}
fn power_on(&mut self) -> Vec<u8> {
self.virtual_card.power_on()
}
fn power_off(&mut self) {
self.virtual_card.power_off()
}
fn reset(&mut self) -> Vec<u8> {
self.virtual_card.reset()
}
fn transmit_apdu(&mut self, apdu: &[u8]) -> Vec<u8> {
self.virtual_card.process_apdu(apdu)
}
fn is_powered(&self) -> bool {
self.virtual_card.powered
}
fn get_atr(&self) -> &[u8] {
&self.virtual_card.atr
}
}
struct IfdState {
slots: [Option<Arc<Mutex<CardState>>>; 1],
}
impl IfdState {
fn new() -> Self {
Self {
slots: [None],
}
}
}
static IFD_STATE: OnceCell<Mutex<IfdState>> = OnceCell::new();
fn get_state() -> &'static Mutex<IfdState> {
IFD_STATE.get_or_init(|| Mutex::new(IfdState::new()))
}
fn log_info(msg: &str) {
eprintln!("[ifd-jcecard] {}", msg);
}
fn log_error(msg: &str) {
eprintln!("[ifd-jcecard] ERROR: {}", msg);
}
#[no_mangle]
pub extern "C" fn IFDHCreateChannelByName(lun: DWORD, device_name: LPSTR) -> RESPONSECODE {
let name = if device_name.is_null() {
"null".to_string()
} else {
unsafe { CStr::from_ptr(device_name) }
.to_string_lossy()
.to_string()
};
log_info(&format!("IFDHCreateChannelByName: LUN={}, device={}", lun, name));
let mut state = get_state().lock();
let slot = (lun & 0xFFFF) as usize;
if slot >= state.slots.len() {
log_error(&format!("Invalid slot: {}", slot));
return IFD_COMMUNICATION_ERROR;
}
let card_state = CardState::new();
state.slots[slot] = Some(Arc::new(Mutex::new(card_state)));
log_info("Channel created successfully (embedded virtual card)");
IFD_SUCCESS
}
#[no_mangle]
pub extern "C" fn IFDHCreateChannel(lun: DWORD, channel: DWORD) -> RESPONSECODE {
log_info(&format!("IFDHCreateChannel: LUN={}, channel={}", lun, channel));
let mut state = get_state().lock();
let slot = (lun & 0xFFFF) as usize;
if slot >= state.slots.len() {
log_error(&format!("Invalid slot: {}", slot));
return IFD_COMMUNICATION_ERROR;
}
let card_state = CardState::new();
state.slots[slot] = Some(Arc::new(Mutex::new(card_state)));
log_info("Channel created successfully (embedded virtual card)");
IFD_SUCCESS
}
#[no_mangle]
pub extern "C" fn IFDHCloseChannel(lun: DWORD) -> RESPONSECODE {
log_info(&format!("IFDHCloseChannel: LUN={}", lun));
let mut state = get_state().lock();
let slot = (lun & 0xFFFF) as usize;
if slot >= state.slots.len() {
return IFD_COMMUNICATION_ERROR;
}
if let Some(card_state) = state.slots[slot].take() {
let mut cs = card_state.lock();
cs.power_off();
}
IFD_SUCCESS
}
#[no_mangle]
pub extern "C" fn IFDHGetCapabilities(
lun: DWORD,
tag: DWORD,
length: PDWORD,
value: PUCHAR,
) -> RESPONSECODE {
log_info(&format!("IFDHGetCapabilities: LUN={}, tag=0x{:04X}", lun, tag));
if length.is_null() {
return IFD_COMMUNICATION_ERROR;
}
let state = get_state().lock();
let slot = (lun & 0xFFFF) as usize;
match tag {
TAG_IFD_ATR => {
if slot >= state.slots.len() {
return IFD_COMMUNICATION_ERROR;
}
if let Some(ref card_arc) = state.slots[slot] {
let card = card_arc.lock();
if card.is_powered() {
let atr = card.get_atr();
let atr_len = atr.len().min(MAX_ATR_SIZE);
unsafe {
*length = atr_len as DWORD;
if !value.is_null() {
ptr::copy_nonoverlapping(atr.as_ptr(), value, atr_len);
}
}
return IFD_SUCCESS;
}
}
IFD_ICC_NOT_PRESENT
}
TAG_IFD_SLOTS_NUMBER => {
unsafe {
*length = 1;
if !value.is_null() {
*value = 1;
}
}
IFD_SUCCESS
}
TAG_IFD_THREAD_SAFE => {
unsafe {
*length = 1;
if !value.is_null() {
*value = 0; }
}
IFD_SUCCESS
}
TAG_IFD_SLOT_THREAD_SAFE => {
unsafe {
*length = 1;
if !value.is_null() {
*value = 1; }
}
IFD_SUCCESS
}
_ => {
log_info(&format!("Unknown tag: 0x{:04X}", tag));
IFD_ERROR_TAG
}
}
}
#[no_mangle]
pub extern "C" fn IFDHSetCapabilities(
_lun: DWORD,
_tag: DWORD,
_length: DWORD,
_value: PUCHAR,
) -> RESPONSECODE {
IFD_ERROR_NOT_SUPPORTED
}
#[no_mangle]
pub extern "C" fn IFDHSetProtocolParameters(
lun: DWORD,
protocol: DWORD,
_flags: UCHAR,
_pts1: UCHAR,
_pts2: UCHAR,
_pts3: UCHAR,
) -> RESPONSECODE {
log_info(&format!(
"IFDHSetProtocolParameters: LUN={}, protocol={}",
lun, protocol
));
IFD_SUCCESS
}
#[no_mangle]
pub extern "C" fn IFDHPowerICC(
lun: DWORD,
action: DWORD,
atr: PUCHAR,
atr_length: PDWORD,
) -> RESPONSECODE {
log_info(&format!("IFDHPowerICC: LUN={}, action={}", lun, action));
let state = get_state().lock();
let slot = (lun & 0xFFFF) as usize;
if slot >= state.slots.len() {
return IFD_COMMUNICATION_ERROR;
}
let card_arc = match &state.slots[slot] {
Some(arc) => arc.clone(),
None => return IFD_COMMUNICATION_ERROR,
};
drop(state);
let mut card = card_arc.lock();
match action {
IFD_POWER_UP => {
log_info("Powering up virtual card");
let card_atr = card.power_on();
log_info(&format!("Card powered on, ATR length: {}", card_atr.len()));
if !atr.is_null() && !atr_length.is_null() {
let copy_len = card_atr.len().min(MAX_ATR_SIZE);
unsafe {
ptr::copy_nonoverlapping(card_atr.as_ptr(), atr, copy_len);
*atr_length = copy_len as DWORD;
}
}
IFD_SUCCESS
}
IFD_POWER_DOWN => {
log_info("Powering down virtual card");
card.power_off();
IFD_SUCCESS
}
IFD_RESET => {
log_info("Resetting virtual card");
let card_atr = card.reset();
log_info(&format!("Card reset, ATR length: {}", card_atr.len()));
if !atr.is_null() && !atr_length.is_null() {
let copy_len = card_atr.len().min(MAX_ATR_SIZE);
unsafe {
ptr::copy_nonoverlapping(card_atr.as_ptr(), atr, copy_len);
*atr_length = copy_len as DWORD;
}
}
IFD_SUCCESS
}
_ => {
log_error(&format!("Unknown power action: {}", action));
IFD_ERROR_NOT_SUPPORTED
}
}
}
#[no_mangle]
pub extern "C" fn IFDHTransmitToICC(
lun: DWORD,
send_pci: SCARD_IO_HEADER,
tx_buffer: PUCHAR,
tx_length: DWORD,
rx_buffer: PUCHAR,
rx_length: PDWORD,
_recv_pci: *mut SCARD_IO_HEADER,
) -> RESPONSECODE {
log_info(&format!(
"IFDHTransmitToICC: LUN={}, protocol={}, tx_len={}",
lun, send_pci.protocol, tx_length
));
if tx_buffer.is_null() || rx_buffer.is_null() || rx_length.is_null() {
return IFD_COMMUNICATION_ERROR;
}
let state = get_state().lock();
let slot = (lun & 0xFFFF) as usize;
if slot >= state.slots.len() {
return IFD_COMMUNICATION_ERROR;
}
let card_arc = match &state.slots[slot] {
Some(arc) => arc.clone(),
None => return IFD_COMMUNICATION_ERROR,
};
drop(state);
let apdu = unsafe { std::slice::from_raw_parts(tx_buffer, tx_length as usize) };
log_info(&format!("APDU: {:02X?}", apdu));
let mut card = card_arc.lock();
let response = card.transmit_apdu(apdu);
log_info(&format!("Response: {:02X?}", response));
let max_len = unsafe { *rx_length } as usize;
let copy_len = response.len().min(max_len);
unsafe {
ptr::copy_nonoverlapping(response.as_ptr(), rx_buffer, copy_len);
*rx_length = copy_len as DWORD;
}
IFD_SUCCESS
}
#[no_mangle]
pub extern "C" fn IFDHICCPresence(lun: DWORD) -> RESPONSECODE {
let state = get_state().lock();
let slot = (lun & 0xFFFF) as usize;
if slot >= state.slots.len() {
return IFD_ICC_NOT_PRESENT;
}
if state.slots[slot].is_some() {
return IFD_ICC_PRESENT;
}
IFD_ICC_NOT_PRESENT
}
#[no_mangle]
pub extern "C" fn IFDHControl(
_lun: DWORD,
_control_code: DWORD,
_tx_buffer: PUCHAR,
_tx_length: DWORD,
_rx_buffer: PUCHAR,
_rx_length: DWORD,
_bytes_returned: PDWORD,
) -> RESPONSECODE {
IFD_ERROR_NOT_SUPPORTED
}