1#![allow(dead_code)]
11#![allow(clippy::not_unsafe_ptr_arg_deref)]
13#![allow(clippy::upper_case_acronyms)]
15
16pub mod apdu;
18pub mod tlv;
19pub mod card;
20pub mod crypto;
21pub mod openpgp;
22pub mod piv;
23
24use once_cell::sync::OnceCell;
25use parking_lot::Mutex;
26use std::ffi::{c_char, c_uchar, c_ulong, CStr};
27use std::ptr;
28use std::sync::Arc;
29use log::{debug, info, error};
30
31use apdu::{parse_apdu, Response};
32use card::{atr, CardDataStore};
33use openpgp::OpenPGPApplet;
34use openpgp::applet::OPENPGP_AID_PREFIX;
35use piv::{PIVApplet, applet::PIV_AID};
36
37type DWORD = c_ulong;
39type PDWORD = *mut DWORD;
40type PUCHAR = *mut c_uchar;
41type LPSTR = *const c_char;
42type RESPONSECODE = c_ulong;
43type UCHAR = c_uchar;
44
45const IFD_SUCCESS: RESPONSECODE = 0;
47const IFD_ERROR_TAG: RESPONSECODE = 600;
48const IFD_ERROR_NOT_SUPPORTED: RESPONSECODE = 606;
49const IFD_COMMUNICATION_ERROR: RESPONSECODE = 612;
50const IFD_ICC_PRESENT: RESPONSECODE = 615;
51const IFD_ICC_NOT_PRESENT: RESPONSECODE = 614;
52
53const TAG_IFD_ATR: DWORD = 0x0303;
55const TAG_IFD_SLOTS_NUMBER: DWORD = 0x0FAE;
56const TAG_IFD_THREAD_SAFE: DWORD = 0x0FAD;
57const TAG_IFD_SLOT_THREAD_SAFE: DWORD = 0x0FBE;
58
59const IFD_POWER_UP: DWORD = 500;
61const IFD_POWER_DOWN: DWORD = 501;
62const IFD_RESET: DWORD = 502;
63
64#[repr(C)]
66#[derive(Debug, Clone, Copy)]
67pub struct SCARD_IO_HEADER {
68 pub protocol: DWORD,
69 pub length: DWORD,
70}
71
72const MAX_ATR_SIZE: usize = 33;
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77enum ActiveApplet {
78 None,
79 OpenPGP,
80 PIV,
81}
82
83struct VirtualCard {
85 openpgp: OpenPGPApplet,
87 piv: PIVApplet,
89 active_applet: ActiveApplet,
91 atr: Vec<u8>,
93 powered: bool,
95}
96
97impl VirtualCard {
98 fn new() -> Self {
100 let mut store = CardDataStore::new(None);
102 store.load(); Self {
104 openpgp: OpenPGPApplet::new(store),
105 piv: PIVApplet::new(),
106 active_applet: ActiveApplet::None,
107 atr: atr::create_openpgp_atr(),
108 powered: false,
109 }
110 }
111
112 fn power_on(&mut self) -> Vec<u8> {
114 self.powered = true;
115 self.active_applet = ActiveApplet::None;
116 info!("Virtual card powered on");
117 self.atr.clone()
118 }
119
120 fn power_off(&mut self) {
122 self.powered = false;
123 self.active_applet = ActiveApplet::None;
124 self.openpgp.reset();
125 self.piv.reset();
126 info!("Virtual card powered off");
127 }
128
129 fn reset(&mut self) -> Vec<u8> {
131 self.openpgp.reset();
132 self.piv.reset();
133 self.active_applet = ActiveApplet::None;
134 self.powered = true;
135 info!("Virtual card reset");
136 self.atr.clone()
137 }
138
139 fn process_apdu(&mut self, apdu_bytes: &[u8]) -> Vec<u8> {
141 if !self.powered {
142 return vec![0x69, 0x85];
144 }
145
146 let cmd = match parse_apdu(apdu_bytes) {
148 Ok(apdu) => apdu,
149 Err(e) => {
150 error!("Failed to parse APDU: {:?}", e);
151 return vec![0x67, 0x00];
153 }
154 };
155
156 debug!("Processing APDU: CLA={:02X} INS={:02X} P1={:02X} P2={:02X}",
157 cmd.cla, cmd.ins, cmd.p1, cmd.p2);
158
159 if cmd.ins == 0xA4 {
161 if cmd.p1 == 0x04 {
162 return self.handle_select(&cmd);
164 } else {
165 return vec![0x6D, 0x00];
168 }
169 }
170
171 let response = match self.active_applet {
173 ActiveApplet::OpenPGP => self.openpgp.process_apdu(&cmd),
174 ActiveApplet::PIV => self.piv.process_apdu(&cmd),
175 ActiveApplet::None => {
176 Response::error(apdu::SW::CONDITIONS_NOT_SATISFIED)
178 }
179 };
180
181 self.response_to_bytes(&response)
183 }
184
185 fn handle_select(&mut self, cmd: &apdu::APDU) -> Vec<u8> {
187 if cmd.data.starts_with(OPENPGP_AID_PREFIX) {
189 self.active_applet = ActiveApplet::OpenPGP;
190 info!("Selected OpenPGP applet");
191 let response = self.openpgp.process_apdu(cmd);
192 return self.response_to_bytes(&response);
193 }
194
195 if cmd.data.starts_with(PIV_AID) {
197 self.active_applet = ActiveApplet::PIV;
198 info!("Selected PIV applet");
199 let response = self.piv.process_apdu(cmd);
200 return self.response_to_bytes(&response);
201 }
202
203 const YUBIKEY_MGMT_AID: &[u8] = &[0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17];
206 if cmd.data == YUBIKEY_MGMT_AID {
207 info!("Selected Yubikey Management applet (returning version string)");
208 let mut response: Vec<u8> = b"jcecard - FW version 5.4.3".to_vec();
210 response.push(0x90);
211 response.push(0x00);
212 return response;
213 }
214
215 debug!("Unknown AID: {:02X?}", cmd.data);
217 vec![0x6A, 0x82]
218 }
219
220 fn response_to_bytes(&self, response: &Response) -> Vec<u8> {
222 let mut result = response.data.clone();
223 result.push(response.sw1);
224 result.push(response.sw2);
225 result
226 }
227}
228
229struct CardState {
231 virtual_card: VirtualCard,
233}
234
235impl CardState {
236 fn new() -> Self {
237 Self {
238 virtual_card: VirtualCard::new(),
239 }
240 }
241
242 fn power_on(&mut self) -> Vec<u8> {
244 self.virtual_card.power_on()
245 }
246
247 fn power_off(&mut self) {
249 self.virtual_card.power_off()
250 }
251
252 fn reset(&mut self) -> Vec<u8> {
254 self.virtual_card.reset()
255 }
256
257 fn transmit_apdu(&mut self, apdu: &[u8]) -> Vec<u8> {
259 self.virtual_card.process_apdu(apdu)
260 }
261
262 fn is_powered(&self) -> bool {
264 self.virtual_card.powered
265 }
266
267 fn get_atr(&self) -> &[u8] {
269 &self.virtual_card.atr
270 }
271}
272
273struct IfdState {
275 slots: [Option<Arc<Mutex<CardState>>>; 1],
277}
278
279impl IfdState {
280 fn new() -> Self {
281 Self {
282 slots: [None],
283 }
284 }
285}
286
287static IFD_STATE: OnceCell<Mutex<IfdState>> = OnceCell::new();
289
290fn get_state() -> &'static Mutex<IfdState> {
291 IFD_STATE.get_or_init(|| Mutex::new(IfdState::new()))
292}
293
294fn log_info(msg: &str) {
295 eprintln!("[ifd-jcecard] {}", msg);
296}
297
298fn log_error(msg: &str) {
299 eprintln!("[ifd-jcecard] ERROR: {}", msg);
300}
301
302#[no_mangle]
308pub extern "C" fn IFDHCreateChannelByName(lun: DWORD, device_name: LPSTR) -> RESPONSECODE {
309 let name = if device_name.is_null() {
310 "null".to_string()
311 } else {
312 unsafe { CStr::from_ptr(device_name) }
313 .to_string_lossy()
314 .to_string()
315 };
316 log_info(&format!("IFDHCreateChannelByName: LUN={}, device={}", lun, name));
317
318 let mut state = get_state().lock();
319 let slot = (lun & 0xFFFF) as usize;
320
321 if slot >= state.slots.len() {
322 log_error(&format!("Invalid slot: {}", slot));
323 return IFD_COMMUNICATION_ERROR;
324 }
325
326 let card_state = CardState::new();
328 state.slots[slot] = Some(Arc::new(Mutex::new(card_state)));
329
330 log_info("Channel created successfully (embedded virtual card)");
331 IFD_SUCCESS
332}
333
334#[no_mangle]
336pub extern "C" fn IFDHCreateChannel(lun: DWORD, channel: DWORD) -> RESPONSECODE {
337 log_info(&format!("IFDHCreateChannel: LUN={}, channel={}", lun, channel));
338
339 let mut state = get_state().lock();
340 let slot = (lun & 0xFFFF) as usize;
341
342 if slot >= state.slots.len() {
343 log_error(&format!("Invalid slot: {}", slot));
344 return IFD_COMMUNICATION_ERROR;
345 }
346
347 let card_state = CardState::new();
348 state.slots[slot] = Some(Arc::new(Mutex::new(card_state)));
349
350 log_info("Channel created successfully (embedded virtual card)");
351 IFD_SUCCESS
352}
353
354#[no_mangle]
356pub extern "C" fn IFDHCloseChannel(lun: DWORD) -> RESPONSECODE {
357 log_info(&format!("IFDHCloseChannel: LUN={}", lun));
358
359 let mut state = get_state().lock();
360 let slot = (lun & 0xFFFF) as usize;
361
362 if slot >= state.slots.len() {
363 return IFD_COMMUNICATION_ERROR;
364 }
365
366 if let Some(card_state) = state.slots[slot].take() {
367 let mut cs = card_state.lock();
368 cs.power_off();
369 }
370
371 IFD_SUCCESS
372}
373
374#[no_mangle]
376pub extern "C" fn IFDHGetCapabilities(
377 lun: DWORD,
378 tag: DWORD,
379 length: PDWORD,
380 value: PUCHAR,
381) -> RESPONSECODE {
382 log_info(&format!("IFDHGetCapabilities: LUN={}, tag=0x{:04X}", lun, tag));
383
384 if length.is_null() {
385 return IFD_COMMUNICATION_ERROR;
386 }
387
388 let state = get_state().lock();
389 let slot = (lun & 0xFFFF) as usize;
390
391 match tag {
392 TAG_IFD_ATR => {
393 if slot >= state.slots.len() {
394 return IFD_COMMUNICATION_ERROR;
395 }
396
397 if let Some(ref card_arc) = state.slots[slot] {
398 let card = card_arc.lock();
399 if card.is_powered() {
400 let atr = card.get_atr();
401 let atr_len = atr.len().min(MAX_ATR_SIZE);
402 unsafe {
403 *length = atr_len as DWORD;
404 if !value.is_null() {
405 ptr::copy_nonoverlapping(atr.as_ptr(), value, atr_len);
406 }
407 }
408 return IFD_SUCCESS;
409 }
410 }
411 IFD_ICC_NOT_PRESENT
412 }
413 TAG_IFD_SLOTS_NUMBER => {
414 unsafe {
415 *length = 1;
416 if !value.is_null() {
417 *value = 1;
418 }
419 }
420 IFD_SUCCESS
421 }
422 TAG_IFD_THREAD_SAFE => {
423 unsafe {
424 *length = 1;
425 if !value.is_null() {
426 *value = 0; }
428 }
429 IFD_SUCCESS
430 }
431 TAG_IFD_SLOT_THREAD_SAFE => {
432 unsafe {
433 *length = 1;
434 if !value.is_null() {
435 *value = 1; }
437 }
438 IFD_SUCCESS
439 }
440 _ => {
441 log_info(&format!("Unknown tag: 0x{:04X}", tag));
442 IFD_ERROR_TAG
443 }
444 }
445}
446
447#[no_mangle]
449pub extern "C" fn IFDHSetCapabilities(
450 _lun: DWORD,
451 _tag: DWORD,
452 _length: DWORD,
453 _value: PUCHAR,
454) -> RESPONSECODE {
455 IFD_ERROR_NOT_SUPPORTED
456}
457
458#[no_mangle]
460pub extern "C" fn IFDHSetProtocolParameters(
461 lun: DWORD,
462 protocol: DWORD,
463 _flags: UCHAR,
464 _pts1: UCHAR,
465 _pts2: UCHAR,
466 _pts3: UCHAR,
467) -> RESPONSECODE {
468 log_info(&format!(
469 "IFDHSetProtocolParameters: LUN={}, protocol={}",
470 lun, protocol
471 ));
472 IFD_SUCCESS
473}
474
475#[no_mangle]
477pub extern "C" fn IFDHPowerICC(
478 lun: DWORD,
479 action: DWORD,
480 atr: PUCHAR,
481 atr_length: PDWORD,
482) -> RESPONSECODE {
483 log_info(&format!("IFDHPowerICC: LUN={}, action={}", lun, action));
484
485 let state = get_state().lock();
486 let slot = (lun & 0xFFFF) as usize;
487
488 if slot >= state.slots.len() {
489 return IFD_COMMUNICATION_ERROR;
490 }
491
492 let card_arc = match &state.slots[slot] {
493 Some(arc) => arc.clone(),
494 None => return IFD_COMMUNICATION_ERROR,
495 };
496 drop(state); let mut card = card_arc.lock();
499
500 match action {
501 IFD_POWER_UP => {
502 log_info("Powering up virtual card");
503 let card_atr = card.power_on();
504 log_info(&format!("Card powered on, ATR length: {}", card_atr.len()));
505 if !atr.is_null() && !atr_length.is_null() {
506 let copy_len = card_atr.len().min(MAX_ATR_SIZE);
507 unsafe {
508 ptr::copy_nonoverlapping(card_atr.as_ptr(), atr, copy_len);
509 *atr_length = copy_len as DWORD;
510 }
511 }
512 IFD_SUCCESS
513 }
514 IFD_POWER_DOWN => {
515 log_info("Powering down virtual card");
516 card.power_off();
517 IFD_SUCCESS
518 }
519 IFD_RESET => {
520 log_info("Resetting virtual card");
521 let card_atr = card.reset();
522 log_info(&format!("Card reset, ATR length: {}", card_atr.len()));
523 if !atr.is_null() && !atr_length.is_null() {
524 let copy_len = card_atr.len().min(MAX_ATR_SIZE);
525 unsafe {
526 ptr::copy_nonoverlapping(card_atr.as_ptr(), atr, copy_len);
527 *atr_length = copy_len as DWORD;
528 }
529 }
530 IFD_SUCCESS
531 }
532 _ => {
533 log_error(&format!("Unknown power action: {}", action));
534 IFD_ERROR_NOT_SUPPORTED
535 }
536 }
537}
538
539#[no_mangle]
541pub extern "C" fn IFDHTransmitToICC(
542 lun: DWORD,
543 send_pci: SCARD_IO_HEADER,
544 tx_buffer: PUCHAR,
545 tx_length: DWORD,
546 rx_buffer: PUCHAR,
547 rx_length: PDWORD,
548 _recv_pci: *mut SCARD_IO_HEADER,
549) -> RESPONSECODE {
550 log_info(&format!(
551 "IFDHTransmitToICC: LUN={}, protocol={}, tx_len={}",
552 lun, send_pci.protocol, tx_length
553 ));
554
555 if tx_buffer.is_null() || rx_buffer.is_null() || rx_length.is_null() {
556 return IFD_COMMUNICATION_ERROR;
557 }
558
559 let state = get_state().lock();
560 let slot = (lun & 0xFFFF) as usize;
561
562 if slot >= state.slots.len() {
563 return IFD_COMMUNICATION_ERROR;
564 }
565
566 let card_arc = match &state.slots[slot] {
567 Some(arc) => arc.clone(),
568 None => return IFD_COMMUNICATION_ERROR,
569 };
570 drop(state);
571
572 let apdu = unsafe { std::slice::from_raw_parts(tx_buffer, tx_length as usize) };
574 log_info(&format!("APDU: {:02X?}", apdu));
575
576 let mut card = card_arc.lock();
577 let response = card.transmit_apdu(apdu);
578
579 log_info(&format!("Response: {:02X?}", response));
580 let max_len = unsafe { *rx_length } as usize;
581 let copy_len = response.len().min(max_len);
582 unsafe {
583 ptr::copy_nonoverlapping(response.as_ptr(), rx_buffer, copy_len);
584 *rx_length = copy_len as DWORD;
585 }
586 IFD_SUCCESS
587}
588
589#[no_mangle]
591pub extern "C" fn IFDHICCPresence(lun: DWORD) -> RESPONSECODE {
592 let state = get_state().lock();
593 let slot = (lun & 0xFFFF) as usize;
594
595 if slot >= state.slots.len() {
596 return IFD_ICC_NOT_PRESENT;
597 }
598
599 if state.slots[slot].is_some() {
601 return IFD_ICC_PRESENT;
602 }
603
604 IFD_ICC_NOT_PRESENT
605}
606
607#[no_mangle]
609pub extern "C" fn IFDHControl(
610 _lun: DWORD,
611 _control_code: DWORD,
612 _tx_buffer: PUCHAR,
613 _tx_length: DWORD,
614 _rx_buffer: PUCHAR,
615 _rx_length: DWORD,
616 _bytes_returned: PDWORD,
617) -> RESPONSECODE {
618 IFD_ERROR_NOT_SUPPORTED
619}