card_backend_pcsc/
lib.rs

1// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer <heiko@schaefer.name>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! This crate implements the traits [CardBackend] and [CardTransaction].
5//! It uses the PCSC middleware to access smart cards.
6//!
7//! This crate is mainly intended for use by the `openpgp-card` crate.
8
9use std::collections::HashMap;
10use std::convert::TryInto;
11
12use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError};
13use iso7816_tlv::simple::Tlv;
14use pcsc::Disposition;
15
16const FEATURE_VERIFY_PIN_DIRECT: u8 = 0x06;
17const FEATURE_MODIFY_PIN_DIRECT: u8 = 0x07;
18
19/// An opened PCSC Card (without open transaction).
20/// Note: No application is `select`-ed on the card while setting up a PcscCard object.
21///
22/// This struct can be used to hold on to a Card, even while no operations
23/// are performed on the Card. To perform operations on the card, a
24/// [PcscTransaction] object needs to be obtained (via [PcscBackend::transaction]).
25pub struct PcscBackend {
26    card: pcsc::Card,
27    mode: pcsc::ShareMode,
28    reader_caps: HashMap<u8, Tlv>,
29
30    // The reader name could be used as a hint about capabilities
31    // (e.g. readers that don't support extended length)
32    #[allow(dead_code)]
33    reader_name: String,
34    // FIXME: add a "adjust_card_caps" fn to card-backend? (could replace `max_cmd_len`)
35}
36
37/// Boxing helper (for easier consumption of PcscBackend in openpgp_card and openpgp_card_sequoia)
38impl From<PcscBackend> for Box<dyn CardBackend + Sync + Send> {
39    fn from(backend: PcscBackend) -> Box<dyn CardBackend + Sync + Send> {
40        Box::new(backend)
41    }
42}
43
44/// An implementation of the CardTransaction trait that uses the PCSC lite
45/// middleware to access the OpenPGP card application on smart cards, via a
46/// PCSC "transaction".
47///
48/// This struct is created from a PcscCard by opening a transaction, using
49/// PcscCard::transaction().
50///
51/// Transactions on a card cannot be opened and left idle
52/// (e.g. Microsoft documents that on Windows, they will be closed after
53/// 5s without a command:
54/// <https://docs.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardbegintransaction?redirectedfrom=MSDN#remarks>)
55pub struct PcscTransaction<'b> {
56    tx: pcsc::Transaction<'b>,
57    reader_caps: HashMap<u8, Tlv>, // FIXME: gets manually cloned
58    was_reset: bool,
59}
60
61impl<'b> PcscTransaction<'b> {
62    /// Start a transaction on `card`.
63    ///
64    /// If `reselect_application` is set, the application is SELECTed,
65    /// if the card reports having been reset.
66    fn new(
67        card: &'b mut PcscBackend,
68        reselect_application: Option<&[u8]>,
69    ) -> Result<Self, SmartcardError> {
70        log::trace!("Start a transaction");
71
72        let mut was_reset = false;
73
74        let mode = card.mode;
75        let reader_caps = card.reader_caps.clone();
76
77        let mut c = &mut card.card;
78
79        loop {
80            match c.transaction2() {
81                Ok(tx) => {
82                    // A pcsc transaction has been successfully started
83
84                    let mut pt = Self {
85                        tx,
86                        reader_caps,
87                        was_reset: false,
88                    };
89
90                    if was_reset {
91                        log::trace!("Card was reset");
92
93                        pt.was_reset = true;
94
95                        // If the caller expects that an application on the
96                        // card has been selected, re-select the application
97                        // here.
98                        //
99                        // When initially opening a card, we don't do this
100                        // (freshly opened cards don't have an application
101                        // "SELECT"ed).
102                        if let Some(app) = reselect_application {
103                            log::trace!("Will re-select an application after card reset");
104
105                            let mut res = CardTransaction::select(&mut pt, app)?;
106                            log::trace!("select res: {:0x?}", res);
107
108                            // Drop any bytes before the status code.
109                            //
110                            // e.g. SELECT on Basic Card 3.4 returns:
111                            // [6f, 1d,
112                            //  62, 15, 84, 10, d2, 76, 0, 1, 24, 1, 3, 4, 0, 5, 0, 0, a8, 35, 0, 0, 8a, 1, 5, 64, 4, 53, 2, c4, 41,
113                            //  90, 0]
114                            if res.len() > 2 {
115                                res.drain(0..res.len() - 2);
116                            }
117
118                            if res != [0x90, 0x00] {
119                                break Err(SmartcardError::Error(format!(
120                                        "Error while attempting to (re-)select {:x?}, status code {:x?}",
121                                        app, res
122                                    )));
123                            }
124
125                            log::trace!("re-select ok");
126                        }
127                    }
128
129                    break Ok(pt);
130                }
131                Err((c_, pcsc::Error::ResetCard)) => {
132                    // Card was reset, need to reconnect
133                    was_reset = true;
134
135                    c = c_;
136
137                    log::trace!("start_tx: do reconnect");
138
139                    c.reconnect(mode, pcsc::Protocols::ANY, Disposition::ResetCard)
140                        .map_err(|e| SmartcardError::Error(format!("Reconnect failed: {e:?}")))?;
141
142                    log::trace!("start_tx: reconnected.");
143
144                    // -> try opening a transaction again, in the next loop run
145                }
146                Err((_, e)) => {
147                    log::trace!("start_tx: error {:?}", e);
148                    break Err(SmartcardError::Error(format!("Error: {e:?}")));
149                }
150            };
151        }
152    }
153
154    /// GET_FEATURE_REQUEST
155    /// (see http://pcscworkgroup.com/Download/Specifications/pcsc10_v2.02.09.pdf)
156    fn features(&mut self) -> Result<Vec<Tlv>, SmartcardError> {
157        let mut recv = vec![0; 1024];
158
159        let cm_ioctl_get_feature_request = pcsc::ctl_code(3400);
160        let res = self
161            .tx
162            .control(cm_ioctl_get_feature_request, &[], &mut recv)
163            .map_err(|e| {
164                SmartcardError::Error(format!("GET_FEATURE_REQUEST control call failed: {e:?}"))
165            })?;
166
167        Ok(Tlv::parse_all(res))
168    }
169
170    /// Get the minimum pin length for pin_id.
171    fn min_pin_len(&self, pin: PinType) -> u8 {
172        match pin {
173            PinType::User | PinType::Sign => 6,
174            PinType::Admin => 8,
175        }
176    }
177
178    /// Get the maximum pin length for pin_id.
179    fn max_pin_len(
180        &self,
181        pin: PinType,
182        card_caps: &Option<CardCaps>,
183    ) -> Result<u8, SmartcardError> {
184        if let Some(card_caps) = card_caps {
185            match pin {
186                PinType::User | PinType::Sign => Ok(card_caps.pw1_max_len()),
187                PinType::Admin => Ok(card_caps.pw3_max_len()),
188            }
189        } else {
190            Err(SmartcardError::Error("card_caps is None".into()))
191        }
192    }
193}
194
195impl CardTransaction for PcscTransaction<'_> {
196    fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>, SmartcardError> {
197        let mut resp_buffer = vec![0; buf_size];
198
199        let resp = self
200            .tx
201            .transmit(cmd, &mut resp_buffer)
202            .map_err(|e| match e {
203                pcsc::Error::NotTransacted => SmartcardError::NotTransacted,
204                _ => SmartcardError::Error(format!("Transmit failed: {e:?}")),
205            })?;
206
207        Ok(resp.to_vec())
208    }
209
210    fn feature_pinpad_verify(&self) -> bool {
211        self.reader_caps.contains_key(&FEATURE_VERIFY_PIN_DIRECT)
212    }
213
214    fn feature_pinpad_modify(&self) -> bool {
215        self.reader_caps.contains_key(&FEATURE_MODIFY_PIN_DIRECT)
216    }
217
218    fn pinpad_verify(
219        &mut self,
220        pin: PinType,
221        card_caps: &Option<CardCaps>,
222    ) -> Result<Vec<u8>, SmartcardError> {
223        let pin_min_size = self.min_pin_len(pin);
224        let pin_max_size = self.max_pin_len(pin, card_caps)?;
225
226        // Default to varlen, for now.
227        // (NOTE: Some readers don't support varlen, and need explicit length
228        // information. Also see https://wiki.gnupg.org/CardReader/PinpadInput)
229        let fixedlen: u8 = 0;
230
231        // APDU: 00 20 00 pin_id <len> (ff)*
232        let mut ab_data = vec![
233            0x00,     /* CLA */
234            0x20,     /* INS: VERIFY */
235            0x00,     /* P1 */
236            pin.id(), /* P2 */
237            fixedlen, /* Lc: 'fixedlen' data bytes */
238        ];
239        ab_data.extend([0xff].repeat(fixedlen as usize));
240
241        // PC/SC v2.02.05 Part 10 PIN verification data structure
242        let mut send: Vec<u8> = vec![
243            // 0 bTimeOut BYTE timeout in seconds (00 means use default
244            // timeout)
245            0x00,
246            // 1 bTimeOut2 BYTE timeout in seconds after first key stroke
247            0x00,
248            // 2 bmFormatString BYTE formatting options USB_CCID_PIN_FORMAT_xxx
249            0x82,
250            // 3 bmPINBlockString BYTE
251            // bits 7-4 bit size of PIN length in APDU
252            // bits 3-0 PIN block size in bytes after justification and formatting
253            fixedlen,
254            // 4 bmPINLengthFormat BYTE
255            // bits 7-5 RFU, bit 4 set if system units are bytes clear if
256            // system units are bits,
257            // bits 3-0 PIN length position in system units
258            0x00,
259            // 5 wPINMaxExtraDigit USHORT XXYY, where XX is minimum PIN size
260            // in digits, YY is maximum
261            pin_max_size,
262            pin_min_size,
263            // 7 bEntryValidationCondition BYTE Conditions under which PIN
264            // entry should be considered complete.
265            //
266            // table for bEntryValidationCondition:
267            // 0x01: Max size reached
268            // 0x02: Validation key pressed
269            // 0x04: Timeout occurred
270            0x07,
271            // 8 bNumberMessage BYTE Number of messages to display for PIN
272            // verification
273            0x01,
274            // 9 wLangIdU SHORT Language for messages
275            0x04,
276            0x09, // US english
277            // 11 bMsgIndex BYTE Message index (should be 00)
278            0x00,
279            // 12 bTeoPrologue BYTE[3] T=1 I-block prologue field to use (fill with 00)
280            0x00,
281            0x00,
282            0x00,
283        ];
284
285        // 15 ulDataLength ULONG length of Data to be sent to the ICC
286        send.extend((ab_data.len() as u32).to_le_bytes());
287
288        // 19 abData BYTE[] Data to send to the ICC
289        send.extend(ab_data);
290
291        log::trace!("pcsc pinpad_verify send: {:x?}", send);
292
293        let mut recv = vec![0xAA; 256];
294
295        let verify_ioctl: [u8; 4] = self
296            .reader_caps
297            .get(&FEATURE_VERIFY_PIN_DIRECT)
298            .ok_or_else(|| SmartcardError::Error("no reader_capability".into()))?
299            .value()
300            .try_into()
301            .map_err(|e| SmartcardError::Error(format!("unexpected feature data: {e:?}")))?;
302
303        let res = self
304            .tx
305            .control(u32::from_be_bytes(verify_ioctl).into(), &send, &mut recv)
306            .map_err(|e: pcsc::Error| SmartcardError::Error(format!("pcsc Error: {e:?}")))?;
307
308        log::trace!(" <- pcsc pinpad_verify result: {:x?}", res);
309
310        Ok(res.to_vec())
311    }
312
313    fn pinpad_modify(
314        &mut self,
315        pin: PinType,
316        card_caps: &Option<CardCaps>,
317    ) -> Result<Vec<u8>, SmartcardError> {
318        let pin_min_size = self.min_pin_len(pin);
319        let pin_max_size = self.max_pin_len(pin, card_caps)?;
320
321        // Default to varlen, for now.
322        // (NOTE: Some readers don't support varlen, and need explicit length
323        // information. Also see https://wiki.gnupg.org/CardReader/PinpadInput)
324        let fixedlen: u8 = 0;
325
326        // APDU: 00 24 00 pin_id <len> [(ff)* x2]
327        let mut ab_data = vec![
328            0x00,         /* CLA */
329            0x24,         /* INS: CHANGE_REFERENCE_DATA */
330            0x00,         /* P1 */
331            pin.id(),     /* P2 */
332            fixedlen * 2, /* Lc: 'fixedlen' data bytes */
333        ];
334        ab_data.extend([0xff].repeat(fixedlen as usize * 2));
335
336        // PC/SC v2.02.05 Part 10 PIN modification data structure
337        let mut send: Vec<u8> = vec![
338            // 0 bTimeOut BYTE timeout in seconds (00 means use default
339            // timeout)
340            0x00,
341            // 1 bTimeOut2 BYTE timeout in seconds after first key stroke
342            0x00,
343            // 2 bmFormatString BYTE formatting options USB_CCID_PIN_FORMAT_xxx
344            0x82,
345            // 3 bmPINBlockString BYTE
346            // bits 7-4 bit size of PIN length in APDU
347            // bits 3-0 PIN block size in bytes after justification and formatting
348            fixedlen,
349            // 4 bmPINLengthFormat BYTE
350            // bits 7-5 RFU, bit 4 set if system units are bytes clear if
351            // system units are bits,
352            // bits 3-0 PIN length position in system units
353            0x00,
354            // 5 bInsertionOffsetOld BYTE Insertion position offset in bytes for
355            // the current PIN
356            0x00,
357            // 6 bInsertionOffsetNew BYTE Insertion position offset in bytes for
358            // the new PIN
359            fixedlen,
360            // 7 wPINMaxExtraDigit USHORT XXYY, where XX is minimum PIN size
361            // in digits, YY is maximum
362            pin_max_size,
363            pin_min_size,
364            // 9 bConfirmPIN
365            0x03, // TODO check?
366            // 10 bEntryValidationCondition BYTE Conditions under which PIN
367            // entry should be considered complete.
368            //
369            // table for bEntryValidationCondition:
370            // 0x01: Max size reached
371            // 0x02: Validation key pressed
372            // 0x04: Timeout occurred
373            0x07,
374            // 11 bNumberMessage BYTE Number of messages to display for PIN
375            // verification
376            0x03, // TODO check? (match with bConfirmPIN?)
377            // 12 wLangId USHORT Language for messages
378            0x04,
379            0x09, // US english
380            // 14 bMsgIndex1-3
381            0x00,
382            0x01,
383            0x02,
384            // 17 bTeoPrologue BYTE[3] T=1 I-block prologue field to use (fill with 00)
385            0x00,
386            0x00,
387            0x00,
388        ];
389
390        // 15 ulDataLength ULONG length of Data to be sent to the ICC
391        send.extend((ab_data.len() as u32).to_le_bytes());
392
393        // 19 abData BYTE[] Data to send to the ICC
394        send.extend(ab_data);
395
396        log::trace!("pcsc pinpad_modify send: {:x?}", send);
397
398        let mut recv = vec![0xAA; 256];
399
400        let modify_ioctl: [u8; 4] = self
401            .reader_caps
402            .get(&FEATURE_MODIFY_PIN_DIRECT)
403            .ok_or_else(|| SmartcardError::Error("no reader_capability".into()))?
404            .value()
405            .try_into()
406            .map_err(|e| SmartcardError::Error(format!("unexpected feature data: {e:?}")))?;
407
408        let res = self
409            .tx
410            .control(u32::from_be_bytes(modify_ioctl).into(), &send, &mut recv)
411            .map_err(|e: pcsc::Error| SmartcardError::Error(format!("pcsc Error: {e:?}")))?;
412
413        log::trace!(" <- pcsc pinpad_modify result: {:x?}", res);
414
415        Ok(res.to_vec())
416    }
417
418    fn was_reset(&self) -> bool {
419        self.was_reset
420    }
421}
422
423impl PcscBackend {
424    /// A list of "raw" opened PCSC Cards and reader names
425    /// (No application is selected)
426    fn raw_pcsc_cards(mode: pcsc::ShareMode) -> Result<Vec<(pcsc::Card, String)>, SmartcardError> {
427        log::trace!("raw_pcsc_cards start");
428
429        let ctx = match pcsc::Context::establish(pcsc::Scope::User) {
430            Ok(ctx) => ctx,
431            Err(err) => {
432                log::trace!("Context::establish failed: {:?}", err);
433                return Err(SmartcardError::ContextError(err.to_string()));
434            }
435        };
436
437        log::trace!("raw_pcsc_cards got context");
438
439        // List available readers.
440        let mut readers_buf = [0; 2048];
441        let readers = match ctx.list_readers(&mut readers_buf) {
442            Ok(readers) => readers,
443            Err(err) => {
444                log::trace!("list_readers failed: {:?}", err);
445                return Err(SmartcardError::ReaderError(err.to_string()));
446            }
447        };
448
449        log::trace!(" readers: {:?}", readers);
450
451        let mut cards = vec![];
452
453        // Find a reader with a SmartCard.
454        for reader in readers {
455            let name = String::from_utf8_lossy(reader.to_bytes());
456
457            log::trace!("Checking reader: {:?}", name);
458
459            // Try connecting to card in this reader
460            let card = match ctx.connect(reader, mode, pcsc::Protocols::ANY) {
461                Ok(card) => card,
462                Err(pcsc::Error::NoSmartcard) => {
463                    log::trace!("No Smartcard");
464
465                    continue; // try next reader
466                }
467                Err(err) => {
468                    log::warn!("Error connecting to card in reader: {:x?}", err);
469
470                    continue;
471                }
472            };
473
474            log::trace!("Found card");
475
476            cards.push((card, name.to_string()));
477        }
478
479        Ok(cards)
480    }
481
482    /// Returns an Iterator over Smart Cards that are accessible via PCSC.
483    ///
484    /// No application is SELECTed on the cards.
485    /// You can not assume that any particular application is available on the cards.
486    pub fn cards(
487        mode: Option<pcsc::ShareMode>,
488    ) -> Result<impl Iterator<Item = Result<Self, SmartcardError>>, SmartcardError> {
489        let mode = mode.unwrap_or(pcsc::ShareMode::Shared);
490
491        let cards = Self::raw_pcsc_cards(mode)?;
492
493        Ok(cards.into_iter().map(move |card| {
494            let backend = PcscBackend {
495                card: card.0,
496                mode,
497                reader_caps: Default::default(),
498                reader_name: card.1,
499            };
500
501            backend.initialize_card()
502        }))
503    }
504
505    /// Returns an Iterator over Smart Cards that are accessible via PCSC.
506    /// Like [Self::cards], but returns the cards as [CardBackend].
507    pub fn card_backends(
508        mode: Option<pcsc::ShareMode>,
509    ) -> Result<
510        impl Iterator<Item = Result<Box<dyn CardBackend + Send + Sync>, SmartcardError>>,
511        SmartcardError,
512    > {
513        let cards = PcscBackend::cards(mode)?;
514
515        Ok(cards.map(|c| match c {
516            Ok(c) => Ok(Box::new(c) as Box<dyn CardBackend + Send + Sync>),
517            Err(e) => Err(e),
518        }))
519    }
520
521    /// Initialize this PcscBackend (obtains and stores feature lists from reader,
522    /// to determine if the reader offers PIN pad functionality).
523    fn initialize_card(mut self) -> Result<Self, SmartcardError> {
524        log::trace!("pcsc initialize_card");
525
526        let mut h: HashMap<u8, Tlv> = HashMap::default();
527
528        let mut txc = PcscTransaction::new(&mut self, None)?;
529
530        // Get Features from reader (pinpad verify/modify)
531        if let Ok(feat) = txc.features() {
532            for tlv in feat {
533                log::trace!(" Found reader feature {:?}", tlv);
534                h.insert(tlv.tag().into(), tlv);
535            }
536        }
537
538        drop(txc);
539
540        for (a, b) in h {
541            self.reader_caps.insert(a, b);
542        }
543
544        Ok(self)
545    }
546}
547
548impl CardBackend for PcscBackend {
549    fn limit_card_caps(&self, card_caps: CardCaps) -> CardCaps {
550        let mut ext = card_caps.ext_support();
551
552        // Disable "extended length" support when the card reader is known not to support it
553        if self.reader_name.starts_with("ACS ACR122U") {
554            log::debug!("Disabling ext_support for reader {}", self.reader_name);
555            ext = false;
556        }
557
558        CardCaps::new(
559            ext,
560            card_caps.chaining_support(),
561            card_caps.max_cmd_bytes(),
562            card_caps.max_rsp_bytes(),
563            card_caps.pw1_max_len(),
564            card_caps.pw3_max_len(),
565        )
566    }
567
568    /// Get a CardTransaction for this PcscBackend (this starts a transaction)
569    fn transaction(
570        &mut self,
571        reselect_application: Option<&[u8]>,
572    ) -> Result<Box<dyn CardTransaction + Send + Sync + '_>, SmartcardError> {
573        Ok(Box::new(PcscTransaction::new(self, reselect_application)?))
574    }
575}