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}