Implement pinpad feature detection and pinpad support for verify/modify (of pw1 and pw3) in pcsc backend.

Extend CardCaps to contain pw1_max_len and pw3_max_len (and initialize these values from ARD).

Add pinpad_verify(), pinpad_modify(), feature_verify()/feature_modify() to CardClient API.
Expose in card_app (and openpgp-card-sequoia card API).

Adjust opgpcard, opgpcard-pin to ue pinpad reader when available.
This commit is contained in:
Heiko Schaefer 2021-11-26 01:07:49 +01:00
parent a7fb5b2b2c
commit 2709b4ad39
No known key found for this signature in database
GPG key ID: 4A849A1904CCBD7D
14 changed files with 655 additions and 107 deletions

View file

@ -255,8 +255,14 @@ pub fn test_keygen(
// Generate a Cert for this set of generated keys // Generate a Cert for this set of generated keys
let mut open = Open::new(&mut ca)?; let mut open = Open::new(&mut ca)?;
let cert = let cert = make_cert(
make_cert(&mut open, key_sig, Some(key_dec), Some(key_aut), "123456")?; &mut open,
key_sig,
Some(key_dec),
Some(key_aut),
Some("123456".to_string()),
&|| {},
)?;
let armored = String::from_utf8(cert.armored().to_vec()?)?; let armored = String::from_utf8(cert.armored().to_vec()?)?;
let res = TestResult::Text(armored); let res = TestResult::Text(armored);

View file

@ -58,6 +58,13 @@ impl<'a> Open<'a> {
pw3: false, pw3: false,
}) })
} }
pub fn feature_pinpad_verify(&self) -> bool {
self.card_app.feature_pinpad_verify()
}
pub fn feature_pinpad_modify(&self) -> bool {
self.card_app.feature_pinpad_modify()
}
pub fn verify_user(&mut self, pin: &str) -> Result<(), Error> { pub fn verify_user(&mut self, pin: &str) -> Result<(), Error> {
let _ = self.card_app.verify_pw1(pin)?; let _ = self.card_app.verify_pw1(pin)?;
@ -65,6 +72,17 @@ impl<'a> Open<'a> {
Ok(()) Ok(())
} }
pub fn verify_user_pinpad(
&mut self,
prompt: &dyn Fn(),
) -> Result<(), Error> {
prompt();
let _ = self.card_app.verify_pw1_pinpad()?;
self.pw1 = true;
Ok(())
}
pub fn verify_user_for_signing(&mut self, pin: &str) -> Result<(), Error> { pub fn verify_user_for_signing(&mut self, pin: &str) -> Result<(), Error> {
let _ = self.card_app.verify_pw1_for_signing(pin)?; let _ = self.card_app.verify_pw1_for_signing(pin)?;
@ -74,12 +92,37 @@ impl<'a> Open<'a> {
Ok(()) Ok(())
} }
pub fn verify_user_for_signing_pinpad(
&mut self,
prompt: &dyn Fn(),
) -> Result<(), Error> {
prompt();
let _ = self.card_app.verify_pw1_for_signing_pinpad()?;
// FIXME: depending on card mode, pw1_sign is only usable once
self.pw1_sign = true;
Ok(())
}
pub fn verify_admin(&mut self, pin: &str) -> Result<(), Error> { pub fn verify_admin(&mut self, pin: &str) -> Result<(), Error> {
let _ = self.card_app.verify_pw3(pin)?; let _ = self.card_app.verify_pw3(pin)?;
self.pw3 = true; self.pw3 = true;
Ok(()) Ok(())
} }
pub fn verify_admin_pinpad(
&mut self,
prompt: &dyn Fn(),
) -> Result<(), Error> {
prompt();
let _ = self.card_app.verify_pw3_pinpad()?;
self.pw3 = true;
Ok(())
}
/// Ask the card if the user password has been successfully verified. /// Ask the card if the user password has been successfully verified.
/// ///
/// NOTE: on some cards this functionality seems broken. /// NOTE: on some cards this functionality seems broken.
@ -102,6 +145,14 @@ impl<'a> Open<'a> {
self.card_app.change_pw1(old, new) self.card_app.change_pw1(old, new)
} }
pub fn change_user_pin_pinpad(
&mut self,
prompt: &dyn Fn(),
) -> Result<Response, Error> {
prompt();
self.card_app.change_pw1_pinpad()
}
pub fn reset_user_pin( pub fn reset_user_pin(
&mut self, &mut self,
rst: &str, rst: &str,
@ -119,6 +170,14 @@ impl<'a> Open<'a> {
self.card_app.change_pw3(old, new) self.card_app.change_pw3(old, new)
} }
pub fn change_admin_pin_pinpad(
&mut self,
prompt: &dyn Fn(),
) -> Result<Response, Error> {
prompt();
self.card_app.change_pw3_pinpad()
}
/// Get a view of the card authenticated for "User" commands. /// Get a view of the card authenticated for "User" commands.
pub fn user_card<'b>(&'a mut self) -> Option<User<'a, 'b>> { pub fn user_card<'b>(&'a mut self) -> Option<User<'a, 'b>> {
if self.pw1 { if self.pw1 {

View file

@ -37,13 +37,18 @@ use crate::{decryptor, signer, PublicKey};
/// Create a Cert from the three subkeys on a card. /// Create a Cert from the three subkeys on a card.
/// (Calling this multiple times will result in different Certs!) /// (Calling this multiple times will result in different Certs!)
/// ///
/// When pw1 is None, attempt to verify via pinpad.
///
/// `prompt` notifies the user when a pinpad needs the user pin as input.
///
/// FIXME: accept optional metadata for user_id(s)? /// FIXME: accept optional metadata for user_id(s)?
pub fn make_cert<'a, 'app>( pub fn make_cert<'a, 'app>(
open: &'a mut Open<'app>, open: &'a mut Open<'app>,
key_sig: PublicKey, key_sig: PublicKey,
key_dec: Option<PublicKey>, key_dec: Option<PublicKey>,
key_aut: Option<PublicKey>, key_aut: Option<PublicKey>,
pw1: &str, pw1: Option<String>,
prompt: &dyn Fn(),
) -> Result<Cert> { ) -> Result<Cert> {
let mut pp = vec![]; let mut pp = vec![];
@ -72,7 +77,11 @@ pub fn make_cert<'a, 'app>(
)?; )?;
// Allow signing on the card // Allow signing on the card
open.verify_user_for_signing(pw1)?; if let Some(pw1) = pw1.clone() {
open.verify_user_for_signing(&pw1)?;
} else {
open.verify_user_for_signing_pinpad(prompt)?;
}
if let Some(mut sign) = open.signing_card() { if let Some(mut sign) = open.signing_card() {
// Card-backed signer for bindings // Card-backed signer for bindings
let mut card_signer = sign.signer_from_pubkey(key_sig.clone()); let mut card_signer = sign.signer_from_pubkey(key_sig.clone());
@ -100,7 +109,11 @@ pub fn make_cert<'a, 'app>(
.set_key_flags(KeyFlags::empty().set_authentication())?; .set_key_flags(KeyFlags::empty().set_authentication())?;
// Allow signing on the card // Allow signing on the card
open.verify_user_for_signing(pw1)?; if let Some(pw1) = pw1.clone() {
open.verify_user_for_signing(&pw1)?;
} else {
open.verify_user_for_signing_pinpad(prompt)?;
}
if let Some(mut sign) = open.signing_card() { if let Some(mut sign) = open.signing_card() {
// Card-backed signer for bindings // Card-backed signer for bindings
let mut card_signer = sign.signer_from_pubkey(key_sig.clone()); let mut card_signer = sign.signer_from_pubkey(key_sig.clone());
@ -141,7 +154,11 @@ pub fn make_cert<'a, 'app>(
)?; )?;
// Allow signing on the card // Allow signing on the card
open.verify_user_for_signing(pw1)?; if let Some(pw1) = pw1 {
open.verify_user_for_signing(&pw1)?;
} else {
open.verify_user_for_signing_pinpad(prompt)?;
}
if let Some(mut sign) = open.signing_card() { if let Some(mut sign) = open.signing_card() {
// Card-backed signer for bindings // Card-backed signer for bindings

View file

@ -9,6 +9,7 @@ use std::convert::TryInto;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use crate::algorithm::{Algo, AlgoInfo, AlgoSimple}; use crate::algorithm::{Algo, AlgoInfo, AlgoSimple};
use crate::apdu::response::RawResponse;
use crate::apdu::{commands, response::Response}; use crate::apdu::{commands, response::Response};
use crate::card_do::{ use crate::card_do::{
ApplicationRelatedData, CardholderRelatedData, Fingerprint, ApplicationRelatedData, CardholderRelatedData, Fingerprint,
@ -89,11 +90,17 @@ impl CardApp {
(255, 255) (255, 255)
}; };
let pw_status = ard.get_pw_status_bytes()?;
let pw1_max = pw_status.get_pw1_max_len();
let pw3_max = pw_status.get_pw3_max_len();
let caps = CardCaps { let caps = CardCaps {
ext_support, ext_support,
chaining_support, chaining_support,
max_cmd_bytes, max_cmd_bytes,
max_rsp_bytes, max_rsp_bytes,
pw1_max_len: pw1_max,
pw3_max_len: pw3_max,
}; };
self.card_client.init_caps(caps); self.card_client.init_caps(caps);
@ -335,6 +342,18 @@ impl CardApp {
Ok(()) Ok(())
} }
// ----------
/// Does the cardreader support direct pinpad verify?
pub fn feature_pinpad_verify(&self) -> bool {
self.card_client.feature_pinpad_verify()
}
/// Does the cardreader support direct pinpad modify?
pub fn feature_pinpad_modify(&self) -> bool {
self.card_client.feature_pinpad_modify()
}
/// Verify pw1 (user) for signing operation (mode 81) and set an /// Verify pw1 (user) for signing operation (mode 81) and set an
/// appropriate access status. /// appropriate access status.
/// ///
@ -349,6 +368,20 @@ impl CardApp {
apdu::send_command(self.card_client(), verify, false)?.try_into() apdu::send_command(self.card_client(), verify, false)?.try_into()
} }
/// Verify pw1 (user) for signing operation (mode 81) and set an
/// appropriate access status. This fn uses a pinpad on the card reader,
/// if no usable pinpad is found, an error is returned.
///
/// Depending on the PW1 status byte (see Extended Capabilities) this
/// access condition is only valid for one PSO:CDS command or remains
/// valid for several attempts.
pub fn verify_pw1_for_signing_pinpad(
&mut self,
) -> Result<Response, Error> {
let res = self.card_client.pinpad_verify(0x81)?;
RawResponse::try_from(res)?.try_into()
}
/// Check the current access of PW1 for signing (mode 81). /// Check the current access of PW1 for signing (mode 81).
/// ///
/// If verification is not required, an empty Ok Response is returned. /// If verification is not required, an empty Ok Response is returned.
@ -367,6 +400,15 @@ impl CardApp {
apdu::send_command(self.card_client(), verify, false)?.try_into() apdu::send_command(self.card_client(), verify, false)?.try_into()
} }
/// Verify PW1 (user) and set an appropriate access status.
/// (For operations except signing, mode 82).
/// This fn uses a pinpad on the card reader, if no usable pinpad is
/// found, an error is returned.
pub fn verify_pw1_pinpad(&mut self) -> Result<Response, Error> {
let res = self.card_client.pinpad_verify(0x82)?;
RawResponse::try_from(res)?.try_into()
}
/// Check the current access of PW1. /// Check the current access of PW1.
/// (For operations except signing, mode 82). /// (For operations except signing, mode 82).
/// ///
@ -385,6 +427,14 @@ impl CardApp {
apdu::send_command(self.card_client(), verify, false)?.try_into() apdu::send_command(self.card_client(), verify, false)?.try_into()
} }
/// Verify PW3 (admin) and set an appropriate access status.
/// This fn uses a pinpad on the card reader, if no usable pinpad is
/// found, an error is returned.
pub fn verify_pw3_pinpad(&mut self) -> Result<Response, Error> {
let res = self.card_client.pinpad_verify(0x83)?;
RawResponse::try_from(res)?.try_into()
}
/// Check the current access of PW3 (admin). /// Check the current access of PW3 (admin).
/// ///
/// If verification is not required, an empty Ok Response is returned. /// If verification is not required, an empty Ok Response is returned.
@ -412,6 +462,14 @@ impl CardApp {
apdu::send_command(self.card_client(), change, false)?.try_into() apdu::send_command(self.card_client(), change, false)?.try_into()
} }
/// Change the value of PW1 (user password).
/// This fn uses a pinpad on the card reader, if no usable pinpad is
/// found, an error is returned.
pub fn change_pw1_pinpad(&mut self) -> Result<Response, Error> {
let res = self.card_client.pinpad_modify(0x81)?;
RawResponse::try_from(res)?.try_into()
}
/// Change the value of PW3 (admin password). /// Change the value of PW3 (admin password).
/// ///
/// The current value of PW3 must be presented in `old` for authorization. /// The current value of PW3 must be presented in `old` for authorization.
@ -428,6 +486,14 @@ impl CardApp {
apdu::send_command(self.card_client(), change, false)?.try_into() apdu::send_command(self.card_client(), change, false)?.try_into()
} }
/// Change the value of PW3 (admin password).
/// This fn uses a pinpad on the card reader, if no usable pinpad is
/// found, an error is returned.
pub fn change_pw3_pinpad(&mut self) -> Result<Response, Error> {
let res = self.card_client.pinpad_modify(0x83)?;
RawResponse::try_from(res)?.try_into()
}
/// Reset the error counter for PW1 (user password) and set a new value /// Reset the error counter for PW1 (user password) and set a new value
/// for PW1. /// for PW1.
/// ///

View file

@ -325,10 +325,10 @@ impl From<u8> for Sex {
pub struct PWStatusBytes { pub struct PWStatusBytes {
pub(crate) pw1_cds_valid_once: bool, pub(crate) pw1_cds_valid_once: bool,
pub(crate) pw1_pin_block: bool, pub(crate) pw1_pin_block: bool,
pub(crate) pw1_len: u8, pub(crate) pw1_len_format: u8,
pub(crate) rc_len: u8, pub(crate) rc_len: u8,
pub(crate) pw3_pin_block: bool, pub(crate) pw3_pin_block: bool,
pub(crate) pw3_len: u8, pub(crate) pw3_len_format: u8,
pub(crate) err_count_pw1: u8, pub(crate) err_count_pw1: u8,
pub(crate) err_count_rst: u8, pub(crate) err_count_rst: u8,
pub(crate) err_count_pw3: u8, pub(crate) err_count_pw3: u8,
@ -349,6 +349,18 @@ impl PWStatusBytes {
self.pw1_cds_valid_once self.pw1_cds_valid_once
} }
pub fn get_pw1_max_len(&self) -> u8 {
self.pw1_len_format & 0x7f
}
pub fn get_rc_max_len(&self) -> u8 {
self.rc_len
}
pub fn get_pw3_max_len(&self) -> u8 {
self.pw3_len_format & 0x7f
}
pub fn get_err_count_pw1(&self) -> u8 { pub fn get_err_count_pw1(&self) -> u8 {
self.err_count_pw1 self.err_count_pw1
} }

View file

@ -21,7 +21,7 @@ impl PWStatusBytes {
data.push(if self.pw1_cds_valid_once { 0 } else { 1 }); data.push(if self.pw1_cds_valid_once { 0 } else { 1 });
if long { if long {
let mut b2 = self.pw1_len; let mut b2 = self.pw1_len_format;
if self.pw1_pin_block { if self.pw1_pin_block {
b2 |= 0x80; b2 |= 0x80;
} }
@ -29,7 +29,7 @@ impl PWStatusBytes {
data.push(self.rc_len); data.push(self.rc_len);
let mut b4 = self.pw3_len; let mut b4 = self.pw3_len_format;
if self.pw3_pin_block { if self.pw3_pin_block {
b4 |= 0x80; b4 |= 0x80;
} }
@ -58,10 +58,10 @@ impl TryFrom<&[u8]> for PWStatusBytes {
Ok(Self { Ok(Self {
pw1_cds_valid_once, pw1_cds_valid_once,
pw1_pin_block, pw1_pin_block,
pw1_len, pw1_len_format: pw1_len,
rc_len, rc_len,
pw3_pin_block, pw3_pin_block,
pw3_len, pw3_len_format: pw3_len,
err_count_pw1, err_count_pw1,
err_count_rst, err_count_rst,
err_count_pw3, err_count_pw3,
@ -92,10 +92,10 @@ mod test {
PWStatusBytes { PWStatusBytes {
pw1_cds_valid_once: true, pw1_cds_valid_once: true,
pw1_pin_block: false, pw1_pin_block: false,
pw1_len: 0x40, pw1_len_format: 0x40,
rc_len: 0x40, rc_len: 0x40,
pw3_pin_block: false, pw3_pin_block: false,
pw3_len: 0x40, pw3_len_format: 0x40,
err_count_pw1: 3, err_count_pw1: 3,
err_count_rst: 0, err_count_rst: 0,
err_count_pw3: 3 err_count_pw3: 3

View file

@ -77,6 +77,18 @@ pub trait CardClient {
fn max_cmd_len(&self) -> Option<usize> { fn max_cmd_len(&self) -> Option<usize> {
None None
} }
/// Does the reader support FEATURE_VERIFY_PIN_DIRECT?
fn feature_pinpad_verify(&self) -> bool;
/// Does the reader support FEATURE_MODIFY_PIN_DIRECT?
fn feature_pinpad_modify(&self) -> bool;
/// Verify the PIN 'id' via the reader pinpad
fn pinpad_verify(&mut self, id: u8) -> Result<Vec<u8>>;
/// Modify the PIN 'id' via the reader pinpad
fn pinpad_modify(&mut self, id: u8) -> Result<Vec<u8>>;
} }
/// A boxed CardClient (which is Send+Sync). /// A boxed CardClient (which is Send+Sync).
@ -96,7 +108,7 @@ impl dyn CardClient {
/// length can be used when communicating with the card. /// length can be used when communicating with the card.
/// ///
/// (This configuration is retrieved from card metadata, specifically from /// (This configuration is retrieved from card metadata, specifically from
/// "Card Capabilities" and "Extended length information") /// "Card Capabilities", "Extended length information" and "PWStatus")
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct CardCaps { pub struct CardCaps {
/// Extended Lc and Le fields /// Extended Lc and Le fields
@ -110,6 +122,12 @@ pub struct CardCaps {
/// Maximum number of bytes in a response APDU /// Maximum number of bytes in a response APDU
max_rsp_bytes: u16, max_rsp_bytes: u16,
/// Maximum length of pw1
pw1_max_len: u8,
/// Maximum length of pw3
pw3_max_len: u8,
} }
impl CardCaps { impl CardCaps {
@ -120,6 +138,14 @@ impl CardCaps {
pub fn get_max_rsp_bytes(&self) -> u16 { pub fn get_max_rsp_bytes(&self) -> u16 {
self.max_rsp_bytes self.max_rsp_bytes
} }
pub fn get_pw1_max_len(&self) -> u8 {
self.pw1_max_len
}
pub fn get_pw3_max_len(&self) -> u8 {
self.pw3_max_len
}
} }
/// Identify a Key slot on an OpenPGP card /// Identify a Key slot on an OpenPGP card

View file

@ -13,6 +13,7 @@ documentation = "https://docs.rs/crate/openpgp-card-pcsc"
[dependencies] [dependencies]
openpgp-card = { path = "../openpgp-card", version = "0.0.5" } openpgp-card = { path = "../openpgp-card", version = "0.0.5" }
iso7816-tlv = "0.4"
pcsc = "2" pcsc = "2"
anyhow = "1" anyhow = "1"
log = "0.4" log = "0.4"

View file

@ -2,13 +2,20 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use iso7816_tlv::simple::Tlv;
use pcsc::{Card, Context, Protocols, Scope, ShareMode}; use pcsc::{Card, Context, Protocols, Scope, ShareMode};
use std::collections::HashMap;
use std::convert::TryInto;
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_MODIFY_PIN_DIRECT: u8 = 0x07;
pub struct PcscClient { pub struct PcscClient {
card: Card, card: Card,
card_caps: Option<CardCaps>, card_caps: Option<CardCaps>,
reader_caps: HashMap<u8, Tlv>,
} }
impl PcscClient { impl PcscClient {
@ -56,11 +63,21 @@ impl PcscClient {
Self { Self {
card, card,
card_caps: None, card_caps: None,
reader_caps: HashMap::new(),
} }
} }
/// Make an initialized CardApp from a PcscClient /// Make an initialized CardApp from a PcscClient.
fn into_card_app(self) -> Result<CardApp> { /// Obtain and store feature lists from reader (pinpad functionality).
fn into_card_app(mut self) -> Result<CardApp> {
// Get Features from reader (pinpad verify/modify)
let feat = self.features()?;
for tlv in feat {
log::debug!("Found reader feature {:?}", tlv);
self.reader_caps.insert(tlv.tag().into(), tlv);
}
// Get initalized CardApp
CardApp::initialize(Box::new(self)) CardApp::initialize(Box::new(self))
} }
@ -136,6 +153,46 @@ impl PcscClient {
Err(Error::Smartcard(SmartcardError::SelectOpenPGPCardFailed)) Err(Error::Smartcard(SmartcardError::SelectOpenPGPCardFailed))
} }
} }
/// Get the minimum pin length for pin_id.
fn get_min_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 get_max_len(&self, pin_id: u8) -> Result<u8> {
if let Some(card_caps) = self.card_caps {
match pin_id {
0x81 | 0x82 => Ok(card_caps.get_pw1_max_len()),
0x83 => Ok(card_caps.get_pw3_max_len()),
_ => Err(anyhow!("Unexpected pin_id {}", pin_id)),
}
} else {
Err(anyhow!("card_caps is None"))
}
}
/// 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 = 0x42000000 + 3400;
let res = self
.card
.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 PcscClient { impl CardClient for PcscClient {
@ -171,4 +228,204 @@ impl CardClient for PcscClient {
fn get_caps(&self) -> Option<&CardCaps> { fn get_caps(&self) -> Option<&CardCaps> {
self.card_caps.as_ref() 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.get_min_len(pin_id)?;
let pin_max_size = self.get_max_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) as u64,
&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.get_min_len(pin_id)?;
let pin_max_size = self.get_max_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) as u64,
&send,
&mut recv,
)?;
log::debug!(" <- pcsc pinpad_modify result: {:x?}", res);
Ok(res.to_vec())
}
} }

View file

@ -262,4 +262,20 @@ impl CardClient for ScdClient {
fn max_cmd_len(&self) -> Option<usize> { fn max_cmd_len(&self) -> Option<usize> {
Some(APDU_CMD_BYTES_MAX) Some(APDU_CMD_BYTES_MAX)
} }
fn feature_pinpad_verify(&self) -> bool {
false // FIXME
}
fn feature_pinpad_modify(&self) -> bool {
false // FIXME
}
fn pinpad_verify(&mut self, _id: u8) -> Result<Vec<u8>> {
unimplemented!() // FIXME
}
fn pinpad_modify(&mut self, _id: u8) -> Result<Vec<u8>> {
unimplemented!() // FIXME
}
} }

View file

@ -11,35 +11,52 @@ use openpgp_card_sequoia::card::Open;
mod cli; mod cli;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let cli = cli::Cli::from_args(); let cli = cli::Cli::from_args();
let mut card = PcscClient::open_by_ident(&cli.ident)?; let mut card = PcscClient::open_by_ident(&cli.ident)?;
let pinpad_verify = card.feature_pinpad_verify();
let pinpad_modify = card.feature_pinpad_modify();
let mut open = Open::new(&mut card)?; let mut open = Open::new(&mut card)?;
match cli.cmd { match cli.cmd {
cli::Command::SetUserPin {} => { cli::Command::SetUserPin {} => {
// get current user pin let res = if !pinpad_modify {
let pin = // get current user pin
rpassword::read_password_from_tty(Some("Enter user PIN: "))?; let pin = rpassword::read_password_from_tty(Some(
"Enter user PIN: ",
))?;
// verify pin // verify pin
open.verify_user(&pin)?; open.verify_user(&pin)?;
println!("PIN was accepted by the card.\n"); println!("PIN was accepted by the card.\n");
// get new user pin // get new user pin
let newpin1 = rpassword::read_password_from_tty(Some( let newpin1 = rpassword::read_password_from_tty(Some(
"Enter new user PIN: ", "Enter new user PIN: ",
))?; ))?;
let newpin2 = rpassword::read_password_from_tty(Some( let newpin2 = rpassword::read_password_from_tty(Some(
"Repeat the new user PIN: ", "Repeat the new user PIN: ",
))?; ))?;
if newpin1 != newpin2 { if newpin1 != newpin2 {
return Err(anyhow::anyhow!("PINs do not match.").into()); return Err(anyhow::anyhow!("PINs do not match.").into());
} }
// set new user pin
open.change_user_pin(&pin, &newpin1)
} else {
// set new user pin via pinpad
open.change_user_pin_pinpad(&|| {
println!(
"Enter old user PIN on card reader pinpad, \
then new user PIN (twice)."
)
})
};
// set new user pin
let res = open.change_user_pin(&pin, &newpin1);
if res.is_err() { if res.is_err() {
println!("\nFailed to change the user PIN!"); println!("\nFailed to change the user PIN!");
if let Err(err) = res { if let Err(err) = res {
@ -50,37 +67,55 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
} }
cli::Command::SetAdminPin {} => { cli::Command::SetAdminPin {} => {
// get current admin pin if !pinpad_modify {
let pin = // get current admin pin
rpassword::read_password_from_tty(Some("Enter admin PIN: "))?; let pin = rpassword::read_password_from_tty(Some(
"Enter admin PIN: ",
))?;
// verify pin // verify pin
open.verify_admin(&pin)?; open.verify_admin(&pin)?;
// get new admin pin // get new admin pin
let newpin1 = rpassword::read_password_from_tty(Some( let newpin1 = rpassword::read_password_from_tty(Some(
"Enter new admin PIN: ", "Enter new admin PIN: ",
))?; ))?;
let newpin2 = rpassword::read_password_from_tty(Some( let newpin2 = rpassword::read_password_from_tty(Some(
"Repeat the new admin PIN: ", "Repeat the new admin PIN: ",
))?; ))?;
if newpin1 != newpin2 { if newpin1 != newpin2 {
return Err(anyhow::anyhow!("PINs do not match.").into()); return Err(anyhow::anyhow!("PINs do not match.").into());
}
// set new admin pin from input
open.change_admin_pin(&pin, &newpin1)?;
} else {
// set new admin pin with pinpad
open.change_admin_pin_pinpad(&|| {
println!(
"Enter old admin PIN on card reader pinpad, \
then new admin PIN (twice)."
)
})?;
} }
// set new user pin
open.change_admin_pin(&pin, &newpin1)?;
println!("\nAdmin PIN has been set."); println!("\nAdmin PIN has been set.");
} }
cli::Command::SetResetCode {} => { cli::Command::SetResetCode {} => {
// get current admin pin
let pin =
rpassword::read_password_from_tty(Some("Enter admin PIN: "))?;
// verify admin pin // verify admin pin
open.verify_admin(&pin)?; if !pinpad_verify {
// get current admin pin
let pin = rpassword::read_password_from_tty(Some(
"Enter admin PIN: ",
))?;
open.verify_admin(&pin)?;
} else {
open.verify_admin_pinpad(&|| {
println!("Enter admin PIN on card reader pinpad.")
})?;
}
println!("PIN was accepted by the card.\n"); println!("PIN was accepted by the card.\n");
if let Some(mut admin) = open.admin_card() { if let Some(mut admin) = open.admin_card() {
@ -93,7 +128,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
))?; ))?;
if newpin1 == newpin2 { if newpin1 == newpin2 {
admin.set_resetting_code(&pin)?; admin.set_resetting_code(&newpin1)?;
} else { } else {
return Err(anyhow::anyhow!("PINs do not match.").into()); return Err(anyhow::anyhow!("PINs do not match.").into());
} }
@ -109,13 +144,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// either with resetting code, or by presenting pw3 // either with resetting code, or by presenting pw3
let rst = if admin { let rst = if admin {
// get current admin pin if !pinpad_verify {
let pin = rpassword::read_password_from_tty(Some( // get current admin pin
"Enter admin PIN: ", let pin = rpassword::read_password_from_tty(Some(
))?; "Enter admin PIN: ",
))?;
// verify pin // verify pin
open.verify_admin(&pin)?; open.verify_admin(&pin)?;
} else {
open.verify_admin_pinpad(&|| {
println!("Enter admin PIN on card reader pinpad.")
})?;
}
println!("PIN was accepted by the card.\n"); println!("PIN was accepted by the card.\n");
None None

View file

@ -43,7 +43,7 @@ pub enum Command {
ident: String, ident: String,
#[structopt(name = "Admin PIN file", short = "P", long = "admin-pin")] #[structopt(name = "Admin PIN file", short = "P", long = "admin-pin")]
admin_pin: PathBuf, admin_pin: Option<PathBuf>,
#[structopt(subcommand)] #[structopt(subcommand)]
cmd: AdminCommand, cmd: AdminCommand,
@ -53,7 +53,7 @@ pub enum Command {
ident: String, ident: String,
#[structopt(name = "User PIN file", short = "p", long = "user-pin")] #[structopt(name = "User PIN file", short = "p", long = "user-pin")]
user_pin: PathBuf, user_pin: Option<PathBuf>,
#[structopt( #[structopt(
name = "recipient-cert-file", name = "recipient-cert-file",
@ -70,7 +70,7 @@ pub enum Command {
ident: String, ident: String,
#[structopt(name = "User PIN file", short = "p", long = "user-pin")] #[structopt(name = "User PIN file", short = "p", long = "user-pin")]
user_pin: PathBuf, user_pin: Option<PathBuf>,
#[structopt(name = "detached", short = "d", long = "detached")] #[structopt(name = "detached", short = "d", long = "detached")]
detached: bool, detached: bool,
@ -129,7 +129,7 @@ pub enum AdminCommand {
/// are optional. /// are optional.
Generate { Generate {
#[structopt(name = "User PIN file", short = "p", long = "user-pin")] #[structopt(name = "User PIN file", short = "p", long = "user-pin")]
user_pin: PathBuf, user_pin: Option<PathBuf>,
#[structopt( #[structopt(
about = "Output file (stdout if unset)", about = "Output file (stdout if unset)",

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name> // SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::Result; use anyhow::{anyhow, Result};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use structopt::StructOpt; use structopt::StructOpt;
@ -43,7 +43,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
cert_file, cert_file,
input, input,
} => { } => {
decrypt(&ident, &user_pin, &cert_file, input.as_deref())?; decrypt(&ident, user_pin, &cert_file, input.as_deref())?;
} }
cli::Command::Sign { cli::Command::Sign {
ident, ident,
@ -53,12 +53,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
input, input,
} => { } => {
if detached { if detached {
sign_detached( sign_detached(&ident, user_pin, &cert_file, input.as_deref())?;
&ident,
&user_pin,
&cert_file,
input.as_deref(),
)?;
} else { } else {
return Err(anyhow::anyhow!( return Err(anyhow::anyhow!(
"Only detached signatures are supported for now" "Only detached signatures are supported for now"
@ -79,12 +74,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
match cmd { match cmd {
cli::AdminCommand::Name { name } => { cli::AdminCommand::Name { name } => {
let mut admin = util::get_admin(&mut open, &admin_pin)?; let mut admin = util::get_admin(&mut open, admin_pin)?;
let _ = admin.set_name(&name)?; let _ = admin.set_name(&name)?;
} }
cli::AdminCommand::Url { url } => { cli::AdminCommand::Url { url } => {
let mut admin = util::get_admin(&mut open, &admin_pin)?; let mut admin = util::get_admin(&mut open, admin_pin)?;
let _ = admin.set_url(&url)?; let _ = admin.set_url(&url)?;
} }
@ -94,8 +89,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
dec_fp, dec_fp,
auth_fp, auth_fp,
} => { } => {
let admin = util::get_admin(&mut open, &admin_pin)?;
let key = Cert::from_file(keyfile)?; let key = Cert::from_file(keyfile)?;
let admin = util::get_admin(&mut open, admin_pin)?;
if (&sig_fp, &dec_fp, &auth_fp) == (&None, &None, &None) { if (&sig_fp, &dec_fp, &auth_fp) == (&None, &None, &None) {
// If no fingerprint has been provided, we check if // If no fingerprint has been provided, we check if
@ -115,13 +110,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
no_auth, no_auth,
algo, algo,
} => { } => {
let pw3 = util::get_pin(&admin_pin)?;
let pw1 = util::get_pin(&user_pin)?;
generate_keys( generate_keys(
open, open,
&pw3, admin_pin,
&pw1, user_pin,
output, output,
!no_decrypt, !no_decrypt,
!no_auth, !no_auth,
@ -318,7 +310,7 @@ fn print_status(ident: Option<String>, verbose: bool) -> Result<()> {
fn decrypt( fn decrypt(
ident: &str, ident: &str,
pin_file: &Path, pin_file: Option<PathBuf>,
cert_file: &Path, cert_file: &Path,
input: Option<&Path>, input: Option<&Path>,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
@ -343,7 +335,7 @@ fn decrypt(
fn sign_detached( fn sign_detached(
ident: &str, ident: &str,
pin_file: &Path, pin_file: Option<PathBuf>,
cert_file: &Path, cert_file: &Path,
input: Option<&Path>, input: Option<&Path>,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
@ -445,8 +437,8 @@ fn key_import_explicit(
fn generate_keys( fn generate_keys(
mut open: Open, mut open: Open,
pw3: &str, pw3_path: Option<PathBuf>,
pw1: &str, pw1_path: Option<PathBuf>,
output: Option<PathBuf>, output: Option<PathBuf>,
decrypt: bool, decrypt: bool,
auth: bool, auth: bool,
@ -484,23 +476,37 @@ fn generate_keys(
// 2) Then, generate keys on the card. // 2) Then, generate keys on the card.
// We need "admin" access to the card for this). // We need "admin" access to the card for this).
open.verify_admin(pw3)?;
let (key_sig, key_dec, key_aut) = { let (key_sig, key_dec, key_aut) = {
if let Some(mut admin) = open.admin_card() { if let Ok(mut admin) = util::get_admin(&mut open, pw3_path) {
gen_subkeys(&mut admin, decrypt, auth, algos)? gen_subkeys(&mut admin, decrypt, auth, algos)?
} else { } else {
// FIXME: couldn't get admin mode return Err(anyhow!("Failed to open card in admin mode."));
unimplemented!()
} }
}; };
// 3) Generate a Cert from the generated keys. For this, we // 3) Generate a Cert from the generated keys. For this, we
// need "signing" access to the card (to make binding signatures within // need "signing" access to the card (to make binding signatures within
// the Cert). // the Cert).
let pin = if let Some(pw1) = pw1_path {
Some(util::get_pin(&pw1)?)
} else {
if open.feature_pinpad_verify() {
println!();
println!(
"Next: generating your public cert. You will need to enter \
your user PIN multiple times to make binding signatures."
);
} else {
return Err(anyhow!(
"No user PIN file provided, and no pinpad found"
));
}
None
};
let cert = make_cert(&mut open, key_sig, key_dec, key_aut, pw1)?; let cert = make_cert(&mut open, key_sig, key_dec, key_aut, pin, &|| {
println!("Enter user PIN on card reader pinpad.")
})?;
let armored = String::from_utf8(cert.armored().to_vec()?)?; let armored = String::from_utf8(cert.armored().to_vec()?)?;
// Write armored certificate to the output file (or stdout) // Write armored certificate to the output file (or stdout)

View file

@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name> // SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::{Context, Result}; use anyhow::{anyhow, Context, Result};
use std::path::{Path, PathBuf};
use openpgp_card::{CardApp, Error}; use openpgp_card::{CardApp, Error};
use openpgp_card_pcsc::PcscClient; use openpgp_card_pcsc::PcscClient;
use openpgp_card_sequoia::card::{Admin, Open, Sign, User}; use openpgp_card_sequoia::card::{Admin, Open, Sign, User};
use std::path::Path;
pub(crate) fn cards() -> Result<Vec<CardApp>> { pub(crate) fn cards() -> Result<Vec<CardApp>> {
PcscClient::cards() PcscClient::cards()
@ -18,29 +18,70 @@ pub(crate) fn open_card(ident: &str) -> Result<CardApp, Error> {
pub(crate) fn get_user<'app, 'open>( pub(crate) fn get_user<'app, 'open>(
open: &'app mut Open<'app>, open: &'app mut Open<'app>,
pin_file: &Path, pin_file: Option<PathBuf>,
) -> Result<User<'app, 'open>, Box<dyn std::error::Error>> { ) -> Result<User<'app, 'open>, Box<dyn std::error::Error>> {
open.verify_user(&get_pin(pin_file)?)?; if let Some(path) = pin_file {
open.verify_user(&get_pin(&path)?)?;
} else {
if !open.feature_pinpad_verify() {
return Err(anyhow!(
"No user PIN file provided, and no pinpad found"
)
.into());
};
open.verify_user_pinpad(&|| {
println!("Enter user PIN on card reader pinpad.")
})?;
}
open.user_card() open.user_card()
.ok_or_else(|| anyhow::anyhow!("Couldn't get user access").into()) .ok_or_else(|| anyhow!("Couldn't get user access").into())
} }
pub(crate) fn get_sign<'app, 'open>( pub(crate) fn get_sign<'app, 'open>(
open: &'app mut Open<'app>, open: &'app mut Open<'app>,
pin_file: &Path, pin_file: Option<PathBuf>,
) -> Result<Sign<'app, 'open>, Box<dyn std::error::Error>> { ) -> Result<Sign<'app, 'open>, Box<dyn std::error::Error>> {
open.verify_user_for_signing(&get_pin(pin_file)?)?; if let Some(path) = pin_file {
open.verify_user_for_signing(&get_pin(&path)?)?;
} else {
if !open.feature_pinpad_verify() {
return Err(anyhow!(
"No user PIN file provided, and no pinpad found"
)
.into());
}
open.verify_user_for_signing_pinpad(&|| {
println!("Enter user PIN on card reader pinpad.")
})?;
}
open.signing_card() open.signing_card()
.ok_or_else(|| anyhow::anyhow!("Couldn't get sign access").into()) .ok_or_else(|| anyhow!("Couldn't get sign access").into())
} }
// pub fn admin_card<'b>(&'b mut self) -> Option<Admin<'a, 'b>> {
pub(crate) fn get_admin<'app, 'open>( pub(crate) fn get_admin<'app, 'open>(
open: &'app mut Open<'app>, open: &'open mut Open<'app>,
pin_file: &Path, pin_file: Option<PathBuf>,
) -> Result<Admin<'app, 'open>, Box<dyn std::error::Error>> { ) -> Result<Admin<'app, 'open>, Box<dyn std::error::Error>> {
open.verify_admin(&get_pin(pin_file)?)?; if let Some(path) = pin_file {
open.verify_admin(&get_pin(&path)?)?;
} else {
if !open.feature_pinpad_verify() {
return Err(anyhow!(
"No admin PIN file provided, and no pinpad found"
)
.into());
}
open.verify_admin_pinpad(&|| {
println!("Enter admin PIN on card reader pinpad.")
})?;
}
open.admin_card() open.admin_card()
.ok_or_else(|| anyhow::anyhow!("Couldn't get admin access").into()) .ok_or_else(|| anyhow!("Couldn't get admin access").into())
} }
pub(crate) fn get_pin(pin_file: &Path) -> Result<String> { pub(crate) fn get_pin(pin_file: &Path) -> Result<String> {