ifd_jcecard/
lib.rs

1//! IFD Handler for jcecard virtual OpenPGP/PIV card
2//!
3//! This is a PC/SC IFD (Interface Device) handler that implements a virtual
4//! smart card reader with embedded OpenPGP and PIV applets.
5//!
6//! The virtual card supports:
7//! - OpenPGP card specification (ISO/IEC 7816-4/8)
8//! - PIV card specification (NIST SP 800-73-4)
9
10#![allow(dead_code)]
11// Allow raw pointer dereference in extern "C" functions - required for PC/SC IFD API
12#![allow(clippy::not_unsafe_ptr_arg_deref)]
13// Allow uppercase acronyms for Windows API type names (DWORD, LPSTR, etc.)
14#![allow(clippy::upper_case_acronyms)]
15
16// Core modules
17pub 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
37// PC/SC lite types
38type 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
45// Response codes
46const 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
53// Tags for GetCapabilities
54const 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
59// Power actions
60const IFD_POWER_UP: DWORD = 500;
61const IFD_POWER_DOWN: DWORD = 501;
62const IFD_RESET: DWORD = 502;
63
64// SCARD_IO_HEADER structure (simplified)
65#[repr(C)]
66#[derive(Debug, Clone, Copy)]
67pub struct SCARD_IO_HEADER {
68    pub protocol: DWORD,
69    pub length: DWORD,
70}
71
72// Maximum ATR size
73const MAX_ATR_SIZE: usize = 33;
74
75/// Active applet in the virtual card
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77enum ActiveApplet {
78    None,
79    OpenPGP,
80    PIV,
81}
82
83/// Virtual card containing both OpenPGP and PIV applets
84struct VirtualCard {
85    /// OpenPGP applet
86    openpgp: OpenPGPApplet,
87    /// PIV applet
88    piv: PIVApplet,
89    /// Currently active applet
90    active_applet: ActiveApplet,
91    /// Card ATR
92    atr: Vec<u8>,
93    /// Whether the card is powered
94    powered: bool,
95}
96
97impl VirtualCard {
98    /// Create a new virtual card
99    fn new() -> Self {
100        // Create and load CardDataStore for OpenPGP applet
101        let mut store = CardDataStore::new(None);
102        store.load();  // Load existing state or initialize defaults
103        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    /// Power on the card
113    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    /// Power off the card
121    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    /// Reset the card
130    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    /// Process an APDU command
140    fn process_apdu(&mut self, apdu_bytes: &[u8]) -> Vec<u8> {
141        if !self.powered {
142            // Return SW 6985 (Conditions not satisfied)
143            return vec![0x69, 0x85];
144        }
145
146        // Parse APDU
147        let cmd = match parse_apdu(apdu_bytes) {
148            Ok(apdu) => apdu,
149            Err(e) => {
150                error!("Failed to parse APDU: {:?}", e);
151                // Return SW 6700 (Wrong length)
152                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        // Check for SELECT command
160        if cmd.ins == 0xA4 {
161            if cmd.p1 == 0x04 {
162                // SELECT by DF name (AID) - route to applet selection
163                return self.handle_select(&cmd);
164            } else {
165                // SELECT MF (P1=0x00) or other SELECT variants not supported
166                // Real Yubikey returns INS_NOT_SUPPORTED for these
167                return vec![0x6D, 0x00];
168            }
169        }
170
171        // Route to active applet
172        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                // No applet selected - return SW 6985 (Conditions not satisfied)
177                Response::error(apdu::SW::CONDITIONS_NOT_SATISFIED)
178            }
179        };
180
181        // Convert response to bytes
182        self.response_to_bytes(&response)
183    }
184
185    /// Handle SELECT command for applet routing
186    fn handle_select(&mut self, cmd: &apdu::APDU) -> Vec<u8> {
187        // Check if it's OpenPGP AID
188        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        // Check if it's PIV AID
196        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        // Check if it's Yubikey Management AID (A0 00 00 05 27 47 11 17)
204        // scdaemon queries this to get firmware version
205        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            // Return version 5.4.3 to indicate full OpenPGP 3.4 support
209            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        // Unknown AID - return SW 6A82 (File not found)
216        debug!("Unknown AID: {:02X?}", cmd.data);
217        vec![0x6A, 0x82]
218    }
219
220    /// Convert Response to raw bytes (data + SW1 + SW2)
221    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
229/// Holds the card state
230struct CardState {
231    /// Virtual card
232    virtual_card: VirtualCard,
233}
234
235impl CardState {
236    fn new() -> Self {
237        Self {
238            virtual_card: VirtualCard::new(),
239        }
240    }
241
242    /// Power on the card
243    fn power_on(&mut self) -> Vec<u8> {
244        self.virtual_card.power_on()
245    }
246
247    /// Power off the card
248    fn power_off(&mut self) {
249        self.virtual_card.power_off()
250    }
251
252    /// Reset the card
253    fn reset(&mut self) -> Vec<u8> {
254        self.virtual_card.reset()
255    }
256
257    /// Send APDU and get response
258    fn transmit_apdu(&mut self, apdu: &[u8]) -> Vec<u8> {
259        self.virtual_card.process_apdu(apdu)
260    }
261
262    /// Check if powered
263    fn is_powered(&self) -> bool {
264        self.virtual_card.powered
265    }
266
267    /// Get ATR
268    fn get_atr(&self) -> &[u8] {
269        &self.virtual_card.atr
270    }
271}
272
273/// Global state for the IFD handler
274struct IfdState {
275    /// Card state per slot (we support only slot 0 for now)
276    slots: [Option<Arc<Mutex<CardState>>>; 1],
277}
278
279impl IfdState {
280    fn new() -> Self {
281        Self {
282            slots: [None],
283        }
284    }
285}
286
287// Global state
288static 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// ============================================================================
303// IFD Handler API Implementation
304// ============================================================================
305
306/// Create a communication channel to the reader
307#[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    // Create card state with embedded virtual card
327    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/// Create a communication channel (legacy)
335#[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/// Close the communication channel
355#[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/// Get reader capabilities
375#[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; // Not thread safe at IFD level
427                }
428            }
429            IFD_SUCCESS
430        }
431        TAG_IFD_SLOT_THREAD_SAFE => {
432            unsafe {
433                *length = 1;
434                if !value.is_null() {
435                    *value = 1; // Slot level is thread safe
436                }
437            }
438            IFD_SUCCESS
439        }
440        _ => {
441            log_info(&format!("Unknown tag: 0x{:04X}", tag));
442            IFD_ERROR_TAG
443        }
444    }
445}
446
447/// Set reader capabilities (not supported)
448#[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/// Set protocol parameters (minimal implementation)
459#[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/// Power the ICC (Integrated Circuit Card)
476#[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); // Release state lock before card operations
497
498    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/// Transmit data to the ICC
540#[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    // Build APDU from tx_buffer
573    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/// Check if ICC is present
590#[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    // Virtual card is always present
600    if state.slots[slot].is_some() {
601        return IFD_ICC_PRESENT;
602    }
603
604    IFD_ICC_NOT_PRESENT
605}
606
607/// Control the reader (not supported)
608#[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}