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
let mut open = Open::new(&mut ca)?;
let cert =
make_cert(&mut open, key_sig, Some(key_dec), Some(key_aut), "123456")?;
let cert = make_cert(
&mut open,
key_sig,
Some(key_dec),
Some(key_aut),
Some("123456".to_string()),
&|| {},
)?;
let armored = String::from_utf8(cert.armored().to_vec()?)?;
let res = TestResult::Text(armored);

View file

@ -58,6 +58,13 @@ impl<'a> Open<'a> {
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> {
let _ = self.card_app.verify_pw1(pin)?;
@ -65,6 +72,17 @@ impl<'a> Open<'a> {
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> {
let _ = self.card_app.verify_pw1_for_signing(pin)?;
@ -74,12 +92,37 @@ impl<'a> Open<'a> {
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> {
let _ = self.card_app.verify_pw3(pin)?;
self.pw3 = true;
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.
///
/// NOTE: on some cards this functionality seems broken.
@ -102,6 +145,14 @@ impl<'a> Open<'a> {
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(
&mut self,
rst: &str,
@ -119,6 +170,14 @@ impl<'a> Open<'a> {
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.
pub fn user_card<'b>(&'a mut self) -> Option<User<'a, 'b>> {
if self.pw1 {

View file

@ -37,13 +37,18 @@ use crate::{decryptor, signer, PublicKey};
/// Create a Cert from the three subkeys on a card.
/// (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)?
pub fn make_cert<'a, 'app>(
open: &'a mut Open<'app>,
key_sig: PublicKey,
key_dec: Option<PublicKey>,
key_aut: Option<PublicKey>,
pw1: &str,
pw1: Option<String>,
prompt: &dyn Fn(),
) -> Result<Cert> {
let mut pp = vec![];
@ -72,7 +77,11 @@ pub fn make_cert<'a, 'app>(
)?;
// 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() {
// Card-backed signer for bindings
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())?;
// 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() {
// Card-backed signer for bindings
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
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() {
// Card-backed signer for bindings

View file

@ -9,6 +9,7 @@ use std::convert::TryInto;
use anyhow::{anyhow, Result};
use crate::algorithm::{Algo, AlgoInfo, AlgoSimple};
use crate::apdu::response::RawResponse;
use crate::apdu::{commands, response::Response};
use crate::card_do::{
ApplicationRelatedData, CardholderRelatedData, Fingerprint,
@ -89,11 +90,17 @@ impl CardApp {
(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 {
ext_support,
chaining_support,
max_cmd_bytes,
max_rsp_bytes,
pw1_max_len: pw1_max,
pw3_max_len: pw3_max,
};
self.card_client.init_caps(caps);
@ -335,6 +342,18 @@ impl CardApp {
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
/// appropriate access status.
///
@ -349,6 +368,20 @@ impl CardApp {
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).
///
/// 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()
}
/// 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.
/// (For operations except signing, mode 82).
///
@ -385,6 +427,14 @@ impl CardApp {
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).
///
/// 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()
}
/// 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).
///
/// 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()
}
/// 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
/// for PW1.
///

View file

@ -325,10 +325,10 @@ impl From<u8> for Sex {
pub struct PWStatusBytes {
pub(crate) pw1_cds_valid_once: 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) 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_rst: u8,
pub(crate) err_count_pw3: u8,
@ -349,6 +349,18 @@ impl PWStatusBytes {
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 {
self.err_count_pw1
}

View file

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

View file

@ -77,6 +77,18 @@ pub trait CardClient {
fn max_cmd_len(&self) -> Option<usize> {
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).
@ -96,7 +108,7 @@ impl dyn CardClient {
/// length can be used when communicating with the card.
///
/// (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)]
pub struct CardCaps {
/// Extended Lc and Le fields
@ -110,6 +122,12 @@ pub struct CardCaps {
/// Maximum number of bytes in a response APDU
max_rsp_bytes: u16,
/// Maximum length of pw1
pw1_max_len: u8,
/// Maximum length of pw3
pw3_max_len: u8,
}
impl CardCaps {
@ -120,6 +138,14 @@ impl CardCaps {
pub fn get_max_rsp_bytes(&self) -> u16 {
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

View file

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

View file

@ -2,13 +2,20 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::{anyhow, Result};
use iso7816_tlv::simple::Tlv;
use pcsc::{Card, Context, Protocols, Scope, ShareMode};
use std::collections::HashMap;
use std::convert::TryInto;
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 {
card: Card,
card_caps: Option<CardCaps>,
reader_caps: HashMap<u8, Tlv>,
}
impl PcscClient {
@ -56,11 +63,21 @@ impl PcscClient {
Self {
card,
card_caps: None,
reader_caps: HashMap::new(),
}
}
/// Make an initialized CardApp from a PcscClient
fn into_card_app(self) -> Result<CardApp> {
/// Make an initialized CardApp from a PcscClient.
/// 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))
}
@ -136,6 +153,46 @@ impl PcscClient {
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 {
@ -171,4 +228,204 @@ impl CardClient for PcscClient {
fn get_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.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> {
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;
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let cli = cli::Cli::from_args();
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)?;
match cli.cmd {
cli::Command::SetUserPin {} => {
// get current user pin
let pin =
rpassword::read_password_from_tty(Some("Enter user PIN: "))?;
let res = if !pinpad_modify {
// get current user pin
let pin = rpassword::read_password_from_tty(Some(
"Enter user PIN: ",
))?;
// verify pin
open.verify_user(&pin)?;
println!("PIN was accepted by the card.\n");
// verify pin
open.verify_user(&pin)?;
println!("PIN was accepted by the card.\n");
// get new user pin
let newpin1 = rpassword::read_password_from_tty(Some(
"Enter new user PIN: ",
))?;
let newpin2 = rpassword::read_password_from_tty(Some(
"Repeat the new user PIN: ",
))?;
// get new user pin
let newpin1 = rpassword::read_password_from_tty(Some(
"Enter new user PIN: ",
))?;
let newpin2 = rpassword::read_password_from_tty(Some(
"Repeat the new user PIN: ",
))?;
if newpin1 != newpin2 {
return Err(anyhow::anyhow!("PINs do not match.").into());
}
if newpin1 != newpin2 {
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() {
println!("\nFailed to change the user PIN!");
if let Err(err) = res {
@ -50,37 +67,55 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
cli::Command::SetAdminPin {} => {
// get current admin pin
let pin =
rpassword::read_password_from_tty(Some("Enter admin PIN: "))?;
if !pinpad_modify {
// get current admin pin
let pin = rpassword::read_password_from_tty(Some(
"Enter admin PIN: ",
))?;
// verify pin
open.verify_admin(&pin)?;
// verify pin
open.verify_admin(&pin)?;
// get new admin pin
let newpin1 = rpassword::read_password_from_tty(Some(
"Enter new admin PIN: ",
))?;
let newpin2 = rpassword::read_password_from_tty(Some(
"Repeat the new admin PIN: ",
))?;
// get new admin pin
let newpin1 = rpassword::read_password_from_tty(Some(
"Enter new admin PIN: ",
))?;
let newpin2 = rpassword::read_password_from_tty(Some(
"Repeat the new admin PIN: ",
))?;
if newpin1 != newpin2 {
return Err(anyhow::anyhow!("PINs do not match.").into());
if newpin1 != newpin2 {
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.");
}
cli::Command::SetResetCode {} => {
// get current admin pin
let pin =
rpassword::read_password_from_tty(Some("Enter 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");
if let Some(mut admin) = open.admin_card() {
@ -93,7 +128,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
))?;
if newpin1 == newpin2 {
admin.set_resetting_code(&pin)?;
admin.set_resetting_code(&newpin1)?;
} else {
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
let rst = if admin {
// get current admin pin
let pin = rpassword::read_password_from_tty(Some(
"Enter admin PIN: ",
))?;
if !pinpad_verify {
// get current admin pin
let pin = rpassword::read_password_from_tty(Some(
"Enter admin PIN: ",
))?;
// verify pin
open.verify_admin(&pin)?;
// verify 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");
None

View file

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

View file

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0
use anyhow::Result;
use anyhow::{anyhow, Result};
use std::path::{Path, PathBuf};
use structopt::StructOpt;
@ -43,7 +43,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
cert_file,
input,
} => {
decrypt(&ident, &user_pin, &cert_file, input.as_deref())?;
decrypt(&ident, user_pin, &cert_file, input.as_deref())?;
}
cli::Command::Sign {
ident,
@ -53,12 +53,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
input,
} => {
if detached {
sign_detached(
&ident,
&user_pin,
&cert_file,
input.as_deref(),
)?;
sign_detached(&ident, user_pin, &cert_file, input.as_deref())?;
} else {
return Err(anyhow::anyhow!(
"Only detached signatures are supported for now"
@ -79,12 +74,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
match cmd {
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)?;
}
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)?;
}
@ -94,8 +89,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
dec_fp,
auth_fp,
} => {
let admin = util::get_admin(&mut open, &admin_pin)?;
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 no fingerprint has been provided, we check if
@ -115,13 +110,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
no_auth,
algo,
} => {
let pw3 = util::get_pin(&admin_pin)?;
let pw1 = util::get_pin(&user_pin)?;
generate_keys(
open,
&pw3,
&pw1,
admin_pin,
user_pin,
output,
!no_decrypt,
!no_auth,
@ -318,7 +310,7 @@ fn print_status(ident: Option<String>, verbose: bool) -> Result<()> {
fn decrypt(
ident: &str,
pin_file: &Path,
pin_file: Option<PathBuf>,
cert_file: &Path,
input: Option<&Path>,
) -> Result<(), Box<dyn std::error::Error>> {
@ -343,7 +335,7 @@ fn decrypt(
fn sign_detached(
ident: &str,
pin_file: &Path,
pin_file: Option<PathBuf>,
cert_file: &Path,
input: Option<&Path>,
) -> Result<(), Box<dyn std::error::Error>> {
@ -445,8 +437,8 @@ fn key_import_explicit(
fn generate_keys(
mut open: Open,
pw3: &str,
pw1: &str,
pw3_path: Option<PathBuf>,
pw1_path: Option<PathBuf>,
output: Option<PathBuf>,
decrypt: bool,
auth: bool,
@ -484,23 +476,37 @@ fn generate_keys(
// 2) Then, generate keys on the card.
// We need "admin" access to the card for this).
open.verify_admin(pw3)?;
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)?
} else {
// FIXME: couldn't get admin mode
unimplemented!()
return Err(anyhow!("Failed to open card in admin mode."));
}
};
// 3) Generate a Cert from the generated keys. For this, we
// need "signing" access to the card (to make binding signatures within
// 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()?)?;
// Write armored certificate to the output file (or stdout)

View file

@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// 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_pcsc::PcscClient;
use openpgp_card_sequoia::card::{Admin, Open, Sign, User};
use std::path::Path;
pub(crate) fn cards() -> Result<Vec<CardApp>> {
PcscClient::cards()
@ -18,29 +18,70 @@ pub(crate) fn open_card(ident: &str) -> Result<CardApp, Error> {
pub(crate) fn get_user<'app, 'open>(
open: &'app mut Open<'app>,
pin_file: &Path,
pin_file: Option<PathBuf>,
) -> 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()
.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>(
open: &'app mut Open<'app>,
pin_file: &Path,
pin_file: Option<PathBuf>,
) -> 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()
.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>(
open: &'app mut Open<'app>,
pin_file: &Path,
open: &'open mut Open<'app>,
pin_file: Option<PathBuf>,
) -> 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()
.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> {