diff --git a/openpgp-card-sequoia/README.md b/openpgp-card-sequoia/README.md index e5b2d2b..5417c3b 100644 --- a/openpgp-card-sequoia/README.md +++ b/openpgp-card-sequoia/README.md @@ -1,5 +1,5 @@ @@ -16,7 +16,7 @@ Note: the current API of this crate is an early draft, reflected by version numb **Example code** -The program `main.rs` performs a number of functions on an OpenPGP card. +The program `examples/test.rs` performs a number of functions on an OpenPGP card. To run it, you need to set an environment variable to the identifier of the OpenPGP card you want to use. @@ -25,11 +25,11 @@ program! ``` $ export TEST_CARD_IDENT="0123:4567ABCD" -$ cargo run +$ cargo run --example test ``` You can see more debugging output by increasing the log-level, like this: ``` -$ RUST_LOG=trace cargo run +$ RUST_LOG=trace cargo run --example test ``` \ No newline at end of file diff --git a/openpgp-card-sequoia/examples/test.rs b/openpgp-card-sequoia/examples/test.rs index fcf4644..42e834c 100644 --- a/openpgp-card-sequoia/examples/test.rs +++ b/openpgp-card-sequoia/examples/test.rs @@ -13,8 +13,8 @@ use openpgp_card::card_do::Sex; use openpgp_card::KeyType; use openpgp_card_pcsc::PcscBackend; -use openpgp_card_sequoia::card::{Card, Open}; use openpgp_card_sequoia::sq_util; +use openpgp_card_sequoia::{state::Open, Card}; // Filename of test key and test message to use diff --git a/openpgp-card-sequoia/src/card.rs b/openpgp-card-sequoia/src/card.rs deleted file mode 100644 index fc27421..0000000 --- a/openpgp-card-sequoia/src/card.rs +++ /dev/null @@ -1,702 +0,0 @@ -// 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() - } - - pub fn attestation_key_fingerprint(&mut self) -> Result, Error> { - self.state.ard.attestation_key_fingerprint() - } - - pub fn attestation_key_algorithm_attributes(&mut self) -> Result, Error> { - self.state.ard.attestation_key_algorithm_attributes() - } - - pub fn attestation_key_generation_time(&mut self) -> Result, Error> { - self.state.ard.attestation_key_generation_time() - } - - /// 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), - } - } -} diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 7a5f830..b17fa72 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -1,8 +1,17 @@ // SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -//! A higher-level wrapper around the openpgp-card crate. -//! It uses sequoia_openpgp for OpenPGP operations. +//! This crate offers ergonomic abstractions to use +//! [OpenPGP cards](https://en.wikipedia.org/wiki/OpenPGP_card). +//! The central abstraction is the [Card] type, which offers access to all card operations. +//! +//! A [Card] object is always in one of the possible [State]s. The [State] determines which +//! operations can be performed on the card. +//! +//! This crate is a convenient higher-level wrapper around the +//! [openpgp-card](https://crates.io/crates/openpgp-card) crate (which exposes low level access +//! to OpenPGP card functionality). +//! [sequoia-openpgp](https://crates.io/crates/sequoia-openpgp) is used to perform OpenPGP operations. //! //! # Backends //! @@ -13,7 +22,7 @@ //! //! ```no_run //! use openpgp_card_pcsc::PcscBackend; -//! use openpgp_card_sequoia::card::{Card, Open}; +//! use openpgp_card_sequoia::{Card, state::Open}; //! //! # fn main() -> Result<(), Box> { //! for backend in PcscBackend::cards(None)? { @@ -30,10 +39,10 @@ //! //! ```no_run //! use openpgp_card_pcsc::PcscBackend; -//! use openpgp_card_sequoia::card::{Card, Open}; +//! use openpgp_card_sequoia::{Card, state::Open}; //! //! # fn main() -> Result<(), Box> { -//! let backend = PcscBackend::open_by_ident("abcd:12345678", None)?; +//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?; //! let mut card: Card = backend.into(); //! let mut transaction = card.transaction()?; //! # Ok(()) @@ -46,17 +55,16 @@ //! //! To use a card for decryption, it needs to be opened, user authorization //! needs to be available. A `sequoia_openpgp::crypto::Decryptor` -//! implementation can then be obtained by providing a Cert (public key) -//! that corresponds to the private encryption key on the card: +//! implementation can then be obtained: //! //! ```no_run //! use openpgp_card_pcsc::PcscBackend; -//! use openpgp_card_sequoia::card::{Card, Open}; +//! use openpgp_card_sequoia::{Card, state::Open}; //! //! # fn main() -> Result<(), Box> { //! // Open card via PCSC //! use sequoia_openpgp::policy::StandardPolicy; -//! let backend = PcscBackend::open_by_ident("abcd:12345678", None)?; +//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?; //! let mut card: Card = backend.into(); //! let mut transaction = card.transaction()?; //! @@ -78,21 +86,20 @@ //! //! To use a card for signing, it needs to be opened, signing authorization //! needs to be available. A `sequoia_openpgp::crypto::Signer` -//! implementation can then be obtained by providing a Cert (public key) -//! that corresponds to the private signing key on the card. +//! implementation can then be obtained. //! -//! (Note that by default, an OpenPGP Card will only allow one signing +//! (Note that by default, some OpenPGP Cards will only allow one signing //! operation to be performed after the password has been presented for //! signing. Depending on the card's configuration you need to present the //! user password before each signing operation!) //! //! ```no_run //! use openpgp_card_pcsc::PcscBackend; -//! use openpgp_card_sequoia::card::{Card, Open}; +//! use openpgp_card_sequoia::{Card, state::Open}; //! //! # fn main() -> Result<(), Box> { //! // Open card via PCSC -//! let backend = PcscBackend::open_by_ident("abcd:12345678", None)?; +//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?; //! let mut card: Card = backend.into(); //! let mut transaction = card.transaction()?; //! @@ -114,11 +121,11 @@ //! //! ```no_run //! use openpgp_card_pcsc::PcscBackend; -//! use openpgp_card_sequoia::card::{Card, Open}; +//! use openpgp_card_sequoia::{Card, state::Open}; //! //! # fn main() -> Result<(), Box> { //! // Open card via PCSC -//! let backend = PcscBackend::open_by_ident("abcd:12345678", None)?; +//! let backend = PcscBackend::open_by_ident("abcd:01234567", None)?; //! let mut card: Card = backend.into(); //! let mut transaction = card.transaction()?; //! @@ -127,23 +134,733 @@ //! let mut admin = transaction.admin_card().expect("This should not fail"); //! //! // Set the Name and URL fields on the card -//! admin.set_name("Bar<; + +/// Representation of an OpenPGP card. +/// +/// A card transitions between `State`s by starting a transaction (that groups together a number +/// of operations into an atomic sequence) and via PIN presentation. +/// +/// Depending on the `State` of the card, and the access privileges that are associated with that +/// state, different operations can be performed. In many cases, client software will want to +/// transition between states while performing one activity for the user. +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() + } + + pub fn attestation_key_fingerprint(&mut self) -> Result, Error> { + self.state.ard.attestation_key_fingerprint() + } + + pub fn attestation_key_algorithm_attributes(&mut self) -> Result, Error> { + self.state.ard.attestation_key_algorithm_attributes() + } + + pub fn attestation_key_generation_time(&mut self) -> Result, Error> { + self.state.ard.attestation_key_generation_time() + } + + /// 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_material(&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() + } + + // -- higher level abstractions + + /// Get PublicKey representation for a key slot on the card + pub fn public_key(&mut self, kt: KeyType) -> Result, Error> { + // FIXME: only read these once, if multiple subkeys are retrieved from the card + let times = self.key_generation_times()?; + let fps = self.fingerprints()?; + + match kt { + KeyType::Signing => { + if let Ok(pkm) = self.public_key_material(KeyType::Signing) { + if let Some(ts) = times.signature() { + return Ok(Some(public_key_material_and_fp_to_key( + &pkm, + KeyType::Signing, + ts, + fps.signature().expect("Signature fingerprint is unset"), + )?)); + } + } + Ok(None) + } + KeyType::Decryption => { + if let Ok(pkm) = self.public_key_material(KeyType::Decryption) { + if let Some(ts) = times.decryption() { + return Ok(Some(public_key_material_and_fp_to_key( + &pkm, + KeyType::Decryption, + ts, + fps.decryption().expect("Decryption fingerprint is unset"), + )?)); + } + } + Ok(None) + } + KeyType::Authentication => { + if let Ok(pkm) = self.public_key_material(KeyType::Authentication) { + if let Some(ts) = times.authentication() { + return Ok(Some(public_key_material_and_fp_to_key( + &pkm, + KeyType::Authentication, + ts, + fps.authentication() + .expect("Authentication fingerprint is unset"), + )?)); + } + } + Ok(None) + } + _ => unimplemented!(), + } + } +} + +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 = self + .state + .tx + .public_key(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 = self + .state + .tx + .public_key(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 = self + .state + .tx + .public_key(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), + } + } +} diff --git a/openpgp-card-sequoia/src/sq_util.rs b/openpgp-card-sequoia/src/sq_util.rs index 0b91f7f..e141f41 100644 --- a/openpgp-card-sequoia/src/sq_util.rs +++ b/openpgp-card-sequoia/src/sq_util.rs @@ -21,6 +21,7 @@ use openpgp::serialize::stream::{Message, Signer}; use openpgp::{Cert, Fingerprint}; use sequoia_openpgp as openpgp; +use crate::{CardDecryptor, CardSigner}; use openpgp_card::{Error, KeyType}; /// Retrieve a (sub)key from a Cert, for a given KeyType. @@ -146,3 +147,37 @@ where Ok(decrypted) } + +/// Wrapper to easily perform a sign operation +pub fn sign(s: CardSigner, input: &mut dyn io::Read) -> Result { + let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?; + { + let message = Message::new(&mut armorer); + let mut message = Signer::new(message, s).detached().build()?; + + // Process input data, via message + io::copy(input, &mut message)?; + + message.finalize()?; + } + + let buffer = armorer.finalize()?; + + String::from_utf8(buffer).context("Failed to convert signature to utf8") +} + +/// Wrapper to easily perform a decrypt operation +pub fn decrypt(d: CardDecryptor, msg: Vec, p: &dyn Policy) -> Result> { + let mut decrypted = Vec::new(); + { + let reader = io::BufReader::new(&msg[..]); + + let db = DecryptorBuilder::from_reader(reader)?; + let mut decryptor = db.with_policy(p, None, d)?; + + // Read all data from decryptor and store in decrypted + io::copy(&mut decryptor, &mut decrypted)?; + } + + Ok(decrypted) +} diff --git a/openpgp-card-sequoia/src/state.rs b/openpgp-card-sequoia/src/state.rs new file mode 100644 index 0000000..2143f3f --- /dev/null +++ b/openpgp-card-sequoia/src/state.rs @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! States of a card are modeled by the types `Open`, `Transaction`, `User`, `Sign`, `Admin`. + +use openpgp_card::card_do::ApplicationRelatedData; +use openpgp_card::{OpenPgp, OpenPgpTransaction}; + +use crate::Card; + +/// States that a `Card` can be in. +/// +/// See the implementations for more detail. +pub trait State {} + +impl State for Open {} +impl State for Transaction<'_> {} +impl State for User<'_, '_> {} +impl State for Sign<'_, '_> {} +impl State for Admin<'_, '_> {} + +/// State of an OpenPGP card in its base state, no transaction has been started. +/// +/// A transaction can be started on the card, in this state. +pub struct Open { + pub(crate) pgp: OpenPgp, +} + +/// State of an OpenPGP card once a transaction has been started. +/// +/// The cards is in its base state, base authorization applies. +/// Card-Operations that don't require PIN validation can be performed in this state. +/// This includes many read-operations from the card. +/// +/// (Note that a factory-reset can be performed in this base state.) +pub struct Transaction<'a> { + pub(crate) 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. + pub(crate) ard: ApplicationRelatedData, + + // verify status of pw1 + pub(crate) pw1: bool, + + // verify status of pw1 for signing + pub(crate) pw1_sign: bool, + + // verify status of pw3 + pub(crate) pw3: bool, +} + +/// State of an OpenPGP card after successfully verifying the User PIN +/// (this verification allow user operations other than signing). +/// +/// In this state, e.g. decryption operations and authentication operations can be performed. +pub struct User<'app, 'open> { + pub(crate) tx: &'open mut Card>, +} + +/// State of an OpenPGP card after successfully verifying PW1 for signing. +/// +/// In this state, signatures can be issued. +pub struct Sign<'app, 'open> { + pub(crate) tx: &'open mut Card>, +} + +/// State of an OpenPGP card after successful verification the Admin PIN. +/// +/// In this state, the card can be configured, e.g.: importing key material onto the card, +/// or setting the cardholder name. +pub struct Admin<'app, 'open> { + pub(crate) tx: &'open mut Card>, +} diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs index ec58199..34d4530 100644 --- a/openpgp-card-sequoia/src/util.rs +++ b/openpgp-card-sequoia/src/util.rs @@ -5,11 +5,9 @@ use std::convert::TryFrom; use std::convert::TryInto; -use std::io; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; -use openpgp::armor; use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation; use openpgp::crypto::mpi; use openpgp::packet::Signature; @@ -18,9 +16,6 @@ use openpgp::packet::{ signature::SignatureBuilder, Key, UserID, }; -use openpgp::parse::{stream::DecryptorBuilder, Parse}; -use openpgp::policy::Policy; -use openpgp::serialize::stream::{Message, Signer}; use openpgp::types::{ HashAlgorithm, KeyFlags, PublicKeyAlgorithm, SignatureType, SymmetricAlgorithm, Timestamp, }; @@ -32,11 +27,9 @@ use openpgp_card::card_do::{Fingerprint, KeyGenerationTime}; use openpgp_card::crypto_data::{CardUploadableKey, PublicKeyMaterial}; use openpgp_card::{Error, KeyType}; -use crate::card::{Card, Transaction}; -use crate::decryptor::CardDecryptor; use crate::privkey::SequoiaKey; -use crate::signer::CardSigner; -use crate::PublicKey; +use crate::state::Transaction; +use crate::{Card, PublicKey}; /// Create a Cert from the three subkeys on a card. /// (Calling this multiple times will result in different Certs!) @@ -225,57 +218,6 @@ pub fn public_key_material_and_fp_to_key( )) } -/// Get a PublicKey representation for a key slot on the card -pub fn key_slot(open: &mut Card, kt: KeyType) -> Result, Error> { - // FIXME: only read these once, if multiple subkeys are retrieved from the card - let times = open.key_generation_times()?; - let fps = open.fingerprints()?; - - match kt { - KeyType::Signing => { - if let Ok(pkm) = open.public_key(KeyType::Signing) { - if let Some(ts) = times.signature() { - return Ok(Some(public_key_material_and_fp_to_key( - &pkm, - KeyType::Signing, - ts, - fps.signature().expect("Signature fingerprint is unset"), - )?)); - } - } - Ok(None) - } - KeyType::Decryption => { - if let Ok(pkm) = open.public_key(KeyType::Decryption) { - if let Some(ts) = times.decryption() { - return Ok(Some(public_key_material_and_fp_to_key( - &pkm, - KeyType::Decryption, - ts, - fps.decryption().expect("Decryption fingerprint is unset"), - )?)); - } - } - Ok(None) - } - KeyType::Authentication => { - if let Ok(pkm) = open.public_key(KeyType::Authentication) { - if let Some(ts) = times.authentication() { - return Ok(Some(public_key_material_and_fp_to_key( - &pkm, - KeyType::Authentication, - ts, - fps.authentication() - .expect("Authentication fingerprint is unset"), - )?)); - } - } - Ok(None) - } - _ => unimplemented!(), - } -} - /// Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial. /// /// For ECC decryption keys, `hash` and `sym` can be optionally specified. @@ -412,35 +354,3 @@ pub fn vka_as_uploadable_key( let sqk = SequoiaKey::new(vka, password); Box::new(sqk) } - -pub fn sign(s: CardSigner, input: &mut dyn io::Read) -> Result { - let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?; - { - let message = Message::new(&mut armorer); - let mut message = Signer::new(message, s).detached().build()?; - - // Process input data, via message - io::copy(input, &mut message)?; - - message.finalize()?; - } - - let buffer = armorer.finalize()?; - - String::from_utf8(buffer).context("Failed to convert signature to utf8") -} - -pub fn decrypt(d: CardDecryptor, msg: Vec, p: &dyn Policy) -> Result> { - let mut decrypted = Vec::new(); - { - let reader = io::BufReader::new(&msg[..]); - - let db = DecryptorBuilder::from_reader(reader)?; - let mut decryptor = db.with_policy(p, None, d)?; - - // Read all data from decryptor and store in decrypted - io::copy(&mut decryptor, &mut decrypted)?; - } - - Ok(decrypted) -}