// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use anyhow::{anyhow, Result}; use pcsc::{Card, Context, Protocols, Scope, ShareMode}; use openpgp_card::{CardApp, CardCaps, CardClient, Error, SmartcardError}; pub struct PcscClient { card: Card, card_caps: Option, } impl PcscClient { fn new(card: Card) -> Self { Self { card, card_caps: None, } } /// Get an initialized CardApp from a card fn into_card_app(self) -> Result { CardApp::initialize(Box::new(self)) } /// Opened PCSC Cards without selecting the OpenPGP card application fn pcsc_cards() -> Result, SmartcardError> { let ctx = match Context::establish(Scope::User) { Ok(ctx) => ctx, Err(err) => { return Err(SmartcardError::ContextError(err.to_string())) } }; // List available readers. let mut readers_buf = [0; 2048]; let readers = match ctx.list_readers(&mut readers_buf) { Ok(readers) => readers, Err(err) => { return Err(SmartcardError::ReaderError(err.to_string())); } }; let mut found_reader = false; let mut cards = vec![]; // Find a reader with a SmartCard. for reader in readers { // We've seen at least one smartcard reader found_reader = true; // Try connecting to card in this reader let card = match ctx.connect(reader, ShareMode::Shared, Protocols::ANY) { Ok(card) => card, Err(pcsc::Error::NoSmartcard) => { continue; // try next reader } Err(err) => { return Err(SmartcardError::SmartCardConnectionError( err.to_string(), )); } }; cards.push(card); } if !found_reader { Err(SmartcardError::NoReaderFoundError) } else { Ok(cards) } } /// All PCSC cards, wrapped as PcscClient fn unopened_cards() -> Result> { Ok(Self::pcsc_cards() .map_err(|err| anyhow!(err))? .into_iter() .map(PcscClient::new) .collect()) } /// Return all cards on which the OpenPGP application could be selected. /// /// Each card is opened and has the OpenPGP application selected. /// Cards are initialized via init_caps(). pub fn cards() -> Result> { let mut cards = vec![]; for mut card in Self::unopened_cards()? { if Self::select(&mut card).is_ok() { if let Ok(ca) = card.into_card_app() { cards.push(ca); } } } Ok(cards) } /// Try to select the OpenPGP application on a card fn select(card_client: &mut PcscClient) -> Result<(), Error> { if ::select(card_client).is_ok() { Ok(()) } else { Err(Error::Smartcard(SmartcardError::SelectOpenPGPCardFailed)) } } /// Returns the OpenPGP card that matches `ident`, if it is available. /// A fully initialized CardApp is returned: application has been /// selected, init_caps() has been performed. pub fn open_by_ident(ident: &str) -> Result { for mut card in Self::unopened_cards()? { Self::select(&mut card)?; let mut ca = card.into_card_app()?; let ard = ca.get_application_related_data()?; let aid = ard.get_application_id()?; if aid.ident() == ident { return Ok(ca); } } Err(Error::Smartcard(SmartcardError::CardNotFound( ident.to_string(), ))) } } impl CardClient for PcscClient { fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result> { let mut resp_buffer = vec![0; buf_size]; let resp = self.card.transmit(cmd, &mut resp_buffer).map_err(|e| { Error::Smartcard(SmartcardError::Error(format!( "Transmit failed: {:?}", e ))) })?; log::debug!(" <- APDU response: {:x?}", resp); Ok(resp.to_vec()) } fn init_caps(&mut self, caps: CardCaps) { self.card_caps = Some(caps); } fn get_caps(&self) -> Option<&CardCaps> { self.card_caps.as_ref() } }