// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Perform operations on a card. Different states of a card are modeled by //! different types, such as `Card`, `Open`, `User`, `Sign`, `Admin`. use sequoia_openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation; use sequoia_openpgp::packet::key::SecretParts; use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use openpgp_card::algorithm::{Algo, AlgoInfo, AlgoSimple}; use openpgp_card::card_do::{ ApplicationIdentifier, ApplicationRelatedData, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, Lang, PWStatusBytes, SecuritySupportTemplate, Sex, TouchPolicy, UIF, }; use openpgp_card::crypto_data::PublicKeyMaterial; use openpgp_card::{CardBackend, Error, KeySet, KeyType, OpenPgp, OpenPgpTransaction}; use crate::decryptor::CardDecryptor; use crate::signer::CardSigner; use crate::util::{public_to_fingerprint, vka_as_uploadable_key}; use crate::PublicKey; pub trait State {} impl State for Open {} impl State for Transaction<'_> {} impl State for User<'_, '_> {} impl State for Sign<'_, '_> {} impl State for Admin<'_, '_> {} /// Representation of an opened OpenPGP card in its base state (i.e. no /// passwords have been verified, default authorization applies). /// No transaction has been started. pub struct Open { pgp: OpenPgp, } /// Representation of an opened OpenPGP card in its base state (i.e. no /// passwords have been verified, default authorization applies). /// A transaction has been started. pub struct Transaction<'a> { opt: OpenPgpTransaction<'a>, // Cache of "application related data". // // FIXME: Should be invalidated when changing data on the card! // (e.g. uploading keys, etc) // // This field should probably be an Option<> that gets invalidated when appropriate and // re-fetched lazily. ard: ApplicationRelatedData, // verify status of pw1 pw1: bool, // verify status of pw1 for signing pw1_sign: bool, // verify status of pw3 pw3: bool, } /// An OpenPGP card after successfully verifying PW1 in mode 82 /// (verification for user operations other than signing) pub struct User<'app, 'open> { tx: &'open mut Card>, } /// An OpenPGP card after successfully verifying PW1 in mode 81 /// (verification for signing) pub struct Sign<'app, 'open> { tx: &'open mut Card>, } /// An OpenPGP card after successful verification of PW3 ("Admin privileges") pub struct Admin<'app, 'open> { tx: &'open mut Card>, } pub struct Card where S: State, { state: S, } impl From for Card where B: Into>, { fn from(backend: B) -> Self { let pgp = OpenPgp::new(backend.into()); Card:: { state: Open { pgp }, } } } impl Card { pub fn transaction(&mut self) -> Result, Error> { let opt = self.state.pgp.transaction()?; Card::::new(opt) } } impl<'a> Card> { /// Do not use! /// /// FIXME: this interface is currently used in `card-functionality`, for testing. /// It will be removed. pub fn new(mut opt: OpenPgpTransaction<'a>) -> Result { let ard = opt.application_related_data()?; Ok(Self { state: Transaction { opt, ard, pw1: false, pw1_sign: false, pw3: false, }, }) } /// Replace cached "application related data" in this instance of Open /// with the current data on the card. /// /// This is needed e.g. after importing or generating keys on a card, to /// see these changes reflected in `self.ard`. pub fn reload_ard(&mut self) -> Result<(), Error> { // FIXME: this should be implemented internally, transparent to users self.state.ard = self.state.opt.application_related_data()?; Ok(()) } pub fn feature_pinpad_verify(&mut self) -> bool { self.state.opt.feature_pinpad_verify() } pub fn feature_pinpad_modify(&mut self) -> bool { self.state.opt.feature_pinpad_modify() } pub fn verify_user(&mut self, pin: &[u8]) -> Result<(), Error> { self.state.opt.verify_pw1_user(pin)?; self.state.pw1 = true; Ok(()) } pub fn verify_user_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> { pinpad_prompt(); self.state.opt.verify_pw1_user_pinpad()?; self.state.pw1 = true; Ok(()) } pub fn verify_user_for_signing(&mut self, pin: &[u8]) -> Result<(), Error> { self.state.opt.verify_pw1_sign(pin)?; // FIXME: depending on card mode, pw1_sign is only usable once self.state.pw1_sign = true; Ok(()) } pub fn verify_user_for_signing_pinpad( &mut self, pinpad_prompt: &dyn Fn(), ) -> Result<(), Error> { pinpad_prompt(); self.state.opt.verify_pw1_sign_pinpad()?; // FIXME: depending on card mode, pw1_sign is only usable once self.state.pw1_sign = true; Ok(()) } pub fn verify_admin(&mut self, pin: &[u8]) -> Result<(), Error> { self.state.opt.verify_pw3(pin)?; self.state.pw3 = true; Ok(()) } pub fn verify_admin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> { pinpad_prompt(); self.state.opt.verify_pw3_pinpad()?; self.state.pw3 = true; Ok(()) } /// Ask the card if the user password has been successfully verified. /// /// NOTE: on some cards this functionality seems broken. pub fn check_user_verified(&mut self) -> Result<(), Error> { self.state.opt.check_pw1_user() } /// Ask the card if the admin password has been successfully verified. /// /// NOTE: on some cards this functionality seems broken. pub fn check_admin_verified(&mut self) -> Result<(), Error> { self.state.opt.check_pw3() } pub fn change_user_pin(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> { self.state.opt.change_pw1(old, new) } pub fn change_user_pin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> { pinpad_prompt(); self.state.opt.change_pw1_pinpad() } pub fn reset_user_pin(&mut self, rst: &[u8], new: &[u8]) -> Result<(), Error> { self.state.opt.reset_retry_counter_pw1(new, Some(rst)) } pub fn change_admin_pin(&mut self, old: &[u8], new: &[u8]) -> Result<(), Error> { self.state.opt.change_pw3(old, new) } pub fn change_admin_pin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> { pinpad_prompt(); self.state.opt.change_pw3_pinpad() } /// Get a view of the card authenticated for "User" commands. pub fn user_card<'b>(&'b mut self) -> Option>> { Some(Card:: { state: User { tx: self }, }) } /// Get a view of the card authenticated for Signing. pub fn signing_card<'b>(&'b mut self) -> Option>> { Some(Card:: { state: Sign { tx: self }, }) } /// Get a view of the card authenticated for "Admin" commands. pub fn admin_card<'b>(&'b mut self) -> Option>> { Some(Card:: { state: Admin { tx: self }, }) } // --- application data --- pub fn application_identifier(&self) -> Result { self.state.ard.application_id() } pub fn historical_bytes(&self) -> Result { self.state.ard.historical_bytes() } pub fn extended_length_information(&self) -> Result, Error> { self.state.ard.extended_length_information() } #[allow(dead_code)] fn general_feature_management() -> Option { unimplemented!() } #[allow(dead_code)] fn discretionary_data_objects() { unimplemented!() } pub fn extended_capabilities(&self) -> Result { self.state.ard.extended_capabilities() } pub fn algorithm_attributes(&self, key_type: KeyType) -> Result { self.state.ard.algorithm_attributes(key_type) } /// PW status Bytes pub fn pw_status_bytes(&self) -> Result { self.state.ard.pw_status_bytes() } pub fn fingerprints(&self) -> Result, Error> { self.state.ard.fingerprints() } pub fn ca_fingerprints(&self) -> Result<[Option; 3], Error> { self.state.ard.ca_fingerprints() } pub fn key_generation_times(&self) -> Result, Error> { self.state.ard.key_generation_times() } pub fn key_information(&self) -> Result, Error> { self.state.ard.key_information() } pub fn uif_signing(&self) -> Result, Error> { self.state.ard.uif_pso_cds() } pub fn uif_decryption(&self) -> Result, Error> { self.state.ard.uif_pso_dec() } pub fn uif_authentication(&self) -> Result, Error> { self.state.ard.uif_pso_aut() } pub fn uif_attestation(&self) -> Result, Error> { self.state.ard.uif_attestation() } // --- optional private DOs (0101 - 0104) --- // --- login data (5e) --- // --- URL (5f50) --- pub fn url(&mut self) -> Result { Ok(String::from_utf8_lossy(&self.state.opt.url()?).to_string()) } // --- cardholder related data (65) --- pub fn cardholder_related_data(&mut self) -> Result { self.state.opt.cardholder_related_data() } // Unicode codepoints are a superset of iso-8859-1 characters fn latin1_to_string(s: &[u8]) -> String { s.iter().map(|&c| c as char).collect() } /// Get cardholder name as a String (this also normalizes the "<" and "<<" filler chars) pub fn cardholder_name(&mut self) -> Result, Error> { let crd = self.state.opt.cardholder_related_data()?; if let Some(name) = crd.name() { let name = Self::latin1_to_string(name); // re-format name ("last< = name.split("<<").collect(); let name = name.iter().cloned().rev().collect::>().join(" "); // replace item separators with spaces let name = name.replace('<', " "); Ok(Some(name)) } else { Ok(None) } } // --- security support template (7a) --- pub fn security_support_template(&mut self) -> Result { self.state.opt.security_support_template() } /// SELECT DATA ("select a DO in the current template"). pub fn select_data(&mut self, num: u8, tag: &[u8], yk_workaround: bool) -> Result<(), Error> { self.state.opt.select_data(num, tag, yk_workaround) } /// Get cardholder certificate. /// /// Call select_data() before calling this fn to select a particular /// certificate (if the card supports multiple certificates). pub fn cardholder_certificate(&mut self) -> Result, Error> { self.state.opt.cardholder_certificate() } /// "GET NEXT DATA" for the DO cardholder certificate. /// /// Cardholder certificate data for multiple slots can be read from the card by first calling /// cardholder_certificate(), followed by up to two calls to next_cardholder_certificate(). pub fn next_cardholder_certificate(&mut self) -> Result, Error> { self.state.opt.next_cardholder_certificate() } // DO "Algorithm Information" (0xFA) pub fn algorithm_information(&mut self) -> Result, Error> { // The DO "Algorithm Information" (Tag FA) shall be present if // Algorithm attributes can be changed let ec = self.extended_capabilities()?; if !ec.algo_attrs_changeable() { // Algorithm attributes can not be changed, // list_supported_algo is not supported return Ok(None); } self.state.opt.algorithm_information() } /// "MANAGE SECURITY ENVIRONMENT" /// Make `key_ref` usable for the operation normally done by the key designated by `for_operation` pub fn manage_security_environment( &mut self, for_operation: KeyType, key_ref: KeyType, ) -> Result<(), Error> { self.state .opt .manage_security_environment(for_operation, key_ref) } // ---------- /// Get "Attestation Certificate (Yubico)" pub fn attestation_certificate(&mut self) -> Result, Error> { self.state.opt.attestation_certificate() } /// Firmware Version, YubiKey specific (?) pub fn firmware_version(&mut self) -> Result, Error> { self.state.opt.firmware_version() } /// Set "identity", Nitrokey Start specific (possible values: 0, 1, 2). /// /// /// A Nitrokey Start can present as 3 different virtual OpenPGP cards. /// This command enables one of those virtual cards. /// /// Each virtual card identity behaves like a separate, independent OpenPGP card. pub fn set_identity(&mut self, id: u8) -> Result<(), Error> { // FIXME: what is in the returned data - is it ever useful? let _ = self.state.opt.set_identity(id)?; Ok(()) } // ---------- pub fn public_key(&mut self, key_type: KeyType) -> Result { self.state.opt.public_key(key_type) } // ---------- /// Delete all state on this OpenPGP card pub fn factory_reset(&mut self) -> Result<(), Error> { self.state.opt.factory_reset() } } impl<'app, 'open> Card> { /// Helper fn to easily access underlying openpgp_card object fn card(&mut self) -> &mut OpenPgpTransaction<'app> { &mut self.state.tx.state.opt } pub fn decryptor( &mut self, touch_prompt: &'open (dyn Fn() + Send + Sync), ) -> Result, Error> { let pk = crate::util::key_slot(self.state.tx, KeyType::Decryption)? .expect("Couldn't get decryption pubkey from card"); Ok(CardDecryptor::with_pubkey(self.card(), pk, touch_prompt)) } pub fn decryptor_from_public( &mut self, pubkey: PublicKey, touch_prompt: &'open (dyn Fn() + Send + Sync), ) -> CardDecryptor<'_, 'app> { CardDecryptor::with_pubkey(self.card(), pubkey, touch_prompt) } pub fn authenticator( &mut self, touch_prompt: &'open (dyn Fn() + Send + Sync), ) -> Result, Error> { let pk = crate::util::key_slot(self.state.tx, KeyType::Authentication)? .expect("Couldn't get authentication pubkey from card"); Ok(CardSigner::with_pubkey_for_auth( self.card(), pk, touch_prompt, )) } pub fn authenticator_from_public( &mut self, pubkey: PublicKey, touch_prompt: &'open (dyn Fn() + Send + Sync), ) -> CardSigner<'_, 'app> { CardSigner::with_pubkey_for_auth(self.card(), pubkey, touch_prompt) } } impl<'app, 'open> Card> { /// Helper fn to easily access underlying openpgp_card object fn card(&mut self) -> &mut OpenPgpTransaction<'app> { &mut self.state.tx.state.opt } pub fn signer( &mut self, touch_prompt: &'open (dyn Fn() + Send + Sync), ) -> Result, Error> { // FIXME: depending on the setting in "PW1 Status byte", only one // signature can be made after verification for signing let pk = crate::util::key_slot(self.state.tx, KeyType::Signing)? .expect("Couldn't get signing pubkey from card"); Ok(CardSigner::with_pubkey(self.card(), pk, touch_prompt)) } pub fn signer_from_public( &mut self, pubkey: PublicKey, touch_prompt: &'open (dyn Fn() + Send + Sync), ) -> CardSigner<'_, 'app> { // FIXME: depending on the setting in "PW1 Status byte", only one // signature can be made after verification for signing CardSigner::with_pubkey(self.card(), pubkey, touch_prompt) } /// Generate Attestation (Yubico) pub fn generate_attestation( &mut self, key_type: KeyType, touch_prompt: &'open (dyn Fn() + Send + Sync), ) -> Result<(), Error> { // Touch is required if: // - the card supports the feature // - and the policy is set to a value other than 'Off' if let Some(uif) = self.state.tx.state.ard.uif_attestation()? { if uif.touch_policy().touch_required() { (touch_prompt)(); } } self.card().generate_attestation(key_type) } } impl<'app, 'open> Card> { pub fn as_open(&'_ mut self) -> &mut Card> { self.state.tx } /// Helper fn to easily access underlying openpgp_card object fn card(&mut self) -> &mut OpenPgpTransaction<'app> { &mut self.state.tx.state.opt } } impl Card> { pub fn set_name(&mut self, name: &str) -> Result<(), Error> { // All chars must be in ASCII7 if name.chars().any(|c| !c.is_ascii()) { return Err(Error::InternalError("Invalid char in name".into())); }; // FIXME: encode spaces and do ordering if name.len() >= 40 { return Err(Error::InternalError("name too long".into())); } self.card().set_name(name.as_bytes()) } pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> { if lang.len() > 8 { return Err(Error::InternalError("lang too long".into())); } self.card().set_lang(lang) } pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { self.card().set_sex(sex) } pub fn set_url(&mut self, url: &str) -> Result<(), Error> { if url.chars().any(|c| !c.is_ascii()) { return Err(Error::InternalError("Invalid char in url".into())); } // Check for max len let ec = self.state.tx.extended_capabilities()?; if ec.max_len_special_do() == None || url.len() <= ec.max_len_special_do().unwrap() as usize { // If we don't know the max length for URL ("special DO"), // or if it's within the acceptable length: // send the url update to the card. self.card().set_url(url.as_bytes()) } else { Err(Error::InternalError("URL too long".into())) } } pub fn set_uif(&mut self, key: KeyType, policy: TouchPolicy) -> Result<(), Error> { let uif = match key { KeyType::Signing => self.state.tx.state.ard.uif_pso_cds()?, KeyType::Decryption => self.state.tx.state.ard.uif_pso_dec()?, KeyType::Authentication => self.state.tx.state.ard.uif_pso_aut()?, KeyType::Attestation => self.state.tx.state.ard.uif_attestation()?, _ => unimplemented!(), }; if let Some(mut uif) = uif { uif.set_touch_policy(policy); match key { KeyType::Signing => self.card().set_uif_pso_cds(&uif)?, KeyType::Decryption => self.card().set_uif_pso_dec(&uif)?, KeyType::Authentication => self.card().set_uif_pso_aut(&uif)?, KeyType::Attestation => self.card().set_uif_attestation(&uif)?, _ => unimplemented!(), } } else { return Err(Error::UnsupportedFeature( "User Interaction Flag not available".into(), )); }; Ok(()) } pub fn set_resetting_code(&mut self, pin: &[u8]) -> Result<(), Error> { self.card().set_resetting_code(pin) } pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> { self.card().set_pso_enc_dec_key(key) } pub fn reset_user_pin(&mut self, new: &[u8]) -> Result<(), Error> { self.card().reset_retry_counter_pw1(new, None) } /// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType. /// /// (The caller needs to make sure that `vka` is suitable as `key_type`) pub fn upload_key( &mut self, vka: ValidErasedKeyAmalgamation, key_type: KeyType, password: Option, ) -> Result<(), Error> { let key = vka_as_uploadable_key(vka, password); self.card().key_import(key, key_type) } /// Wrapper fn for `public_to_fingerprint` that uses SHA256/AES128 as default parameters. /// /// FIXME: This is a hack. /// These parameters should probably be automatically determined based on the algorithm used? fn ptf( pkm: &PublicKeyMaterial, time: KeyGenerationTime, key_type: KeyType, ) -> Result { public_to_fingerprint( pkm, &time, key_type, Some(HashAlgorithm::SHA256), Some(SymmetricAlgorithm::AES128), ) } pub fn generate_key_simple( &mut self, key_type: KeyType, algo: Option, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { match algo { Some(algo) => self.card().generate_key_simple(Self::ptf, key_type, algo), None => self.card().generate_key(Self::ptf, key_type, None), } } }