From 2709b4ad394bb35b33333b3eda10cfa17d70db6e Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 26 Nov 2021 01:07:49 +0100 Subject: [PATCH] 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. --- card-functionality/src/tests.rs | 10 +- openpgp-card-sequoia/src/card.rs | 59 ++++++ openpgp-card-sequoia/src/util.rs | 25 ++- openpgp-card/src/card_app.rs | 66 +++++++ openpgp-card/src/card_do.rs | 16 +- openpgp-card/src/card_do/pw_status.rs | 12 +- openpgp-card/src/lib.rs | 28 ++- pcsc/Cargo.toml | 1 + pcsc/src/lib.rs | 261 +++++++++++++++++++++++++- scdc/src/lib.rs | 16 ++ tools/src/bin/opgpcard-pin/main.rs | 135 ++++++++----- tools/src/bin/opgpcard/cli.rs | 8 +- tools/src/bin/opgpcard/main.rs | 60 +++--- tools/src/bin/opgpcard/util.rs | 65 +++++-- 14 files changed, 655 insertions(+), 107 deletions(-) diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 578e73f..355c1fe 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -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); diff --git a/openpgp-card-sequoia/src/card.rs b/openpgp-card-sequoia/src/card.rs index e6585d7..e83a4a9 100644 --- a/openpgp-card-sequoia/src/card.rs +++ b/openpgp-card-sequoia/src/card.rs @@ -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 { + 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 { + 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> { if self.pw1 { diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs index f376394..db36576 100644 --- a/openpgp-card-sequoia/src/util.rs +++ b/openpgp-card-sequoia/src/util.rs @@ -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, key_aut: Option, - pw1: &str, + pw1: Option, + prompt: &dyn Fn(), ) -> Result { 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 diff --git a/openpgp-card/src/card_app.rs b/openpgp-card/src/card_app.rs index b18aba4..5c9bf3e 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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. /// diff --git a/openpgp-card/src/card_do.rs b/openpgp-card/src/card_do.rs index 44f8f90..c63025c 100644 --- a/openpgp-card/src/card_do.rs +++ b/openpgp-card/src/card_do.rs @@ -325,10 +325,10 @@ impl From 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 } diff --git a/openpgp-card/src/card_do/pw_status.rs b/openpgp-card/src/card_do/pw_status.rs index 1af9cbf..9a6407a 100644 --- a/openpgp-card/src/card_do/pw_status.rs +++ b/openpgp-card/src/card_do/pw_status.rs @@ -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 diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 8618d8c..48a81f9 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -77,6 +77,18 @@ pub trait CardClient { fn max_cmd_len(&self) -> Option { 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>; + + /// Modify the PIN 'id' via the reader pinpad + fn pinpad_modify(&mut self, id: u8) -> Result>; } /// 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 diff --git a/pcsc/Cargo.toml b/pcsc/Cargo.toml index cc4a9ae..d3cb273 100644 --- a/pcsc/Cargo.toml +++ b/pcsc/Cargo.toml @@ -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" diff --git a/pcsc/src/lib.rs b/pcsc/src/lib.rs index 4993ff7..a578894 100644 --- a/pcsc/src/lib.rs +++ b/pcsc/src/lib.rs @@ -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, + reader_caps: HashMap, } 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 { + /// Make an initialized CardApp from a PcscClient. + /// Obtain and store feature lists from reader (pinpad functionality). + fn into_card_app(mut self) -> Result { + // 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 { + 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 { + 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, 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> { + 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 (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 = 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> { + 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 [(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 = 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()) + } } diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index f9ab60e..eab30ba 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -262,4 +262,20 @@ impl CardClient for ScdClient { fn max_cmd_len(&self) -> Option { 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> { + unimplemented!() // FIXME + } + + fn pinpad_modify(&mut self, _id: u8) -> Result> { + unimplemented!() // FIXME + } } diff --git a/tools/src/bin/opgpcard-pin/main.rs b/tools/src/bin/opgpcard-pin/main.rs index 68f8e03..3abf213 100644 --- a/tools/src/bin/opgpcard-pin/main.rs +++ b/tools/src/bin/opgpcard-pin/main.rs @@ -11,35 +11,52 @@ use openpgp_card_sequoia::card::Open; mod cli; fn main() -> Result<(), Box> { + 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> { } } 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> { ))?; 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> { // 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 diff --git a/tools/src/bin/opgpcard/cli.rs b/tools/src/bin/opgpcard/cli.rs index 20cd001..3acc2a0 100644 --- a/tools/src/bin/opgpcard/cli.rs +++ b/tools/src/bin/opgpcard/cli.rs @@ -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, #[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, #[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, #[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, #[structopt( about = "Output file (stdout if unset)", diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 31e1aeb..469a237 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // 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> { 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> { 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> { 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> { 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> { 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, verbose: bool) -> Result<()> { fn decrypt( ident: &str, - pin_file: &Path, + pin_file: Option, cert_file: &Path, input: Option<&Path>, ) -> Result<(), Box> { @@ -343,7 +335,7 @@ fn decrypt( fn sign_detached( ident: &str, - pin_file: &Path, + pin_file: Option, cert_file: &Path, input: Option<&Path>, ) -> Result<(), Box> { @@ -445,8 +437,8 @@ fn key_import_explicit( fn generate_keys( mut open: Open, - pw3: &str, - pw1: &str, + pw3_path: Option, + pw1_path: Option, output: Option, 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) diff --git a/tools/src/bin/opgpcard/util.rs b/tools/src/bin/opgpcard/util.rs index 7fd45f5..4a31607 100644 --- a/tools/src/bin/opgpcard/util.rs +++ b/tools/src/bin/opgpcard/util.rs @@ -1,12 +1,12 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // 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> { PcscClient::cards() @@ -18,29 +18,70 @@ pub(crate) fn open_card(ident: &str) -> Result { pub(crate) fn get_user<'app, 'open>( open: &'app mut Open<'app>, - pin_file: &Path, + pin_file: Option, ) -> Result, Box> { - 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, ) -> Result, Box> { - 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> { + pub(crate) fn get_admin<'app, 'open>( - open: &'app mut Open<'app>, - pin_file: &Path, + open: &'open mut Open<'app>, + pin_file: Option, ) -> Result, Box> { - 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 {