685 lines
23 KiB
Rust
685 lines
23 KiB
Rust
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
|
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
//! This crate implements the `PcscClient` backend for the `openpgp-card`
|
|
//! crate, which uses the PCSC lite middleware to access the OpenPGP
|
|
//! application on smart cards.
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use iso7816_tlv::simple::Tlv;
|
|
use pcsc::{Card, Context, Protocols, Scope, ShareMode, Transaction};
|
|
use std::collections::HashMap;
|
|
use std::convert::TryInto;
|
|
|
|
use openpgp_card::card_do::ApplicationRelatedData;
|
|
use openpgp_card::{CardCaps, CardClient, Error, SmartcardError};
|
|
|
|
const FEATURE_VERIFY_PIN_DIRECT: u8 = 0x06;
|
|
const FEATURE_MODIFY_PIN_DIRECT: u8 = 0x07;
|
|
|
|
#[macro_export]
|
|
macro_rules! start_tx {
|
|
($card:expr, $reselect:expr) => {{
|
|
use anyhow::anyhow;
|
|
use openpgp_card::{Error, SmartcardError};
|
|
use pcsc::{Disposition, Protocols, ShareMode, Transaction};
|
|
|
|
let mut was_reset = false;
|
|
|
|
loop {
|
|
let res = $card.transaction();
|
|
|
|
match res {
|
|
Ok(mut tx) => {
|
|
// A transaction has been successfully started
|
|
|
|
if was_reset {
|
|
log::debug!(
|
|
"start_tx: card was reset, select() openpgp"
|
|
);
|
|
|
|
let mut txc = PcscTxClient::new(&mut tx, None);
|
|
if $reselect {
|
|
PcscTxClient::select(&mut txc)?;
|
|
}
|
|
}
|
|
|
|
break Ok(tx);
|
|
}
|
|
Err(pcsc::Error::ResetCard) => {
|
|
// Card was reset, need to reconnect
|
|
was_reset = true;
|
|
|
|
drop(res);
|
|
|
|
log::debug!("start_tx: do reconnect");
|
|
|
|
{
|
|
$card
|
|
.reconnect(
|
|
ShareMode::Shared,
|
|
Protocols::ANY,
|
|
Disposition::ResetCard,
|
|
)
|
|
.map_err(|e| {
|
|
Error::Smartcard(SmartcardError::Error(
|
|
format!("Reconnect failed: {:?}", e),
|
|
))
|
|
})?;
|
|
}
|
|
|
|
log::debug!("start_tx: reconnected.");
|
|
|
|
// -> try opening a transaction again
|
|
}
|
|
Err(e) => {
|
|
log::debug!("start_tx: error {:?}", e);
|
|
break Err(Error::Smartcard(SmartcardError::Error(
|
|
format!("Error: {:?}", e),
|
|
)));
|
|
}
|
|
};
|
|
}
|
|
}};
|
|
}
|
|
|
|
/// An implementation of the CardClient trait that uses the PCSC lite
|
|
/// middleware to access the OpenPGP card application on smart cards.
|
|
pub struct PcscClient {
|
|
card: Card,
|
|
card_caps: Option<CardCaps>,
|
|
reader_caps: HashMap<u8, Tlv>,
|
|
}
|
|
|
|
pub struct PcscTxClient<'a, 'b> {
|
|
tx: &'a mut Transaction<'b>,
|
|
card_caps: Option<CardCaps>,
|
|
}
|
|
|
|
impl<'a, 'b> PcscTxClient<'a, 'b> {
|
|
pub fn new(
|
|
tx: &'a mut Transaction<'b>,
|
|
card_caps: Option<CardCaps>,
|
|
) -> Self {
|
|
PcscTxClient { tx, card_caps }
|
|
}
|
|
|
|
/// Try to select the OpenPGP application on a card
|
|
pub fn select(card_client: &'a mut PcscTxClient) -> 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 PcscTxClient,
|
|
) -> 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
|
|
)))
|
|
})
|
|
}
|
|
|
|
/// GET_FEATURE_REQUEST
|
|
/// (see http://pcscworkgroup.com/Download/Specifications/pcsc10_v2.02.09.pdf)
|
|
fn features(&mut self) -> Result<Vec<Tlv>, Error> {
|
|
let mut recv = vec![0; 1024];
|
|
|
|
let cm_ioctl_get_feature_request = pcsc::ctl_code(3400);
|
|
let res = self
|
|
.tx
|
|
.control(cm_ioctl_get_feature_request, &[], &mut recv)
|
|
.map_err(|e| {
|
|
Error::Smartcard(SmartcardError::Error(format!(
|
|
"GET_FEATURE_REQUEST control call failed: {:?}",
|
|
e
|
|
)))
|
|
})?;
|
|
|
|
Ok(Tlv::parse_all(res))
|
|
}
|
|
}
|
|
|
|
impl CardClient for PcscTxClient<'_, '_> {
|
|
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> {
|
|
self.card_caps.as_ref()
|
|
}
|
|
|
|
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 {
|
|
pub fn card(&mut self) -> &mut Card {
|
|
&mut self.card
|
|
}
|
|
|
|
/// A list of "raw" opened PCSC Cards (without selecting the OpenPGP card
|
|
/// application)
|
|
fn raw_pcsc_cards() -> Result<Vec<Card>, SmartcardError> {
|
|
let ctx = match Context::establish(Scope::User) {
|
|
Ok(ctx) => ctx,
|
|
Err(err) => {
|
|
log::debug!("Context::establish failed: {:?}", 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) => {
|
|
log::debug!("list_readers failed: {:?}", err);
|
|
return Err(SmartcardError::ReaderError(err.to_string()));
|
|
}
|
|
};
|
|
|
|
log::debug!("readers: {:?}", readers);
|
|
|
|
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;
|
|
|
|
log::debug!("Checking reader: {:?}", reader);
|
|
|
|
// Try connecting to card in this reader
|
|
let card =
|
|
match ctx.connect(reader, ShareMode::Shared, Protocols::ANY) {
|
|
Ok(card) => card,
|
|
Err(pcsc::Error::NoSmartcard) => {
|
|
log::debug!("No Smartcard");
|
|
|
|
continue; // try next reader
|
|
}
|
|
Err(err) => {
|
|
log::warn!(
|
|
"Error connecting to card in reader: {:x?}",
|
|
err
|
|
);
|
|
|
|
continue;
|
|
}
|
|
};
|
|
|
|
log::debug!("Found card");
|
|
|
|
cards.push(card);
|
|
}
|
|
|
|
if !found_reader {
|
|
Err(SmartcardError::NoReaderFoundError)
|
|
} else {
|
|
Ok(cards)
|
|
}
|
|
}
|
|
|
|
fn cards_filter(ident: Option<&str>) -> Result<Vec<PcscClient>, Error> {
|
|
let mut cas: Vec<PcscClient> = vec![];
|
|
|
|
for mut card in
|
|
Self::raw_pcsc_cards().map_err(|sce| Error::Smartcard(sce))?
|
|
{
|
|
log::debug!("cards_filter: next card");
|
|
|
|
let stat = card.status2_owned();
|
|
log::debug!("cards_filter, status2: {:x?}", stat);
|
|
|
|
let mut store_card = false;
|
|
{
|
|
// start transaction
|
|
log::debug!("1");
|
|
let mut tx: Transaction = start_tx!(card, false)?;
|
|
|
|
let mut txc = PcscTxClient::new(&mut tx, None);
|
|
log::debug!("3");
|
|
{
|
|
if let Err(e) = PcscTxClient::select(&mut txc) {
|
|
log::debug!("4a");
|
|
log::debug!(
|
|
"cards_filter: error during select: {:?}",
|
|
e
|
|
);
|
|
} else {
|
|
log::debug!(
|
|
"4b: opened the OpenPGP application, will read ARD"
|
|
);
|
|
// successfully opened the OpenPGP application
|
|
|
|
// -- debug: status --
|
|
drop(txc);
|
|
let stat = tx.status2_owned().map_err(|e| {
|
|
Error::Smartcard(SmartcardError::Error(format!(
|
|
"{:?}",
|
|
e
|
|
)))
|
|
})?;
|
|
log::debug!("4b card status: {:x?}", stat);
|
|
let mut txc = PcscTxClient::new(&mut tx, None);
|
|
// -- /debug: status --
|
|
|
|
if let Some(ident) = ident {
|
|
if let Ok(ard) =
|
|
PcscTxClient::application_related_data(
|
|
&mut txc,
|
|
)
|
|
{
|
|
let aid = ard.application_id()?;
|
|
|
|
if aid.ident() == ident.to_ascii_uppercase() {
|
|
// FIXME: handle multiple cards with matching ident
|
|
log::debug!(
|
|
"open_by_ident: Opened and selected {:?}",
|
|
ident
|
|
);
|
|
|
|
// we want to return this one card
|
|
store_card = true;
|
|
} else {
|
|
log::debug!(
|
|
"open_by_ident: Found, but won't use {:?}",
|
|
aid.ident()
|
|
);
|
|
|
|
// FIXME: end transaction
|
|
// txc.end();
|
|
}
|
|
} else {
|
|
// couldn't read ARD for this card ...
|
|
// ignore and move on
|
|
continue;
|
|
}
|
|
} else {
|
|
// we want to return all cards
|
|
store_card = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// transaction ends
|
|
// drop(txc);
|
|
// drop(tx);
|
|
}
|
|
|
|
if store_card {
|
|
let pcsc = PcscClient::new(card);
|
|
cas.push(pcsc.initialize_card()?);
|
|
}
|
|
}
|
|
|
|
log::debug!("cards_filter: found {} cards", cas.len());
|
|
|
|
Ok(cas)
|
|
}
|
|
|
|
/// Return all cards on which the OpenPGP application could be selected.
|
|
///
|
|
/// Each card has the OpenPGP application selected, CardCaps have been
|
|
/// initialized.
|
|
pub fn cards() -> Result<Vec<PcscClient>, Error> {
|
|
Self::cards_filter(None)
|
|
}
|
|
|
|
/// Returns the OpenPGP card that matches `ident`, if it is available.
|
|
/// A fully initialized CardApp is returned: the OpenPGP application has
|
|
/// been selected, CardCaps have been set.
|
|
pub fn open_by_ident(ident: &str) -> Result<PcscClient, Error> {
|
|
log::debug!("open_by_ident for {:?}", ident);
|
|
|
|
let mut cards = Self::cards_filter(Some(ident))?;
|
|
|
|
if !cards.is_empty() {
|
|
// FIXME: handle >1 cards found
|
|
|
|
Ok(cards.pop().unwrap())
|
|
} else {
|
|
Err(Error::Smartcard(SmartcardError::CardNotFound(
|
|
ident.to_string(),
|
|
)))
|
|
}
|
|
}
|
|
|
|
fn new(card: Card) -> Self {
|
|
Self {
|
|
card,
|
|
card_caps: None,
|
|
reader_caps: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Make an initialized CardApp from a PcscClient:
|
|
/// - Obtain and store feature lists from reader (pinpad functionality).
|
|
/// - Get ARD from card, set CardCaps based on ARD.
|
|
fn initialize_card(mut self) -> Result<Self> {
|
|
log::debug!("pcsc initialize_card");
|
|
|
|
let mut tx: Transaction = start_tx!(self.card, true)?;
|
|
let mut txc = PcscTxClient::new(&mut tx, self.card_caps);
|
|
|
|
// Get Features from reader (pinpad verify/modify)
|
|
if let Ok(feat) = txc.features() {
|
|
for tlv in feat {
|
|
log::debug!("Found reader feature {:?}", tlv);
|
|
self.reader_caps.insert(tlv.tag().into(), tlv);
|
|
}
|
|
}
|
|
|
|
// Initalize CardClient (set CardCaps from ARD)
|
|
<dyn CardClient>::initialize(&mut txc)?;
|
|
|
|
self.card_caps = txc.card_caps().cloned();
|
|
|
|
drop(txc);
|
|
drop(tx);
|
|
|
|
Ok(self)
|
|
}
|
|
|
|
/// Get the minimum pin length for pin_id.
|
|
fn min_pin_len(&self, pin_id: u8) -> Result<u8> {
|
|
match pin_id {
|
|
0x81 | 0x82 => Ok(6),
|
|
0x83 => Ok(8),
|
|
_ => Err(anyhow!("Unexpected pin_id {}", pin_id)),
|
|
}
|
|
}
|
|
/// Get the maximum pin length for pin_id.
|
|
fn max_pin_len(&self, pin_id: u8) -> Result<u8> {
|
|
if let Some(card_caps) = self.card_caps {
|
|
match pin_id {
|
|
0x81 | 0x82 => Ok(card_caps.pw1_max_len()),
|
|
0x83 => Ok(card_caps.pw3_max_len()),
|
|
_ => Err(anyhow!("Unexpected pin_id {}", pin_id)),
|
|
}
|
|
} else {
|
|
Err(anyhow!("card_caps is None"))
|
|
}
|
|
}
|
|
|
|
pub fn card_caps(&self) -> Option<CardCaps> {
|
|
self.card_caps
|
|
}
|
|
}
|
|
|
|
// impl CardClient for PcscClient {
|
|
// fn transmit(
|
|
// &mut self,
|
|
// cmd: &[u8],
|
|
// buf_size: usize,
|
|
// ) -> Result<Vec<u8>, Error> {
|
|
// let stat = self.card.status2_owned();
|
|
// log::debug!("PcscClient transmit - status2: {:x?}", stat);
|
|
//
|
|
// let mut tx: Transaction = start_tx!(self.card, true)?;
|
|
//
|
|
// log::debug!("PcscClient transmit 2");
|
|
// let mut txc = PcscTxClient::new(&mut tx, self.card_caps);
|
|
// 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) {
|
|
// self.card_caps = Some(caps);
|
|
// }
|
|
//
|
|
// fn card_caps(&self) -> Option<&CardCaps> {
|
|
// self.card_caps.as_ref()
|
|
// }
|
|
//
|
|
// fn feature_pinpad_verify(&self) -> bool {
|
|
// self.reader_caps.contains_key(&FEATURE_VERIFY_PIN_DIRECT)
|
|
// }
|
|
//
|
|
// fn feature_pinpad_modify(&self) -> bool {
|
|
// self.reader_caps.contains_key(&FEATURE_MODIFY_PIN_DIRECT)
|
|
// }
|
|
//
|
|
// fn pinpad_verify(&mut self, pin_id: u8) -> Result<Vec<u8>> {
|
|
// let pin_min_size = self.min_pin_len(pin_id)?;
|
|
// let pin_max_size = self.max_pin_len(pin_id)?;
|
|
//
|
|
// // Default to varlen, for now.
|
|
// // (NOTE: Some readers don't support varlen, and need explicit length
|
|
// // information. Also see https://wiki.gnupg.org/CardReader/PinpadInput)
|
|
// let fixedlen: u8 = 0;
|
|
//
|
|
// // APDU: 00 20 00 pin_id <len> (ff)*
|
|
// let mut ab_data = vec![
|
|
// 0x00, /* CLA */
|
|
// 0x20, /* INS: VERIFY */
|
|
// 0x00, /* P1 */
|
|
// pin_id, /* P2 */
|
|
// fixedlen, /* Lc: 'fixedlen' data bytes */
|
|
// ];
|
|
// ab_data.extend([0xff].repeat(fixedlen as usize));
|
|
//
|
|
// // PC/SC v2.02.05 Part 10 PIN verification data structure
|
|
// let mut send: Vec<u8> = vec![
|
|
// // 0 bTimeOut BYTE timeout in seconds (00 means use default
|
|
// // timeout)
|
|
// 0x00,
|
|
// // 1 bTimeOut2 BYTE timeout in seconds after first key stroke
|
|
// 0x00,
|
|
// // 2 bmFormatString BYTE formatting options USB_CCID_PIN_FORMAT_xxx
|
|
// 0x82,
|
|
// // 3 bmPINBlockString BYTE
|
|
// // bits 7-4 bit size of PIN length in APDU
|
|
// // bits 3-0 PIN block size in bytes after justification and formatting
|
|
// fixedlen,
|
|
// // 4 bmPINLengthFormat BYTE
|
|
// // bits 7-5 RFU, bit 4 set if system units are bytes clear if
|
|
// // system units are bits,
|
|
// // bits 3-0 PIN length position in system units
|
|
// 0x00,
|
|
// // 5 wPINMaxExtraDigit USHORT XXYY, where XX is minimum PIN size
|
|
// // in digits, YY is maximum
|
|
// pin_max_size,
|
|
// pin_min_size,
|
|
// // 7 bEntryValidationCondition BYTE Conditions under which PIN
|
|
// // entry should be considered complete.
|
|
// //
|
|
// // table for bEntryValidationCondition:
|
|
// // 0x01: Max size reached
|
|
// // 0x02: Validation key pressed
|
|
// // 0x04: Timeout occurred
|
|
// 0x07,
|
|
// // 8 bNumberMessage BYTE Number of messages to display for PIN
|
|
// // verification
|
|
// 0x01,
|
|
// // 9 wLangIdU SHORT Language for messages
|
|
// 0x04,
|
|
// 0x09, // US english
|
|
// // 11 bMsgIndex BYTE Message index (should be 00)
|
|
// 0x00,
|
|
// // 12 bTeoPrologue BYTE[3] T=1 I-block prologue field to use (fill with 00)
|
|
// 0x00,
|
|
// 0x00,
|
|
// 0x00,
|
|
// ];
|
|
//
|
|
// // 15 ulDataLength ULONG length of Data to be sent to the ICC
|
|
// send.extend(&(ab_data.len() as u32).to_le_bytes());
|
|
//
|
|
// // 19 abData BYTE[] Data to send to the ICC
|
|
// send.extend(ab_data);
|
|
//
|
|
// log::debug!("pcsc pinpad_verify send: {:x?}", send);
|
|
//
|
|
// let mut recv = vec![0xAA; 256];
|
|
//
|
|
// let verify_ioctl: [u8; 4] = self
|
|
// .reader_caps
|
|
// .get(&FEATURE_VERIFY_PIN_DIRECT)
|
|
// .ok_or_else(|| anyhow!("no reader_capability"))?
|
|
// .value()
|
|
// .try_into()?;
|
|
//
|
|
// let res = self.card.control(
|
|
// u32::from_be_bytes(verify_ioctl).into(),
|
|
// &send,
|
|
// &mut recv,
|
|
// )?;
|
|
//
|
|
// log::debug!(" <- pcsc pinpad_verify result: {:x?}", res);
|
|
//
|
|
// Ok(res.to_vec())
|
|
// }
|
|
//
|
|
// fn pinpad_modify(&mut self, pin_id: u8) -> Result<Vec<u8>> {
|
|
// let pin_min_size = self.min_pin_len(pin_id)?;
|
|
// let pin_max_size = self.max_pin_len(pin_id)?;
|
|
//
|
|
// // Default to varlen, for now.
|
|
// // (NOTE: Some readers don't support varlen, and need explicit length
|
|
// // information. Also see https://wiki.gnupg.org/CardReader/PinpadInput)
|
|
// let fixedlen: u8 = 0;
|
|
//
|
|
// // APDU: 00 24 00 pin_id <len> [(ff)* x2]
|
|
// let mut ab_data = vec![
|
|
// 0x00, /* CLA */
|
|
// 0x24, /* INS: CHANGE_REFERENCE_DATA */
|
|
// 0x00, /* P1 */
|
|
// pin_id, /* P2 */
|
|
// fixedlen * 2, /* Lc: 'fixedlen' data bytes */
|
|
// ];
|
|
// ab_data.extend([0xff].repeat(fixedlen as usize * 2));
|
|
//
|
|
// // PC/SC v2.02.05 Part 10 PIN modification data structure
|
|
// let mut send: Vec<u8> = vec![
|
|
// // 0 bTimeOut BYTE timeout in seconds (00 means use default
|
|
// // timeout)
|
|
// 0x00,
|
|
// // 1 bTimeOut2 BYTE timeout in seconds after first key stroke
|
|
// 0x00,
|
|
// // 2 bmFormatString BYTE formatting options USB_CCID_PIN_FORMAT_xxx
|
|
// 0x82,
|
|
// // 3 bmPINBlockString BYTE
|
|
// // bits 7-4 bit size of PIN length in APDU
|
|
// // bits 3-0 PIN block size in bytes after justification and formatting
|
|
// fixedlen,
|
|
// // 4 bmPINLengthFormat BYTE
|
|
// // bits 7-5 RFU, bit 4 set if system units are bytes clear if
|
|
// // system units are bits,
|
|
// // bits 3-0 PIN length position in system units
|
|
// 0x00,
|
|
// // 5 bInsertionOffsetOld BYTE Insertion position offset in bytes for
|
|
// // the current PIN
|
|
// 0x00,
|
|
// // 6 bInsertionOffsetNew BYTE Insertion position offset in bytes for
|
|
// // the new PIN
|
|
// fixedlen,
|
|
// // 7 wPINMaxExtraDigit USHORT XXYY, where XX is minimum PIN size
|
|
// // in digits, YY is maximum
|
|
// pin_max_size,
|
|
// pin_min_size,
|
|
// // 9 bConfirmPIN
|
|
// 0x03, // TODO check?
|
|
// // 10 bEntryValidationCondition BYTE Conditions under which PIN
|
|
// // entry should be considered complete.
|
|
// //
|
|
// // table for bEntryValidationCondition:
|
|
// // 0x01: Max size reached
|
|
// // 0x02: Validation key pressed
|
|
// // 0x04: Timeout occurred
|
|
// 0x07,
|
|
// // 11 bNumberMessage BYTE Number of messages to display for PIN
|
|
// // verification
|
|
// 0x03, // TODO check? (match with bConfirmPIN?)
|
|
// // 12 wLangId USHORT Language for messages
|
|
// 0x04,
|
|
// 0x09, // US english
|
|
// // 14 bMsgIndex1-3
|
|
// 0x00,
|
|
// 0x01,
|
|
// 0x02,
|
|
// // 17 bTeoPrologue BYTE[3] T=1 I-block prologue field to use (fill with 00)
|
|
// 0x00,
|
|
// 0x00,
|
|
// 0x00,
|
|
// ];
|
|
//
|
|
// // 15 ulDataLength ULONG length of Data to be sent to the ICC
|
|
// send.extend(&(ab_data.len() as u32).to_le_bytes());
|
|
//
|
|
// // 19 abData BYTE[] Data to send to the ICC
|
|
// send.extend(ab_data);
|
|
//
|
|
// log::debug!("pcsc pinpad_modify send: {:x?}", send);
|
|
//
|
|
// let mut recv = vec![0xAA; 256];
|
|
//
|
|
// let modify_ioctl: [u8; 4] = self
|
|
// .reader_caps
|
|
// .get(&FEATURE_MODIFY_PIN_DIRECT)
|
|
// .ok_or_else(|| anyhow!("no reader_capability"))?
|
|
// .value()
|
|
// .try_into()?;
|
|
//
|
|
// let res = self.card.control(
|
|
// u32::from_be_bytes(modify_ioctl).into(),
|
|
// &send,
|
|
// &mut recv,
|
|
// )?;
|
|
//
|
|
// log::debug!(" <- pcsc pinpad_modify result: {:x?}", res);
|
|
//
|
|
// Ok(res.to_vec())
|
|
// }
|
|
// }
|