Implement TxClient to use pcsc with transactions (transaction opening code is duplicated)

This commit is contained in:
Heiko Schaefer 2022-01-01 17:52:49 +01:00
parent 0baf36df67
commit 748c334403
No known key found for this signature in database
GPG key ID: 4A849A1904CCBD7D
2 changed files with 253 additions and 67 deletions

View file

@ -42,6 +42,10 @@ use anyhow::Result;
use std::convert::TryInto; use std::convert::TryInto;
use crate::apdu::commands; use crate::apdu::commands;
use crate::card_do::ApplicationRelatedData;
use crate::tlv::tag::Tag;
use crate::tlv::value::Value;
use crate::tlv::Tlv;
/// The CardClient trait defines communication with an OpenPGP card via a /// The CardClient trait defines communication with an OpenPGP card via a
/// backend implementation (e.g. the pcsc backend in the crate /// backend implementation (e.g. the pcsc backend in the crate
@ -94,12 +98,25 @@ pub trait CardClient {
/// A boxed CardClient (which is Send+Sync). /// A boxed CardClient (which is Send+Sync).
pub type CardClientBox = Box<dyn CardClient + Send + Sync>; pub type CardClientBox = Box<dyn CardClient + Send + Sync>;
impl dyn CardClient { impl<'a> dyn CardClient + 'a {
/// Select the OpenPGP card application /// Select the OpenPGP card application
pub fn select(&mut self) -> Result<Response, Error> { pub fn select(&mut self) -> Result<Response, Error> {
let select_openpgp = commands::select_openpgp(); let select_openpgp = commands::select_openpgp();
apdu::send_command(self, select_openpgp, false)?.try_into() apdu::send_command(self, select_openpgp, false)?.try_into()
} }
// FIXME: this is a duplicate from card_app
pub fn application_related_data(
&mut self,
) -> Result<ApplicationRelatedData> {
let ad = commands::application_related_data();
let resp = apdu::send_command(self, ad, true)?;
let value = Value::from(resp.data()?, true)?;
log::debug!(" App data Value: {:x?}", value);
Ok(ApplicationRelatedData(Tlv::new(Tag::from([0x6E]), value)))
}
} }
/// Configuration of the capabilities of a card. /// Configuration of the capabilities of a card.

View file

@ -7,10 +7,13 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use iso7816_tlv::simple::Tlv; use iso7816_tlv::simple::Tlv;
use pcsc::{Card, Context, Protocols, Scope, ShareMode}; use pcsc::{
Card, Context, Disposition, Protocols, Scope, ShareMode, Transaction,
};
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
use openpgp_card::card_do::ApplicationRelatedData;
use openpgp_card::{CardApp, CardCaps, CardClient, Error, SmartcardError}; use openpgp_card::{CardApp, CardCaps, CardClient, Error, SmartcardError};
const FEATURE_VERIFY_PIN_DIRECT: u8 = 0x06; const FEATURE_VERIFY_PIN_DIRECT: u8 = 0x06;
@ -24,6 +27,90 @@ pub struct PcscClient {
reader_caps: HashMap<u8, Tlv>, reader_caps: HashMap<u8, Tlv>,
} }
struct TxClient<'a, 'b> {
tx: &'a mut Transaction<'b>,
}
impl<'a, 'b> TxClient<'a, 'b> {
pub fn new(tx: &'a mut Transaction<'b>) -> Self {
TxClient { tx }
}
}
impl<'a, 'b> TxClient<'a, 'b> {
/// Try to select the OpenPGP application on a card
fn select(card_client: &'a mut TxClient) -> Result<(), Error> {
if <dyn CardClient>::select(card_client).is_ok() {
Ok(())
} else {
Err(Error::Smartcard(SmartcardError::SelectOpenPGPCardFailed))
}
}
/// Get application_related_data from card
fn application_related_data(
card_client: &mut TxClient,
) -> Result<ApplicationRelatedData, Error> {
<dyn CardClient>::application_related_data(card_client).map_err(|e| {
Error::Smartcard(SmartcardError::Error(format!(
"TxClient: failed to get application_related_data {:x?}",
e
)))
})
}
}
impl CardClient for TxClient<'_, '_> {
fn transmit(
&mut self,
cmd: &[u8],
buf_size: usize,
) -> Result<Vec<u8>, Error> {
let mut resp_buffer = vec![0; buf_size];
let resp =
self.tx
.transmit(cmd, &mut resp_buffer)
.map_err(|e| match e {
pcsc::Error::NotTransacted => {
Error::Smartcard(SmartcardError::NotTransacted)
}
_ => Error::Smartcard(SmartcardError::Error(format!(
"Transmit failed: {:?}",
e
))),
})?;
log::debug!(" <- APDU response: {:x?}", resp);
Ok(resp.to_vec())
}
fn init_card_caps(&mut self, caps: CardCaps) {
self.card_caps = Some(caps);
}
fn card_caps(&self) -> Option<&CardCaps> {
None
}
fn feature_pinpad_verify(&self) -> bool {
todo!()
}
fn feature_pinpad_modify(&self) -> bool {
todo!()
}
fn pinpad_verify(&mut self, _id: u8) -> Result<Vec<u8>> {
todo!()
}
fn pinpad_modify(&mut self, _id: u8) -> Result<Vec<u8>> {
todo!()
}
}
impl PcscClient { impl PcscClient {
/// A list of "raw" opened PCSC Cards (without selecting the OpenPGP card /// A list of "raw" opened PCSC Cards (without selecting the OpenPGP card
/// application) /// application)
@ -90,30 +177,66 @@ impl PcscClient {
} }
} }
/// All PCSC cards, wrapped as PcscClient
fn unopened_cards() -> Result<Vec<PcscClient>> {
Ok(Self::raw_pcsc_cards()
.map_err(|err| anyhow!(err))?
.into_iter()
.map(PcscClient::new)
.collect())
}
fn cards_filter(ident: Option<&str>) -> Result<Vec<CardApp>> { fn cards_filter(ident: Option<&str>) -> Result<Vec<CardApp>> {
let mut cards = vec![]; let mut cas: Vec<CardApp> = vec![];
for mut card in Self::unopened_cards()? { for mut card in Self::raw_pcsc_cards()? {
log::debug!("cards_filter: next card"); log::debug!("cards_filter: next card");
// FIXME: start transaction let stat = card.status2_owned();
// let tx = card.card.transaction()?; log::debug!("cards_filter, status2: {:x?}", stat);
if let Err(e) = Self::select(&mut card) { let mut store_card = false;
log::debug!("cards_filter: error during select: {:?}", e); {
// start transaction
log::debug!("1");
let mut tx = loop {
let res = card.transaction();
match res {
Ok(tx) => break tx,
Err(pcsc::Error::ResetCard) => {
// Card was reset, need to reconnect
drop(res);
log::debug!("1a");
{
card.reconnect(
ShareMode::Shared,
Protocols::ANY,
Disposition::ResetCard,
)?;
}
log::debug!("1b");
// try again
}
Err(e) => {
return Err(e.into());
}
};
};
log::debug!("2");
let mut txc = TxClient::new(&mut tx);
log::debug!("3");
{
if let Err(e) = TxClient::select(&mut txc) {
log::debug!("4a");
log::debug!(
"cards_filter: error during select: {:?}",
e
);
} else { } else {
if let Ok(mut ca) = card.into_card_app() { log::debug!("4b");
// successfully opened the OpenPGP application
if let Some(ident) = ident { if let Some(ident) = ident {
let ard = ca.application_related_data()?; let ard =
TxClient::application_related_data(&mut txc)?;
let aid = ard.application_id()?; let aid = ard.application_id()?;
if aid.ident() == ident.to_ascii_uppercase() { if aid.ident() == ident.to_ascii_uppercase() {
@ -123,31 +246,38 @@ impl PcscClient {
ident ident
); );
cards.push(ca); // we want to return this one card
store_card = true;
} else { } else {
log::debug!( log::debug!(
"open_by_ident: Found, but won't use {:?}", "open_by_ident: Found, but won't use {:?}",
aid.ident() aid.ident()
); );
}
} else {
// return all cards
cards.push(ca);
}
}
}
// FIXME: end transaction // FIXME: end transaction
// tx.end(Disposition::LeaveCard).map_err(|(_, e)| { // txc.end();
// Error::Smartcard(SmartcardError::Error( }
// format!("PCSC Transaction::end failed: {:?}", e) } else {
// )) // we want to return all cards
// })?; store_card = true;
}
}
} }
log::debug!("cards_filter: found {} cards", cards.len()); // transaction ends
// drop(txc);
// drop(tx);
}
Ok(cards) if store_card {
let pcsc = PcscClient::new(card);
cas.push(pcsc.into_card_app()?);
}
}
log::debug!("cards_filter: found {} cards", cas.len());
Ok(cas)
} }
/// Return all cards on which the OpenPGP application could be selected. /// Return all cards on which the OpenPGP application could be selected.
@ -200,15 +330,6 @@ impl PcscClient {
CardApp::initialize(Box::new(self)) CardApp::initialize(Box::new(self))
} }
/// Try to select the OpenPGP application on a card
fn select(card_client: &mut PcscClient) -> Result<(), Error> {
if <dyn CardClient>::select(card_client).is_ok() {
Ok(())
} else {
Err(Error::Smartcard(SmartcardError::SelectOpenPGPCardFailed))
}
}
/// Get the minimum pin length for pin_id. /// Get the minimum pin length for pin_id.
fn min_pin_len(&self, pin_id: u8) -> Result<u8> { fn min_pin_len(&self, pin_id: u8) -> Result<u8> {
match pin_id { match pin_id {
@ -256,24 +377,72 @@ impl CardClient for PcscClient {
cmd: &[u8], cmd: &[u8],
buf_size: usize, buf_size: usize,
) -> Result<Vec<u8>, Error> { ) -> Result<Vec<u8>, Error> {
let mut resp_buffer = vec![0; buf_size]; let mut was_reset = false;
let resp = let stat = self.card.status2_owned();
self.card.transmit(cmd, &mut resp_buffer).map_err( log::debug!("PcscClient transmit - status2: {:x?}", stat);
|e| match e {
pcsc::Error::NotTransacted => { let mut tx = loop {
Error::Smartcard(SmartcardError::NotTransacted) let res = self.card.transaction();
match res {
Ok(mut tx) => {
// A transaction has been successfully started
if was_reset {
log::debug!("card was reset, select() openpgp");
let mut txc = TxClient::new(&mut tx);
TxClient::select(&mut txc)?;
} }
_ => Error::Smartcard(SmartcardError::Error(format!(
"Transmit failed: {:?}",
e
))),
},
)?;
log::debug!(" <- APDU response: {:x?}", resp); break tx;
}
Err(pcsc::Error::ResetCard) => {
// Error getting Transaction.
// Card was reset -> need to reconnect.
Ok(resp.to_vec()) was_reset = true;
drop(res);
log::debug!("PcscClient transmit 1a");
{
self.card
.reconnect(
ShareMode::Shared,
Protocols::ANY,
Disposition::ResetCard,
)
.map_err(|e| {
Error::Smartcard(SmartcardError::Error(
format!("Reconnect failed: {:?}", e),
))
})?;
}
log::debug!("PcscClient transmit 1b");
// try again
}
Err(e) => {
return Err(Error::Smartcard(SmartcardError::Error(
format!("Error: {:?}", e),
)));
}
};
};
log::debug!("PcscClient transmit 2");
let mut txc = TxClient::new(&mut tx);
log::debug!("PcscClient transmit 3 (got TxClient!)");
let res = txc.transmit(cmd, buf_size);
log::debug!("PcscClient transmit res {:x?}", res);
res
} }
fn init_card_caps(&mut self, caps: CardCaps) { fn init_card_caps(&mut self, caps: CardCaps) {