openpgp-card-0.5.0/.cargo_vcs_info.json0000644000000001520000000000100134130ustar { "git": { "sha1": "4cdeabc476f26790976b3fde41ea18b9dd66c457" }, "path_in_vcs": "openpgp-card" }openpgp-card-0.5.0/Cargo.toml0000644000000022420000000000100114130ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "openpgp-card" version = "0.5.0" authors = ["Heiko Schaefer "] description = "A client implementation for the OpenPGP card specification" documentation = "https://docs.rs/crate/openpgp-card" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://gitlab.com/openpgp-card/openpgp-card" [dependencies.card-backend] version = "0.2" [dependencies.chrono] version = "0.4" [dependencies.hex-slice] version = "0.1" [dependencies.log] version = "0.4" [dependencies.nom] version = "7" [dependencies.secrecy] version = "0.8" [dependencies.sha2] version = "0.10" [dependencies.thiserror] version = "1" [dev-dependencies.hex-literal] version = "0.4" openpgp-card-0.5.0/Cargo.toml.orig000064400000000000000000000012411046102023000150720ustar 00000000000000# SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer # SPDX-License-Identifier: MIT OR Apache-2.0 [package] name = "openpgp-card" description = "A client implementation for the OpenPGP card specification" license = "MIT OR Apache-2.0" version = "0.5.0" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/openpgp-card/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card" [dependencies] card-backend = { path = "../card-backend", version = "0.2" } chrono = "0.4" hex-slice = "0.1" log = "0.4" nom = "7" secrecy = "0.8" sha2 = "0.10" thiserror = "1" [dev-dependencies] hex-literal = "0.4" openpgp-card-0.5.0/README.md000064400000000000000000000016501046102023000134660ustar 00000000000000 **OpenPGP card client library** This crate implements a client library for the [OpenPGP card](https://gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.4.1.pdf) specification, in Rust. This library provides OpenPGP library agnostic access to OpenPGP cards. Its communication with cards is based on simple data structures that closely match the formats defined in the OpenPGP card specification. **Card access backends** This crate doesn't contain code to talk to cards. Implementations of the traits `CardBackend`/`CardTransaction` need to be provided for access to cards. The crates [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc) and the experimental crate [card-backend-scdc](https://crates.io/crates/card-backend-scdc) provide implementations of these traits for use with this crate. openpgp-card-0.5.0/src/errors.rs000064400000000000000000000030201046102023000146510ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Error types used by this crate. //! //! [`Error`] is a wrapper enum for all error types that are used. //! //! The two main classes of errors are: //! - [`SmartcardError`], for problems on the reader/smartcard layer //! - [`StatusBytes`], which models error statuses reported by the OpenPGP //! card application use card_backend::SmartcardError; use crate::ocard::StatusBytes; /// Enum wrapper for the error types of this crate #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { #[error("Error interacting with smartcard: {0}")] Smartcard(SmartcardError), #[error("OpenPGP card error status: {0}")] CardStatus(StatusBytes), #[error("Command too long ({0} bytes)")] CommandTooLong(usize), #[error("Unexpected response length: {0}")] ResponseLength(usize), #[error("Data not found: {0}")] NotFound(String), #[error("Couldn't parse data: {0}")] ParseError(String), #[error("Unsupported algorithm: {0}")] UnsupportedAlgo(String), #[error("Unsupported feature: {0}")] UnsupportedFeature(String), // FIXME: placeholder, remove again later? #[error("Internal error: {0}")] InternalError(String), } impl From for Error { fn from(oce: StatusBytes) -> Self { Error::CardStatus(oce) } } impl From for Error { fn from(sce: SmartcardError) -> Self { Error::Smartcard(sce) } } openpgp-card-0.5.0/src/lib.rs000064400000000000000000001137711046102023000141220ustar 00000000000000// SPDX-FileCopyrightText: 2021-2024 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Client library for //! [OpenPGP card](https://en.wikipedia.org/wiki/OpenPGP_card) //! devices (such as Gnuk, Nitrokey, YubiKey, or Java smartcards running an //! OpenPGP card application). //! //! This library aims to offer //! - low-level access to all features in the OpenPGP //! [card specification](https://gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.4.1.pdf) //! via the [crate::ocard] package, //! - without relying on a particular //! [OpenPGP implementation](https://www.openpgp.org/software/developer/). //! //! The library exposes two modes of access to cards: //! - low-level, unmediated, access to card functionality (see [crate::ocard]), and //! - a more opinionated, typed wrapper API that performs some amount of caching [Card]. //! //! Note that this library can't directly access cards by itself. //! Instead, users need to supply a backend that implements the //! [`card_backend::CardBackend`] and [`card_backend::CardTransaction`] traits. //! For example [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc) //! offers a backend implementation that uses [PC/SC](https://en.wikipedia.org/wiki/PC/SC) to //! communicate with Smart Cards. //! //! See the [architecture diagram](https://gitlab.com/openpgp-card/openpgp-card#architecture) //! for an overview of the ecosystem around this crate. extern crate core; mod errors; pub mod ocard; pub mod state; use card_backend::{CardBackend, SmartcardError}; use secrecy::SecretString; pub use crate::errors::Error; use crate::ocard::algorithm::{AlgoSimple, AlgorithmAttributes, AlgorithmInformation}; use crate::ocard::crypto::{CardUploadableKey, PublicKeyMaterial}; use crate::ocard::data::{ ApplicationIdentifier, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint, HistoricalBytes, KdfDo, KeyGenerationTime, KeyInformation, KeySet, Lang, PWStatusBytes, Sex, TouchPolicy, UserInteractionFlag, }; use crate::ocard::{kdf::map_pin, KeyType}; use crate::state::{Admin, Open, Sign, State, Transaction, User}; /// For caching DOs in a Transaction enum Cached { Uncached, None, Value(T), } /// A PIN in an OpenPGP card application. /// /// - Pw1 is the "User PIN" /// - Rc is the "Resetting Code" /// - Pw3 is the "Admin PIN" pub(crate) enum PinType { Pw1, Rc, Pw3, } /// Optional PIN, used as a parameter to `Card::into_*_card`. /// /// Effectively acts like a `Option`, but with a number of `From` /// implementations for convenience. pub struct OptionalPin(Option); impl From> for OptionalPin { fn from(value: Option) -> Self { OptionalPin(value) } } impl From for OptionalPin { fn from(value: SecretString) -> Self { OptionalPin(Some(value)) } } /// 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 workflow for the user. pub struct Card where S: State, { state: S, } impl Card { /// Takes an iterator over [`CardBackend`]s, tries to SELECT the OpenPGP card /// application on each of them, and checks if its application id matches /// `ident`. /// Returns a [`Card`] for the first match, if any. pub fn open_by_ident( cards: impl Iterator, SmartcardError>>, ident: &str, ) -> Result { for b in cards.filter_map(|c| c.ok()) { let mut card = Self::new(b)?; let aid = { let mut tx = card.transaction()?; tx.state.ard().application_id()? }; if aid.ident() == ident.to_ascii_uppercase() { return Ok(card); } } Err(Error::NotFound(format!("Couldn't find card {}", ident))) } /// Returns a [`Card`] based on `backend` (after SELECTing the /// OpenPGP card application). pub fn new(backend: B) -> Result where B: Into>, { let pgp = crate::ocard::OpenPGP::new(backend)?; Ok(Card:: { state: Open { pgp }, }) } /// Starts a transaction on the underlying backend (if the backend /// implementation supports transactions, otherwise the backend /// will operate without transaction guarantees). /// /// The resulting [`Card`] object allows performing /// operations on the card. pub fn transaction(&mut self) -> Result, Error> { let opt = self.state.pgp.transaction()?; Card::::new(opt) } /// Retrieve the underlying [`CardBackend`]. /// /// This is useful to take the card object into a different context /// (e.g. to perform operations on the card with the `yubikey-management` /// crate, without closing the connection to the card). pub fn into_backend(self) -> Box { self.state.pgp.into_card() } } impl<'a> Card> { /// Internal constructor fn new(mut opt: crate::ocard::Transaction<'a>) -> Result { let ard = opt.application_related_data()?; Ok(Self { state: Transaction::new(opt, ard), }) } // FIXME: remove later? pub fn card(&mut self) -> &mut crate::ocard::Transaction<'a> { &mut self.state.opt } /// Drop cached "application related data" and "kdf do" in this [Card] instance. /// /// This is necessary e.g. after importing or generating keys on a card, to /// drop the now obsolete cached [`crate::ocard::data::ApplicationRelatedData`] and [`KdfDo`]. pub fn invalidate_cache(&mut self) -> Result<(), Error> { self.state.invalidate_cache(); Ok(()) } /// True if the reader for this card supports PIN verification with a pin pad. pub fn feature_pinpad_verify(&mut self) -> bool { self.state.opt.feature_pinpad_verify() } /// True if the reader for this card supports PIN modification with a pin pad. pub fn feature_pinpad_modify(&mut self) -> bool { self.state.opt.feature_pinpad_modify() } /// Verify the User PIN (for operations such as decryption) pub fn verify_user_pin(&mut self, pin: SecretString) -> Result<(), Error> { let pin = map_pin(pin, PinType::Pw1, self.state.kdf_do())?; self.state.opt.verify_pw1_user(pin)?; self.state.pw1 = true; Ok(()) } /// Verify the User PIN with a physical PIN pad (if available, /// see [`Self::feature_pinpad_verify`]). 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(()) } /// Verify the User PIN for signing operations. /// /// (Note that depending on the configuration of the card, this may enable /// performing just one signing operation, or an unlimited amount of /// signing operations). pub fn verify_user_signing_pin(&mut self, pin: SecretString) -> Result<(), Error> { let pin = map_pin(pin, PinType::Pw1, self.state.kdf_do())?; self.state.opt.verify_pw1_sign(pin)?; // FIXME: depending on card mode, pw1_sign is only usable once self.state.pw1_sign = true; Ok(()) } /// Verify the User PIN for signing operations with a physical PIN pad /// (if available, see [`Self::feature_pinpad_verify`]). pub fn verify_user_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(()) } /// Verify the Admin PIN. pub fn verify_admin_pin(&mut self, pin: SecretString) -> Result<(), Error> { let pin = map_pin(pin, PinType::Pw3, self.state.kdf_do())?; self.state.opt.verify_pw3(pin)?; self.state.pw3 = true; Ok(()) } /// Verify the Admin PIN with a physical PIN pad /// (if available, see [`Self::feature_pinpad_verify`]). 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 and may decrease /// the pin's error count! 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 and may decrease /// the pin's error count! pub fn check_admin_verified(&mut self) -> Result<(), Error> { self.state.opt.check_pw3() } /// Change the User PIN, based on the old User PIN. pub fn change_user_pin(&mut self, old: SecretString, new: SecretString) -> Result<(), Error> { let old = map_pin(old, PinType::Pw1, self.state.kdf_do())?; let new = map_pin(new, PinType::Pw1, self.state.kdf_do())?; self.state.opt.change_pw1(old, new) } /// Change the User PIN, based on the old User PIN, with a physical PIN /// pad (if available, see [`Self::feature_pinpad_modify`]). pub fn change_user_pin_pinpad(&mut self, pinpad_prompt: &dyn Fn()) -> Result<(), Error> { pinpad_prompt(); self.state.opt.change_pw1_pinpad() } /// Change the User PIN, based on the resetting code `rst`. pub fn reset_user_pin(&mut self, rst: SecretString, new: SecretString) -> Result<(), Error> { let rst = map_pin(rst, PinType::Rc, self.state.kdf_do())?; let new = map_pin(new, PinType::Pw1, self.state.kdf_do())?; self.state.opt.reset_retry_counter_pw1(new, Some(rst)) } /// Change the Admin PIN, based on the old Admin PIN. pub fn change_admin_pin(&mut self, old: SecretString, new: SecretString) -> Result<(), Error> { let old = map_pin(old, PinType::Pw3, self.state.kdf_do())?; let new = map_pin(new, PinType::Pw3, self.state.kdf_do())?; self.state.opt.change_pw3(old, new) } /// Change the Admin PIN, based on the old Admin PIN, with a physical PIN /// pad (if available, see [`Self::feature_pinpad_modify`]). 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 in the [`Card`] state, and authenticate /// for that state with `pin`, if available. /// /// If `pin` is not None, `verify_user` is called with that pin. pub fn to_user_card<'b, P>(&'b mut self, pin: P) -> Result>, Error> where P: Into, { let pin: OptionalPin = pin.into(); if let Some(pin) = pin.0 { self.verify_user_pin(pin)?; } Ok(Card:: { state: User { tx: self }, }) } /// Get a view of the card in the [`Card`] state, and authenticate /// for that state with `pin`, if available. /// /// If `pin` is not None, `verify_user_for_signing` is called with that pin. pub fn to_signing_card<'b, P>(&'b mut self, pin: P) -> Result>, Error> where P: Into, { let pin: OptionalPin = pin.into(); if let Some(pin) = pin.0 { self.verify_user_signing_pin(pin)?; } Ok(Card:: { state: Sign { tx: self }, }) } /// Get a view of the card in the [`Card`] state, and authenticate /// for that state with `pin`, if available. /// /// If `pin` is not None, `verify_admin` is called with that pin. pub fn to_admin_card<'b, P>(&'b mut self, pin: P) -> Result>, Error> where P: Into, { let pin: OptionalPin = pin.into(); if let Some(pin) = pin.0 { self.verify_admin_pin(pin)?; } Ok(Card:: { state: Admin { tx: self }, }) } // --- application data --- /// The Application Identifier is unique for each card. /// It includes a manufacturer code and serial number. /// /// (This is an immutable field on the card. The value is cached in the /// underlying Card object. It can be retrieved without incurring a call /// to the card) pub fn application_identifier(&self) -> Result { // Use immutable data cache from underlying Card object self.state.opt.application_identifier() } /// The "Extended Capabilities" data object describes features of a card /// to the caller. /// This includes the availability and length of various data fields. /// /// (This is an immutable field on the card. The value is cached in the /// underlying Card object. It can be retrieved without incurring a call /// to the card) pub fn extended_capabilities(&self) -> Result { // Use immutable data cache from underlying Card object self.state.opt.extended_capabilities() } /// The "Historical Bytes" data object describes features of a card /// to the caller. /// The information in this field is probably not relevant for most /// users of this library, however, some of it is used for the internal /// operation of the `openpgp-card` library. /// /// (This is an immutable field on the card. The value is cached in the /// underlying Card object. It can be retrieved without incurring a call /// to the card) pub fn historical_bytes(&self) -> Result { // Use immutable data cache from underlying Card object match self.state.opt.historical_bytes()? { Some(hb) => Ok(hb), None => Err(Error::NotFound( "Card doesn't have historical bytes DO".to_string(), )), } } /// The "Extended Length Information" data object was introduced in /// version 3.0 of the OpenPGP card standard. /// /// The information in this field should not be relevant for /// users of this library. /// However, it is used for the internal operation of the `openpgp-card` /// library. /// /// (This is an immutable field on the card. The value is cached in the /// underlying Card object. It can be retrieved without incurring a call /// to the card) pub fn extended_length_information(&self) -> Result, Error> { // Use immutable data cache from underlying Card object self.state.opt.extended_length_info() } #[allow(dead_code)] fn general_feature_management() -> Option { unimplemented!() } #[allow(dead_code)] fn discretionary_data_objects() { unimplemented!() } /// PW Status Bytes pub fn pw_status_bytes(&mut self) -> Result { self.state.ard().pw_status_bytes() } /// Get algorithm attributes for a key slot. pub fn algorithm_attributes( &mut self, key_type: KeyType, ) -> Result { self.state.ard().algorithm_attributes(key_type) } /// Get the Fingerprints for the three basic [`KeyType`]s. /// /// (The fingerprints for the three basic key slots are stored in a /// shared field on the card, thus they can be retrieved in one go) pub fn fingerprints(&mut self) -> Result, Error> { self.state.ard().fingerprints() } /// Get the Fingerprint for one [`KeyType`]. /// /// This function allows retrieval for all slots, including /// [`KeyType::Attestation`], if available. pub fn fingerprint(&mut self, key_type: KeyType) -> Result, Error> { let fp = match key_type { KeyType::Signing => self.fingerprints()?.signature().cloned(), KeyType::Decryption => self.fingerprints()?.decryption().cloned(), KeyType::Authentication => self.fingerprints()?.authentication().cloned(), KeyType::Attestation => self.state.ard().attestation_key_fingerprint()?, }; Ok(fp) } /// Get the Key Creation Times for the three basic [`KeyType`]s. /// /// (The creation time for the three basic key slots are stored in a /// shared field on the card, thus they can be retrieved in one go) pub fn key_generation_times(&mut self) -> Result, Error> { self.state.ard().key_generation_times() } /// Get the Key Creation Time for one [`KeyType`]. /// /// This function allows retrieval for all slots, including /// [`KeyType::Attestation`], if available. pub fn key_generation_time( &mut self, key_type: KeyType, ) -> Result, Error> { let ts = match key_type { KeyType::Signing => self.key_generation_times()?.signature().cloned(), KeyType::Decryption => self.key_generation_times()?.decryption().cloned(), KeyType::Authentication => self.key_generation_times()?.authentication().cloned(), KeyType::Attestation => self.state.ard().attestation_key_generation_time()?, }; Ok(ts) } pub fn key_information(&mut self) -> Result, Error> { self.state.ard().key_information() } /// Get the [`UserInteractionFlag`] for a key slot. /// This includes the [`TouchPolicy`], if the card supports touch /// confirmation. pub fn user_interaction_flag( &mut self, key_type: KeyType, ) -> Result, Error> { match key_type { KeyType::Signing => self.state.ard().uif_pso_cds(), KeyType::Decryption => self.state.ard().uif_pso_dec(), KeyType::Authentication => self.state.ard().uif_pso_aut(), KeyType::Attestation => self.state.ard().uif_attestation(), } } /// List of CA-Fingerprints of “Ultimately Trusted Keys”. /// May be used to verify Public Keys from servers. pub fn ca_fingerprints(&mut self) -> Result<[Option; 3], Error> { self.state.ard().ca_fingerprints() } /// Get optional "Private use" data from the card. /// /// The presence and maximum length of these DOs is announced /// in [`ExtendedCapabilities`]. /// /// If available, there are 4 data fields for private use: /// /// - `1`: read accessible without PIN verification /// - `2`: read accessible without PIN verification /// - `3`: read accessible with User PIN verification /// - `4`: read accessible with Admin PIN verification pub fn private_use_do(&mut self, num: u8) -> Result, Error> { self.state.opt.private_use_do(num) } /// Login Data /// /// This DO can be used to store any information used for the Log-In /// process in a client/server authentication (e.g. user name of a /// network). /// The maximum length of this DO is announced in Extended Capabilities. pub fn login_data(&mut self) -> Result, Error> { self.state.opt.login_data() } // --- URL (5f50) --- /// Get "cardholder" URL from the card. /// /// "The URL should contain a link to a set of public keys in OpenPGP format, related to /// the card." pub fn url(&mut self) -> Result { Ok(String::from_utf8_lossy(&self.state.opt.url()?).to_string()) } /// Cardholder related data (contains the fields: Name, Language preferences and Sex) 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. /// /// This is an ISO 8859-1 (Latin 1) String of up to 39 characters. /// /// Note that the standard specifies that this field should be encoded /// according to ISO/IEC 7501-1: /// /// "The data element consists of surname (e. g. family name and given /// name(s)) and forename(s) (including name suffix, e. g., Jr. and number). /// Each item is separated by a ´<´ filler character (3C), the family- and /// fore-name(s) are separated by two ´<<´ filler characters." /// /// This library doesn't perform this encoding. pub fn cardholder_name(&mut self) -> Result { let crd = self.state.opt.cardholder_related_data()?; match crd.name() { Some(name) => Ok(Self::latin1_to_string(name)), None => Ok("".to_string()), } } /// Get the current digital signature count (how many signatures have been issued by the card) pub fn digital_signature_count(&mut self) -> Result { Ok(self .state .opt .security_support_template()? .signature_count()) } /// SELECT DATA ("select a DO in the current template"). pub fn select_data(&mut self, num: u8, tag: &[u8]) -> Result<(), Error> { self.state.opt.select_data(num, tag) } /// 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() } /// Get KDF DO configuration (from cache). pub fn kdf_do(&mut self) -> Result { if let Some(kdf) = self.state.kdf_do() { Ok(kdf.clone()) } else { Err(Error::NotFound("No KDF DO found".to_string())) } } /// Algorithm Information (list of supported Algorithm attributes). 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(()) } // ---------- /// Get the raw public key material for a key slot on the card pub fn public_key_material(&mut self, key_type: KeyType) -> Result { self.state.opt.public_key(key_type) } // ---------- /// Reset 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 crate::ocard::Transaction<'app> { &mut self.state.tx.state.opt } // FIXME // 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) // } /// Set optional "Private use" data on the card. /// /// The presence and maximum length of these DOs is announced /// in [`ExtendedCapabilities`]. /// /// If available, there are 4 data fields for private use: /// /// - `1`: write accessible with User PIN verification /// - `2`: write accessible with Admin PIN verification /// - `3`: write accessible with User PIN verification /// - `4`: write accessible with Admin PIN verification pub fn set_private_use_do(&mut self, num: u8, data: Vec) -> Result<(), Error> { self.card().set_private_use_do(num, data) } } impl<'app, 'open> Card> { /// Helper fn to easily access underlying openpgp_card object fn card(&mut self) -> &mut crate::ocard::Transaction<'app> { &mut self.state.tx.state.opt } // FIXME // 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_transaction(&'_ mut self) -> &mut Card> { self.state.tx } /// Helper fn to easily access underlying openpgp_card object fn card(&mut self) -> &mut crate::ocard::Transaction<'app> { &mut self.state.tx.state.opt } } impl Card> { /// Set cardholder name. /// /// This is an ISO 8859-1 (Latin 1) String of max. 39 characters. /// /// Note that the standard specifies that this field should be encoded according /// to ISO/IEC 7501-1: /// /// "The data element consists of surname (e. g. family name and given /// name(s)) and forename(s) (including name suffix, e. g., Jr. and number). /// Each item is separated by a ´<´ filler character (3C), the family- and /// fore-name(s) are separated by two ´<<´ filler characters." /// /// This library doesn't perform this encoding. pub fn set_cardholder_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) } /// Set optional "Private use" data on the card. /// /// The presence and maximum length of these DOs is announced /// in [`ExtendedCapabilities`]. /// /// If available, there are 4 data fields for private use: /// /// - `1`: write accessible with User PIN verification /// - `2`: write accessible with Admin PIN verification /// - `3`: write accessible with User PIN verification /// - `4`: write accessible with Admin PIN verification pub fn set_private_use_do(&mut self, num: u8, data: Vec) -> Result<(), Error> { self.card().set_private_use_do(num, data) } pub fn set_login_data(&mut self, login_data: &[u8]) -> Result<(), Error> { self.card().set_login(login_data) } /// Set "cardholder" URL on the card. /// /// "The URL should contain a link to a set of public keys in OpenPGP format, related to /// the card." 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().is_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())) } } /// Set PW Status Bytes. /// /// According to the spec, length information should not be changed. /// /// If `long` is false, sends 1 byte to the card, otherwise 4. /// /// So, effectively, with `long == false` the setting `pw1_cds_multi` /// can be changed. /// With `long == true`, the settings `pw1_pin_block` and `pw3_pin_block` /// can also be changed. pub fn set_pw_status_bytes( &mut self, pw_status: &PWStatusBytes, long: bool, ) -> Result<(), Error> { self.card().set_pw_status_bytes(pw_status, long) } /// Configure the "only valid for one PSO:CDS" setting in PW Status Bytes. /// /// If `once` is `true`, the User PIN must be verified before each /// signing operation on the card. /// If `once` is `false`, one User PIN verification is good for an /// unlimited number of signing operations. pub fn set_user_pin_signing_validity(&mut self, once: bool) -> Result<(), Error> { let mut pws = self.as_transaction().pw_status_bytes()?; pws.set_pw1_cds_valid_once(once); self.set_pw_status_bytes(&pws, false) } /// Set the touch policy for a key slot (if the card supports this /// feature). /// /// Note that the current touch policy setting (if available) can be read /// via [`Card::user_interaction_flag`]. pub fn set_touch_policy(&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()?, }; 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)?, } } else { return Err(Error::UnsupportedFeature( "User Interaction Flag not available".into(), )); }; Ok(()) } /// Set the User PIN on the card (also resets the User PIN error count) pub fn reset_user_pin(&mut self, new: SecretString) -> Result<(), Error> { let new = map_pin(new, PinType::Pw1, self.state.tx.state.kdf_do())?; self.card().reset_retry_counter_pw1(new, None) } /// Define the "resetting code" on the card pub fn set_resetting_code(&mut self, pin: SecretString) -> Result<(), Error> { let pin = map_pin(pin, PinType::Rc, self.state.tx.state.kdf_do())?; self.card().set_resetting_code(pin) } /// Set optional AES encryption/decryption key /// (32 bytes for AES256, or 16 bytes for AES128), /// if the card supports this feature. /// /// The availability of this feature is announced in /// [`Card::extended_capabilities`]. pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> { self.card().set_pso_enc_dec_key(key) } /// Import a private key to the card into a specific key slot. /// /// (The caller needs to make sure that the content of `key` is suitable for `key_type`) pub fn import_key( &mut self, key: Box, key_type: KeyType, ) -> Result<(), Error> { self.card().key_import(key, key_type) } /// Configure the `algorithm_attributes` for key slot `key_type` based on /// the algorithm `algo`. /// This can be useful in preparation for [`Self::generate_key`]. /// /// This is a convenience wrapper for [`Self::set_algorithm_attributes`] /// that determines the exact appropriate [`AlgorithmAttributes`] by /// reading information from the card. pub fn set_algorithm(&mut self, key_type: KeyType, algo: AlgoSimple) -> Result<(), Error> { let attr = algo.matching_algorithm_attributes(self.card(), key_type)?; self.set_algorithm_attributes(key_type, &attr) } /// Configure the key slot `key_type` to `algorithm_attributes`. /// This can be useful in preparation for [`Self::generate_key`]. /// /// Note that legal values for [`AlgorithmAttributes`] are card-specific. /// Different OpenPGP card implementations may support different /// algorithms, sometimes with differing requirements for the encoding /// (e.g. field sizes) /// /// See [`Self::set_algorithm`] for a convenience function that sets /// the algorithm attributes based on an [`AlgoSimple`]. pub fn set_algorithm_attributes( &mut self, key_type: KeyType, algorithm_attributes: &AlgorithmAttributes, ) -> Result<(), Error> { self.card() .set_algorithm_attributes(key_type, algorithm_attributes) } /// Generate a new cryptographic key in slot `key_type`, with the currently /// configured cryptographic algorithm /// (see [`Self::set_algorithm`] for changing the algorithm setting). pub fn generate_key( &mut self, fp_from_pub: fn( &PublicKeyMaterial, KeyGenerationTime, KeyType, ) -> Result, key_type: KeyType, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { self.card().generate_key(fp_from_pub, key_type) } } openpgp-card-0.5.0/src/ocard/algorithm.rs000064400000000000000000000301351046102023000164220ustar 00000000000000// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Data structures that specify algorithms to use on an OpenPGP card. //! //! [`AlgorithmAttributes`] (and its components) model "Algorithm Attributes" //! as described in the OpenPGP card specification. //! //! [`AlgoSimple`] offers a shorthand for specifying an algorithm, //! specifically for key generation on the card. use std::convert::TryFrom; use std::fmt; use crate::ocard::crypto::EccType; use crate::ocard::keys; use crate::ocard::{oid, KeyType, Transaction}; use crate::Error; /// A shorthand way to specify algorithms (e.g. for key generation). #[derive(Clone, Copy, Debug)] pub enum AlgoSimple { RSA1k, RSA2k, RSA3k, RSA4k, NIST256, NIST384, NIST521, Curve25519, } impl TryFrom<&str> for AlgoSimple { type Error = Error; fn try_from(algo: &str) -> Result { use AlgoSimple::*; Ok(match algo { "RSA1k" => RSA1k, "RSA2k" => RSA2k, "RSA3k" => RSA3k, "RSA4k" => RSA4k, "NIST256" => NIST256, "NIST384" => NIST384, "NIST521" => NIST521, "Curve25519" => Curve25519, _ => return Err(Error::UnsupportedAlgo(format!("unexpected algo {algo}"))), }) } } impl AlgoSimple { /// Get algorithm attributes for slot `key_type` from this AlgoSimple. /// /// AlgoSimple doesn't specify card specific details (such as bit-size /// of e for RSA, and import format). /// This function determines these values based on information from the /// card behind `tx`. pub fn matching_algorithm_attributes( &self, tx: &mut Transaction, key_type: KeyType, ) -> Result { let ard = tx.application_related_data()?; let algorithm_attributes = ard.algorithm_attributes(key_type)?; let algo_info = tx.algorithm_information_cached().ok().flatten(); self.determine_algo_attributes(key_type, algorithm_attributes, algo_info) } /// Get corresponding EccType by KeyType (except for Curve25519) fn ecc_type(key_type: KeyType) -> EccType { match key_type { KeyType::Signing | KeyType::Authentication | KeyType::Attestation => EccType::ECDSA, KeyType::Decryption => EccType::ECDH, } } /// Get corresponding EccType by KeyType for Curve25519 fn ecc_type_25519(key_type: KeyType) -> EccType { match key_type { KeyType::Signing | KeyType::Authentication | KeyType::Attestation => EccType::EdDSA, KeyType::Decryption => EccType::ECDH, } } /// Get corresponding Curve by KeyType for 25519 (Ed25519 vs Cv25519) fn curve_for_25519(key_type: KeyType) -> Curve { match key_type { KeyType::Signing | KeyType::Authentication | KeyType::Attestation => Curve::Ed25519, KeyType::Decryption => Curve::Curve25519, } } /// Return the appropriate Algo for this AlgoSimple. /// /// This mapping depends on the actual card in use /// (e.g.: the size of "e", in RSA can differ; /// or a different `import_format` can be selected). /// /// These card-specific settings are derived from `algorithm_attributes` and `algo_info`. pub(crate) fn determine_algo_attributes( &self, key_type: KeyType, algorithm_attributes: AlgorithmAttributes, algo_info: Option, ) -> Result { let algo = match self { Self::RSA1k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( 1024, key_type, algorithm_attributes, algo_info, )?), Self::RSA2k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( 2048, key_type, algorithm_attributes, algo_info, )?), Self::RSA3k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( 3072, key_type, algorithm_attributes, algo_info, )?), Self::RSA4k => AlgorithmAttributes::Rsa(keys::determine_rsa_attrs( 4096, key_type, algorithm_attributes, algo_info, )?), Self::NIST256 => AlgorithmAttributes::Ecc(keys::determine_ecc_attrs( Curve::NistP256r1.oid(), Self::ecc_type(key_type), key_type, algo_info, )?), Self::NIST384 => AlgorithmAttributes::Ecc(keys::determine_ecc_attrs( Curve::NistP384r1.oid(), Self::ecc_type(key_type), key_type, algo_info, )?), Self::NIST521 => AlgorithmAttributes::Ecc(keys::determine_ecc_attrs( Curve::NistP521r1.oid(), Self::ecc_type(key_type), key_type, algo_info, )?), Self::Curve25519 => AlgorithmAttributes::Ecc(keys::determine_ecc_attrs( Self::curve_for_25519(key_type).oid(), Self::ecc_type_25519(key_type), key_type, algo_info, )?), }; Ok(algo) } } /// "Algorithm Information" enumerates which algorithms the current card supports /// [Spec section 4.4.3.11] /// /// Modern OpenPGP cards (starting with version v3.4) provide a list of /// algorithms they support for each key slot. /// The Algorithm Information list specifies which [`AlgorithmAttributes`] /// can be used on that card (for key generation or key import). #[derive(Debug, Clone, Eq, PartialEq)] pub struct AlgorithmInformation(pub(crate) Vec<(KeyType, AlgorithmAttributes)>); /// Algorithm Attributes [Spec section 4.4.3.9] /// /// [`AlgorithmAttributes`] describes the algorithm settings for a key on the card. /// /// This setting specifies the data format of: /// - Key import /// - Key generation /// - Export of public key data from the card (e.g. after key generation) #[derive(Clone, Eq, PartialEq)] pub enum AlgorithmAttributes { Rsa(RsaAttributes), Ecc(EccAttributes), Unknown(Vec), } impl fmt::Debug for AlgorithmAttributes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Rsa(rsa) => { write!( f, "RSA {} [e {}{}]", rsa.len_n, rsa.len_e, if rsa.import_format != 0 { format!(", format {}", rsa.import_format) } else { "".to_string() } ) } Self::Ecc(ecc) => { write!( f, "{:?} ({:?}){}", ecc.ecc_type, ecc.curve, if ecc.import_format == Some(0xff) { " with pub" } else { "" } ) } Self::Unknown(u) => { write!(f, "Unknown: {u:?}") } } } } impl fmt::Display for AlgorithmAttributes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Rsa(rsa) => { write!(f, "RSA {}", rsa.len_n) } Self::Ecc(ecc) => { write!(f, "{:?} ({:?})", ecc.ecc_type, ecc.curve) } Self::Unknown(u) => { write!(f, "Unknown: {u:?}") } } } } impl AlgorithmAttributes { /// Get a DO representation of the Algo, for setting algorithm /// attributes on the card. pub(crate) fn to_data_object(&self) -> Result, Error> { match self { AlgorithmAttributes::Rsa(rsa) => Self::rsa_algo_attrs(rsa), AlgorithmAttributes::Ecc(ecc) => Self::ecc_algo_attrs(ecc.oid(), ecc.ecc_type()), _ => Err(Error::UnsupportedAlgo(format!("Unexpected Algo {self:?}"))), } } /// Helper: generate `data` for algorithm attributes with RSA fn rsa_algo_attrs(algo_attrs: &RsaAttributes) -> Result, Error> { // Algorithm ID (01 = RSA (Encrypt or Sign)) let mut algo_attributes = vec![0x01]; // Length of modulus n in bit algo_attributes.extend(algo_attrs.len_n().to_be_bytes()); // Length of public exponent e in bit algo_attributes.push(0x00); algo_attributes.push(algo_attrs.len_e() as u8); algo_attributes.push(algo_attrs.import_format()); Ok(algo_attributes) } /// Helper: generate `data` for algorithm attributes with ECC fn ecc_algo_attrs(oid: &[u8], ecc_type: EccType) -> Result, Error> { let algo_id = match ecc_type { EccType::EdDSA => 0x16, EccType::ECDH => 0x12, EccType::ECDSA => 0x13, }; let mut algo_attributes = vec![algo_id]; algo_attributes.extend(oid); // Leave Import-Format unset, for default (pg. 35) Ok(algo_attributes) } } /// RSA specific attributes of [`AlgorithmAttributes`] #[derive(Debug, Clone, Eq, PartialEq)] pub struct RsaAttributes { len_n: u16, len_e: u16, import_format: u8, } impl RsaAttributes { pub fn new(len_n: u16, len_e: u16, import_format: u8) -> Self { Self { len_n, len_e, import_format, } } pub fn len_n(&self) -> u16 { self.len_n } pub fn len_e(&self) -> u16 { self.len_e } pub fn import_format(&self) -> u8 { self.import_format } } /// ECC specific attributes of [`AlgorithmAttributes`] #[derive(Debug, Clone, Eq, PartialEq)] pub struct EccAttributes { ecc_type: EccType, curve: Curve, import_format: Option, } impl EccAttributes { pub fn new(ecc_type: EccType, curve: Curve, import_format: Option) -> Self { Self { ecc_type, curve, import_format, } } pub fn ecc_type(&self) -> EccType { self.ecc_type } pub fn curve(&self) -> &Curve { &self.curve } pub fn oid(&self) -> &[u8] { self.curve.oid() } pub fn import_format(&self) -> Option { self.import_format } } /// Enum for naming ECC curves, and mapping them to/from their OIDs. #[derive(Debug, Clone, Eq, PartialEq)] pub enum Curve { NistP256r1, NistP384r1, NistP521r1, BrainpoolP256r1, BrainpoolP384r1, BrainpoolP512r1, Secp256k1, Ed25519, Curve25519, Ed448, X448, Unknown(Vec), } impl Curve { pub fn oid(&self) -> &[u8] { use Curve::*; match self { NistP256r1 => oid::NIST_P256R1, NistP384r1 => oid::NIST_P384R1, NistP521r1 => oid::NIST_P521R1, BrainpoolP256r1 => oid::BRAINPOOL_P256R1, BrainpoolP384r1 => oid::BRAINPOOL_P384R1, BrainpoolP512r1 => oid::BRAINPOOL_P512R1, Secp256k1 => oid::SECP256K1, Ed25519 => oid::ED25519, Curve25519 => oid::CV25519, Ed448 => oid::ED448, X448 => oid::X448, Unknown(oid) => oid, } } } impl TryFrom<&[u8]> for Curve { type Error = Error; fn try_from(oid: &[u8]) -> Result { use Curve::*; let curve = match oid { oid::NIST_P256R1 => NistP256r1, oid::NIST_P384R1 => NistP384r1, oid::NIST_P521R1 => NistP521r1, oid::BRAINPOOL_P256R1 => BrainpoolP256r1, oid::BRAINPOOL_P384R1 => BrainpoolP384r1, oid::BRAINPOOL_P512R1 => BrainpoolP512r1, oid::SECP256K1 => Secp256k1, oid::ED25519 => Ed25519, oid::CV25519 => Curve25519, oid::ED448 => Ed448, oid::X448 => X448, _ => Unknown(oid.to_vec()), }; Ok(curve) } } openpgp-card-0.5.0/src/ocard/apdu/command.rs000064400000000000000000000116731046102023000170110ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Data structure for APDU Commands //! (Commands get sent to the card, which will usually send back a `Response`) use secrecy::ExposeSecret as _; use secrecy::SecretVec; #[derive(Clone, Copy)] pub enum Expect { Empty, Some, Short(u8), } #[derive(Debug)] pub(crate) struct Command { // Class byte (CLA) cla: u8, // Instruction byte (INS) ins: u8, // Parameter bytes (P1/P2) p1: u8, p2: u8, // NOTE: data must be smaller than 64 kbyte data: Data, } impl Command { /// Create an APDU Command. /// /// `data` must be smaller than 64 kbyte. If a larger `data` is passed, /// this fn will panic. pub fn new(cla: u8, ins: u8, p1: u8, p2: u8, data: impl Into) -> Self { // This constructor is the only place `data` gets set, so it's // sufficient to check it here. let data = data.into(); if data.len() > u16::MAX as usize { panic!("'data' too big, must be <64 kbyte"); } Command { cla, ins, p1, p2, data, } } pub(crate) fn ins(&self) -> u8 { self.ins } pub(crate) fn p1(&self) -> u8 { self.p1 } pub(crate) fn p2(&self) -> u8 { self.p2 } pub(crate) fn data(&self) -> &Data { &self.data } /// Serialize a Command (for sending to a card). /// /// See OpenPGP card spec, chapter 7 (pg 47) pub(crate) fn serialize( &self, ext_len: bool, expect_response: Expect, ) -> Result, crate::Error> { // FIXME? (from scd/apdu.c): // T=0 does not allow the use of Lc together with Le; // thus disable Le in this case. // "number of bytes in the command data field" assert!(self.data.len() <= u16::MAX as usize); let nc = self.data.len() as u16; let mut buf = vec![self.cla, self.ins, self.p1, self.p2]; buf.extend(Self::make_lc(nc, ext_len)?); match &self.data { Data::Plain(data) => buf.extend(data), Data::Sensitive(sensitive) => buf.extend(sensitive.expose_secret()), } buf.extend(Self::make_le(nc, ext_len, expect_response)); Ok(buf) } /// Encode len for Lc field fn make_lc(len: u16, ext_len: bool) -> Result, crate::Error> { if !ext_len && len > 0xff { return Err(crate::Error::InternalError(format!( "Command len = {len:x?}, but extended length is unsupported by backend" ))); } if len == 0 { Ok(vec![]) } else if !ext_len { Ok(vec![len as u8]) } else { Ok(vec![0, (len >> 8) as u8, (len & 255) as u8]) } } /// Encode value for Le field /// ("maximum number of bytes expected in the response data field"). fn make_le(nc: u16, ext_len: bool, expect_response: Expect) -> Vec { match (ext_len, expect_response) { (_, Expect::Empty) => { // No response data expected. // "If the Le field is absent, then Ne is zero" vec![] } (false, Expect::Some) => { // A short Le field consists of one byte with any value vec![0] } (false, Expect::Short(size)) => { // A short Le field consists of one byte with any value vec![size] } (true, Expect::Some) => { if nc == 0 { // "three bytes (one byte set to '00' followed by two // bytes with any value) if the Lc field is absent" vec![0, 0, 0] } else { // "two bytes (with any value) if an extended Lc field // is present" vec![0, 0] } } _ => { unreachable!("This should not happen") } } } } pub(crate) enum Data { Plain(Vec), Sensitive(SecretVec), } impl std::fmt::Debug for Data { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Data::Plain(vec) => write!(f, "{vec:02x?}"), Data::Sensitive(_) => write!(f, "REDACTED"), } } } impl Data { #[must_use] fn len(&self) -> usize { match self { Data::Plain(vec) => vec.len(), Data::Sensitive(secret_vec) => secret_vec.expose_secret().len(), } } #[must_use] pub fn is_empty(&self) -> bool { self.len() == 0 } } impl From> for Data { fn from(value: Vec) -> Self { Data::Plain(value) } } impl From> for Data { fn from(value: SecretVec) -> Self { Data::Sensitive(value) } } openpgp-card-0.5.0/src/ocard/apdu/response.rs000064400000000000000000000071041046102023000172230ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use std::convert::{TryFrom, TryInto}; use crate::ocard::StatusBytes; use crate::Error; /// Response from the card to a command. /// /// This object contains pure payload, without the status bytes. /// Creating a `Response` object is only possible when the response from /// the card showed an "ok" status code (if the status bytes were no ok, /// you will receive an Error, never a Response). #[derive(Debug)] struct Response { data: Vec, } /// "Raw" APDU Response, including the status bytes. /// /// This type is used for processing inside the openpgp-card crate /// (raw responses with a non-ok status sometimes need to be processed, /// e.g. when a card sends a response in "chained" format). #[allow(unused)] #[derive(Clone, Debug)] pub(crate) struct RawResponse { data: Vec, status: StatusBytes, } impl TryFrom for () { type Error = Error; fn try_from(value: RawResponse) -> Result { let value: Response = value.try_into()?; if !value.data.is_empty() { unimplemented!() } else { Ok(()) } } } impl TryFrom for Vec { type Error = Error; fn try_from(value: RawResponse) -> Result { let value: Response = value.try_into()?; Ok(value.data) } } impl TryFrom for Response { type Error = Error; fn try_from(value: RawResponse) -> Result { if value.is_ok() { Ok(Response { data: value.data }) } else { Err(value.status().into()) } } } impl RawResponse { pub fn check_ok(&self) -> Result<(), StatusBytes> { if !self.is_ok() { Err(self.status()) } else { Ok(()) } } pub fn data(&self) -> Result<&[u8], StatusBytes> { self.check_ok()?; Ok(&self.data) } /// access data even if the result status is an error status pub(crate) fn raw_data(&self) -> &[u8] { &self.data } pub(crate) fn raw_mut_data(&mut self) -> &mut Vec { &mut self.data } pub(crate) fn set_status(&mut self, new_status: StatusBytes) { self.status = new_status; } pub fn status(&self) -> StatusBytes { self.status } /// Is the response status "ok"? (0x90, 0x00) pub fn is_ok(&self) -> bool { self.status() == StatusBytes::Ok } } impl TryFrom> for RawResponse { type Error = Error; fn try_from(mut data: Vec) -> Result { let sw2 = data.pop().ok_or(Error::ResponseLength(data.len()))?; let sw1 = data.pop().ok_or(Error::ResponseLength(data.len()))?; let status = (sw1, sw2).into(); Ok(RawResponse { data, status }) } } #[cfg(test)] mod tests { use std::convert::TryFrom; use crate::ocard::apdu::response::RawResponse; #[test] fn test_two_bytes_data_response() { let res = RawResponse::try_from(vec![0x01, 0x02, 0x90, 0x00]).unwrap(); assert!(res.is_ok()); assert_eq!(res.data, vec![0x01, 0x02]); } #[test] fn test_no_data_response() { let res = RawResponse::try_from(vec![0x90, 0x00]).unwrap(); assert!(res.is_ok()); assert_eq!(res.data, vec![]); } #[test] fn test_more_data_response() { let res = RawResponse::try_from(vec![0xAB, 0x61, 0x02]).unwrap(); assert!(!res.is_ok()); assert_eq!(res.data, vec![0xAB]); } } openpgp-card-0.5.0/src/ocard/apdu.rs000064400000000000000000000154551046102023000153750ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! APDU "Application Protocol Data Unit" //! Commands and responses to commands pub(crate) mod command; pub mod response; use std::convert::TryFrom; use card_backend::{CardCaps, CardTransaction}; use secrecy::ExposeSecret as _; use crate::ocard::apdu::command::{Command, Data, Expect}; use crate::ocard::apdu::response::RawResponse; use crate::ocard::commands; use crate::ocard::StatusBytes; use crate::Error; /// "Maximum amount of bytes in a short APDU command or response" (from pcsc) const MAX_BUFFER_SIZE: usize = 264; /// Send a Command and return the result as a Response. /// /// If the reply is truncated, this fn assembles all the parts and returns /// them as one aggregated Response. pub(crate) fn send_command( card_tx: &mut C, cmd: Command, card_caps: Option, expect_reply: bool, ) -> Result where C: CardTransaction + ?Sized, { log::debug!(" -> full APDU command: {:02x?}", cmd); let mut resp = RawResponse::try_from(send_command_low_level( card_tx, &cmd, card_caps, if expect_reply { Expect::Some } else { Expect::Empty }, )?)?; if let StatusBytes::UnknownStatus(0x6c, size) = resp.status() { resp = RawResponse::try_from(send_command_low_level( card_tx, &cmd, card_caps, Expect::Short(size), )?)?; } while let StatusBytes::OkBytesAvailable(bytes) = resp.status() { // More data is available for this command from the card log::trace!(" chained response, getting more data"); // Get next chunk of data let next = RawResponse::try_from(send_command_low_level( card_tx, &commands::get_response(), card_caps, Expect::Short(bytes), )?)?; match next.status() { StatusBytes::OkBytesAvailable(_) | StatusBytes::Ok => { log::trace!(" appending {} bytes to response", next.raw_data().len()); // Append new data to resp.data and overwrite status. resp.raw_mut_data().extend_from_slice(next.raw_data()); resp.set_status(next.status()); } error => return Err(error.into()), } } log::debug!( " <- APDU response [len {}]: {:02x?}", resp.raw_data().len(), resp ); Ok(resp) } /// Send the given Command (chained, if required) to the card and /// return the response as a vector of `u8`. /// /// If the response is chained, this fn only returns one chunk, the caller /// needs to re-assemble the chained response-parts. fn send_command_low_level( card_tx: &mut C, cmd: &Command, card_caps: Option, expect_response: Expect, ) -> Result, Error> where C: CardTransaction + ?Sized, { let (ext_support, chaining_support, max_cmd_bytes, max_rsp_bytes) = if let Some(caps) = card_caps { log::trace!("found card caps data!"); ( caps.ext_support(), caps.chaining_support(), caps.max_cmd_bytes() as usize, caps.max_rsp_bytes() as usize, ) } else { log::trace!("found NO card caps data!"); // default settings (false, false, 255, 255) }; log::trace!( "ext le/lc {}, chaining {}, max cmd {}, max rsp {}", ext_support, chaining_support, max_cmd_bytes, max_rsp_bytes ); // Decide if we want to use "extended length fields". // // Current approach: we only use extended length if the card supports it, // and only if the current command has more than 255 bytes of data. // // (This could be a problem with cards that don't support chained // responses, when a response if >255 bytes long - e.g. getting public // key data from cards?) let ext_len = ext_support && (max_cmd_bytes > 0xFF); let buf_size = if !ext_len { MAX_BUFFER_SIZE } else { max_rsp_bytes }; log::trace!("buf_size {}", buf_size); if chaining_support && !cmd.data().is_empty() { // Send command in chained mode log::trace!("chained command mode"); let cmd_chunk_size = if ext_support { max_cmd_bytes } else { 255 }; // Break up payload into chunks that fit into one command, each let chunks: Vec<_> = match cmd.data() { Data::Plain(vec) => vec.chunks(cmd_chunk_size).collect(), Data::Sensitive(sensitive) => { sensitive.expose_secret().chunks(cmd_chunk_size).collect() } }; for (i, d) in chunks.iter().enumerate() { let last = i == chunks.len() - 1; let cla = if last { 0x00 } else { 0x10 }; let partial = Command::new(cla, cmd.ins(), cmd.p1(), cmd.p2(), d.to_vec()); let serialized = partial.serialize(ext_len, expect_response)?; log::trace!(" -> chained APDU command: {:02x?}", &serialized); let resp = card_tx.transmit(&serialized, buf_size)?; log::trace!(" <- APDU response: {:02x?}", &resp); if resp.len() < 2 { return Err(Error::ResponseLength(resp.len())); } if !last { // check that the returned status is ok let sw1 = resp[resp.len() - 2]; let sw2 = resp[resp.len() - 1]; let status = StatusBytes::from((sw1, sw2)); // ISO: "If SW1-SW2 is set to '6883', then the last // command of the chain is expected." if !(status == StatusBytes::Ok || status == StatusBytes::LastCommandOfChainExpected) { // Unexpected status for a non-final chunked response return Err(status.into()); } // ISO: "If SW1-SW2 is set to '6884', then command // chaining is not supported." } else { // this is the last Response in the chain -> return return Ok(resp); } } unreachable!("This state should be unreachable"); } else { let serialized = cmd.serialize(ext_len, expect_response)?; // Can't send this command to the card, because it is too long and // the card doesn't support command chaining. if serialized.len() > max_cmd_bytes { return Err(Error::CommandTooLong(serialized.len())); } log::trace!(" -> APDU command: {:02x?}", &serialized); let resp = card_tx.transmit(&serialized, buf_size)?; log::trace!(" <- APDU response: {:02x?}", resp); Ok(resp) } } openpgp-card-0.5.0/src/ocard/commands.rs000064400000000000000000000175061046102023000162440ustar 00000000000000// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Pre-defined `Command` values for the OpenPGP card application use secrecy::{ExposeSecret, SecretVec}; use crate::ocard::apdu::command::{Command, Data}; use crate::ocard::tags::{ShortTag, Tags}; use crate::ocard::{KeyType, OPENPGP_APPLICATION}; /// 7.2.1 SELECT /// (select the OpenPGP application on the card) pub(crate) fn select_openpgp() -> Command { Command::new(0x00, 0xA4, 0x04, 0x00, OPENPGP_APPLICATION.to_vec()) } /// 7.2.6 GET DATA fn get_data>(tag: T) -> Command { match tag.into() { ShortTag::One(tag0) => Command::new(0x00, 0xCA, 0, tag0, vec![]), ShortTag::Two(tag0, tag1) => Command::new(0x00, 0xCA, tag0, tag1, vec![]), } } /// GET DO "Application related data" pub(crate) fn application_related_data() -> Command { get_data(Tags::ApplicationRelatedData) } /// GET DO "private use" pub(crate) fn private_use_do(num: u8) -> Command { match num { 1 => get_data(Tags::PrivateUse1), 2 => get_data(Tags::PrivateUse2), 3 => get_data(Tags::PrivateUse3), 4 => get_data(Tags::PrivateUse4), _ => panic!("this should never happen"), // FIXME } } /// GET DO "Uniform resource locator" pub(crate) fn url() -> Command { get_data(Tags::Url) } /// GET DO "Login Data" pub(crate) fn login_data() -> Command { get_data(Tags::LoginData) } /// GET DO "Cardholder related data" pub(crate) fn cardholder_related_data() -> Command { get_data(Tags::CardholderRelatedData) } /// GET DO "Security support template" pub(crate) fn security_support_template() -> Command { get_data(Tags::SecuritySupportTemplate) } /// GET DO "Cardholder certificate" pub(crate) fn cardholder_certificate() -> Command { get_data(Tags::CardholderCertificate) } /// GET NEXT DATA for DO "Cardholder certificate" pub(crate) fn get_next_cardholder_certificate() -> Command { Command::new(0x00, 0xCC, 0x7f, 0x21, vec![]) } /// GET DO "Algorithm Information" pub(crate) fn algo_info() -> Command { get_data(Tags::AlgorithmInformation) } /// GET DO "KDF DO" pub(crate) fn kdf_do() -> Command { get_data(Tags::KdfDo) } /// GET DO "Attestation Certificate (Yubico)" pub(crate) fn attestation_certificate() -> Command { get_data(Tags::AttestationCertificate) } /// GET Firmware Version (yubikey specific?) pub(crate) fn firmware_version() -> Command { Command::new(0x00, 0xF1, 0x00, 0x00, vec![]) } /// Set identity [0-2] (NitroKey Start specific(?)) pub(crate) fn set_identity(id: u8) -> Command { Command::new(0x00, 0x85, 0x00, id, vec![]) } /// GET RESPONSE pub(crate) fn get_response() -> Command { Command::new(0x00, 0xC0, 0x00, 0x00, vec![]) } /// SELECT DATA pub(crate) fn select_data(num: u8, data: Vec) -> Command { Command::new(0x00, 0xA5, num, 0x04, data) } /// VERIFY pin for PW1 (81) pub(crate) fn verify_pw1_81(pin: SecretVec) -> Command { Command::new(0x00, 0x20, 0x00, 0x81, pin) } /// VERIFY pin for PW1 (82) pub(crate) fn verify_pw1_82(pin: SecretVec) -> Command { Command::new(0x00, 0x20, 0x00, 0x82, pin) } /// VERIFY pin for PW3 (83) pub(crate) fn verify_pw3(pin: SecretVec) -> Command { Command::new(0x00, 0x20, 0x00, 0x83, pin) } /// 7.2.8 PUT DATA, pub(crate) fn put_data, D: Into>(tag: T, data: D) -> Command { let data: Data = data.into(); match tag.into() { ShortTag::One(tag0) => Command::new(0x00, 0xda, 0, tag0, data), ShortTag::Two(tag0, tag1) => Command::new(0x00, 0xda, tag0, tag1, data), } } /// PUT DO "private use" pub(crate) fn put_private_use_do(num: u8, data: Vec) -> Command { match num { 1 => put_data(Tags::PrivateUse1, data), 2 => put_data(Tags::PrivateUse2, data), 3 => put_data(Tags::PrivateUse3, data), 4 => put_data(Tags::PrivateUse4, data), _ => panic!("this should never happen"), // FIXME } } /// PUT DO Login Data pub(crate) fn put_login_data(login_data: Vec) -> Command { put_data(Tags::LoginData, login_data) } /// PUT DO Name pub(crate) fn put_name(name: Vec) -> Command { put_data(Tags::Name, name) } /// PUT DO Language preferences pub(crate) fn put_lang(lang: Vec) -> Command { put_data(Tags::LanguagePref, lang) } /// PUT DO Sex pub(crate) fn put_sex(sex: u8) -> Command { put_data(Tags::Sex, vec![sex]) } /// PUT DO Uniform resource locator (URL) pub(crate) fn put_url(url: Vec) -> Command { put_data(Tags::Url, url) } /// PUT DO "PW status bytes" pub(crate) fn put_pw_status(data: Vec) -> Command { put_data(Tags::PWStatusBytes, data) } /// PUT DO "Cardholder certificate" pub(crate) fn put_cardholder_certificate(data: Vec) -> Command { put_data(Tags::CardholderCertificate, data) } /// "RESET RETRY COUNTER" (PW1, user pin) /// Reset the counter of PW1 and set a new pin. pub(crate) fn reset_retry_counter_pw1( resetting_code: Option>, new_pin: SecretVec, ) -> Command { if let Some(resetting_code) = resetting_code { // Present the Resetting Code (DO D3) in the command data (P1 = 00) // Data field: Resetting Code + New PW let mut data = vec![]; data.extend(resetting_code.expose_secret()); data.extend(new_pin.expose_secret()); Command::new(0x00, 0x2C, 0x00, 0x81, SecretVec::new(data)) } else { // Use after correct verification of PW3 (P1 = 02) // (Usage of secure messaging is equivalent to PW3) Command::new(0x00, 0x2C, 0x02, 0x81, new_pin) } } /// "CHANGE REFERENCE DATA" - change PW1 (user pin) pub(crate) fn change_pw1(data: SecretVec) -> Command { Command::new(0x00, 0x24, 0x00, 0x81, data) } /// "CHANGE REFERENCE DATA" - change PW3 (admin pin) pub(crate) fn change_pw3(data: SecretVec) -> Command { Command::new(0x00, 0x24, 0x00, 0x83, data) } /// 7.2.10 PSO: COMPUTE DIGITAL SIGNATURE pub(crate) fn signature(data: Vec) -> Command { Command::new(0x00, 0x2A, 0x9e, 0x9a, data) } /// 7.2.11 PSO: DECIPHER (decryption) pub(crate) fn decryption(data: Vec) -> Command { Command::new(0x00, 0x2A, 0x80, 0x86, data) } /// 7.2.13 PSO: INTERNAL AUTHENTICATE pub(crate) fn internal_authenticate(data: Vec) -> Command { Command::new(0x00, 0x88, 0x00, 0x00, data) } /// 7.2.14 GENERATE ASYMMETRIC KEY PAIR pub(crate) fn gen_key(data: Vec) -> Command { Command::new(0x00, 0x47, 0x80, 0x00, data) } /// Read public key template (see 7.2.14) pub(crate) fn get_pub_key(data: Vec) -> Command { Command::new(0x00, 0x47, 0x81, 0x00, data) } /// key import (see 4.4.3.12, 7.2.8) pub(crate) fn key_import(data: Vec) -> Command { // The key import uses a PUT DATA command with odd INS (DB) and an // Extended header list (DO 4D) as described in ISO 7816-8 Command::new(0x00, 0xDB, 0x3F, 0xFF, data) } /// Generate attestation (Yubico) /// /// key: 0x01 (SIG), 0x02 (DEC), 0x03 (AUT) /// /// https://developers.yubico.com/PGP/Attestation.html pub(crate) fn generate_attestation(key: u8) -> Command { Command::new(0x80, 0xFB, key, 0x00, vec![]) } /// 7.2.16 TERMINATE DF pub(crate) fn terminate_df() -> Command { Command::new(0x00, 0xe6, 0x00, 0x00, vec![]) } /// 7.2.17 ACTIVATE FILE pub(crate) fn activate_file() -> Command { Command::new(0x00, 0x44, 0x00, 0x00, vec![]) } /// 7.2.18 MANAGE SECURITY ENVIRONMENT pub(crate) fn manage_security_environment(for_operation: KeyType, key_ref: KeyType) -> Command { let p2 = match for_operation { KeyType::Authentication => 0xA4, KeyType::Decryption => 0xB8, _ => unreachable!(), //FIXME }; let data = match key_ref { KeyType::Decryption => vec![0x83, 0x01, 0x02], KeyType::Authentication => vec![0x83, 0x01, 0x03], _ => unreachable!(), }; Command::new(0, 0x22, 0x41, p2, data) } openpgp-card-0.5.0/src/ocard/crypto.rs000064400000000000000000000114641046102023000157600ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Data structures for cryptographic material: //! Private key data, public key data, cryptograms for decryption, hash //! data for signing. use crate::ocard::algorithm::AlgorithmAttributes; use crate::ocard::data::{Fingerprint, KeyGenerationTime}; use crate::ocard::oid; use crate::Error; /// A hash value that can be signed by the card. pub enum Hash<'a> { SHA1([u8; 0x14]), SHA256([u8; 0x20]), SHA384([u8; 0x30]), SHA512([u8; 0x40]), ECDSA(&'a [u8]), EdDSA(&'a [u8]), } impl Hash<'_> { /// This fn is currently only used in the context of creating a /// digestinfo for SHA*. Other OIDs are not implemented. pub(crate) fn oid(&self) -> Option<&'static [u8]> { match self { Self::SHA1(_) => Some(oid::SHA1), Self::SHA256(_) => Some(oid::SHA256), Self::SHA384(_) => Some(oid::SHA384), Self::SHA512(_) => Some(oid::SHA512), Self::EdDSA(_) => panic!("OIDs for EdDSA are unimplemented"), Self::ECDSA(_) => panic!("OIDs for ECDSA are unimplemented"), } } pub(crate) fn digest(&self) -> &[u8] { match self { Self::SHA1(d) => &d[..], Self::SHA256(d) => &d[..], Self::SHA384(d) => &d[..], Self::SHA512(d) => &d[..], Self::EdDSA(d) => d, Self::ECDSA(d) => d, } } } /// Data that can be decrypted on the card. pub enum Cryptogram<'a> { // message/ciphertext RSA(&'a [u8]), // ephemeral ECDH(&'a [u8]), } // --------- /// A PGP-implementation-agnostic wrapper for private key data, to upload /// to an OpenPGP card pub trait CardUploadableKey { /// private key data fn private_key(&self) -> Result; /// timestamp of (sub)key creation fn timestamp(&self) -> KeyGenerationTime; /// fingerprint fn fingerprint(&self) -> Result; } /// Algorithm-independent container for private key material to upload to /// an OpenPGP card pub enum PrivateKeyMaterial { R(Box), E(Box), } /// RSA-specific container for private key material to upload to an OpenPGP /// card. pub trait RSAKey { // FIXME: use a mechanism like sequoia_openpgp::crypto::mem::Protected // for private key material? fn e(&self) -> &[u8]; fn p(&self) -> &[u8]; fn q(&self) -> &[u8]; fn pq(&self) -> Box<[u8]>; fn dp1(&self) -> Box<[u8]>; fn dq1(&self) -> Box<[u8]>; fn n(&self) -> &[u8]; } /// ECC-specific container for private key material to upload to an OpenPGP /// card. pub trait EccKey { // FIXME: use a mechanism like sequoia_openpgp::crypto::mem::Protected // for private key material? fn oid(&self) -> &[u8]; fn private(&self) -> Vec; fn public(&self) -> Vec; fn ecc_type(&self) -> EccType; } /// Algorithm-independent container for public key material retrieved from /// an OpenPGP card #[derive(Debug)] pub enum PublicKeyMaterial { R(RSAPub), E(EccPub), } impl std::fmt::Display for PublicKeyMaterial { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use hex_slice::AsHex; match self { Self::R(rsa) => { write!( f, "RSA, n: {:02X}, e: {:02X}", rsa.n.plain_hex(false), rsa.v.plain_hex(false) ) } Self::E(ecc) => { write!( f, "ECC [{}], data: {:02X}", ecc.algo(), ecc.data.plain_hex(false) ) } } } } /// RSA-specific container for public key material from an OpenPGP card. #[derive(Debug)] pub struct RSAPub { /// Modulus (a number denoted as n coded on x bytes) n: Vec, /// Public exponent (a number denoted as v, e.g. 65537 dec.) v: Vec, } impl RSAPub { pub fn new(n: Vec, v: Vec) -> Self { Self { n, v } } pub fn n(&self) -> &[u8] { &self.n } pub fn v(&self) -> &[u8] { &self.v } } /// ECC-specific container for public key material from an OpenPGP card. #[derive(Debug)] pub struct EccPub { data: Vec, algo: AlgorithmAttributes, } impl EccPub { pub fn new(data: Vec, algo: AlgorithmAttributes) -> Self { Self { data, algo } } pub fn data(&self) -> &[u8] { &self.data } pub fn algo(&self) -> &AlgorithmAttributes { &self.algo } } /// A marker to distinguish between elliptic curve algorithms (ECDH, ECDSA, /// EdDSA) #[derive(PartialEq, Eq, Debug, Clone, Copy)] pub enum EccType { ECDH, ECDSA, EdDSA, } openpgp-card-0.5.0/src/ocard/data/algo_attrs.rs000064400000000000000000000111001046102023000174730ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! 4.4.3.9 Algorithm Attributes use std::convert::TryFrom; use nom::branch::alt; use nom::bytes::complete::tag; use nom::combinator::map; use nom::{branch, bytes::complete as bytes, number::complete as number}; use crate::ocard::algorithm::{AlgorithmAttributes, Curve, EccAttributes, RsaAttributes}; use crate::ocard::crypto::EccType; use crate::ocard::data::complete; fn parse_oid_cv25519(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::Curve25519.oid()), |_| Curve::Curve25519)(input) } fn parse_oid_ed25519(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::Ed25519.oid()), |_| Curve::Ed25519)(input) } fn parse_oid_secp256k1(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::Secp256k1.oid()), |_| Curve::Secp256k1)(input) } fn parse_oid_nist256(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::NistP256r1.oid()), |_| Curve::NistP256r1)(input) } fn parse_oid_nist384(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::NistP384r1.oid()), |_| Curve::NistP384r1)(input) } fn parse_oid_nist521(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::NistP521r1.oid()), |_| Curve::NistP521r1)(input) } fn parse_oid_brainpool_p256r1(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::BrainpoolP256r1.oid()), |_| { Curve::BrainpoolP256r1 })(input) } fn parse_oid_brainpool_p384r1(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::BrainpoolP384r1.oid()), |_| { Curve::BrainpoolP384r1 })(input) } fn parse_oid_brainpool_p512r1(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::BrainpoolP512r1.oid()), |_| { Curve::BrainpoolP512r1 })(input) } fn parse_oid_ed448(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::Ed448.oid()), |_| Curve::Ed448)(input) } fn parse_oid_x448(input: &[u8]) -> nom::IResult<&[u8], Curve> { map(tag(Curve::X448.oid()), |_| Curve::X448)(input) } fn parse_oid(input: &[u8]) -> nom::IResult<&[u8], Curve> { alt(( parse_oid_nist256, parse_oid_nist384, parse_oid_nist521, parse_oid_brainpool_p256r1, parse_oid_brainpool_p384r1, parse_oid_brainpool_p512r1, parse_oid_secp256k1, parse_oid_ed25519, parse_oid_cv25519, parse_oid_ed448, parse_oid_x448, ))(input) } fn parse_rsa(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { let (input, _) = bytes::tag([0x01])(input)?; let (input, len_n) = number::be_u16(input)?; let (input, len_e) = number::be_u16(input)?; let (input, import_format) = number::u8(input)?; Ok(( input, AlgorithmAttributes::Rsa(RsaAttributes::new(len_n, len_e, import_format)), )) } fn parse_import_format(input: &[u8]) -> nom::IResult<&[u8], Option> { let (input, b) = bytes::take(1usize)(input)?; Ok((input, Some(b[0]))) } fn default_import_format(input: &[u8]) -> nom::IResult<&[u8], Option> { Ok((input, None)) } fn parse_ecdh(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { let (input, _) = bytes::tag([0x12])(input)?; let (input, curve) = parse_oid(input)?; let (input, import_format) = alt((parse_import_format, default_import_format))(input)?; Ok(( input, AlgorithmAttributes::Ecc(EccAttributes::new(EccType::ECDH, curve, import_format)), )) } fn parse_ecdsa(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { let (input, _) = bytes::tag([0x13])(input)?; let (input, curve) = parse_oid(input)?; let (input, import_format) = alt((parse_import_format, default_import_format))(input)?; Ok(( input, AlgorithmAttributes::Ecc(EccAttributes::new(EccType::ECDSA, curve, import_format)), )) } fn parse_eddsa(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { let (input, _) = bytes::tag([0x16])(input)?; let (input, curve) = parse_oid(input)?; let (input, import_format) = alt((parse_import_format, default_import_format))(input)?; Ok(( input, AlgorithmAttributes::Ecc(EccAttributes::new(EccType::EdDSA, curve, import_format)), )) } pub(crate) fn parse(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { branch::alt((parse_rsa, parse_ecdsa, parse_eddsa, parse_ecdh))(input) } impl TryFrom<&[u8]> for AlgorithmAttributes { type Error = crate::Error; fn try_from(data: &[u8]) -> Result { complete(parse(data)) } } // Tests in algo_info cover this module openpgp-card-0.5.0/src/ocard/data/algo_info.rs000064400000000000000000000442621046102023000173100ustar 00000000000000// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Algorithm Information [Spec section 4.4.3.11] use std::convert::TryFrom; use std::fmt; use nom::branch::alt; use nom::combinator::map; use nom::{branch, bytes::complete as bytes, combinator, multi, sequence}; use crate::ocard::algorithm::{AlgorithmAttributes, AlgorithmInformation}; use crate::ocard::data::{algo_attrs, complete}; use crate::ocard::tlv; use crate::ocard::KeyType; impl AlgorithmInformation { pub fn for_keytype(&self, kt: KeyType) -> Vec<&AlgorithmAttributes> { self.0 .iter() .filter(|(k, _)| *k == kt) .map(|(_, a)| a) .collect() } } impl fmt::Display for AlgorithmInformation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (kt, a) in &self.0 { let kt = match kt { KeyType::Signing => "SIG", KeyType::Decryption => "DEC", KeyType::Authentication => "AUT", KeyType::Attestation => "ATT", }; writeln!(f, "{kt}: {a:?} ")?; } Ok(()) } } fn key_type(input: &[u8]) -> nom::IResult<&[u8], KeyType> { alt(( map(bytes::tag([0xc1]), |_| KeyType::Signing), map(bytes::tag([0xc2]), |_| KeyType::Decryption), map(bytes::tag([0xc3]), |_| KeyType::Authentication), map(bytes::tag([0xda]), |_| KeyType::Attestation), ))(input) } fn unknown(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { Ok((&[], AlgorithmAttributes::Unknown(input.to_vec()))) } fn parse_one(input: &[u8]) -> nom::IResult<&[u8], AlgorithmAttributes> { let (input, a) = combinator::map( combinator::flat_map(tlv::length::length, bytes::take), |i| alt((combinator::all_consuming(algo_attrs::parse), unknown))(i), )(input)?; Ok((input, a?.1)) } fn parse_list(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, AlgorithmAttributes)>> { multi::many0(sequence::pair(key_type, parse_one))(input) } fn parse_tl_list(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, AlgorithmAttributes)>> { let (input, (_, _, list)) = sequence::tuple((bytes::tag([0xfa]), tlv::length::length, parse_list))(input)?; Ok((input, list)) } fn parse(input: &[u8]) -> nom::IResult<&[u8], Vec<(KeyType, AlgorithmAttributes)>> { // Handle two variations of input format: // a) TLV format (e.g. YubiKey 5) // b) Plain list (e.g. Gnuk, FOSS-Store Smartcard 3.4) // -- Gnuk: do_alg_info (uint16_t tag, int with_tag) branch::alt(( combinator::all_consuming(parse_list), combinator::all_consuming(parse_tl_list), ))(input) } impl TryFrom<&[u8]> for AlgorithmInformation { type Error = crate::Error; fn try_from(input: &[u8]) -> Result { Ok(AlgorithmInformation(complete(parse(input))?)) } } // test #[cfg(test)] mod test { use std::convert::TryFrom; use crate::ocard::algorithm::{ AlgorithmAttributes::*, AlgorithmInformation, Curve::*, EccAttributes, RsaAttributes, }; use crate::ocard::crypto::EccType::*; use crate::ocard::KeyType::*; #[test] fn test_gnuk() { let data = [ 0xc1, 0x6, 0x1, 0x8, 0x0, 0x0, 0x20, 0x0, 0xc1, 0x6, 0x1, 0x10, 0x0, 0x0, 0x20, 0x0, 0xc1, 0x9, 0x13, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0xc1, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0xa, 0xc1, 0xa, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0xda, 0x47, 0xf, 0x1, 0xc2, 0x6, 0x1, 0x8, 0x0, 0x0, 0x20, 0x0, 0xc2, 0x6, 0x1, 0x10, 0x0, 0x0, 0x20, 0x0, 0xc2, 0x9, 0x13, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0xc2, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0xa, 0xc2, 0xb, 0x12, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x97, 0x55, 0x1, 0x5, 0x1, 0xc3, 0x6, 0x1, 0x8, 0x0, 0x0, 0x20, 0x0, 0xc3, 0x6, 0x1, 0x10, 0x0, 0x0, 0x20, 0x0, 0xc3, 0x9, 0x13, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0xc3, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0xa, 0xc3, 0xa, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0xda, 0x47, 0xf, 0x1, ]; let ai = AlgorithmInformation::try_from(&data[..]).unwrap(); assert_eq!( ai, AlgorithmInformation(vec![ (Signing, Rsa(RsaAttributes::new(2048, 32, 0))), (Signing, Rsa(RsaAttributes::new(4096, 32, 0))), (Signing, Ecc(EccAttributes::new(ECDSA, NistP256r1, None))), (Signing, Ecc(EccAttributes::new(ECDSA, Secp256k1, None))), (Signing, Ecc(EccAttributes::new(EdDSA, Ed25519, None))), (Decryption, Rsa(RsaAttributes::new(2048, 32, 0))), (Decryption, Rsa(RsaAttributes::new(4096, 32, 0))), (Decryption, Ecc(EccAttributes::new(ECDSA, NistP256r1, None))), (Decryption, Ecc(EccAttributes::new(ECDSA, Secp256k1, None))), (Decryption, Ecc(EccAttributes::new(ECDH, Curve25519, None))), (Authentication, Rsa(RsaAttributes::new(2048, 32, 0))), (Authentication, Rsa(RsaAttributes::new(4096, 32, 0))), ( Authentication, Ecc(EccAttributes::new(ECDSA, NistP256r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, Secp256k1, None)) ), ( Authentication, Ecc(EccAttributes::new(EdDSA, Ed25519, None)) ) ]) ); } #[test] fn test_opgp_card_34() { let data = [ 0xc1, 0x6, 0x1, 0x8, 0x0, 0x0, 0x20, 0x0, 0xc1, 0x6, 0x1, 0xc, 0x0, 0x0, 0x20, 0x0, 0xc1, 0x6, 0x1, 0x10, 0x0, 0x0, 0x20, 0x0, 0xc1, 0x9, 0x13, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0xc1, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0x22, 0xc1, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0x23, 0xc1, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0x7, 0xc1, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xb, 0xc1, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xd, 0xc2, 0x6, 0x1, 0x8, 0x0, 0x0, 0x20, 0x0, 0xc2, 0x6, 0x1, 0xc, 0x0, 0x0, 0x20, 0x0, 0xc2, 0x6, 0x1, 0x10, 0x0, 0x0, 0x20, 0x0, 0xc2, 0x9, 0x12, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0xc2, 0x6, 0x12, 0x2b, 0x81, 0x4, 0x0, 0x22, 0xc2, 0x6, 0x12, 0x2b, 0x81, 0x4, 0x0, 0x23, 0xc2, 0xa, 0x12, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0x7, 0xc2, 0xa, 0x12, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xb, 0xc2, 0xa, 0x12, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xd, 0xc3, 0x6, 0x1, 0x8, 0x0, 0x0, 0x20, 0x0, 0xc3, 0x6, 0x1, 0xc, 0x0, 0x0, 0x20, 0x0, 0xc3, 0x6, 0x1, 0x10, 0x0, 0x0, 0x20, 0x0, 0xc3, 0x9, 0x13, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0xc3, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0x22, 0xc3, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0x23, 0xc3, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0x7, 0xc3, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xb, 0xc3, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xd, ]; let ai = AlgorithmInformation::try_from(&data[..]).unwrap(); assert_eq!( ai, AlgorithmInformation(vec![ (Signing, Rsa(RsaAttributes::new(2048, 32, 0))), (Signing, Rsa(RsaAttributes::new(3072, 32, 0))), (Signing, Rsa(RsaAttributes::new(4096, 32, 0))), (Signing, Ecc(EccAttributes::new(ECDSA, NistP256r1, None))), (Signing, Ecc(EccAttributes::new(ECDSA, NistP384r1, None))), (Signing, Ecc(EccAttributes::new(ECDSA, NistP521r1, None))), ( Signing, Ecc(EccAttributes::new(ECDSA, BrainpoolP256r1, None)) ), ( Signing, Ecc(EccAttributes::new(ECDSA, BrainpoolP384r1, None)) ), ( Signing, Ecc(EccAttributes::new(ECDSA, BrainpoolP512r1, None)) ), (Decryption, Rsa(RsaAttributes::new(2048, 32, 0))), (Decryption, Rsa(RsaAttributes::new(3072, 32, 0))), (Decryption, Rsa(RsaAttributes::new(4096, 32, 0))), (Decryption, Ecc(EccAttributes::new(ECDH, NistP256r1, None))), (Decryption, Ecc(EccAttributes::new(ECDH, NistP384r1, None))), (Decryption, Ecc(EccAttributes::new(ECDH, NistP521r1, None))), ( Decryption, Ecc(EccAttributes::new(ECDH, BrainpoolP256r1, None)) ), ( Decryption, Ecc(EccAttributes::new(ECDH, BrainpoolP384r1, None)) ), ( Decryption, Ecc(EccAttributes::new(ECDH, BrainpoolP512r1, None)) ), (Authentication, Rsa(RsaAttributes::new(2048, 32, 0))), (Authentication, Rsa(RsaAttributes::new(3072, 32, 0))), (Authentication, Rsa(RsaAttributes::new(4096, 32, 0))), ( Authentication, Ecc(EccAttributes::new(ECDSA, NistP256r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, NistP384r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, NistP521r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, BrainpoolP256r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, BrainpoolP384r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, BrainpoolP512r1, None)) ) ]) ); } #[test] fn test_yk5() { let data = [ 0xfa, 0x82, 0x1, 0xe2, 0xc1, 0x6, 0x1, 0x8, 0x0, 0x0, 0x11, 0x0, 0xc1, 0x6, 0x1, 0xc, 0x0, 0x0, 0x11, 0x0, 0xc1, 0x6, 0x1, 0x10, 0x0, 0x0, 0x11, 0x0, 0xc1, 0x9, 0x13, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0xc1, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0x22, 0xc1, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0x23, 0xc1, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0xa, 0xc1, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0x7, 0xc1, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xb, 0xc1, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xd, 0xc1, 0xa, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0xda, 0x47, 0xf, 0x1, 0xc1, 0xb, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x97, 0x55, 0x1, 0x5, 0x1, 0xc2, 0x6, 0x1, 0x8, 0x0, 0x0, 0x11, 0x0, 0xc2, 0x6, 0x1, 0xc, 0x0, 0x0, 0x11, 0x0, 0xc2, 0x6, 0x1, 0x10, 0x0, 0x0, 0x11, 0x0, 0xc2, 0x9, 0x12, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0xc2, 0x6, 0x12, 0x2b, 0x81, 0x4, 0x0, 0x22, 0xc2, 0x6, 0x12, 0x2b, 0x81, 0x4, 0x0, 0x23, 0xc2, 0x6, 0x12, 0x2b, 0x81, 0x4, 0x0, 0xa, 0xc2, 0xa, 0x12, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0x7, 0xc2, 0xa, 0x12, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xb, 0xc2, 0xa, 0x12, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xd, 0xc2, 0xa, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0xda, 0x47, 0xf, 0x1, 0xc2, 0xb, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x97, 0x55, 0x1, 0x5, 0x1, 0xc3, 0x6, 0x1, 0x8, 0x0, 0x0, 0x11, 0x0, 0xc3, 0x6, 0x1, 0xc, 0x0, 0x0, 0x11, 0x0, 0xc3, 0x6, 0x1, 0x10, 0x0, 0x0, 0x11, 0x0, 0xc3, 0x9, 0x13, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0xc3, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0x22, 0xc3, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0x23, 0xc3, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0xa, 0xc3, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0x7, 0xc3, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xb, 0xc3, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xd, 0xc3, 0xa, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0xda, 0x47, 0xf, 0x1, 0xc3, 0xb, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x97, 0x55, 0x1, 0x5, 0x1, 0xda, 0x6, 0x1, 0x8, 0x0, 0x0, 0x11, 0x0, 0xda, 0x6, 0x1, 0xc, 0x0, 0x0, 0x11, 0x0, 0xda, 0x6, 0x1, 0x10, 0x0, 0x0, 0x11, 0x0, 0xda, 0x9, 0x13, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x3, 0x1, 0x7, 0xda, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0x22, 0xda, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0x23, 0xda, 0x6, 0x13, 0x2b, 0x81, 0x4, 0x0, 0xa, 0xda, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0x7, 0xda, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xb, 0xda, 0xa, 0x13, 0x2b, 0x24, 0x3, 0x3, 0x2, 0x8, 0x1, 0x1, 0xd, 0xda, 0xa, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0xda, 0x47, 0xf, 0x1, 0xda, 0xb, 0x16, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x97, 0x55, 0x1, 0x5, 0x1, ]; let ai = AlgorithmInformation::try_from(&data[..]).unwrap(); assert_eq!( ai, AlgorithmInformation(vec![ (Signing, Rsa(RsaAttributes::new(2048, 17, 0))), (Signing, Rsa(RsaAttributes::new(3072, 17, 0))), (Signing, Rsa(RsaAttributes::new(4096, 17, 0))), (Signing, Ecc(EccAttributes::new(ECDSA, NistP256r1, None))), (Signing, Ecc(EccAttributes::new(ECDSA, NistP384r1, None))), (Signing, Ecc(EccAttributes::new(ECDSA, NistP521r1, None))), (Signing, Ecc(EccAttributes::new(ECDSA, Secp256k1, None))), ( Signing, Ecc(EccAttributes::new(ECDSA, BrainpoolP256r1, None)) ), ( Signing, Ecc(EccAttributes::new(ECDSA, BrainpoolP384r1, None)) ), ( Signing, Ecc(EccAttributes::new(ECDSA, BrainpoolP512r1, None)) ), (Signing, Ecc(EccAttributes::new(EdDSA, Ed25519, None))), (Signing, Ecc(EccAttributes::new(EdDSA, Curve25519, None))), (Decryption, Rsa(RsaAttributes::new(2048, 17, 0))), (Decryption, Rsa(RsaAttributes::new(3072, 17, 0))), (Decryption, Rsa(RsaAttributes::new(4096, 17, 0))), (Decryption, Ecc(EccAttributes::new(ECDH, NistP256r1, None))), (Decryption, Ecc(EccAttributes::new(ECDH, NistP384r1, None))), (Decryption, Ecc(EccAttributes::new(ECDH, NistP521r1, None))), (Decryption, Ecc(EccAttributes::new(ECDH, Secp256k1, None))), ( Decryption, Ecc(EccAttributes::new(ECDH, BrainpoolP256r1, None)) ), ( Decryption, Ecc(EccAttributes::new(ECDH, BrainpoolP384r1, None)) ), ( Decryption, Ecc(EccAttributes::new(ECDH, BrainpoolP512r1, None)) ), (Decryption, Ecc(EccAttributes::new(EdDSA, Ed25519, None))), (Decryption, Ecc(EccAttributes::new(EdDSA, Curve25519, None))), (Authentication, Rsa(RsaAttributes::new(2048, 17, 0))), (Authentication, Rsa(RsaAttributes::new(3072, 17, 0))), (Authentication, Rsa(RsaAttributes::new(4096, 17, 0))), ( Authentication, Ecc(EccAttributes::new(ECDSA, NistP256r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, NistP384r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, NistP521r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, Secp256k1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, BrainpoolP256r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, BrainpoolP384r1, None)) ), ( Authentication, Ecc(EccAttributes::new(ECDSA, BrainpoolP512r1, None)) ), ( Authentication, Ecc(EccAttributes::new(EdDSA, Ed25519, None)) ), ( Authentication, Ecc(EccAttributes::new(EdDSA, Curve25519, None)) ), (Attestation, Rsa(RsaAttributes::new(2048, 17, 0))), (Attestation, Rsa(RsaAttributes::new(3072, 17, 0))), (Attestation, Rsa(RsaAttributes::new(4096, 17, 0))), ( Attestation, Ecc(EccAttributes::new(ECDSA, NistP256r1, None)) ), ( Attestation, Ecc(EccAttributes::new(ECDSA, NistP384r1, None)) ), ( Attestation, Ecc(EccAttributes::new(ECDSA, NistP521r1, None)) ), (Attestation, Ecc(EccAttributes::new(ECDSA, Secp256k1, None))), ( Attestation, Ecc(EccAttributes::new(ECDSA, BrainpoolP256r1, None)) ), ( Attestation, Ecc(EccAttributes::new(ECDSA, BrainpoolP384r1, None)) ), ( Attestation, Ecc(EccAttributes::new(ECDSA, BrainpoolP512r1, None)) ), (Attestation, Ecc(EccAttributes::new(EdDSA, Ed25519, None))), ( Attestation, Ecc(EccAttributes::new(EdDSA, Curve25519, None)) ) ]) ); } } openpgp-card-0.5.0/src/ocard/data/application_id.rs000064400000000000000000000075561046102023000203370ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! 4.2.1 Application Identifier (AID) use std::convert::TryFrom; use nom::{bytes::complete as bytes, number::complete as number}; use crate::ocard::data::{complete, ApplicationIdentifier}; fn parse(input: &[u8]) -> nom::IResult<&[u8], ApplicationIdentifier> { let (input, _) = bytes::tag([0xd2, 0x76, 0x0, 0x1, 0x24])(input)?; let (input, application) = number::u8(input)?; let (input, version) = number::be_u16(input)?; let (input, manufacturer) = number::be_u16(input)?; let (input, serial) = number::be_u32(input)?; let (input, _) = nom::combinator::all_consuming(bytes::tag([0x0, 0x0]))(input)?; Ok(( input, ApplicationIdentifier { application, version, manufacturer, serial, }, )) } impl TryFrom<&[u8]> for ApplicationIdentifier { type Error = crate::Error; fn try_from(data: &[u8]) -> Result { complete(parse(data)) } } impl ApplicationIdentifier { pub fn application(&self) -> u8 { self.application } pub fn version(&self) -> u16 { self.version } pub fn manufacturer(&self) -> u16 { self.manufacturer } pub fn serial(&self) -> u32 { self.serial } /// Mapping of manufacturer id to a name, data from: /// [2022-04-07] /// /// Also see: /// pub fn manufacturer_name(&self) -> &'static str { match self.manufacturer { 0x0000 => "Testcard", 0x0001 => "PPC Card Systems", 0x0002 => "Prism Payment Technologies", 0x0003 => "OpenFortress Digital signatures", 0x0004 => "Wewid AB", 0x0005 => "ZeitControl cardsystems GmbH", 0x0006 => "Yubico AB", 0x0007 => "OpenKMS", 0x0008 => "LogoEmail", 0x0009 => "Fidesmo AB", 0x000A => "Dangerous Things", 0x000F => "Nitrokey GmbH", 0x000B => "Feitian Technologies", 0x002A => "Magrathea", 0x0042 => "GnuPG e.V.", 0x1337 => "Warsaw Hackerspace", 0x2342 => "warpzone e.V.", 0x4354 => "Confidential Technologies", 0x5443 => "TIF-IT e.V.", 0x63AF => "Trustica s.r.o", 0xBA53 => "c-base e.V.", 0xBD0E => "Paranoidlabs", 0xF1D0 => "CanoKeys", 0xF517 => "Free Software Initiative of Japan", 0xF5EC => "F-Secure", 0xFF00..=0xFFFE => "Range reserved for randomly assigned serial numbers.", 0xFFFF => "Testcard", _ => "Unknown", } } /// This ident is constructed as the concatenation of manufacturer /// id, a colon, and the card serial (in hexadecimal representation, /// with uppercase hex digits). /// /// It is a more easily human-readable, shorter form of the full /// 16-byte AID ("Application Identifier"). /// /// Example: "1234:5678ABCD". pub fn ident(&self) -> String { format!("{:04X}:{:08X}", self.manufacturer, self.serial) } } #[cfg(test)] mod test { use super::*; #[test] fn test_gnuk() { let data = [ 0xd2, 0x76, 0x0, 0x1, 0x24, 0x1, 0x2, 0x0, 0xff, 0xfe, 0x43, 0x19, 0x42, 0x40, 0x0, 0x0, ]; let aid = ApplicationIdentifier::try_from(&data[..]).expect("failed to parse application id"); assert_eq!( aid, ApplicationIdentifier { application: 0x1, version: 0x200, manufacturer: 0xfffe, serial: 0x43194240, } ); } } openpgp-card-0.5.0/src/ocard/data/cardholder.rs000064400000000000000000000046561046102023000174650ustar 00000000000000// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Cardholder Related Data (see spec pg. 22) use std::convert::TryFrom; use crate::ocard::data::{CardholderRelatedData, Lang, Sex}; use crate::ocard::tags::Tags; use crate::ocard::tlv::{value::Value, Tlv}; impl CardholderRelatedData { pub fn name(&self) -> Option<&[u8]> { self.name.as_deref() } /// The name field is defined as latin1 encoded, /// with ´<´ and ´<<´ filler characters to separate elements and name-parts. /// /// (The filler/separation characters are not processed by this fn) pub(crate) fn latin1_to_string(s: &[u8]) -> String { s.iter().map(|&c| c as char).collect() } pub fn lang(&self) -> Option<&[Lang]> { self.lang.as_deref() } pub fn sex(&self) -> Option { self.sex } } impl TryFrom<&[u8]> for CardholderRelatedData { type Error = crate::Error; fn try_from(data: &[u8]) -> Result { let value = Value::from(data, true)?; let tlv = Tlv::new(Tags::CardholderRelatedData, value); let name: Option> = tlv.find(Tags::Name).map(|v| v.serialize().to_vec()); let lang: Option> = tlv.find(Tags::LanguagePref).map(|v| { v.serialize() .chunks(2) .map(|c| match c.len() { 2 => Lang::from(&[c[0], c[1]]), 1 => Lang::from(&[c[0]]), _ => unreachable!(), }) .collect() }); let sex = tlv .find(Tags::Sex) .map(|v| v.serialize()) .filter(|v| v.len() == 1) .map(|v| Sex::from(v[0])); Ok(CardholderRelatedData { name, lang, sex }) } } #[cfg(test)] mod test { use super::*; #[test] fn test() { let data = [ 0x5b, 0x8, 0x42, 0x61, 0x72, 0x3c, 0x3c, 0x46, 0x6f, 0x6f, 0x5f, 0x2d, 0x4, 0x64, 0x65, 0x65, 0x6e, 0x5f, 0x35, 0x1, 0x32, ]; let ch = CardholderRelatedData::try_from(&data[..]).expect("failed to parse cardholder"); assert_eq!( ch, CardholderRelatedData { name: Some("Bar< // SPDX-License-Identifier: MIT OR Apache-2.0 //! 4.4.3.7 Extended Capabilities use std::convert::TryFrom; use crate::ocard::data::ExtendedCapabilities; use crate::Error; impl ExtendedCapabilities { /// Secure Messaging supported. /// /// (This feature is currently only available in the SmartPGP implementation) pub fn secure_messaging(&self) -> bool { self.secure_messaging } /// Support for GET CHALLENGE. /// /// (GET CHALLENGE generates a random number of a specified length on the smart card) pub fn get_challenge(&self) -> bool { self.get_challenge } /// Maximum length of random number that can be requested from the card /// (if GET CHALLENGE is supported). /// /// If GET CHALLENGE is not supported, the coding is 0 pub fn max_len_challenge(&self) -> u16 { self.max_len_challenge } /// Support for Key Import pub fn key_import(&self) -> bool { self.key_import } /// PW Status changeable /// (also see [`crate::ocard::data::PWStatusBytes`]) pub fn pw_status_change(&self) -> bool { self.pw_status_change } /// Support for Private use DOs pub fn private_use_dos(&self) -> bool { self.private_use_dos } /// Algorithm attributes changeable /// (also see [`crate::ocard::algorithm::AlgorithmAttributes`]) pub fn algo_attrs_changeable(&self) -> bool { self.algo_attrs_changeable } /// Support for encryption/decryption with AES pub fn aes(&self) -> bool { self.aes } /// KDF-related functionality available pub fn kdf_do(&self) -> bool { self.kdf_do } /// Maximum length of Cardholder Certificates pub fn max_len_cardholder_cert(&self) -> u16 { self.max_len_cardholder_cert } /// Maximum length of "special DOs" /// (Private Use, Login data, URL, Algorithm attributes, KDF etc.) /// /// (OpenPGP card version 3.x only) pub fn max_len_special_do(&self) -> Option { self.max_len_special_do } /// (Private Use, Login data, URL, Algorithm attributes, KDF etc.) /// /// (OpenPGP card version 3.x only) pub fn pin_block_2_format_support(&self) -> Option { self.pin_block_2_format_support } /// MANAGE SECURITY ENVIRONMENT supported (for DEC and AUT keys). /// (See [`crate::ocard::Transaction::manage_security_environment`]) /// /// (OpenPGP card version 3.x only) pub fn mse_command_support(&self) -> Option { self.mse_command_support } /// Only available in OpenPGP card version 2.x /// /// (For OpenPGP card version 3.x, see /// [`crate::ocard::data::ExtendedLengthInfo`]) pub(crate) fn max_cmd_len(&self) -> Option { self.max_cmd_len } /// Only available in OpenPGP card version 2.x /// /// (For OpenPGP card version 3.x, see /// [`crate::ocard::data::ExtendedLengthInfo`]) pub(crate) fn max_resp_len(&self) -> Option { self.max_resp_len } } impl TryFrom<(&[u8], u16)> for ExtendedCapabilities { type Error = Error; fn try_from((input, version): (&[u8], u16)) -> Result { // FIXME: check that this fn is not called excessively often let version = ((version >> 8) as u8, (version & 0xff) as u8); // FIXME: earlier versions have shorter extended capabilities assert!(version.0 >= 2); // v2.x and v3.x should have 10 byte sized extended caps assert_eq!( input.len(), 10, "extended capabilities with size != 10 are currently unsupported" ); let b = input[0]; let secure_messaging = b & 0x80 != 0; let get_challenge = b & 0x40 != 0; let key_import = b & 0x20 != 0; let pw_status_change = b & 0x10 != 0; let private_use_dos = b & 0x08 != 0; let algo_attrs_changeable = b & 0x04 != 0; let aes = b & 0x02 != 0; let kdf_do = b & 0x01 != 0; let sm_algo = input[1]; let max_len_challenge = input[2] as u16 * 256 + input[3] as u16; let max_len_cardholder_cert = input[4] as u16 * 256 + input[5] as u16; let mut max_cmd_len = None; let mut max_resp_len = None; let mut max_len_special_do = None; let mut pin_block_2_format_support = None; let mut mse_command_support = None; if version.0 == 2 { // v2.0 until v2.2 max_cmd_len = Some(input[6] as u16 * 256 + input[7] as u16); max_resp_len = Some(input[8] as u16 * 256 + input[9] as u16); } else { // from v3.0 max_len_special_do = Some(input[6] as u16 * 256 + input[7] as u16); let i8 = input[8]; let i9 = input[9]; if i8 > 1 { return Err(Error::ParseError(format!( "Illegal value '{i8}' for pin_block_2_format_support" ))); } pin_block_2_format_support = Some(i8 != 0); if i9 > 1 { return Err(Error::ParseError(format!( "Illegal value '{i9}' for mse_command_support" ))); } mse_command_support = Some(i9 != 0); } Ok(Self { secure_messaging, get_challenge, key_import, pw_status_change, private_use_dos, algo_attrs_changeable, aes, kdf_do, sm_algo, max_len_challenge, max_len_cardholder_cert, max_cmd_len, // v2 max_resp_len, // v2 max_len_special_do, // v3 pin_block_2_format_support, // v3 mse_command_support, // v3 }) } } #[cfg(test)] mod test { use std::convert::TryFrom; use hex_literal::hex; use crate::ocard::data::extended_cap::ExtendedCapabilities; #[test] fn test_yk5() { // YubiKey 5 let data = hex!("7d 00 0b fe 08 00 00 ff 00 00"); let ec = ExtendedCapabilities::try_from((&data[..], 0x0304)).unwrap(); assert_eq!( ec, ExtendedCapabilities { secure_messaging: false, get_challenge: true, key_import: true, pw_status_change: true, private_use_dos: true, algo_attrs_changeable: true, aes: false, kdf_do: true, sm_algo: 0x0, max_len_challenge: 0xbfe, max_len_cardholder_cert: 0x800, max_cmd_len: None, max_resp_len: None, max_len_special_do: Some(0xff), pin_block_2_format_support: Some(false), mse_command_support: Some(false), } ); } #[test] fn test_floss21() { // FLOSS shop2.1 let data = hex!("7c 00 08 00 08 00 08 00 08 00"); let ec = ExtendedCapabilities::try_from((&data[..], 0x0201)).unwrap(); assert_eq!( ec, ExtendedCapabilities { secure_messaging: false, get_challenge: true, key_import: true, pw_status_change: true, private_use_dos: true, algo_attrs_changeable: true, aes: false, kdf_do: false, sm_algo: 0, max_len_challenge: 2048, max_len_cardholder_cert: 2048, max_cmd_len: Some(2048), max_resp_len: Some(2048), max_len_special_do: None, pin_block_2_format_support: None, mse_command_support: None, } ); } } openpgp-card-0.5.0/src/ocard/data/extended_length_info.rs000064400000000000000000000030451046102023000215210ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! 4.1.3.1 Extended length information //! (Introduced in V3.0) use std::convert::TryFrom; use nom::{bytes::complete::tag, number::complete as number, sequence}; use crate::ocard::data::{complete, ExtendedLengthInfo}; fn parse(input: &[u8]) -> nom::IResult<&[u8], (u16, u16)> { let (input, (_, cmd, _, resp)) = nom::combinator::all_consuming(sequence::tuple(( tag([0x2, 0x2]), number::be_u16, tag([0x2, 0x2]), number::be_u16, )))(input)?; Ok((input, (cmd, resp))) } impl ExtendedLengthInfo { pub fn max_command_bytes(&self) -> u16 { self.max_command_bytes } pub fn max_response_bytes(&self) -> u16 { self.max_response_bytes } } impl TryFrom<&[u8]> for ExtendedLengthInfo { type Error = crate::Error; fn try_from(input: &[u8]) -> Result { let eli = complete(parse(input))?; Ok(Self { max_command_bytes: eli.0, max_response_bytes: eli.1, }) } } #[cfg(test)] mod test { use super::*; #[test] fn test_floss34() { let data = [0x2, 0x2, 0x8, 0x0, 0x2, 0x2, 0x8, 0x0]; let eli = ExtendedLengthInfo::try_from(&data[..]).expect("failed to parse extended length info"); assert_eq!( eli, ExtendedLengthInfo { max_command_bytes: 2048, max_response_bytes: 2048, }, ); } } openpgp-card-0.5.0/src/ocard/data/fingerprint.rs000064400000000000000000000076631046102023000177060ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Fingerprint for a single key slot use std::convert::TryFrom; use std::convert::TryInto; use std::fmt; use nom::{bytes::complete as bytes, combinator, sequence}; use crate::ocard::data::{Fingerprint, KeySet}; use crate::Error; impl From<[u8; 20]> for Fingerprint { fn from(data: [u8; 20]) -> Self { Self(data) } } impl TryFrom<&[u8]> for Fingerprint { type Error = Error; fn try_from(input: &[u8]) -> Result { log::trace!("Fingerprint from input: {:x?}, len {}", input, input.len()); if input.len() == 20 { let array: [u8; 20] = input.try_into().unwrap(); Ok(array.into()) } else { Err(Error::ParseError(format!( "Unexpected fingerprint length {}", input.len() ))) } } } impl Fingerprint { pub fn as_bytes(&self) -> &[u8] { &self.0 } } impl fmt::Display for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{self:X}") } } impl fmt::UpperHex for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for b in &self.0 { write!(f, "{b:02X}")?; } Ok(()) } } impl fmt::Debug for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("Fingerprint") .field(&self.to_string()) .finish() } } fn fingerprint(input: &[u8]) -> nom::IResult<&[u8], Option> { combinator::map(bytes::take(20u8), |i: &[u8]| { if i.iter().any(|&c| c > 0) { // We requested 20 bytes, so we can unwrap here let i: [u8; 20] = i.try_into().unwrap(); Some(i.into()) } else { None } })(input) } fn fingerprints(input: &[u8]) -> nom::IResult<&[u8], KeySet> { combinator::into(sequence::tuple((fingerprint, fingerprint, fingerprint)))(input) } impl TryFrom<&[u8]> for KeySet { type Error = Error; fn try_from(input: &[u8]) -> Result { log::trace!("Fingerprint from input: {:x?}, len {}", input, input.len()); // The input may be longer than 3 fingerprint, don't fail if it hasn't // been completely consumed. self::fingerprints(input) .map(|res| res.1) .map_err(|_err| Error::ParseError("Parsing failed".into())) } } #[cfg(test)] mod test { use super::*; #[test] fn test() { let data3 = [ 0xb7, 0xcd, 0x9f, 0x76, 0x37, 0x1e, 0x7, 0x7f, 0x76, 0x1c, 0x82, 0x65, 0x55, 0x54, 0x3e, 0x6d, 0x65, 0x6d, 0x1d, 0x80, 0x62, 0xd7, 0x34, 0x22, 0x65, 0xd2, 0xef, 0x33, 0x64, 0xe3, 0x79, 0x52, 0xd9, 0x5e, 0x94, 0x20, 0x5f, 0x4c, 0xce, 0x8b, 0x3f, 0x9, 0x7a, 0xf2, 0xfd, 0x76, 0xa5, 0xa7, 0x57, 0x9b, 0x51, 0x1f, 0xf, 0x44, 0x9a, 0x25, 0x80, 0x2d, 0xb2, 0xb8, ]; let fp_set: KeySet = (&data3[..]) .try_into() .expect("failed to parse fingerprint set"); assert_eq!( format!("{}", fp_set.signature().unwrap()), "B7CD9F76371E077F761C826555543E6D656D1D80" ); assert_eq!( format!("{}", fp_set.decryption().unwrap()), "62D7342265D2EF3364E37952D95E94205F4CCE8B" ); assert_eq!( format!("{}", fp_set.authentication().unwrap()), "3F097AF2FD76A5A7579B511F0F449A25802DB2B8" ); let data1 = [ 0xb7, 0xcd, 0x9f, 0x76, 0x37, 0x1e, 0x7, 0x7f, 0x76, 0x1c, 0x82, 0x65, 0x55, 0x54, 0x3e, 0x6d, 0x65, 0x6d, 0x1d, 0x80, ]; let fp = Fingerprint::try_from(&data1[..]).expect("failed to parse fingerprint"); assert_eq!(format!("{fp}"), "B7CD9F76371E077F761C826555543E6D656D1D80"); } } openpgp-card-0.5.0/src/ocard/data/historical.rs000064400000000000000000000257571046102023000175240ustar 00000000000000// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! 6 Historical Bytes use std::convert::TryFrom; use crate::ocard::data::{CardCapabilities, CardServiceData, HistoricalBytes}; use crate::Error; impl CardCapabilities { pub fn command_chaining(&self) -> bool { self.command_chaining } pub fn extended_lc_le(&self) -> bool { self.extended_lc_le } pub fn extended_length_information(&self) -> bool { self.extended_length_information } } impl From<[u8; 3]> for CardCapabilities { fn from(data: [u8; 3]) -> Self { let byte3 = data[2]; let command_chaining = byte3 & 0x80 != 0; let extended_lc_le = byte3 & 0x40 != 0; let extended_length_information = byte3 & 0x20 != 0; Self { command_chaining, extended_lc_le, extended_length_information, } } } impl From for CardServiceData { fn from(data: u8) -> Self { let select_by_full_df_name = data & 0x80 != 0; let select_by_partial_df_name = data & 0x40 != 0; let dos_available_in_ef_dir = data & 0x20 != 0; let dos_available_in_ef_atr_info = data & 0x10 != 0; let access_services = [data & 0x8 != 0, data & 0x4 != 0, data & 0x2 != 0]; let mf = data & 0x1 != 0; Self { select_by_full_df_name, select_by_partial_df_name, dos_available_in_ef_dir, dos_available_in_ef_atr_info, access_services, mf, } } } /// Split a compact-tlv "TL" (tag + length) into a 4-bit 'tag' and 4-bit /// 'length'. /// /// The COMPACT-TLV format has a Tag in the first nibble of a byte (bit /// 5-8) and a length in the second nibble (bit 1-4). fn split_tl(tl: u8) -> (u8, u8) { let tag = (tl & 0xf0) >> 4; let len = tl & 0x0f; (tag, len) } impl HistoricalBytes { pub fn card_capabilities(&self) -> Option<&CardCapabilities> { self.cc.as_ref() } pub fn card_service_data(&self) -> Option<&CardServiceData> { self.csd.as_ref() } } impl TryFrom<&[u8]> for HistoricalBytes { type Error = crate::Error; fn try_from(mut data: &[u8]) -> Result { // workaround-hack for "ledger" with zero-padded historical bytes if data.ends_with(&[0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]) { data = data.strip_suffix(&[0x0, 0x0, 0x0, 0x0, 0x0]).unwrap(); } // workaround-hack for "ledger": fix status indicator byte 7 if data == [0x0, 0x31, 0xc5, 0x73, 0xc0, 0x1, 0x80, 0x7, 0x90, 0x0] { data = &[0x0, 0x31, 0xc5, 0x73, 0xc0, 0x1, 0x80, 0x5, 0x90, 0x0]; } let len = data.len(); if len < 4 { // historical bytes cannot be this short return Err(Error::ParseError(format!( "Historical bytes too short ({len} bytes), must be >= 4" ))); } if data[0] != 0 { // The OpenPGP application assumes a category indicator byte // set to '00' (o-card 3.4.1, pg 44) return Err(Error::ParseError( "Unexpected category indicator in historical bytes".into(), )); } // category indicator byte let cib = data[0]; // Card service data byte let mut csd = None; // Card capabilities let mut cc = None; // get information from "COMPACT-TLV data objects" [ISO 12.1.1.2] let mut ctlv = data[1..len - 3].to_vec(); while !ctlv.is_empty() { let (t, l) = split_tl(ctlv[0]); // ctlv must still contain at least 1 + l bytes // (1 byte for the tl, plus `l` bytes of data for this ctlv) // (e.g. len = 4 -> tl + 3byte data) if ctlv.len() < (1 + l as usize) { return Err(Error::ParseError(format!( "Illegal length value in Historical Bytes TL {} len {} l {}", ctlv[0], ctlv.len(), l ))); } match (t, l) { (0x3, 0x1) => { csd = Some(ctlv[1]); ctlv.drain(0..2); } (0x7, 0x3) => { cc = Some([ctlv[1], ctlv[2], ctlv[3]]); ctlv.drain(0..4); } (_, _) => { // Log other unexpected CTLV entries. // (e.g. yubikey neo returns historical bytes as: // "[0, 73, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]") log::trace!("historical bytes: ignored (tag {}, len {})", t, l); ctlv.drain(0..(l as usize + 1)); } } } // status indicator byte let sib = match data[len - 3] { 0 => { // Card does not offer life cycle management, commands // TERMINATE DF and ACTIVATE FILE are not supported 0 } 3 => { // Initialisation state // OpenPGP application can be reset to default values with // an ACTIVATE FILE command 3 } 5 => { // Operational state (activated) // Card supports life cycle management, commands TERMINATE // DF and ACTIVATE FILE are available 5 } _ => { return Err(Error::ParseError( "unexpected status indicator in historical bytes".into(), )); } }; // Ignore final two (status) bytes: // according to the spec, they 'normally' show [0x90, 0x0] - but // YubiKey Neo shows [0x0, 0x0]. // It's unclear if these status bytes are ever useful to process? let cc = cc.map(CardCapabilities::from); let csd = csd.map(CardServiceData::from); Ok(Self { cib, csd, cc, sib }) } } #[cfg(test)] mod test { use std::convert::TryInto; use super::*; #[test] fn test_split_tl() { assert_eq!(split_tl(0x31), (3, 1)); assert_eq!(split_tl(0x73), (7, 3)); assert_eq!(split_tl(0x00), (0, 0)); assert_eq!(split_tl(0xff), (0xf, 0xf)); } #[test] fn test_gnuk() -> Result<(), Error> { // gnuk 1.2 stable let data: &[u8] = &[0x0, 0x31, 0x84, 0x73, 0x80, 0x1, 0x80, 0x5, 0x90, 0x0]; let hist: HistoricalBytes = data.try_into()?; assert_eq!( hist, HistoricalBytes { cib: 0, csd: Some(CardServiceData { select_by_full_df_name: true, select_by_partial_df_name: false, dos_available_in_ef_dir: false, dos_available_in_ef_atr_info: false, access_services: [false, true, false,], mf: false, }), cc: Some(CardCapabilities { command_chaining: true, extended_lc_le: false, extended_length_information: false, }), sib: 5 } ); Ok(()) } #[test] fn test_floss34() -> Result<(), Error> { // floss shop openpgp smartcard 3.4 let data: &[u8] = &[0x0, 0x31, 0xf5, 0x73, 0xc0, 0x1, 0x60, 0x5, 0x90, 0x0]; let hist: HistoricalBytes = data.try_into()?; assert_eq!( hist, HistoricalBytes { cib: 0, csd: Some(CardServiceData { select_by_full_df_name: true, select_by_partial_df_name: true, dos_available_in_ef_dir: true, dos_available_in_ef_atr_info: true, access_services: [false, true, false,], mf: true, },), cc: Some(CardCapabilities { command_chaining: false, extended_lc_le: true, extended_length_information: true, },), sib: 5, } ); Ok(()) } #[test] fn test_yk5() -> Result<(), Error> { // yubikey 5 let data: &[u8] = &[0x0, 0x73, 0x0, 0x0, 0xe0, 0x5, 0x90, 0x0]; let hist: HistoricalBytes = data.try_into()?; assert_eq!( hist, HistoricalBytes { cib: 0, csd: None, cc: Some(CardCapabilities { command_chaining: true, extended_lc_le: true, extended_length_information: true, },), sib: 5, } ); Ok(()) } #[test] fn test_yk4() -> Result<(), Error> { // yubikey 4 let data: &[u8] = &[0x0, 0x73, 0x0, 0x0, 0x80, 0x5, 0x90, 0x0]; let hist: HistoricalBytes = data.try_into()?; assert_eq!( hist, HistoricalBytes { cib: 0, csd: None, cc: Some(CardCapabilities { command_chaining: true, extended_lc_le: false, extended_length_information: false, },), sib: 5, } ); Ok(()) } #[test] fn test_yk_neo() -> Result<(), Error> { // yubikey neo let data: &[u8] = &[ 0x0, 0x73, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; let hist: HistoricalBytes = data.try_into()?; assert_eq!( hist, HistoricalBytes { cib: 0, csd: None, cc: Some(CardCapabilities { command_chaining: true, extended_lc_le: false, extended_length_information: false }), sib: 0 } ); Ok(()) } #[test] fn test_ledger_nano_s() -> Result<(), Error> { let data: &[u8] = &[ 0x0, 0x31, 0xc5, 0x73, 0xc0, 0x1, 0x80, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; let hist: HistoricalBytes = data.try_into()?; assert_eq!( hist, HistoricalBytes { cib: 0, csd: Some(CardServiceData { select_by_full_df_name: true, select_by_partial_df_name: true, dos_available_in_ef_dir: false, dos_available_in_ef_atr_info: false, access_services: [false, true, false], mf: true }), cc: Some(CardCapabilities { command_chaining: true, extended_lc_le: false, extended_length_information: false }), sib: 5 } ); Ok(()) } } openpgp-card-0.5.0/src/ocard/data/kdf_do.rs000064400000000000000000000072751046102023000166040ustar 00000000000000// SPDX-FileCopyrightText: 2024 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use std::convert::{TryFrom, TryInto}; use nom::AsBytes; use crate::ocard::data::KdfDo; use crate::ocard::tags::Tags; use crate::ocard::tlv::tag::Tag; use crate::ocard::tlv::value::Value; use crate::ocard::tlv::Tlv; use crate::Error; impl TryFrom<&[u8]> for KdfDo { type Error = Error; fn try_from(value: &[u8]) -> Result { let tlv = Tlv::new(Tags::KdfDo, Value::from(value, true)?); if let Some(Value::S(data)) = tlv.find(Tag::from([0x81])) { let kdf_algo: u8 = if data.len() == 1 { data[0] } else { return Err(Error::ParseError( "couldn't parse kdf algorithm field in KDF DO".to_string(), )); }; let hash_algo: Option = tlv.find(Tag::from([0x82])).and_then(|v| match v { Value::S(data) => { if data.len() == 1 { Some(data[0]) } else { None } } _ => None, }); let iter_count: Option = tlv.find(Tag::from([0x83])).and_then(|v| match v { Value::S(data) => { if data.len() == 4 { Some(u32::from_be_bytes( data[0..4].as_bytes().try_into().unwrap(), )) } else { None } } _ => None, }); let salt_pw1: Option> = tlv.find(Tag::from([0x84])).and_then(|v| match v { Value::S(data) => Some(data.to_vec()), _ => None, }); let salt_rc: Option> = tlv.find(Tag::from([0x85])).and_then(|v| match v { Value::S(data) => Some(data.to_vec()), _ => None, }); let salt_pw3: Option> = tlv.find(Tag::from([0x86])).and_then(|v| match v { Value::S(data) => Some(data.to_vec()), _ => None, }); let initial_hash_pw1: Option> = tlv.find(Tag::from([0x87])).and_then(|v| match v { Value::S(data) => Some(data.to_vec()), _ => None, }); let initial_hash_pw3: Option> = tlv.find(Tag::from([0x88])).and_then(|v| match v { Value::S(data) => Some(data.to_vec()), _ => None, }); Ok(Self { kdf_algo, hash_algo, iter_count, salt_pw1, salt_rc, salt_pw3, initial_hash_pw1, initial_hash_pw3, }) } else { Err(Error::ParseError("couldn't parse KDF DO".to_string())) } } } impl KdfDo { pub fn kdf_algo(&self) -> u8 { self.kdf_algo } pub fn hash_algo(&self) -> Option { self.hash_algo } pub fn iter_count(&self) -> Option { self.iter_count } pub fn salt_pw1(&self) -> Option<&[u8]> { self.salt_pw1.as_deref() } pub fn salt_rc(&self) -> Option<&[u8]> { self.salt_rc.as_deref() } pub fn salt_pw3(&self) -> Option<&[u8]> { self.salt_pw3.as_deref() } pub fn initial_hash_pw1(&self) -> Option<&[u8]> { self.initial_hash_pw1.as_deref() } pub fn initial_hash_pw3(&self) -> Option<&[u8]> { self.initial_hash_pw3.as_deref() } } openpgp-card-0.5.0/src/ocard/data/key_generation_times.rs000064400000000000000000000051411046102023000215500ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Generation date/time of key pair (see spec pg. 24) use std::convert::TryFrom; use chrono::{DateTime, Utc}; use nom::{combinator, number::complete as number, sequence}; use crate::ocard::data::{KeyGenerationTime, KeySet}; use crate::Error; impl From<&KeyGenerationTime> for DateTime { fn from(kg: &KeyGenerationTime) -> Self { DateTime::from_timestamp(kg.0 as i64, 0).expect("invalid or out-of-range datetime") } } impl From<&KeyGenerationTime> for u32 { fn from(kg: &KeyGenerationTime) -> Self { kg.0 } } impl From for KeyGenerationTime { fn from(data: u32) -> Self { Self(data) } } fn gen_time(input: &[u8]) -> nom::IResult<&[u8], u32> { (number::be_u32)(input) } fn key_generation(input: &[u8]) -> nom::IResult<&[u8], Option> { combinator::map(gen_time, |kg| match kg { 0 => None, kg => Some(KeyGenerationTime(kg)), })(input) } fn key_generation_set(input: &[u8]) -> nom::IResult<&[u8], KeySet> { combinator::into(sequence::tuple(( key_generation, key_generation, key_generation, )))(input) } impl TryFrom<&[u8]> for KeySet { type Error = Error; fn try_from(input: &[u8]) -> Result { // List of generation dates/times of key pairs, binary. // 4 bytes, Big Endian each for Sig, Dec and Aut. Each // value shall be seconds since Jan 1, 1970. Default // value is 00000000 (not specified). log::trace!( "Key generation times from input: {:x?}, len {}", input, input.len() ); // The input may be longer than 3 key generation times, don't fail if it // hasn't been completely consumed. self::key_generation_set(input) .map(|res| res.1) .map_err(|_err| Error::ParseError("Parsing failed".to_string())) } } #[cfg(test)] mod test { use std::convert::TryInto; use super::*; #[test] fn test() { let data3 = [ 0x60, 0xf3, 0xff, 0x71, 0x60, 0xf3, 0xff, 0x72, 0x60, 0xf3, 0xff, 0x83, ]; let fp_set: KeySet = (&data3[..]) .try_into() .expect("failed to parse KeyGenerationTime set"); assert_eq!(fp_set.signature().unwrap().get(), 0x60f3ff71); assert_eq!(fp_set.decryption().unwrap().get(), 0x60f3ff72); assert_eq!(fp_set.authentication().unwrap().get(), 0x60f3ff83); } } openpgp-card-0.5.0/src/ocard/data/pw_status.rs000064400000000000000000000054531046102023000174030ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! PW status Bytes (see spec page 23) use std::convert::TryFrom; use crate::ocard::data::PWStatusBytes; use crate::Error; impl PWStatusBytes { /// PUT DO for PW Status Bytes accepts either 1 or 4 bytes of data. /// This method generates the 1 byte version for 'long==false' and the /// 4 bytes version for 'long==true'. /// /// (See OpenPGP card spec, pg. 28) pub(crate) fn serialize_for_put(&self, long: bool) -> Vec { let mut data = vec![]; // 0 if "valid once", 1 otherwise data.push(u8::from(!self.pw1_cds_valid_once)); if long { let mut b2 = self.pw1_len_format; if self.pw1_pin_block { b2 |= 0x80; } data.push(b2); data.push(self.rc_len); let mut b4 = self.pw3_len_format; if self.pw3_pin_block { b4 |= 0x80; } data.push(b4); } data } } impl TryFrom<&[u8]> for PWStatusBytes { type Error = crate::Error; fn try_from(input: &[u8]) -> Result { if input.len() == 7 { let pw1_cds_valid_once = input[0] == 0x00; let pw1_pin_block = input[1] & 0x80 != 0; let pw1_len = input[1] & 0x7f; let rc_len = input[2]; let pw3_pin_block = input[3] & 0x80 != 0; let pw3_len = input[3] & 0x7f; let err_count_pw1 = input[4]; let err_count_rst = input[5]; let err_count_pw3 = input[6]; Ok(Self { pw1_cds_valid_once, pw1_pin_block, pw1_len_format: pw1_len, rc_len, pw3_pin_block, pw3_len_format: pw3_len, err_count_pw1, err_count_rst, err_count_pw3, }) } else { Err(Error::ParseError(format!( "Unexpected length of PW Status Bytes: {}", input.len() ))) } } } #[cfg(test)] mod test { use std::convert::TryInto; use super::*; #[test] fn test() { let data = [0x0, 0x40, 0x40, 0x40, 0x3, 0x0, 0x3]; let pws: PWStatusBytes = (&data[..]).try_into().expect("failed to parse PWStatus"); assert_eq!( pws, PWStatusBytes { pw1_cds_valid_once: true, pw1_pin_block: false, pw1_len_format: 0x40, rc_len: 0x40, pw3_pin_block: false, pw3_len_format: 0x40, err_count_pw1: 3, err_count_rst: 0, err_count_pw3: 3 } ); } } openpgp-card-0.5.0/src/ocard/data.rs000064400000000000000000001017471046102023000153550ustar 00000000000000// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! OpenPGP card data objects (DO) use std::convert::{TryFrom, TryInto}; use std::fmt::{Display, Formatter, Write}; use chrono::{DateTime, Utc}; use crate::ocard::algorithm::AlgorithmAttributes; use crate::ocard::tags::Tags; use crate::ocard::tlv::length::tlv_encode_length; use crate::ocard::tlv::Tlv; use crate::ocard::KeyType; use crate::Error; mod algo_attrs; mod algo_info; mod application_id; mod cardholder; mod extended_cap; mod extended_length_info; mod fingerprint; mod historical; mod kdf_do; mod key_generation_times; mod pw_status; /// Application Related Data [Spec section 4.4.3.1] /// /// The "application related data" DO contains a set of DOs. /// This struct offers read access to these DOs. /// /// (Note: when any of the information in this DO changes on the card, you /// need to re-read ApplicationRelatedData from the card to receive the /// new values!) pub struct ApplicationRelatedData(pub(crate) Tlv); impl ApplicationRelatedData { /// Get application identifier (AID), ISO 7816-4 pub fn application_id(&self) -> Result { let aid = self.0.find(Tags::ApplicationIdentifier); if let Some(aid) = aid { Ok(ApplicationIdentifier::try_from(&aid.serialize()[..])?) } else { Err(Error::NotFound("Couldn't get Application ID.".to_string())) } } /// Get historical bytes pub fn historical_bytes(&self) -> Result { let hist = self.0.find(Tags::HistoricalBytes); if let Some(hist) = hist { log::trace!("Historical bytes: {:x?}", hist); (hist.serialize().as_slice()).try_into() } else { Err(Error::NotFound( "Failed to get historical bytes.".to_string(), )) } } /// Get extended length information (ISO 7816-4), which /// contains maximum number of bytes for command and response. pub fn extended_length_information(&self) -> Result, Error> { let eli = self.0.find(Tags::ExtendedLengthInformation); log::trace!("Extended length information: {:x?}", eli); if let Some(eli) = eli { // The card has returned extended length information Ok(Some((&eli.serialize()[..]).try_into()?)) } else { // The card didn't return this (optional) DO. That is ok. Ok(None) } } #[allow(dead_code)] fn general_feature_management() -> Option { // FIXME unimplemented!() } #[allow(dead_code)] fn discretionary_data_objects() { // FIXME unimplemented!() } /// Get extended Capabilities pub fn extended_capabilities(&self) -> Result { let app_id = self.application_id()?; let version = app_id.version(); // get from cached "application related data" let ecap = self.0.find(Tags::ExtendedCapabilities); if let Some(ecap) = ecap { Ok(ExtendedCapabilities::try_from(( &ecap.serialize()[..], version, ))?) } else { Err(Error::NotFound( "Failed to get extended capabilities.".to_string(), )) } } /// Get algorithm attributes (for each key type) pub fn algorithm_attributes(&self, key_type: KeyType) -> Result { let aa = self.0.find(key_type.algorithm_tag()); if let Some(aa) = aa { AlgorithmAttributes::try_from(&aa.serialize()[..]) } else { Err(Error::NotFound(format!( "Failed to get algorithm attributes for {key_type:?}." ))) } } /// Get PW status Bytes pub fn pw_status_bytes(&self) -> Result { let psb = self.0.find(Tags::PWStatusBytes); if let Some(psb) = psb { let pws = (&psb.serialize()[..]).try_into()?; log::trace!("PW Status: {:x?}", pws); Ok(pws) } else { Err(Error::NotFound( "Failed to get PW status Bytes.".to_string(), )) } } /// Fingerprint, per key type. /// Zero bytes indicate a not defined private key. pub fn fingerprints(&self) -> Result, Error> { let fp = self.0.find(Tags::Fingerprints); if let Some(fp) = fp { let fp: KeySet = (&fp.serialize()[..]).try_into()?; log::trace!("Fp: {:x?}", fp); Ok(fp) } else { Err(Error::NotFound("Failed to get fingerprints.".into())) } } pub fn ca_fingerprints(&self) -> Result<[Option; 3], Error> { let fp = self.0.find(Tags::CaFingerprints); if let Some(fp) = fp { // FIXME: using a KeySet is a weird hack let fp: KeySet = (&fp.serialize()[..]).try_into()?; let fp = [fp.signature, fp.decryption, fp.authentication]; log::trace!("CA Fp: {:x?}", fp); Ok(fp) } else { Err(Error::NotFound("Failed to get CA fingerprints.".into())) } } /// Generation dates/times of key pairs pub fn key_generation_times(&self) -> Result, Error> { let kg = self.0.find(Tags::GenerationTimes); if let Some(kg) = kg { let kg: KeySet = (&kg.serialize()[..]).try_into()?; log::trace!("Key generation: {:x?}", kg); Ok(kg) } else { Err(Error::NotFound( "Failed to get key generation times.".to_string(), )) } } pub fn key_information(&self) -> Result, Error> { let ki = self.0.find(Tags::KeyInformation); // TODO: return an error in .into(), if the format of the value is bad Ok(ki.map(|v| v.serialize().into())) } pub fn uif_pso_cds(&self) -> Result, Error> { let uif = self.0.find(Tags::UifSig); match uif { None => Ok(None), Some(v) => Ok(Some(v.serialize().try_into()?)), } } pub fn uif_pso_dec(&self) -> Result, Error> { let uif = self.0.find(Tags::UifDec); match uif { None => Ok(None), Some(v) => Ok(Some(v.serialize().try_into()?)), } } pub fn uif_pso_aut(&self) -> Result, Error> { let uif = self.0.find(Tags::UifAuth); match uif { None => Ok(None), Some(v) => Ok(Some(v.serialize().try_into()?)), } } /// Get Attestation key fingerprint. pub fn attestation_key_fingerprint(&self) -> Result, Error> { match self.0.find(Tags::FingerprintAttestation) { None => Ok(None), Some(data) => { // FIXME: move conversion logic to Fingerprint if data.serialize().iter().any(|&b| b != 0) { Ok(Some(Fingerprint::try_from(data.serialize().as_slice())?)) } else { Ok(None) } } } } /// Get Attestation key algorithm attributes. pub fn attestation_key_algorithm_attributes( &mut self, ) -> Result, Error> { match self.0.find(Tags::AlgorithmAttributesAttestation) { None => Ok(None), Some(data) => Ok(Some(AlgorithmAttributes::try_from( data.serialize().as_slice(), )?)), } } /// Get Attestation key generation time. pub fn attestation_key_generation_time(&self) -> Result, Error> { match self.0.find(Tags::GenerationTimeAttestation) { None => Ok(None), Some(data) => { // FIXME: move conversion logic to KeyGenerationTime // Generation time of key, binary. 4 bytes, Big Endian. // Value shall be seconds since Jan 1, 1970. Default value is 00000000 (not specified). assert_eq!(data.serialize().len(), 4); match u32::from_be_bytes(data.serialize().try_into().unwrap()) { 0 => Ok(None), kgt => Ok(Some(kgt.into())), } } } } pub fn uif_attestation(&self) -> Result, Error> { let uif = self.0.find(Tags::UifAttestation); match uif { None => Ok(None), Some(v) => Ok(Some(v.serialize().try_into()?)), } } } /// Security support template [Spec page 24] #[derive(Debug)] pub struct SecuritySupportTemplate { // Digital signature counter [3 bytes] // (counts usage of Compute Digital Signature command) pub(crate) dsc: u32, } impl SecuritySupportTemplate { pub fn signature_count(&self) -> u32 { self.dsc } } /// An OpenPGP key generation Time [Spec page 24] #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct KeyGenerationTime(u32); impl KeyGenerationTime { pub fn get(&self) -> u32 { self.0 } } impl Display for KeyGenerationTime { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", DateTime::::from(self)) } } /// User Interaction Flag [Spec page 24] #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct UserInteractionFlag([u8; 2]); impl TryFrom> for UserInteractionFlag { type Error = Error; fn try_from(v: Vec) -> Result { if v.len() == 2 { Ok(UserInteractionFlag(v.try_into().unwrap())) } else { Err(Error::ParseError(format!("Can't get UID from {v:x?}"))) } } } impl UserInteractionFlag { pub fn touch_policy(&self) -> TouchPolicy { self.0[0].into() } pub fn set_touch_policy(&mut self, tm: TouchPolicy) { self.0[0] = tm.into(); } pub fn features(&self) -> Features { self.0[1].into() } pub(crate) fn as_bytes(&self) -> &[u8] { &self.0[..] } } impl Display for UserInteractionFlag { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "Touch policy: {} [Features: {}]", self.touch_policy(), self.features() ) } } /// User interaction setting: is a 'touch' needed to perform an operation on the card? /// This setting is used in 4.4.3.6 User Interaction Flag (UIF) /// /// See spec pg 24 and /// /// Touch policies were introduced in YubiKey Version 4.2.0 with modes ON, OFF and FIXED. /// YubiKey Version >= 5.2.1 added support for modes CACHED and CACHED_FIXED. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum TouchPolicy { Off, On, Fixed, Cached, CachedFixed, Unknown(u8), } impl TouchPolicy { /// Returns "true" if this TouchPolicy (probably) requires touch confirmation. /// /// Note: When the Policy is set to `Cached` or `CachedFixed`, there is no way to be sure if a /// previous touch confirmation is still valid (touch confirmations are valid for 15s, in /// Cached mode) pub fn touch_required(&self) -> bool { !matches!(self, Self::Off) } } impl Display for TouchPolicy { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { TouchPolicy::Off => write!(f, "Off"), TouchPolicy::On => write!(f, "On"), TouchPolicy::Fixed => write!(f, "Fixed"), TouchPolicy::Cached => write!(f, "Cached"), TouchPolicy::CachedFixed => write!(f, "CachedFixed"), TouchPolicy::Unknown(i) => write!(f, "Unknown({i})"), } } } impl From for u8 { fn from(tm: TouchPolicy) -> Self { match tm { TouchPolicy::Off => 0, TouchPolicy::On => 1, TouchPolicy::Fixed => 2, TouchPolicy::Cached => 3, TouchPolicy::CachedFixed => 4, TouchPolicy::Unknown(i) => i, } } } impl From for TouchPolicy { fn from(i: u8) -> Self { match i { 0 => TouchPolicy::Off, 1 => TouchPolicy::On, 2 => TouchPolicy::Fixed, 3 => TouchPolicy::Cached, 4 => TouchPolicy::CachedFixed, _ => TouchPolicy::Unknown(i), } } } /// Features of "additional hardware for user interaction" [Spec section 4.1.3.2]. /// (Settings for these features are contained in [`UserInteractionFlag`]) pub struct Features(u8); impl From for Features { fn from(i: u8) -> Self { Features(i) } } impl Display for Features { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut ft = vec![]; if self.0 & 0x80 != 0 { ft.push("Display") } if self.0 & 0x40 != 0 { ft.push("Biometric input sensor") } if self.0 & 0x20 != 0 { ft.push("Button") } if self.0 & 0x10 != 0 { ft.push("Keypad") } if self.0 & 0x8 != 0 { ft.push("LED") } if self.0 & 0x4 != 0 { ft.push("Loudspeaker") } if self.0 & 0x2 != 0 { ft.push("Microphone") } if self.0 & 0x1 != 0 { ft.push("Touchscreen") } write!(f, "{}", ft.join(", ")) } } /// Key Information [Spec section 4.4.3.8] pub struct KeyInformation(Vec); impl From> for KeyInformation { fn from(v: Vec) -> Self { KeyInformation(v) } } impl KeyInformation { /// How many "additional" keys do we have information for? pub fn num_additional(&self) -> usize { (self.0.len() - 6) / 2 } fn get_ref(&self, n: usize) -> u8 { self.0[n * 2] } fn get_status(&self, n: usize) -> KeyStatus { self.0[n * 2 + 1].into() } pub fn sig_ref(&self) -> u8 { self.get_ref(0) } pub fn sig_status(&self) -> KeyStatus { self.get_status(0) } pub fn dec_ref(&self) -> u8 { self.get_ref(1) } pub fn dec_status(&self) -> KeyStatus { self.get_status(1) } pub fn aut_ref(&self) -> u8 { self.get_ref(2) } pub fn aut_status(&self) -> KeyStatus { self.get_status(2) } pub fn additional_ref(&self, num: usize) -> u8 { self.get_ref(3 + num) } pub fn additional_status(&self, num: usize) -> KeyStatus { self.get_status(3 + num) } } impl Display for KeyInformation { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { writeln!( f, "signature key (#{}): {}", self.sig_ref(), self.sig_status() )?; writeln!( f, "decryption key (#{}): {}", self.dec_ref(), self.dec_status() )?; writeln!( f, "authentication key (#{}): {}", self.aut_ref(), self.aut_status() )?; for i in 0..self.num_additional() { writeln!( f, "additional key {} (#{}): {}", i, self.additional_ref(i), self.additional_status(i) )?; } Ok(()) } } /// KeyStatus is contained in [`KeyInformation`]. /// It encodes if key material on a card was imported or generated on the card. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum KeyStatus { NotPresent, Generated, Imported, Unknown(u8), } impl From for KeyStatus { fn from(i: u8) -> Self { match i { 0 => KeyStatus::NotPresent, 1 => KeyStatus::Generated, 2 => KeyStatus::Imported, _ => KeyStatus::Unknown(i), } } } impl Display for KeyStatus { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { KeyStatus::NotPresent => write!(f, "not present"), KeyStatus::Generated => write!(f, "generated"), KeyStatus::Imported => write!(f, "imported"), KeyStatus::Unknown(i) => write!(f, "unknown status ({i})"), } } } /// Application Identifier (AID) [Spec section 4.2.1] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct ApplicationIdentifier { application: u8, version: u16, manufacturer: u16, serial: u32, } impl Display for ApplicationIdentifier { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "D276000124 {:02X} {:04X} {:04X} {:08X} 0000", self.application, self.version, self.manufacturer, self.serial ) } } /// Historical Bytes [Spec chapter 6] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct HistoricalBytes { /// category indicator byte cib: u8, /// Card service data (31) csd: Option, /// Card Capabilities (73) cc: Option, /// status indicator byte (o-card 3.4.1, pg 44) sib: u8, } /// Card Capabilities [Spec chapter 6 (Historical Bytes)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CardCapabilities { command_chaining: bool, extended_lc_le: bool, extended_length_information: bool, } impl Display for CardCapabilities { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if self.command_chaining { writeln!(f, "- command chaining")?; } if self.extended_lc_le { writeln!(f, "- extended Lc and Le fields")?; } if self.extended_length_information { writeln!(f, "- extended Length Information")?; } Ok(()) } } /// Card service data [Spec chapter 6 (Historical Bytes)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CardServiceData { select_by_full_df_name: bool, // Application Selection by full DF name (AID) select_by_partial_df_name: bool, // Application Selection by partial DF name dos_available_in_ef_dir: bool, dos_available_in_ef_atr_info: bool, // should be true if extended length supported access_services: [bool; 3], // should be '010' if extended length supported mf: bool, } impl Display for CardServiceData { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if self.select_by_full_df_name { writeln!(f, "- Application Selection by full DF name")?; } if self.select_by_partial_df_name { writeln!(f, "- Application Selection by partial DF name")?; } if self.dos_available_in_ef_dir { writeln!(f, "- DOs available in EF.DIR")?; } if self.dos_available_in_ef_atr_info { writeln!(f, "- DOs available in EF.ATR/INFO")?; } write!( f, "- EF.DIR and EF.ATR/INFO access services by the GET DATA command (BER-TLV): " )?; for a in self.access_services { if a { write!(f, "1")?; } else { write!(f, "0")?; } } writeln!(f)?; if self.mf { writeln!(f, "- Card with MF")?; } Ok(()) } } /// Extended Capabilities [Spec section 4.4.3.7] #[derive(Debug, Eq, Clone, Copy, PartialEq)] pub struct ExtendedCapabilities { secure_messaging: bool, get_challenge: bool, key_import: bool, pw_status_change: bool, private_use_dos: bool, algo_attrs_changeable: bool, aes: bool, kdf_do: bool, sm_algo: u8, max_len_challenge: u16, max_len_cardholder_cert: u16, max_cmd_len: Option, // v2 max_resp_len: Option, // v2 max_len_special_do: Option, // v3 pin_block_2_format_support: Option, // v3 mse_command_support: Option, // v3 } impl Display for ExtendedCapabilities { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if self.secure_messaging { writeln!(f, "- secure messaging")?; } if self.get_challenge { writeln!(f, "- get challenge")?; } if self.key_import { writeln!(f, "- key import")?; } if self.pw_status_change { writeln!(f, "- PW Status changeable")?; } if self.private_use_dos { writeln!(f, "- private use DOs")?; } if self.algo_attrs_changeable { writeln!(f, "- algorithm attributes changeable")?; } if self.aes { writeln!(f, "- PSO:DEC/ENC with AES")?; } if self.kdf_do { writeln!(f, "- KDF-DO")?; } if self.sm_algo != 0 { writeln!(f, "- secure messaging algorithm: {:#02X}", self.sm_algo)?; } if self.max_len_challenge != 0 { writeln!( f, "- maximum length of challenge: {}", self.max_len_challenge )?; } writeln!( f, "- maximum length cardholder certificates: {}", self.max_len_cardholder_cert )?; // v2 if let Some(max_cmd_len) = self.max_cmd_len { writeln!(f, "- maximum command length: {max_cmd_len}")?; } if let Some(max_resp_len) = self.max_resp_len { writeln!(f, "- maximum response length: {max_resp_len}")?; } // v3 if let Some(max_len_special_do) = self.max_len_special_do { writeln!(f, "- maximum length for special DOs: {max_len_special_do}")?; } if self.pin_block_2_format_support == Some(true) { writeln!(f, "- PIN block 2 format supported")?; } if self.mse_command_support == Some(true) { writeln!(f, "- MSE command (for DEC and AUT) supported")?; } Ok(()) } } /// Extended length information [Spec section 4.1.3.1] #[derive(Debug, Eq, Clone, Copy, PartialEq)] pub struct ExtendedLengthInfo { max_command_bytes: u16, max_response_bytes: u16, } impl Display for ExtendedLengthInfo { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { writeln!(f, "- max command length: {}", self.max_command_bytes)?; writeln!(f, "- max response length: {}", self.max_response_bytes)?; Ok(()) } } /// Cardholder Related Data [Spec page 22] #[derive(Debug, PartialEq, Eq)] pub struct CardholderRelatedData { name: Option>, lang: Option>, sex: Option, } impl Display for CardholderRelatedData { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if let Some(name) = &self.name { writeln!(f, "Name: {}", Self::latin1_to_string(name))?; } if let Some(sex) = self.sex { writeln!(f, "Sex: {sex}")?; } if let Some(lang) = &self.lang { for (n, l) in lang.iter().enumerate() { writeln!(f, "Lang {}: {}", n + 1, l)?; } } Ok(()) } } /// Sex [Spec section 4.4.3.5]. /// The Sex setting is accessible via [`CardholderRelatedData`]. /// /// Encoded in accordance with #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Sex { NotKnown, Male, Female, NotApplicable, UndefinedValue(u8), // ISO 5218 doesn't define this value } impl Display for Sex { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::NotKnown => write!(f, "Not known"), Self::Male => write!(f, "Male"), Self::Female => write!(f, "Female"), Self::NotApplicable => write!(f, "Not applicable"), Self::UndefinedValue(v) => write!(f, "Undefined value {v:x?}"), } } } impl From<&Sex> for u8 { fn from(sex: &Sex) -> u8 { match sex { Sex::NotKnown => 0x30, Sex::Male => 0x31, Sex::Female => 0x32, Sex::NotApplicable => 0x39, Sex::UndefinedValue(v) => *v, } } } impl From for Sex { fn from(s: u8) -> Self { match s { 0x30 => Self::NotKnown, 0x31 => Self::Male, 0x32 => Self::Female, 0x39 => Self::NotApplicable, v => Self::UndefinedValue(v), } } } /// Individual language for Language Preferences [Spec section 4.4.3.4]. /// Language preferences are accessible via [`CardholderRelatedData`]. /// /// Encoded according to #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Lang { Value([u8; 2]), Invalid(u8), } impl Display for Lang { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::Value(v) => { write!(f, "{}{}", v[0] as char, v[1] as char) } Self::Invalid(v) => { write!(f, "{v:x?}") } } } } impl From<(char, char)> for Lang { fn from(c: (char, char)) -> Self { Lang::Value([c.0 as u8, c.1 as u8]) } } impl From<[char; 2]> for Lang { fn from(c: [char; 2]) -> Self { Lang::Value([c[0] as u8, c[1] as u8]) } } impl From for Vec { fn from(lang: Lang) -> Self { match lang { Lang::Value(v) => vec![v[0], v[1]], Lang::Invalid(v) => vec![v], } } } impl From<&[u8; 1]> for Lang { fn from(data: &[u8; 1]) -> Self { Lang::Invalid(data[0]) } } impl From<&[u8; 2]> for Lang { fn from(data: &[u8; 2]) -> Self { Lang::Value([data[0], data[1]]) } } /// PW status Bytes [Spec page 23] #[derive(Debug, PartialEq, Eq)] pub struct PWStatusBytes { pub(crate) pw1_cds_valid_once: bool, pub(crate) pw1_pin_block: bool, pub(crate) pw1_len_format: u8, pub(crate) rc_len: u8, pub(crate) pw3_pin_block: bool, pub(crate) pw3_len_format: u8, pub(crate) err_count_pw1: u8, pub(crate) err_count_rst: u8, pub(crate) err_count_pw3: u8, } impl PWStatusBytes { /// Set format of PW1: /// `false` for UTF-8 or derived password, /// `true` for PIN block format 2. pub fn set_pw1_pin_block(&mut self, val: bool) { self.pw1_pin_block = val; } /// Set format of PW3: /// `false` for UTF-8 or derived password, /// `true` for PIN block format 2. pub fn set_pw3_pin_block(&mut self, val: bool) { self.pw3_pin_block = val; } /// Is PW1 (no. 81) only valid for one PSO:CDS command? pub fn pw1_cds_valid_once(&self) -> bool { self.pw1_cds_valid_once } /// Configure if PW1 (no. 81) is only valid for one PSO:CDS command. pub fn set_pw1_cds_valid_once(&mut self, val: bool) { self.pw1_cds_valid_once = val; } /// Max length of PW1 pub fn pw1_max_len(&self) -> u8 { self.pw1_len_format & 0x7f } /// Max length of Resetting Code (RC) for PW1 pub fn rc_max_len(&self) -> u8 { self.rc_len } /// Max length of PW3 pub fn pw3_max_len(&self) -> u8 { self.pw3_len_format & 0x7f } /// Error counter of PW1 (if 0, then PW1 is blocked). pub fn err_count_pw1(&self) -> u8 { self.err_count_pw1 } /// Error counter of Resetting Code (RC) (if 0, then RC is blocked). pub fn err_count_rc(&self) -> u8 { self.err_count_rst } /// Error counter of PW3 (if 0, then PW3 is blocked). pub fn err_count_pw3(&self) -> u8 { self.err_count_pw3 } } /// OpenPGP Fingerprint for a key slot [Spec page 23] #[derive(Clone, Eq, PartialEq)] pub struct Fingerprint([u8; 20]); impl Fingerprint { pub fn to_hex(&self) -> String { self.0.map(|byte| format!("{:02x}", byte)).join("") } pub fn to_spaced_hex(&self) -> String { let mut fp = String::new(); for i in 0..20 { let _ = write!(&mut fp, "{:02x}", self.0[i]); if i < 19 && (i % 2 == 1) { fp.push(' '); } if i == 9 { fp.push(' '); } } fp } } // KDF DO #[derive(Debug, Clone)] pub struct KdfDo { // 81 01 KDF algorithm byte: // 00 = NONE (not used) // 03 = KDF_ITERSALTED_S2K kdf_algo: u8, // 82 01 Hash algorithm byte: // 08 = SHA256 // 0A = SHA512 hash_algo: Option, // 83 04 Iteration count (long integer) iter_count: Option, // 84 xx Salt bytes for User password (PW1) salt_pw1: Option>, // 85 xx Salt bytes for Resetting Code of PW1 salt_rc: Option>, // 86 xx Salt bytes for Admin password (PW3) salt_pw3: Option>, // 87 xx Initial password hash for User-PW initial_hash_pw1: Option>, // 88 xx Initial password hash for Admin-PW initial_hash_pw3: Option>, } impl KdfDo { pub(crate) fn serialize(&self) -> Vec { // FIXME: encode as tlv, then serialize? // FIXME: check that lengths of salts and initials fits in u16? let mut ser = vec![]; ser.push(0x81); ser.push(0x01); ser.push(self.kdf_algo); if let Some(hash_algo) = self.hash_algo { ser.push(0x82); ser.push(0x01); ser.push(hash_algo); } if let Some(iter_count) = self.iter_count { ser.push(0x83); ser.push(0x04); ser.extend_from_slice(&iter_count.to_be_bytes()); } if let Some(salt_pw1) = &self.salt_pw1 { ser.push(0x84); ser.extend_from_slice(&tlv_encode_length(salt_pw1.len() as u16)); ser.extend_from_slice(salt_pw1); } if let Some(salt_rc) = &self.salt_rc { ser.push(0x85); ser.extend_from_slice(&tlv_encode_length(salt_rc.len() as u16)); ser.extend_from_slice(salt_rc); } if let Some(salt_pw3) = &self.salt_pw3 { ser.push(0x86); ser.extend_from_slice(&tlv_encode_length(salt_pw3.len() as u16)); ser.extend_from_slice(salt_pw3); } if let Some(initial_hash_pw1) = &self.initial_hash_pw1 { ser.push(0x87); ser.extend_from_slice(&tlv_encode_length(initial_hash_pw1.len() as u16)); ser.extend_from_slice(initial_hash_pw1); } if let Some(initial_hash_pw3) = &self.initial_hash_pw3 { ser.push(0x88); ser.extend_from_slice(&tlv_encode_length(initial_hash_pw3.len() as u16)); ser.extend_from_slice(initial_hash_pw3); } ser } pub fn iter_salted( hash_algo: u8, salt_pw1: Vec, salt_rc: Vec, salt_pw3: Vec, ) -> Result { const PW1_INITIAL: &str = "123456"; const PW3_INITIAL: &str = "12345678"; const ITER_COUNT: u32 = 0x03000000; // 08 = SHA256 // 0A = SHA512 if hash_algo != 0x08 && hash_algo != 0x0a { return Err(Error::UnsupportedAlgo( "Unsupported hash algorithm".to_string(), )); } let kdf_algo = 0x03; // KDF_ITERSALTED_S2K let hash_algo = Some(hash_algo); let iter_count = Some(ITER_COUNT); let salt_pw1 = Some(salt_pw1); let salt_rc = Some(salt_rc); let salt_pw3 = Some(salt_pw3); let initial_hash_pw1 = Some(crate::ocard::kdf::itersalt( PW1_INITIAL, hash_algo, iter_count, salt_pw1.as_deref(), )?); let initial_hash_pw3 = Some(crate::ocard::kdf::itersalt( PW3_INITIAL, hash_algo, iter_count, salt_pw3.as_deref(), )?); Ok(Self { kdf_algo, hash_algo, iter_count, salt_pw1, salt_rc, salt_pw3, initial_hash_pw1, initial_hash_pw3, }) } } /// Helper fn for nom parsing pub(crate) fn complete(result: nom::IResult<&[u8], O>) -> Result { let (rem, output) = result.map_err(|_err| Error::ParseError("Parsing failed".to_string()))?; if rem.is_empty() { Ok(output) } else { Err(Error::ParseError(format!( "Parsing incomplete, trailing data: {rem:x?}" ))) } } /// A KeySet binds together a triple of information about each Key slot on a card #[derive(Clone, Debug, Eq, PartialEq)] pub struct KeySet { signature: Option, decryption: Option, authentication: Option, } impl From<(Option, Option, Option)> for KeySet { fn from(tuple: (Option, Option, Option)) -> Self { Self { signature: tuple.0, decryption: tuple.1, authentication: tuple.2, } } } impl KeySet { pub fn signature(&self) -> Option<&T> { self.signature.as_ref() } pub fn decryption(&self) -> Option<&T> { self.decryption.as_ref() } pub fn authentication(&self) -> Option<&T> { self.authentication.as_ref() } } openpgp-card-0.5.0/src/ocard/kdf.rs000064400000000000000000000127071046102023000152050ustar 00000000000000// SPDX-FileCopyrightText: 2024 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Handle transformation of user-provided PINs according to the KDF configuration on the card, //! if any. use secrecy::{ExposeSecret, SecretString, SecretVec}; use sha2::Digest; use crate::ocard::data::KdfDo; use crate::{Error, PinType}; trait Hasher { /// Update the hash with the given value. fn update(&mut self, _: &[u8]); /// Finalize the hash and return the result. fn finish(self: Box) -> Vec; } #[derive(Default)] pub struct Sha2_256 { inner: sha2::Sha256, } impl Hasher for Sha2_256 { fn update(&mut self, data: &[u8]) { self.inner.update(data); } fn finish(self: Box) -> Vec { self.inner.finalize().as_slice().to_vec() } } #[derive(Default)] pub struct Sha2_512 { inner: sha2::Sha512, } impl Hasher for crate::ocard::kdf::Sha2_512 { fn update(&mut self, data: &[u8]) { self.inner.update(data); } fn finish(self: Box) -> Vec { self.inner.finalize().as_slice().to_vec() } } /// Map user-provided pw/pin value to a `SecretVec`. /// /// This performs a KDF transformation, if the KDF mode is enabled on the card. pub(crate) fn map_pin( pw: SecretString, pin_type: PinType, kdf_do: Option<&KdfDo>, ) -> Result, Error> { match kdf_do { None => { // KDF DO is not set at all -> use the raw pw bytes as PIN Ok(pw.expose_secret().as_bytes().to_vec().into()) } Some(kdf) if kdf.kdf_algo() == 0 => { // KDF algo is "0" -> use the raw pw bytes as PIN Ok(pw.expose_secret().as_bytes().to_vec().into()) } Some(kdf) => { // KDF transformation needs to be applied to PIN match kdf.kdf_algo() { 3 => Ok(itersalt( pw.expose_secret(), kdf.hash_algo(), kdf.iter_count(), match pin_type { PinType::Pw1 => kdf.salt_pw1(), PinType::Rc => kdf.salt_rc(), PinType::Pw3 => kdf.salt_pw3(), }, )? .into()), _ => Err(Error::UnsupportedFeature( "The KDF mode on the card is currently unsupported".to_string(), )), } } } } /// see https://www.rfc-editor.org/rfc/rfc4880.html#section-3.7.1.3 pub(crate) fn itersalt( pw: &str, hash_algo: Option, count: Option, salt: Option<&[u8]>, ) -> Result, Error> { let hash_algo = match hash_algo { Some(hash_algo) => hash_algo, None => { return Err(Error::InternalError( "No KDF hash algorithm setting found".to_string(), )) } }; // number of bytes that should be hashed let mut count = match count { Some(count) => count, None => { return Err(Error::InternalError( "No KDF iteration count setting found".to_string(), )) } } as usize; let salt = match salt { Some(salt) => salt, None => { return Err(Error::InternalError( "No KDF salt setting found".to_string(), )) } }; // set up hasher let mut hasher: Box = match hash_algo { 0x08 => Box::::default(), 0x0A => Box::::default(), _ => { return Err(Error::InternalError( "KDF: unsupported hash algorithm setting".to_string(), )) } }; if count < salt.len() + pw.len() { return Err(Error::InternalError( "KDF: dubiously small count".to_string(), )); } // salt and pw must be hashed complete, at least once hasher.update(salt); count -= salt.len(); hasher.update(pw.as_bytes()); count -= pw.len(); loop { if count >= salt.len() { hasher.update(salt); count -= salt.len(); } else { hasher.update(&salt[0..count]); break; } if count >= pw.len() { hasher.update(pw.as_bytes()); count -= pw.len(); } else { hasher.update(&pw.as_bytes()[0..count]); break; } } Ok(hasher.finish()) } #[test] fn test_itersalt() { // Examples from OpenPGP card 3.4.1 "Functional Specification" pdf, page 20 let user = itersalt( "123456", Some(0x8), Some(100000), Some(&[0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37]), ) .expect("itersalt"); assert_eq!( user, vec![ 0x77, 0x37, 0x84, 0xA6, 0x02, 0xB6, 0xC8, 0x1E, 0x3F, 0x09, 0x2F, 0x4D, 0x7D, 0x00, 0xE1, 0x7C, 0xC8, 0x22, 0xD8, 0x8F, 0x73, 0x60, 0xFC, 0xF2, 0xD2, 0xEF, 0x2D, 0x9D, 0x90, 0x1F, 0x44, 0xB6 ] ); let admin = itersalt( "12345678", Some(0x8), Some(100000), Some(&[0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48]), ) .expect("itersalt"); assert_eq!( admin, vec![ 0x26, 0x75, 0xD6, 0x16, 0x4A, 0x0D, 0x48, 0x27, 0xD1, 0xD0, 0x0C, 0x7E, 0xEA, 0x62, 0x0D, 0x01, 0x5C, 0x00, 0x03, 0x0A, 0x1C, 0xAB, 0x38, 0xB4, 0xD0, 0xDD, 0x60, 0x0B, 0x27, 0xDC, 0x96, 0x30 ] ); } openpgp-card-0.5.0/src/ocard/keys.rs000064400000000000000000000472121046102023000154130ustar 00000000000000// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Generate and import keys use std::convert::TryFrom; use std::time::{SystemTime, UNIX_EPOCH}; use crate::ocard::algorithm::{ AlgorithmAttributes, AlgorithmInformation, Curve, EccAttributes, RsaAttributes, }; use crate::ocard::apdu::command::Command; use crate::ocard::commands; use crate::ocard::crypto::{ CardUploadableKey, EccKey, EccPub, EccType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey, RSAPub, }; use crate::ocard::data::{Fingerprint, KeyGenerationTime}; use crate::ocard::tags::Tags; use crate::ocard::tlv::{length::tlv_encode_length, tag::Tag, value::Value, Tlv}; use crate::ocard::{KeyType, Transaction}; use crate::Error; /// Generate asymmetric key pair on the card and set metadata (time, fingerprint). /// /// This function doesn't set the algorithm_attributes! /// /// This is a convenience wrapper around [generate_asymmetric_key_pair] that: /// - generates a key pair on the card /// - sets the creation time on the card to the current host time /// - calculates fingerprint for the key and sets it on the card /// /// `fp_from_pub` calculates the fingerprint for a public key data object and /// creation timestamp pub(crate) fn gen_key_set_metadata( card_tx: &mut Transaction, fp_from_pub: fn(&PublicKeyMaterial, KeyGenerationTime, KeyType) -> Result, algorithm_attributes: &AlgorithmAttributes, key_type: KeyType, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { // get creation timestamp let time = SystemTime::now(); let ts = time .duration_since(UNIX_EPOCH) .map_err(|e| Error::InternalError(format!("This should never happen {e}")))? .as_secs() as u32; // generate key let tlv = generate_asymmetric_key_pair(card_tx, key_type)?; // derive pubkey let pubkey = tlv_to_pubkey(&tlv, algorithm_attributes)?; log::trace!("public {:x?}", pubkey); // Store creation timestamp (unix time format, limited to u32) let ts = ts.into(); card_tx.set_creation_time(ts, key_type)?; // calculate/store fingerprint let fp = fp_from_pub(&pubkey, ts, key_type)?; card_tx.set_fingerprint(fp, key_type)?; Ok((pubkey, ts)) } /// Transform a public key Tlv from the card into PublicKeyMaterial fn tlv_to_pubkey(tlv: &Tlv, algo: &AlgorithmAttributes) -> Result { let n = tlv.find(Tags::PublicKeyDataRsaModulus); let v = tlv.find(Tags::PublicKeyDataRsaExponent); let ec = tlv.find(Tags::PublicKeyDataEccPoint); match (n, v, ec) { (Some(n), Some(v), None) => { let rsa = RSAPub::new(n.serialize(), v.serialize()); Ok(PublicKeyMaterial::R(rsa)) } (None, None, Some(ec)) => { let data = ec.serialize(); log::trace!("EC --- len {}, data {:x?}", data.len(), data); let ecc = EccPub::new(data, algo.clone()); Ok(PublicKeyMaterial::E(ecc)) } (_, _, _) => Err(Error::UnsupportedAlgo(format!( "Unexpected public key material from card {tlv:?}" ))), } } /// 7.2.14 GENERATE ASYMMETRIC KEY PAIR /// /// This runs the low level key generation primitive on the card. /// (This does not set algorithm attributes, creation time or fingerprint) pub(crate) fn generate_asymmetric_key_pair( card_tx: &mut Transaction, key_type: KeyType, ) -> Result { log::info!("OpenPgpTransaction: generate_asymmetric_key_pair"); // generate key let crt = control_reference_template(key_type)?; let gen_key_cmd = commands::gen_key(crt.serialize().to_vec()); let resp = card_tx.send_command(gen_key_cmd, true)?; resp.check_ok()?; let tlv = Tlv::try_from(resp.data()?)?; Ok(tlv) } /// Get the public key material for a key from the card. /// /// ("Returns the public key of an asymmetric key pair previously generated /// in the card or imported") /// /// (See 7.2.14 GENERATE ASYMMETRIC KEY PAIR) pub(crate) fn public_key( card_tx: &mut Transaction, key_type: KeyType, ) -> Result { log::info!("OpenPgpTransaction: public_key"); // get current algo let ard = card_tx.application_related_data()?; // FIXME: caching let algo = ard.algorithm_attributes(key_type)?; // get public key let crt = control_reference_template(key_type)?; let get_pub_key_cmd = commands::get_pub_key(crt.serialize().to_vec()); let resp = card_tx.send_command(get_pub_key_cmd, true)?; resp.check_ok()?; let tlv = Tlv::try_from(resp.data()?)?; let pubkey = tlv_to_pubkey(&tlv, &algo)?; Ok(pubkey) } /// Import private key material to the card as a specific KeyType. /// /// If the key is unsuitable for `key_type`, an Error is returned (either /// caused by checks before attempting to upload the key to the card, or by /// an error that the card reports during an attempt to upload the key). pub(crate) fn key_import( card_tx: &mut Transaction, key: Box, key_type: KeyType, ) -> Result<(), Error> { log::info!("OpenPgpTransaction: key_import"); match key.private_key()? { PrivateKeyMaterial::R(rsa_key) => key_import_rsa( card_tx, key_type, rsa_key, key.fingerprint()?, key.timestamp(), ), PrivateKeyMaterial::E(ecc_key) => key_import_ecc( card_tx, key_type, ecc_key, key.fingerprint()?, key.timestamp(), ), } } fn key_import_rsa( card_tx: &mut Transaction, key_type: KeyType, rsa_key: Box, fp: Fingerprint, ts: KeyGenerationTime, ) -> Result<(), Error> { // An error is ok (it's fine if a card doesn't offer a list of supported algorithms) let algo_info = card_tx.algorithm_information_cached().ok().flatten(); // RSA bitsize // (round up to 4-bytes, in case the key has 8+ leading zero bits) let rsa_bits = (((rsa_key.n().len() * 8 + 31) / 32) * 32) as u16; // FIXME: caching? let ard = card_tx.application_related_data()?; let algo_attr = ard.algorithm_attributes(key_type)?; let rsa_attrs = determine_rsa_attrs(rsa_bits, key_type, algo_attr, algo_info)?; let import_cmd = rsa_key_import_cmd(key_type, rsa_key, &rsa_attrs)?; // Now that we have marshalled all necessary information, perform all // set-operations on the card. import_key_set_metadata( card_tx, key_type, import_cmd, fp, ts, AlgorithmAttributes::Rsa(rsa_attrs), ) } fn key_import_ecc( card_tx: &mut Transaction, key_type: KeyType, ecc_key: Box, fp: Fingerprint, ts: KeyGenerationTime, ) -> Result<(), Error> { // An error is ok (it's fine if a card doesn't offer a list of supported algorithms) let algo_info = card_tx.algorithm_information_cached().ok().flatten(); let ecc_attrs = determine_ecc_attrs(ecc_key.oid(), ecc_key.ecc_type(), key_type, algo_info)?; let import_cmd = ecc_key_import_cmd(key_type, ecc_key, &ecc_attrs)?; // Now that we have marshalled all necessary information, perform all // set-operations on the card. import_key_set_metadata( card_tx, key_type, import_cmd, fp, ts, AlgorithmAttributes::Ecc(ecc_attrs), ) } fn import_key_set_metadata( card_tx: &mut Transaction, key_type: KeyType, import_cmd: Command, fp: Fingerprint, ts: KeyGenerationTime, algorithm_attributes: AlgorithmAttributes, ) -> Result<(), Error> { log::info!("Import key material"); // Only set algo attrs if "Extended Capabilities" lists the feature if card_tx.extended_capabilities()?.algo_attrs_changeable() { card_tx.set_algorithm_attributes(key_type, &algorithm_attributes)?; } card_tx.send_command(import_cmd, false)?.check_ok()?; card_tx.set_fingerprint(fp, key_type)?; card_tx.set_creation_time(ts, key_type)?; Ok(()) } /// Determine suitable RsaAttrs for the current card, for an `rsa_bits` /// sized key. /// /// If available, via lookup in `algo_info`, otherwise the current /// algorithm attributes are checked. If neither method yields a /// result, we 'guess' the RsaAttrs setting. pub(crate) fn determine_rsa_attrs( rsa_bits: u16, key_type: KeyType, algo_attr: AlgorithmAttributes, algo_info: Option, ) -> Result { // Figure out suitable RSA algorithm parameters: // Does the card offer a list of algorithms? let rsa_attrs = if let Some(algo_info) = algo_info { // Yes -> Look up the parameters for key_type and rsa_bits. // (Or error, if the list doesn't have an entry for rsa_bits) card_algo_rsa(algo_info, key_type, rsa_bits)? } else { // No -> Get the current algorithm attributes for key_type. // Is the algorithm on the card currently set to RSA? if let AlgorithmAttributes::Rsa(rsa) = algo_attr { // If so, use the algorithm parameters from the card and // adjust the bit length based on the user-provided key. RsaAttributes::new(rsa_bits, rsa.len_e(), rsa.import_format()) } else { // The card doesn't provide an algorithm list, and the // current algorithm on the card is not RSA. // // So we 'guess' a value for len_e (some cards only // support 17, others only support 32). // [If this approach turns out to be insufficient, we // need to determine the model of the card and use a // list of which RSA parameters that model of card // supports] RsaAttributes::new(rsa_bits, 32, 0) } }; Ok(rsa_attrs) } /// Derive EccAttrs from `oid` and `ecc_type`, check if the OID is listed in /// `algo_info`. pub(crate) fn determine_ecc_attrs( oid: &[u8], ecc_type: EccType, key_type: KeyType, algo_info: Option, ) -> Result { // If we have an algo_info, refuse upload if oid is not listed if let Some(algo_info) = algo_info { let algos = check_card_algo_ecc(algo_info, key_type, oid); if algos.is_empty() { // If oid is not in algo_info, return error. return Err(Error::UnsupportedAlgo(format!( "Oid {oid:02x?} unsupported according to algo_info" ))); } // Note: Looking up ecc_type in the card's "Algorithm Information" // seems to do more harm than good, so we don't do it. // Some cards report erroneous information about supported algorithms // - e.g. YubiKey 5 reports support for EdDSA over Cv25519 and // Ed25519, but not ECDH. // // We do however, use import_format from algorithm information. if !algos.is_empty() { return Ok(EccAttributes::new( ecc_type, Curve::try_from(oid)?, algos[0].import_format(), )); } } // Return a default when we have no algo_info. // (Do cards that support ecc but have no algo_info exist?) Ok(EccAttributes::new(ecc_type, Curve::try_from(oid)?, None)) } /// Look up RsaAttrs parameters in algo_info based on key_type and rsa_bits fn card_algo_rsa( algo_info: AlgorithmInformation, key_type: KeyType, rsa_bits: u16, ) -> Result { // Find suitable algorithm parameters (from card's list of algorithms). // Get Algos for this keytype let keytype_algos: Vec<_> = algo_info.for_keytype(key_type); // Get RSA algo attributes let rsa_algos: Vec<_> = keytype_algos .iter() .filter_map(|a| { if let AlgorithmAttributes::Rsa(r) = a { Some(r) } else { None } }) .collect(); // Filter card algorithms by rsa bitlength of the key we want to upload let algo: Vec<_> = rsa_algos .iter() .filter(|&a| a.len_n() == rsa_bits) .collect(); // Did we find a suitable algorithm entry? if !algo.is_empty() { // HACK: The SmartPGP applet reports two variants of RSA (import // format 1 and 3), but in fact only supports the second variant. // Using the last option happens to work better, in that case. Ok((**algo.last().unwrap()).clone()) } else { // RSA with this bit length is not in algo_info Err(Error::UnsupportedAlgo(format!( "RSA {rsa_bits} unsupported according to algo_info" ))) } } /// Get all entries from algo_info with matching `oid` and `key_type`. fn check_card_algo_ecc( algo_info: AlgorithmInformation, key_type: KeyType, oid: &[u8], ) -> Vec { // Find suitable algorithm parameters (from card's list of algorithms). // Get Algos for this keytype let keytype_algos: Vec<_> = algo_info.for_keytype(key_type); // Get attributes let ecc_algos: Vec<_> = keytype_algos .iter() .filter_map(|a| { if let AlgorithmAttributes::Ecc(e) = a { Some(e) } else { None } }) .collect(); // Find entries with this OID in the algorithm information for key_type ecc_algos .iter() .filter(|e| e.oid() == oid) .cloned() .cloned() .collect() } /// Create command for RSA key import fn rsa_key_import_cmd( key_type: KeyType, rsa_key: Box, rsa_attrs: &RsaAttributes, ) -> Result { // Assemble key command (see 4.4.3.12 Private Key Template) // Collect data for "Cardholder private key template" DO (7F48) // // (Describes the content of the Cardholder private key DO) let mut cpkt_data: Vec = vec![]; // "Cardholder private key" (5F48) // // "The key data elements according to the definitions in the CPKT DO // (7F48)." let mut key_data = Vec::new(); // -- Public exponent: e -- cpkt_data.extend(Vec::from(Tags::PrivateKeyDataRsaPublicExponent)); // Expected length of e in bytes, rounding up from the bit value in algo. let len_e_bytes = ((rsa_attrs.len_e() + 7) / 8) as u8; // len_e in bytes has a value of 3-4, it doesn't need TLV encoding cpkt_data.push(len_e_bytes); // Push e, padded with zero bytes from the left let e_as_bytes = rsa_key.e(); if len_e_bytes as usize > e_as_bytes.len() { key_data.extend(vec![0; len_e_bytes as usize - e_as_bytes.len()]); } key_data.extend(e_as_bytes); // -- Prime1: p + Prime2: q -- // len_p and len_q are len_n/2 (value from card algorithm list). // transform unit from bits to bytes. let len_p_bytes: u16 = rsa_attrs.len_n() / 2 / 8; let len_q_bytes: u16 = rsa_attrs.len_n() / 2 / 8; cpkt_data.extend(Vec::from(Tags::PrivateKeyDataRsaPrime1)); // len p in bytes, TLV-encoded cpkt_data.extend_from_slice(&tlv_encode_length(len_p_bytes)); cpkt_data.extend(Vec::from(Tags::PrivateKeyDataRsaPrime2)); // len q in bytes, TLV-encoded cpkt_data.extend_from_slice(&tlv_encode_length(len_q_bytes)); // FIXME: do p/q need to be padded from the left when many leading // bits are zero? key_data.extend(rsa_key.p().iter()); key_data.extend(rsa_key.q().iter()); // import format requires chinese remainder theorem fields if rsa_attrs.import_format() == 2 || rsa_attrs.import_format() == 3 { // PQ: 1/q mod p let pq = rsa_key.pq(); cpkt_data.extend(Vec::from(Tags::PrivateKeyDataRsaPq)); cpkt_data.extend(&tlv_encode_length(pq.len() as u16)); key_data.extend(pq.iter()); // DP1: d mod (p - 1) let dp1 = rsa_key.dp1(); cpkt_data.extend(Vec::from(Tags::PrivateKeyDataRsaDp1)); cpkt_data.extend(&tlv_encode_length(dp1.len() as u16)); key_data.extend(dp1.iter()); // DQ1: d mod (q - 1) let dq1 = rsa_key.dq1(); cpkt_data.extend(Vec::from(Tags::PrivateKeyDataRsaDq1)); cpkt_data.extend(&tlv_encode_length(dq1.len() as u16)); key_data.extend(dq1.iter()); } // import format requires modulus n field if rsa_attrs.import_format() == 1 || rsa_attrs.import_format() == 3 { let n = rsa_key.n(); cpkt_data.extend(Vec::from(Tags::PrivateKeyDataRsaModulus)); cpkt_data.extend(&tlv_encode_length(n.len() as u16)); key_data.extend(n.iter()); } // Assemble the DOs for upload let cpkt = Tlv::new(Tags::CardholderPrivateKeyTemplate, Value::S(cpkt_data)); let cpk = Tlv::new(Tags::ConcatenatedKeyData, Value::S(key_data)); // "Control Reference Template" let crt = control_reference_template(key_type)?; // "Extended header list (DO 4D)" let ehl = Tlv::new(Tags::ExtendedHeaderList, Value::C(vec![crt, cpkt, cpk])); // Return the full key import command Ok(commands::key_import(ehl.serialize().to_vec())) } /// Create command for ECC key import fn ecc_key_import_cmd( key_type: KeyType, ecc_key: Box, ecc_attrs: &EccAttributes, ) -> Result { let private = ecc_key.private(); // Collect data for "Cardholder private key template" DO (7F48) // // (Describes the content of the Cardholder private key DO) let mut cpkt_data = vec![]; // "Cardholder private key" (5F48) // // "The key data elements according to the definitions in the CPKT DO // (7F48)." let mut key_data = Vec::new(); // Process "scalar" cpkt_data.extend(Vec::from(Tags::PrivateKeyDataEccPrivateKey)); cpkt_data.extend_from_slice(&tlv_encode_length(private.len() as u16)); key_data.extend(private); // Process "public", if the import format requires it if ecc_attrs.import_format() == Some(0xff) { let p = ecc_key.public(); cpkt_data.extend(Vec::from(Tags::PrivateKeyDataEccPublicKey)); cpkt_data.extend_from_slice(&tlv_encode_length(p.len() as u16)); key_data.extend(p); } // Assemble DOs // "Cardholder private key template" let cpkt = Tlv::new(Tags::CardholderPrivateKeyTemplate, Value::S(cpkt_data)); // "Cardholder private key" let cpk = Tlv::new(Tags::ConcatenatedKeyData, Value::S(key_data)); // "Control Reference Template" let crt = control_reference_template(key_type)?; // "Extended header list (DO 4D)" (contains the three inner TLV) let ehl = Tlv::new(Tags::ExtendedHeaderList, Value::C(vec![crt, cpkt, cpk])); // key import command Ok(commands::key_import(ehl.serialize().to_vec())) } /// Get "Control Reference Template" Tlv for `key_type` fn control_reference_template(key_type: KeyType) -> Result { // "Control Reference Template" (0xB8 | 0xB6 | 0xA4) let tag = match key_type { KeyType::Decryption => Tags::CrtKeyConfidentiality, KeyType::Signing => Tags::CrtKeySignature, KeyType::Authentication => Tags::CrtKeyAuthentication, KeyType::Attestation => { // The attestation key CRT looks like: [B6 03 84 01 81] // // This is a "Control Reference Template in extended format with Key-Ref". // (See "4.4.3.12 Private Key Template") let tlv = Tlv::new( Tags::CrtKeySignature, // Spec page 38: [..] to indicate the private key: "empty or 84 01 xx" Value::C(vec![Tlv::new( Tag::from([0x84]), // Spec page 43: "Key-Ref 0x81 is reserved for the Attestation key of Yubico." Value::S(vec![0x81]), )]), ); return Ok(tlv); } }; Ok(Tlv::new(tag, Value::S(vec![]))) } openpgp-card-0.5.0/src/ocard/mod.rs000064400000000000000000001526531046102023000152250ustar 00000000000000// SPDX-FileCopyrightText: 2021-2024 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Low-level access to an OpenPGP card application use std::convert::{TryFrom, TryInto}; use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; use secrecy::{ExposeSecret, SecretVec}; use crate::ocard::algorithm::{AlgorithmAttributes, AlgorithmInformation}; use crate::ocard::apdu::command::Command; use crate::ocard::apdu::response::RawResponse; use crate::ocard::crypto::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; use crate::ocard::data::{ ApplicationIdentifier, ApplicationRelatedData, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint, HistoricalBytes, KdfDo, KeyGenerationTime, Lang, PWStatusBytes, SecuritySupportTemplate, Sex, UserInteractionFlag, }; use crate::ocard::tags::{ShortTag, Tags}; use crate::ocard::tlv::value::Value; use crate::ocard::tlv::Tlv; use crate::Error; pub mod algorithm; pub(crate) mod apdu; mod commands; pub mod crypto; pub mod data; pub mod kdf; mod keys; pub(crate) mod oid; pub(crate) mod tags; pub(crate) mod tlv; pub(crate) const OPENPGP_APPLICATION: &[u8] = &[0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]; /// Identify a Key slot on an OpenPGP card #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum KeyType { Signing, Decryption, Authentication, /// Attestation is a Yubico proprietary key slot Attestation, } impl KeyType { /// Get C1/C2/C3/DA values for this KeyTypes, to use as Tag pub(crate) fn algorithm_tag(&self) -> ShortTag { match self { Self::Signing => Tags::AlgorithmAttributesSignature, Self::Decryption => Tags::AlgorithmAttributesDecryption, Self::Authentication => Tags::AlgorithmAttributesAuthentication, Self::Attestation => Tags::AlgorithmAttributesAttestation, } .into() } /// Get C7/C8/C9/DB values for this KeyTypes, to use as Tag. /// /// (NOTE: these Tags are only used for "PUT DO", but GETting /// fingerprint information from the card uses the combined Tag C5) fn fingerprint_put_tag(&self) -> ShortTag { match self { Self::Signing => Tags::FingerprintSignature, Self::Decryption => Tags::FingerprintDecryption, Self::Authentication => Tags::FingerprintAuthentication, Self::Attestation => Tags::FingerprintAttestation, } .into() } /// Get CE/CF/D0/DD values for this KeyTypes, to use as Tag. /// /// (NOTE: these Tags are only used for "PUT DO", but GETting /// timestamp information from the card uses the combined Tag CD) fn timestamp_put_tag(&self) -> ShortTag { match self { Self::Signing => Tags::GenerationTimeSignature, Self::Decryption => Tags::GenerationTimeDecryption, Self::Authentication => Tags::GenerationTimeAuthentication, Self::Attestation => Tags::GenerationTimeAttestation, } .into() } } /// A struct to cache immutable information of a card. /// Some of the data is stored during [`OpenPGP::new`]. /// Other information can optionally be cached later (e.g. `ai`) #[derive(Debug)] struct CardImmutable { aid: ApplicationIdentifier, ec: ExtendedCapabilities, hb: Option, // new in v2.0 eli: Option, // new in v3.0 // First `Option` layer encodes if this cache field has been initialized, // if `Some`, then the second `Option` layer encodes if the field exists on the card. ai: Option>, // new in v3.4 } /// An OpenPGP card object (backed by a CardBackend implementation). /// /// Most users will probably want to use the `PcscCard` backend from the `card-backend-pcsc` crate. /// /// Users of this crate can keep a long-lived [`OpenPGP`] object, including in long-running programs. /// All operations must be performed on a [`Transaction`] (which must be short-lived). pub struct OpenPGP { /// A connection to the smart card card: Box, /// Capabilites of the card, determined from hints by the Backend, /// as well as the Application Related Data card_caps: Option, /// A cache data structure for information that is immutable on OpenPGP cards. /// Some of the information gets initialized when connecting to the card. /// Other information may be cached on first read. immutable: Option, } impl OpenPGP { /// Turn a [`CardBackend`] into a [`OpenPGP`] object: /// /// The OpenPGP application is `SELECT`ed, and the card capabilities /// of the card are retrieved from the "Application Related Data". pub fn new(backend: B) -> Result where B: Into>, { let card: Box = backend.into(); let mut op = Self { card, card_caps: None, immutable: None, }; let (caps, imm) = { let mut tx = op.transaction()?; tx.select()?; // Init card_caps let ard = tx.application_related_data()?; // Determine chaining/extended length support from card // metadata and cache this information in the CardTransaction // implementation (as a CardCaps) let mut ext_support = false; let mut chaining_support = false; if let Ok(hist) = ard.historical_bytes() { if let Some(cc) = hist.card_capabilities() { chaining_support = cc.command_chaining(); ext_support = cc.extended_lc_le(); } } let ext_cap = ard.extended_capabilities()?; // Get max command/response byte sizes from card let (max_cmd_bytes, max_rsp_bytes) = if let Ok(Some(eli)) = ard.extended_length_information() { // In card 3.x, max lengths come from ExtendedLengthInfo (eli.max_command_bytes(), eli.max_response_bytes()) } else if let (Some(cmd), Some(rsp)) = (ext_cap.max_cmd_len(), ext_cap.max_resp_len()) { // In card 2.x, max lengths come from ExtendedCapabilities (cmd, rsp) } else { // Fallback: use 255 if we have no information from the card (255, 255) }; let pw_status = ard.pw_status_bytes()?; let pw1_max = pw_status.pw1_max_len(); let pw3_max = pw_status.pw3_max_len(); let caps = CardCaps::new( ext_support, chaining_support, max_cmd_bytes, max_rsp_bytes, pw1_max, pw3_max, ); let imm = CardImmutable { aid: ard.application_id()?, ec: ard.extended_capabilities()?, hb: Some(ard.historical_bytes()?), eli: ard.extended_length_information()?, ai: None, // FIXME: initialize elsewhere? }; drop(tx); // General mechanism to ask the backend for amendments to // the CardCaps (e.g. to change support for "extended length") // // Also see https://blog.apdu.fr/posts/2011/05/extended-apdu-status-per-reader/ let caps = op.card.limit_card_caps(caps); (caps, imm) }; log::trace!("set card_caps to: {:x?}", caps); op.card_caps = Some(caps); log::trace!("set immutable card state to: {:x?}", imm); op.immutable = Some(imm); Ok(op) } /// Get the internal `CardBackend`. /// /// This is useful to perform operations on the card with a different crate, /// e.g. `yubikey-management`. pub fn into_card(self) -> Box { self.card } /// Start a transaction on the underlying CardBackend. /// The resulting [Transaction] object allows performing commands on the card. /// /// Note: Transactions on the Card cannot be long running. /// They may be reset by the smart card subsystem within seconds, when idle. pub fn transaction(&mut self) -> Result { let card_caps = &mut self.card_caps; let immutable = &mut self.immutable; // FIXME: unwrap let tx = self.card.transaction(Some(OPENPGP_APPLICATION))?; if tx.was_reset() { // FIXME: Signal state invalidation to the library user? // (E.g.: PIN verifications may have been lost.) } Ok(Transaction { tx, card_caps, immutable, }) } } /// To perform commands on a [`OpenPGP`], a [`Transaction`] must be started. /// This struct offers low-level access to OpenPGP card functionality. /// /// On backends that support transactions, operations are grouped together in transaction, while /// an object of this type lives. /// /// A [`Transaction`] on typical underlying card subsystems must be short lived. /// (Typically, smart cards can't be kept open for longer than a few seconds, /// before they are automatically closed.) pub struct Transaction<'a> { tx: Box, card_caps: &'a Option, immutable: &'a mut Option, } impl<'a> Transaction<'a> { pub(crate) fn tx(&mut self) -> &mut dyn CardTransaction { self.tx.as_mut() } pub(crate) fn send_command( &mut self, cmd: Command, expect_reply: bool, ) -> Result { apdu::send_command(&mut *self.tx, cmd, *self.card_caps, expect_reply) } // SELECT /// Select the OpenPGP card application pub fn select(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: select"); self.send_command(commands::select_openpgp(), false)? .try_into() } // TERMINATE DF /// 7.2.16 TERMINATE DF pub fn terminate_df(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: terminate_df"); self.send_command(commands::terminate_df(), false)?; Ok(()) } // ACTIVATE FILE /// 7.2.17 ACTIVATE FILE pub fn activate_file(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: activate_file"); self.send_command(commands::activate_file(), false)?; Ok(()) } // --- pinpad --- /// Does the reader support FEATURE_VERIFY_PIN_DIRECT? pub fn feature_pinpad_verify(&self) -> bool { self.tx.feature_pinpad_verify() } /// Does the reader support FEATURE_MODIFY_PIN_DIRECT? pub fn feature_pinpad_modify(&self) -> bool { self.tx.feature_pinpad_modify() } // --- get data --- /// Get the "application related data" from the card. /// /// (This data should probably be cached in a higher layer. Some parts of /// it are needed regularly, and it does not usually change during /// normal use of a card.) pub fn application_related_data(&mut self) -> Result { log::info!("OpenPgpTransaction: application_related_data"); let resp = self.send_command(commands::application_related_data(), true)?; let value = Value::from(resp.data()?, true)?; log::trace!(" ARD value: {:02x?}", value); Ok(ApplicationRelatedData(Tlv::new( Tags::ApplicationRelatedData, value, ))) } // -- cached card data -- /// Get read access to cached immutable card information fn card_immutable(&self) -> Result<&CardImmutable, Error> { if let Some(imm) = &self.immutable { Ok(imm) } else { // We expect that self.immutable has been initialized here Err(Error::InternalError( "Unexpected state of immutable cache".to_string(), )) } } /// Application Identifier. /// /// This function returns data that is cached during initialization. /// Calling it doesn't require sending a command to the card. pub fn application_identifier(&self) -> Result { Ok(self.card_immutable()?.aid) } /// Extended capabilities. /// /// This function returns data that is cached during initialization. /// Calling it doesn't require sending a command to the card. pub fn extended_capabilities(&self) -> Result { Ok(self.card_immutable()?.ec) } /// Historical Bytes (if available). /// /// This function returns data that is cached during initialization. /// Calling it doesn't require sending a command to the card. pub fn historical_bytes(&self) -> Result, Error> { Ok(self.card_immutable()?.hb) } /// Extended length info (if available). /// /// This function returns data that is cached during initialization. /// Calling it doesn't require sending a command to the card. pub fn extended_length_info(&self) -> Result, Error> { Ok(self.card_immutable()?.eli) } #[allow(dead_code)] pub(crate) fn algorithm_information_cached( &mut self, ) -> Result, Error> { // FIXME: merge this fn with the regular/public `algorithm_information()` fn? if self.immutable.is_none() { // We expect that self.immutable has been initialized here return Err(Error::InternalError( "Unexpected state of immutable cache".to_string(), )); } if self.immutable.as_ref().unwrap().ai.is_none() { // Cached ai is unset, initialize it now! let ai = self.algorithm_information()?; self.immutable.as_mut().unwrap().ai = Some(ai); } Ok(self.immutable.as_ref().unwrap().ai.clone().unwrap()) } // --- login data (5e) --- /// Get URL (5f50) pub fn url(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: url"); self.send_command(commands::url(), true)?.try_into() } /// Get Login Data (5e) pub fn login_data(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: login_data"); self.send_command(commands::login_data(), true)?.try_into() } /// Get cardholder related data (65) pub fn cardholder_related_data(&mut self) -> Result { log::info!("OpenPgpTransaction: cardholder_related_data"); let resp = self.send_command(commands::cardholder_related_data(), true)?; resp.data()?.try_into() } /// Get security support template (7a) pub fn security_support_template(&mut self) -> Result { log::info!("OpenPgpTransaction: security_support_template"); let resp = self.send_command(commands::security_support_template(), true)?; let tlv = Tlv::try_from(resp.data()?)?; let dst = tlv.find(Tags::DigitalSignatureCounter).ok_or_else(|| { Error::NotFound("Couldn't get DigitalSignatureCounter DO".to_string()) })?; if let Value::S(data) = dst { let mut data = data.to_vec(); if data.len() != 3 { return Err(Error::ParseError(format!( "Unexpected length {} for DigitalSignatureCounter DO", data.len() ))); } data.insert(0, 0); // prepend a zero let data: [u8; 4] = data.try_into().unwrap(); let dsc: u32 = u32::from_be_bytes(data); Ok(SecuritySupportTemplate { dsc }) } else { Err(Error::NotFound( "Failed to process SecuritySupportTemplate".to_string(), )) } } /// Get cardholder certificate (each for AUT, DEC and SIG). /// /// Call select_data() before calling this fn to select a particular /// certificate (if the card supports multiple certificates). /// /// According to the OpenPGP card specification: /// /// The cardholder certificate DOs are designed to store a certificate (e. g. X.509) /// for the keys in the card. They can be used to identify the card in a client-server /// authentication, where specific non-OpenPGP-certificates are needed, for S-MIME and /// other x.509 related functions. /// /// (See /// for some discussion of the `cardholder certificate` OpenPGP card feature) #[allow(dead_code)] pub fn cardholder_certificate(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: cardholder_certificate"); self.send_command(commands::cardholder_certificate(), true)? .try_into() } /// Call "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> { log::info!("OpenPgpTransaction: next_cardholder_certificate"); self.send_command(commands::get_next_cardholder_certificate(), true)? .try_into() } /// Get "KDF-DO" (announced in Extended Capabilities) pub fn kdf_do(&mut self) -> Result { log::info!("OpenPgpTransaction: kdf_do"); let kdf_do = self .send_command(commands::kdf_do(), true)? .data()? .try_into()?; log::trace!(" KDF DO value: {:02x?}", kdf_do); Ok(kdf_do) } /// Get "Algorithm Information" pub fn algorithm_information(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: algorithm_information"); let resp = self.send_command(commands::algo_info(), true)?; let ai = resp.data()?.try_into()?; Ok(Some(ai)) } /// Get "Attestation Certificate (Yubico)" pub fn attestation_certificate(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: attestation_certificate"); self.send_command(commands::attestation_certificate(), true)? .try_into() } /// Firmware Version (YubiKey specific (?)) pub fn firmware_version(&mut self) -> Result, Error> { log::info!("OpenPgpTransaction: firmware_version"); self.send_command(commands::firmware_version(), true)? .try_into() } /// Set identity (Nitrokey Start specific (?)). /// [see: /// /// ] pub fn set_identity(&mut self, id: u8) -> Result, Error> { log::info!("OpenPgpTransaction: set_identity"); let resp = self.send_command(commands::set_identity(id), false); // Apparently it's normal to get "NotTransacted" from pcsclite when // the identity switch was successful. if let Err(Error::Smartcard(SmartcardError::NotTransacted)) = resp { Ok(vec![]) } else { resp?.try_into() } } /// SELECT DATA ("select a DO in the current template"). /// /// This command currently only applies to /// [`cardholder_certificate`](Transaction::cardholder_certificate) and /// [`set_cardholder_certificate`](Transaction::set_cardholder_certificate) /// in OpenPGP card. /// (This library leaves it up to consumers to decide on a strategy for dealing with this /// issue. Possible strategies include: /// - asking the card for its [`Transaction::firmware_version`] /// and using the workaround if version <=5.4.3 /// - trying this command first without the workaround, then with workaround if the card /// returns [`StatusBytes::IncorrectParametersCommandDataField`] /// - for read operations: using [`Transaction::next_cardholder_certificate`] /// instead of SELECT DATA) pub fn select_data(&mut self, num: u8, tag: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: select_data"); let tlv = Tlv::new( Tags::GeneralReference, Value::C(vec![Tlv::new(Tags::TagList, Value::S(tag.to_vec()))]), ); let mut data = tlv.serialize(); // YubiKey 5 up to (and including) firmware version 5.4.3 need a workaround // for this command. // // When sending the SELECT DATA command as defined in the card spec, without enabling the // workaround, bad YubiKey firmware versions (<= 5.4.3) return // `StatusBytes::IncorrectParametersCommandDataField` // // FIXME: caching for `firmware_version`? if let Ok(version) = self.firmware_version() { if version.len() == 3 && version[0] == 5 && (version[1] < 4 || (version[1] == 4 && version[2] <= 3)) { // Workaround for YubiKey 5. // This hack is needed <= 5.4.3 according to ykman sources // (see _select_certificate() in ykman/openpgp.py). assert!(data.len() <= 255); // catch blatant misuse: tags are 1-2 bytes long data.insert(0, data.len() as u8); } } let cmd = commands::select_data(num, data); // Possible response data (Control Parameter = CP) don't need to be evaluated by the // application (See "7.2.5 SELECT DATA") self.send_command(cmd, true)?.check_ok()?; Ok(()) } // --- optional private DOs (0101 - 0104) --- /// Get data from "private use" DO. /// /// `num` must be between 1 and 4. pub fn private_use_do(&mut self, num: u8) -> Result, Error> { log::info!("OpenPgpTransaction: private_use_do"); if !(1..=4).contains(&num) { return Err(Error::UnsupportedFeature(format!( "Illegal Private Use DO num '{}'", num, ))); } let cmd = commands::private_use_do(num); self.send_command(cmd, true)?.try_into() } // ---------- /// Reset all state on this OpenPGP card. /// /// Note: the "factory reset" operation is not directly offered by the /// card spec. It is implemented as a series of OpenPGP card commands: /// - send 4 bad requests to verify pw1, /// - send 4 bad requests to verify pw3, /// - terminate_df, /// - activate_file. /// /// With most cards, this sequence of operations causes the card /// to revert to a "blank" state. /// /// (However, e.g. vanilla Gnuk doesn't support this functionality. /// Gnuk needs to be built with the `--enable-factory-reset` /// option to the `configure` script to enable this functionality). pub fn factory_reset(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: factory_reset"); let mut bad_pw_len = 8; // In KDF mode, the "bad password" we try must have the correct length for the KDF hash // algorithm. Otherwise, the PIN retry counter doesn't decrement, and don't lock the card // (which means we don't get to do a reset). if let Ok(kdf_do) = self.kdf_do() { if kdf_do.hash_algo() == Some(0x08) { bad_pw_len = 0x20; } else if kdf_do.hash_algo() == Some(0x0a) { bad_pw_len = 0x40; } } let bad_pw: Vec<_> = std::iter::repeat(0x40).take(bad_pw_len).collect(); // send 4 bad requests to verify pw1 for _ in 0..4 { let resp = self.verify_pw1_sign(bad_pw.clone().into()); if !(matches!( resp, Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) | Err(Error::CardStatus(StatusBytes::AuthenticationMethodBlocked)) | Err(Error::CardStatus( StatusBytes::ExecutionErrorNonVolatileMemoryUnchanged )) | Err(Error::CardStatus(StatusBytes::PasswordNotChecked(_))) | Err(Error::CardStatus(StatusBytes::ConditionOfUseNotSatisfied)) )) { return Err(Error::InternalError( "Unexpected status for reset, at pw1.".into(), )); } } // send 4 bad requests to verify pw3 for _ in 0..4 { let resp = self.verify_pw3(bad_pw.clone().into()); if !(matches!( resp, Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) | Err(Error::CardStatus(StatusBytes::AuthenticationMethodBlocked)) | Err(Error::CardStatus( StatusBytes::ExecutionErrorNonVolatileMemoryUnchanged )) | Err(Error::CardStatus(StatusBytes::PasswordNotChecked(_))) | Err(Error::CardStatus(StatusBytes::ConditionOfUseNotSatisfied)) )) { return Err(Error::InternalError( "Unexpected status for reset, at pw3.".into(), )); } } self.terminate_df()?; self.activate_file()?; Ok(()) } // --- verify/modify --- /// Verify pw1 (user) for signing operation (mode 81). /// /// 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_sign(&mut self, pin: SecretVec) -> Result<(), Error> { log::info!("OpenPgpTransaction: verify_pw1_sign"); let cmd = commands::verify_pw1_81(pin); self.send_command(cmd, false)?.try_into() } /// Verify pw1 (user) for signing operation (mode 81) using 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_sign_pinpad(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: verify_pw1_sign_pinpad"); let cc = *self.card_caps; let res = self.tx().pinpad_verify(PinType::Sign, &cc)?; 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. /// /// (Note: /// - some cards don't correctly implement this feature, e.g. YubiKey 5 /// - some cards that don't support this instruction may decrease the pin's error count, /// eventually requiring the user to reset the pin) pub fn check_pw1_sign(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: check_pw1_sign"); let verify = commands::verify_pw1_81(vec![].into()); self.send_command(verify, false)?.try_into() } /// Verify PW1 (user). /// (For operations except signing, mode 82). pub fn verify_pw1_user(&mut self, pin: SecretVec) -> Result<(), Error> { log::info!("OpenPgpTransaction: verify_pw1_user"); let verify = commands::verify_pw1_82(pin); self.send_command(verify, false)?.try_into() } /// Verify PW1 (user) for operations except signing (mode 82), /// using a pinpad on the card reader. If no usable pinpad is found, /// an error is returned. pub fn verify_pw1_user_pinpad(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: verify_pw1_user_pinpad"); let cc = *self.card_caps; let res = self.tx().pinpad_verify(PinType::User, &cc)?; RawResponse::try_from(res)?.try_into() } /// Check the current access of PW1. /// (For operations except signing, mode 82). /// /// If verification is not required, an empty Ok Response is returned. /// /// (Note: /// - some cards don't correctly implement this feature, e.g. YubiKey 5 /// - some cards that don't support this instruction may decrease the pin's error count, /// eventually requiring the user to reset the pin) pub fn check_pw1_user(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: check_pw1_user"); let verify = commands::verify_pw1_82(vec![].into()); self.send_command(verify, false)?.try_into() } /// Verify PW3 (admin). pub fn verify_pw3(&mut self, pin: SecretVec) -> Result<(), Error> { log::info!("OpenPgpTransaction: verify_pw3"); let verify = commands::verify_pw3(pin); self.send_command(verify, false)?.try_into() } /// Verify PW3 (admin) using a pinpad on the card reader. If no usable /// pinpad is found, an error is returned. pub fn verify_pw3_pinpad(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: verify_pw3_pinpad"); let cc = *self.card_caps; let res = self.tx().pinpad_verify(PinType::Admin, &cc)?; RawResponse::try_from(res)?.try_into() } /// Check the current access of PW3 (admin). /// /// If verification is not required, an empty Ok Response is returned. /// /// (Note: /// - some cards don't correctly implement this feature, e.g. YubiKey 5 /// - some cards that don't support this instruction may decrease the pin's error count, /// eventually requiring the user to factory reset the card) pub fn check_pw3(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: check_pw3"); let verify = commands::verify_pw3(vec![].into()); self.send_command(verify, false)?.try_into() } /// Change the value of PW1 (user password). /// /// The current value of PW1 must be presented in `old` for authorization. pub fn change_pw1(&mut self, old: SecretVec, new: SecretVec) -> Result<(), Error> { log::info!("OpenPgpTransaction: change_pw1"); let mut data = vec![]; data.extend(old.expose_secret()); data.extend(new.expose_secret()); let change = commands::change_pw1(data.into()); self.send_command(change, false)?.try_into() } /// Change the value of PW1 (0x81) using a pinpad on the /// card reader. If no usable pinpad is found, an error is returned. pub fn change_pw1_pinpad(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: change_pw1_pinpad"); let cc = *self.card_caps; // Note: for change PW, only 0x81 and 0x83 are used! // 0x82 is implicitly the same as 0x81. let res = self.tx().pinpad_modify(PinType::Sign, &cc)?; 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. pub fn change_pw3(&mut self, old: SecretVec, new: SecretVec) -> Result<(), Error> { log::info!("OpenPgpTransaction: change_pw3"); let mut data = vec![]; data.extend(old.expose_secret()); data.extend(new.expose_secret()); let change = commands::change_pw3(data.into()); self.send_command(change, false)?.try_into() } /// Change the value of PW3 (admin password) using a pinpad on the /// card reader. If no usable pinpad is found, an error is returned. pub fn change_pw3_pinpad(&mut self) -> Result<(), Error> { log::info!("OpenPgpTransaction: change_pw3_pinpad"); let cc = *self.card_caps; let res = self.tx().pinpad_modify(PinType::Admin, &cc)?; RawResponse::try_from(res)?.try_into() } /// Reset the error counter for PW1 (user password) and set a new value /// for PW1. /// /// For authorization, either: /// - PW3 must have been verified previously, /// - secure messaging must be currently used, /// - the resetting_code must be presented. pub fn reset_retry_counter_pw1( &mut self, new_pw1: SecretVec, resetting_code: Option>, ) -> Result<(), Error> { log::info!("OpenPgpTransaction: reset_retry_counter_pw1"); let cmd = commands::reset_retry_counter_pw1(resetting_code, new_pw1); self.send_command(cmd, false)?.try_into() } // --- decrypt --- /// Decrypt the ciphertext in `dm`, on the card. /// /// (This is a wrapper around the low-level pso_decipher /// operation, it builds the required `data` field from `dm`) pub fn decipher(&mut self, dm: Cryptogram) -> Result, Error> { match dm { Cryptogram::RSA(message) => { // "Padding indicator byte (00) for RSA" (pg. 69) let mut data = vec![0x0]; data.extend_from_slice(message); // Call the card to decrypt `data` self.pso_decipher(data) } Cryptogram::ECDH(eph) => { // "In case of ECDH the card supports a partial decrypt // only. The input is a cipher DO with the following data:" // A6 xx Cipher DO // -> 7F49 xx Public Key DO // -> 86 xx External Public Key // External Public Key let epk = Tlv::new(Tags::ExternalPublicKey, Value::S(eph.to_vec())); // Public Key DO let pkdo = Tlv::new(Tags::PublicKey, Value::C(vec![epk])); // Cipher DO let cdo = Tlv::new(Tags::Cipher, Value::C(vec![pkdo])); self.pso_decipher(cdo.serialize()) } } } /// Run decryption operation on the smartcard (low level operation) /// (7.2.11 PSO: DECIPHER) /// /// (consider using the [`Self::decipher`] method if you don't want to create /// the data field manually) pub fn pso_decipher(&mut self, data: Vec) -> Result, Error> { log::info!("OpenPgpTransaction: pso_decipher"); // The OpenPGP card is already connected and PW1 82 has been verified let dec_cmd = commands::decryption(data); let resp = self.send_command(dec_cmd, true)?; Ok(resp.data()?.to_vec()) } /// Set the key to be used for the pso_decipher and the internal_authenticate commands. /// /// Valid until next reset of of the card or the next call to `select` /// The only keys that can be configured by this command are the `Decryption` and `Authentication` keys. /// /// The following first sets the *Authentication* key to be used for [`Self::pso_decipher`] /// and then sets the *Decryption* key to be used for [`Self::internal_authenticate`]. /// /// ```no_run /// # use openpgp_card::ocard::{KeyType, Transaction}; /// # let mut tx: Transaction<'static> = panic!(); /// tx.manage_security_environment(KeyType::Decryption, KeyType::Authentication)?; /// tx.manage_security_environment(KeyType::Authentication, KeyType::Decryption)?; /// # Result::<(), openpgp_card::Error>::Ok(()) /// ``` pub fn manage_security_environment( &mut self, for_operation: KeyType, key_ref: KeyType, ) -> Result<(), Error> { log::info!("OpenPgpTransaction: manage_security_environment"); if !matches!(for_operation, KeyType::Authentication | KeyType::Decryption) || !matches!(key_ref, KeyType::Authentication | KeyType::Decryption) { return Err(Error::UnsupportedAlgo("Only Decryption and Authentication keys can be manipulated by manage_security_environment".to_string())); } let cmd = commands::manage_security_environment(for_operation, key_ref); let resp = self.send_command(cmd, false)?; resp.check_ok()?; Ok(()) } // --- sign --- /// Sign `hash`, on the card. /// /// This is a wrapper around the low-level /// pso_compute_digital_signature operation. /// It builds the required `data` field from `hash`. /// /// For RSA, this means a "DigestInfo" data structure is generated. /// (see 7.2.10.2 DigestInfo for RSA). /// /// With ECC the hash data is processed as is, using /// [`Self::pso_compute_digital_signature`]. pub fn signature_for_hash(&mut self, hash: Hash) -> Result, Error> { self.pso_compute_digital_signature(digestinfo(hash)) } /// Run signing operation on the smartcard (low level operation) /// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) /// /// (consider using the [`Self::signature_for_hash`] method if you don't /// want to create the data field manually) pub fn pso_compute_digital_signature(&mut self, data: Vec) -> Result, Error> { log::info!("OpenPgpTransaction: pso_compute_digital_signature"); let cds_cmd = commands::signature(data); let resp = self.send_command(cds_cmd, true)?; Ok(resp.data().map(|d| d.to_vec())?) } // --- internal authenticate --- /// Auth-sign `hash`, on the card. /// /// This is a wrapper around the low-level /// internal_authenticate operation. /// It builds the required `data` field from `hash`. /// /// For RSA, this means a "DigestInfo" data structure is generated. /// (see 7.2.10.2 DigestInfo for RSA). /// /// With ECC the hash data is processed as is. pub fn authenticate_for_hash(&mut self, hash: Hash) -> Result, Error> { self.internal_authenticate(digestinfo(hash)) } /// Run signing operation on the smartcard (low level operation) /// (7.2.13 INTERNAL AUTHENTICATE) /// /// (consider using the `authenticate_for_hash()` method if you don't /// want to create the data field manually) pub fn internal_authenticate(&mut self, data: Vec) -> Result, Error> { log::info!("OpenPgpTransaction: internal_authenticate"); let ia_cmd = commands::internal_authenticate(data); let resp = self.send_command(ia_cmd, true)?; Ok(resp.data().map(|d| d.to_vec())?) } // --- PUT DO --- /// Set data of "private use" DO. /// /// `num` must be between 1 and 4. /// /// Access condition: /// - 1/3 need PW1 (82) /// - 2/4 need PW3 pub fn set_private_use_do(&mut self, num: u8, data: Vec) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_private_use_do"); if !(1..=4).contains(&num) { return Err(Error::UnsupportedFeature(format!( "Illegal Private Use DO num '{}'", num, ))); } let cmd = commands::put_private_use_do(num, data); self.send_command(cmd, true)?.try_into() } pub fn set_login(&mut self, login: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_login"); let cmd = commands::put_login_data(login.to_vec()); self.send_command(cmd, false)?.try_into() } pub fn set_name(&mut self, name: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_name"); let cmd = commands::put_name(name.to_vec()); self.send_command(cmd, false)?.try_into() } pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_lang"); let bytes: Vec<_> = lang.iter().flat_map(|&l| Vec::::from(l)).collect(); let cmd = commands::put_lang(bytes); self.send_command(cmd, false)?.try_into() } pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_sex"); let cmd = commands::put_sex((&sex).into()); self.send_command(cmd, false)?.try_into() } pub fn set_url(&mut self, url: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_url"); let cmd = commands::put_url(url.to_vec()); self.send_command(cmd, false)?.try_into() } /// Set cardholder certificate (for AUT, DEC or SIG). /// /// Call select_data() before calling this fn to select a particular /// certificate (if the card supports multiple certificates). pub fn set_cardholder_certificate(&mut self, data: Vec) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_cardholder_certificate"); let cmd = commands::put_cardholder_certificate(data); self.send_command(cmd, false)?.try_into() } /// Set algorithm attributes for a key slot (4.4.3.9 Algorithm Attributes) /// /// Note: `algorithm_attributes` needs to precisely specify the /// RSA bit-size of e (if applicable), and import format, with values /// that the current card supports. pub fn set_algorithm_attributes( &mut self, key_type: KeyType, algorithm_attributes: &AlgorithmAttributes, ) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_algorithm_attributes"); // Don't set algorithm if the feature is not available? let ecap = self.extended_capabilities()?; if !ecap.algo_attrs_changeable() { // Don't change the algorithm attributes, if the card doesn't support change // FIXME: Compare current and requested setting and return an error, if they differ? return Ok(()); } // Command to PUT the algorithm attributes let cmd = commands::put_data( key_type.algorithm_tag(), algorithm_attributes.to_data_object()?, ); self.send_command(cmd, false)?.try_into() } /// Set PW Status Bytes. /// /// If `long` is false, send 1 byte to the card, otherwise 4. /// According to the spec, length information should not be changed. /// /// So, effectively, with 'long == false' the setting `pw1_cds_multi` /// can be changed. /// With 'long == true', the settings `pw1_pin_block` and `pw3_pin_block` /// can also be changed. /// /// (See OpenPGP card spec, pg. 28) pub fn set_pw_status_bytes( &mut self, pw_status: &PWStatusBytes, long: bool, ) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_pw_status_bytes"); let data = pw_status.serialize_for_put(long); let cmd = commands::put_pw_status(data); self.send_command(cmd, false)?.try_into() } pub fn set_fingerprint(&mut self, fp: Fingerprint, key_type: KeyType) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_fingerprint"); let cmd = commands::put_data(key_type.fingerprint_put_tag(), fp.as_bytes().to_vec()); self.send_command(cmd, false)?.try_into() } pub fn set_ca_fingerprint_1(&mut self, fp: Fingerprint) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_ca_fingerprint_1"); let cmd = commands::put_data(Tags::CaFingerprint1, fp.as_bytes().to_vec()); self.send_command(cmd, false)?.try_into() } pub fn set_ca_fingerprint_2(&mut self, fp: Fingerprint) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_ca_fingerprint_2"); let cmd = commands::put_data(Tags::CaFingerprint2, fp.as_bytes().to_vec()); self.send_command(cmd, false)?.try_into() } pub fn set_ca_fingerprint_3(&mut self, fp: Fingerprint) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_ca_fingerprint_3"); let cmd = commands::put_data(Tags::CaFingerprint3, fp.as_bytes().to_vec()); self.send_command(cmd, false)?.try_into() } pub fn set_creation_time( &mut self, time: KeyGenerationTime, key_type: KeyType, ) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_creation_time"); // Timestamp update let time_value: Vec = time.get().to_be_bytes().to_vec(); let cmd = commands::put_data(key_type.timestamp_put_tag(), time_value); self.send_command(cmd, false)?.try_into() } // FIXME: optional DO SM-Key-ENC // FIXME: optional DO SM-Key-MAC /// Set resetting code /// (4.3.4 Resetting Code) pub fn set_resetting_code(&mut self, resetting_code: SecretVec) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_resetting_code"); let cmd = commands::put_data(Tags::ResettingCode, resetting_code); self.send_command(cmd, false)?.try_into() } /// Set AES key for symmetric decryption/encryption operations. /// /// Optional DO (announced in Extended Capabilities) for /// PSO:ENC/DEC with AES (32 bytes dec. in case of /// AES256, 16 bytes dec. in case of AES128). pub fn set_pso_enc_dec_key(&mut self, key: &[u8]) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_pso_enc_dec_key"); let cmd = commands::put_data(Tags::PsoEncDecKey, key.to_vec()); self.send_command(cmd, false)?.try_into() } /// Set UIF for PSO:CDS pub fn set_uif_pso_cds(&mut self, uif: &UserInteractionFlag) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_uif_pso_cds"); let cmd = commands::put_data(Tags::UifSig, uif.as_bytes().to_vec()); self.send_command(cmd, false)?.try_into() } /// Set UIF for PSO:DEC pub fn set_uif_pso_dec(&mut self, uif: &UserInteractionFlag) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_uif_pso_dec"); let cmd = commands::put_data(Tags::UifDec, uif.as_bytes().to_vec()); self.send_command(cmd, false)?.try_into() } /// Set UIF for PSO:AUT pub fn set_uif_pso_aut(&mut self, uif: &UserInteractionFlag) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_uif_pso_aut"); let cmd = commands::put_data(Tags::UifAuth, uif.as_bytes().to_vec()); self.send_command(cmd, false)?.try_into() } /// Set UIF for Attestation key pub fn set_uif_attestation(&mut self, uif: &UserInteractionFlag) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_uif_attestation"); let cmd = commands::put_data(Tags::UifAttestation, uif.as_bytes().to_vec()); self.send_command(cmd, false)?.try_into() } /// Generate Attestation (Yubico) pub fn generate_attestation(&mut self, key_type: KeyType) -> Result<(), Error> { log::info!("OpenPgpTransaction: generate_attestation"); let key = match key_type { KeyType::Signing => 0x01, KeyType::Decryption => 0x02, KeyType::Authentication => 0x03, _ => return Err(Error::InternalError("Unexpected KeyType".to_string())), }; let cmd = commands::generate_attestation(key); self.send_command(cmd, false)?.try_into() } // FIXME: Attestation key algo attr, FP, CA-FP, creation time // FIXME: SM keys (ENC and MAC) with Tags D1 and D2 /// Set KDF DO attributes pub fn set_kdf_do(&mut self, kdf_do: &KdfDo) -> Result<(), Error> { log::info!("OpenPgpTransaction: set_kdf_do"); let cmd = commands::put_data(Tags::KdfDo, kdf_do.serialize()); self.send_command(cmd, false)?.try_into() } // FIXME: certificate used with secure messaging // FIXME: Attestation Certificate (Yubico) // ----------------- /// Import an existing private key to the card. /// (This implicitly sets the algorithm attributes, fingerprint and timestamp) pub fn key_import( &mut self, key: Box, key_type: KeyType, ) -> Result<(), Error> { keys::key_import(self, key, key_type) } /// Generate a key on the card. /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) pub fn generate_key( &mut self, fp_from_pub: fn( &PublicKeyMaterial, KeyGenerationTime, KeyType, ) -> Result, key_type: KeyType, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { // get current (possibly updated) state of algorithm_attributes let ard = self.application_related_data()?; // no caching, here! let cur_algo = ard.algorithm_attributes(key_type)?; keys::gen_key_set_metadata(self, fp_from_pub, &cur_algo, key_type) } /// Get public key material from the card. /// /// Note: this fn returns a set of raw public key data (not an /// OpenPGP data structure). /// /// Note also that the information from the card is insufficient to /// reconstruct a pre-existing OpenPGP public key that corresponds to /// the private key on the card. pub fn public_key(&mut self, key_type: KeyType) -> Result { keys::public_key(self, key_type) } } fn digestinfo(hash: Hash) -> Vec { match hash { Hash::SHA1(_) | Hash::SHA256(_) | Hash::SHA384(_) | Hash::SHA512(_) => { let tlv = Tlv::new( Tags::Sequence, Value::C(vec![ Tlv::new( Tags::Sequence, Value::C(vec![ Tlv::new( Tags::ObjectIdentifier, // unwrapping is ok, for SHA* Value::S(hash.oid().unwrap().to_vec()), ), Tlv::new(Tags::Null, Value::S(vec![])), ]), ), Tlv::new(Tags::OctetString, Value::S(hash.digest().to_vec())), ]), ); tlv.serialize() } Hash::EdDSA(d) => d.to_vec(), Hash::ECDSA(d) => d.to_vec(), } } /// OpenPGP card "Status Bytes" (ok statuses and errors) #[derive(thiserror::Error, Debug, PartialEq, Eq, Copy, Clone)] #[non_exhaustive] pub enum StatusBytes { #[error("Command correct")] Ok, #[error("Command correct, [{0}] bytes available in response")] OkBytesAvailable(u8), #[error("Selected file or DO in termination state")] TerminationState, #[error("Password not checked, {0} allowed retries")] PasswordNotChecked(u8), #[error("Execution error with non-volatile memory unchanged")] ExecutionErrorNonVolatileMemoryUnchanged, #[error("Triggering by the card {0}")] TriggeringByCard(u8), #[error("Memory failure")] MemoryFailure, #[error("Security-related issues (reserved for UIF in this application)")] SecurityRelatedIssues, #[error("Wrong length (Lc and/or Le)")] WrongLength, #[error("Logical channel not supported")] LogicalChannelNotSupported, #[error("Secure messaging not supported")] SecureMessagingNotSupported, #[error("Last command of the chain expected")] LastCommandOfChainExpected, #[error("Command chaining not supported")] CommandChainingNotSupported, #[error("Security status not satisfied")] SecurityStatusNotSatisfied, #[error("Authentication method blocked")] AuthenticationMethodBlocked, #[error("Condition of use not satisfied")] ConditionOfUseNotSatisfied, #[error("Expected secure messaging DOs missing (e. g. SM-key)")] ExpectedSecureMessagingDOsMissing, #[error("SM data objects incorrect (e. g. wrong TLV-structure in command data)")] SMDataObjectsIncorrect, #[error("Incorrect parameters in the command data field")] IncorrectParametersCommandDataField, #[error("File or application not found")] FileOrApplicationNotFound, #[error("Referenced data, reference data or DO not found")] ReferencedDataNotFound, #[error("Wrong parameters P1-P2")] WrongParametersP1P2, #[error("Instruction code (INS) not supported or invalid")] INSNotSupported, #[error("Class (CLA) not supported")] CLANotSupported, #[error("No precise diagnosis")] NoPreciseDiagnosis, #[error("Unknown OpenPGP card status: [{0:x}, {1:x}]")] UnknownStatus(u8, u8), } impl From<(u8, u8)> for StatusBytes { fn from(status: (u8, u8)) -> Self { match (status.0, status.1) { (0x90, 0x00) => StatusBytes::Ok, (0x61, bytes) => StatusBytes::OkBytesAvailable(bytes), (0x62, 0x85) => StatusBytes::TerminationState, (0x63, 0xC0..=0xCF) => StatusBytes::PasswordNotChecked(status.1 & 0xf), (0x64, 0x00) => StatusBytes::ExecutionErrorNonVolatileMemoryUnchanged, (0x64, 0x02..=0x80) => StatusBytes::TriggeringByCard(status.1), (0x65, 0x01) => StatusBytes::MemoryFailure, (0x66, 0x00) => StatusBytes::SecurityRelatedIssues, (0x67, 0x00) => StatusBytes::WrongLength, (0x68, 0x81) => StatusBytes::LogicalChannelNotSupported, (0x68, 0x82) => StatusBytes::SecureMessagingNotSupported, (0x68, 0x83) => StatusBytes::LastCommandOfChainExpected, (0x68, 0x84) => StatusBytes::CommandChainingNotSupported, (0x69, 0x82) => StatusBytes::SecurityStatusNotSatisfied, (0x69, 0x83) => StatusBytes::AuthenticationMethodBlocked, (0x69, 0x85) => StatusBytes::ConditionOfUseNotSatisfied, (0x69, 0x87) => StatusBytes::ExpectedSecureMessagingDOsMissing, (0x69, 0x88) => StatusBytes::SMDataObjectsIncorrect, (0x6A, 0x80) => StatusBytes::IncorrectParametersCommandDataField, (0x6A, 0x82) => StatusBytes::FileOrApplicationNotFound, (0x6A, 0x88) => StatusBytes::ReferencedDataNotFound, (0x6B, 0x00) => StatusBytes::WrongParametersP1P2, (0x6D, 0x00) => StatusBytes::INSNotSupported, (0x6E, 0x00) => StatusBytes::CLANotSupported, (0x6F, 0x00) => StatusBytes::NoPreciseDiagnosis, _ => StatusBytes::UnknownStatus(status.0, status.1), } } } openpgp-card-0.5.0/src/ocard/oid.rs000064400000000000000000000027271046102023000152150ustar 00000000000000// SPDX-FileCopyrightText: 2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! OID constants pub(crate) const NIST_P256R1: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]; pub(crate) const NIST_P384R1: &[u8] = &[0x2B, 0x81, 0x04, 0x00, 0x22]; pub(crate) const NIST_P521R1: &[u8] = &[0x2B, 0x81, 0x04, 0x00, 0x23]; pub(crate) const BRAINPOOL_P256R1: &[u8] = &[0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07]; pub(crate) const BRAINPOOL_P384R1: &[u8] = &[0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0b]; pub(crate) const BRAINPOOL_P512R1: &[u8] = &[0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0d]; pub(crate) const SECP256K1: &[u8] = &[0x2B, 0x81, 0x04, 0x00, 0x0A]; pub(crate) const ED25519: &[u8] = &[0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01]; pub(crate) const CV25519: &[u8] = &[0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01]; pub(crate) const ED448: &[u8] = &[0x2b, 0x65, 0x71]; pub(crate) const X448: &[u8] = &[0x2b, 0x65, 0x6f]; // Values taken from https://www.gnupg.org/ftp/specs/OpenPGP-smart-card-application-2.1.pdf // section "7.2.8.2 DigestInfo for RSA" pub(crate) const SHA1: &[u8] = &[0x2b, 0x0e, 0x03, 0x02, 0x1a]; pub(crate) const SHA256: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]; pub(crate) const SHA384: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02]; pub(crate) const SHA512: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03]; openpgp-card-0.5.0/src/ocard/tags.rs000064400000000000000000000206411046102023000153730ustar 00000000000000// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::ocard::tlv::tag::Tag; /// Tags, as specified and used in the OpenPGP card 3.4.1 spec. /// All tags in OpenPGP card are either 1 or 2 bytes long. #[derive(Debug, Clone, Copy, Eq, PartialEq)] #[allow(dead_code)] pub(crate) enum Tags { // BER identifiers OctetString, Null, ObjectIdentifier, Sequence, // GET DATA PrivateUse1, PrivateUse2, PrivateUse3, PrivateUse4, ApplicationIdentifier, LoginData, Url, HistoricalBytes, CardholderRelatedData, Name, LanguagePref, Sex, ApplicationRelatedData, ExtendedLengthInformation, GeneralFeatureManagement, DiscretionaryDataObjects, ExtendedCapabilities, AlgorithmAttributesSignature, AlgorithmAttributesDecryption, AlgorithmAttributesAuthentication, PWStatusBytes, Fingerprints, CaFingerprints, GenerationTimes, KeyInformation, UifSig, UifDec, UifAuth, UifAttestation, SecuritySupportTemplate, DigitalSignatureCounter, CardholderCertificate, AlgorithmAttributesAttestation, FingerprintAttestation, CaFingerprintAttestation, GenerationTimeAttestation, KdfDo, AlgorithmInformation, CertificateSecureMessaging, AttestationCertificate, // PUT DATA (additional Tags that don't get used for GET DATA) FingerprintSignature, FingerprintDecryption, FingerprintAuthentication, CaFingerprint1, CaFingerprint2, CaFingerprint3, GenerationTimeSignature, GenerationTimeDecryption, GenerationTimeAuthentication, // FIXME: +D1, D2 ResettingCode, PsoEncDecKey, // OTHER // 4.4.3.12 Private Key Template ExtendedHeaderList, CardholderPrivateKeyTemplate, ConcatenatedKeyData, CrtKeySignature, CrtKeyConfidentiality, CrtKeyAuthentication, PrivateKeyDataRsaPublicExponent, PrivateKeyDataRsaPrime1, PrivateKeyDataRsaPrime2, PrivateKeyDataRsaPq, PrivateKeyDataRsaDp1, PrivateKeyDataRsaDq1, PrivateKeyDataRsaModulus, PrivateKeyDataEccPrivateKey, PrivateKeyDataEccPublicKey, // 7.2.14 GENERATE ASYMMETRIC KEY PAIR PublicKey, PublicKeyDataRsaModulus, PublicKeyDataRsaExponent, PublicKeyDataEccPoint, // 7.2.11 PSO: DECIPHER Cipher, ExternalPublicKey, // 7.2.5 SELECT DATA GeneralReference, TagList, } impl From for Vec { fn from(t: Tags) -> Self { ShortTag::from(t).into() } } impl From for Tag { fn from(t: Tags) -> Self { ShortTag::from(t).into() } } impl From for ShortTag { fn from(t: Tags) -> Self { match t { // BER identifiers https://en.wikipedia.org/wiki/X.690#BER_encoding Tags::OctetString => [0x04].into(), Tags::Null => [0x05].into(), Tags::ObjectIdentifier => [0x06].into(), Tags::Sequence => [0x30].into(), // GET DATA Tags::PrivateUse1 => [0x01, 0x01].into(), Tags::PrivateUse2 => [0x01, 0x02].into(), Tags::PrivateUse3 => [0x01, 0x03].into(), Tags::PrivateUse4 => [0x01, 0x04].into(), Tags::ApplicationIdentifier => [0x4f].into(), Tags::LoginData => [0x5e].into(), Tags::Url => [0x5f, 0x50].into(), Tags::HistoricalBytes => [0x5f, 0x52].into(), Tags::CardholderRelatedData => [0x65].into(), Tags::Name => [0x5b].into(), Tags::LanguagePref => [0x5f, 0x2d].into(), Tags::Sex => [0x5f, 0x35].into(), Tags::ApplicationRelatedData => [0x6e].into(), Tags::ExtendedLengthInformation => [0x7f, 0x66].into(), Tags::GeneralFeatureManagement => [0x7f, 0x74].into(), Tags::DiscretionaryDataObjects => [0x73].into(), Tags::ExtendedCapabilities => [0xc0].into(), Tags::AlgorithmAttributesSignature => [0xc1].into(), Tags::AlgorithmAttributesDecryption => [0xc2].into(), Tags::AlgorithmAttributesAuthentication => [0xc3].into(), Tags::PWStatusBytes => [0xc4].into(), Tags::Fingerprints => [0xc5].into(), Tags::CaFingerprints => [0xc6].into(), Tags::GenerationTimes => [0xcd].into(), Tags::KeyInformation => [0xde].into(), Tags::UifSig => [0xd6].into(), Tags::UifDec => [0xd7].into(), Tags::UifAuth => [0xd8].into(), Tags::UifAttestation => [0xd9].into(), Tags::SecuritySupportTemplate => [0x7a].into(), Tags::DigitalSignatureCounter => [0x93].into(), Tags::CardholderCertificate => [0x7f, 0x21].into(), Tags::AlgorithmAttributesAttestation => [0xda].into(), Tags::FingerprintAttestation => [0xdb].into(), Tags::CaFingerprintAttestation => [0xdc].into(), Tags::GenerationTimeAttestation => [0xdd].into(), Tags::KdfDo => [0xf9].into(), Tags::AlgorithmInformation => [0xfa].into(), Tags::CertificateSecureMessaging => [0xfb].into(), Tags::AttestationCertificate => [0xfc].into(), // PUT DATA Tags::FingerprintSignature => [0xc7].into(), Tags::FingerprintDecryption => [0xc8].into(), Tags::FingerprintAuthentication => [0xc9].into(), Tags::CaFingerprint1 => [0xca].into(), Tags::CaFingerprint2 => [0xcb].into(), Tags::CaFingerprint3 => [0xcc].into(), Tags::GenerationTimeSignature => [0xce].into(), Tags::GenerationTimeDecryption => [0xcf].into(), Tags::GenerationTimeAuthentication => [0xd0].into(), Tags::ResettingCode => [0xd3].into(), Tags::PsoEncDecKey => [0xd5].into(), // OTHER // 4.4.3.12 Private Key Template Tags::ExtendedHeaderList => [0x4d].into(), Tags::CardholderPrivateKeyTemplate => [0x7f, 0x48].into(), Tags::ConcatenatedKeyData => [0x5f, 0x48].into(), Tags::CrtKeySignature => [0xb6].into(), Tags::CrtKeyConfidentiality => [0xb8].into(), Tags::CrtKeyAuthentication => [0xa4].into(), Tags::PrivateKeyDataRsaPublicExponent => [0x91].into(), Tags::PrivateKeyDataRsaPrime1 => [0x92].into(), // Note: value reused! Tags::PrivateKeyDataRsaPrime2 => [0x93].into(), Tags::PrivateKeyDataRsaPq => [0x94].into(), Tags::PrivateKeyDataRsaDp1 => [0x95].into(), Tags::PrivateKeyDataRsaDq1 => [0x96].into(), Tags::PrivateKeyDataRsaModulus => [0x97].into(), Tags::PrivateKeyDataEccPrivateKey => [0x92].into(), // Note: value reused! Tags::PrivateKeyDataEccPublicKey => [0x99].into(), // 7.2.14 GENERATE ASYMMETRIC KEY PAIR Tags::PublicKey => [0x7f, 0x49].into(), Tags::PublicKeyDataRsaModulus => [0x81].into(), Tags::PublicKeyDataRsaExponent => [0x82].into(), Tags::PublicKeyDataEccPoint => [0x86].into(), // 7.2.11 PSO: DECIPHER Tags::Cipher => [0xa6].into(), Tags::ExternalPublicKey => [0x86].into(), // 7.2.5 SELECT DATA Tags::GeneralReference => [0x60].into(), Tags::TagList => [0x5c].into(), } } } /// A ShortTag is a Tlv tag that is guaranteed to be either 1 or 2 bytes long. /// /// This covers any tag that can be used in the OpenPGP card context (the spec doesn't describe how /// longer tags might be used.) /// /// (The type tlv::Tag will usually/always contain 1 or 2 byte long tags, in this library. /// But its length is not guaranteed by the type system) #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub(crate) enum ShortTag { One(u8), Two(u8, u8), } impl From for Tag { fn from(n: ShortTag) -> Self { match n { ShortTag::One(t0) => [t0].into(), ShortTag::Two(t0, t1) => [t0, t1].into(), } } } impl From<[u8; 1]> for ShortTag { fn from(v: [u8; 1]) -> Self { ShortTag::One(v[0]) } } impl From<[u8; 2]> for ShortTag { fn from(v: [u8; 2]) -> Self { ShortTag::Two(v[0], v[1]) } } impl From for Vec { fn from(t: ShortTag) -> Self { match t { ShortTag::One(t0) => vec![t0], ShortTag::Two(t0, t1) => vec![t0, t1], } } } openpgp-card-0.5.0/src/ocard/tlv/length.rs000064400000000000000000000021121046102023000165140ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Length in a TLV data structure /// Helper fn to encode length fields in TLV structures (see spec 4.4.4) pub(crate) fn tlv_encode_length(len: u16) -> Vec { if len > 255 { vec![0x82, (len >> 8) as u8, (len & 255) as u8] } else if len > 127 { vec![0x81, len as u8] } else { vec![len as u8] } } use nom::{branch, bytes::complete as bytes, combinator, number::complete as number, sequence}; fn length1(input: &[u8]) -> nom::IResult<&[u8], u8> { combinator::verify(number::u8, |&c| c < 0x80)(input) } fn length2(input: &[u8]) -> nom::IResult<&[u8], u8> { sequence::preceded(bytes::tag(&[0x81]), number::u8)(input) } fn length3(input: &[u8]) -> nom::IResult<&[u8], u16> { sequence::preceded(bytes::tag(&[0x82]), number::be_u16)(input) } pub(crate) fn length(input: &[u8]) -> nom::IResult<&[u8], u16> { branch::alt(( combinator::into(length1), combinator::into(length2), length3, ))(input) } openpgp-card-0.5.0/src/ocard/tlv/tag.rs000064400000000000000000000066361046102023000160250ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Tag in a TLV data structure. //! //! A Tag can span multiple octets, in the OpenPGP card context, only Tags spanning 1 or 2 octets //! should come up. However, this type can deal with arbitrary length Tags. //! //! (The `ShortTag` type models tags that are exactly 1 or 2 octets long) //! //! use nom::{branch, bytes::complete as bytes, combinator, number::complete as number, sequence}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Tag(Vec); impl Tag { pub fn is_constructed(&self) -> bool { if self.0.is_empty() { false } else { self.0[0] & 0x20 != 0 } } pub fn get(&self) -> &[u8] { &self.0 } } impl From<&[u8]> for Tag { fn from(t: &[u8]) -> Self { Tag(t.to_vec()) } } impl From<[u8; 1]> for Tag { fn from(t: [u8; 1]) -> Self { Tag(t.to_vec()) } } impl From<[u8; 2]> for Tag { fn from(t: [u8; 2]) -> Self { Tag(t.to_vec()) } } fn multi_byte_tag(input: &[u8]) -> nom::IResult<&[u8], &[u8]> { combinator::recognize(sequence::pair(multi_byte_tag_first, multi_byte_tag_rest))(input) } fn multi_byte_tag_first(input: &[u8]) -> nom::IResult<&[u8], u8> { combinator::verify(number::u8, is_multi_byte_tag_first)(input) } fn is_multi_byte_tag_first(c: &u8) -> bool { c.trailing_ones() >= 5 } fn multi_byte_tag_rest(input: &[u8]) -> nom::IResult<&[u8], &[u8]> { fn is_first(c: &u8) -> bool { c.trailing_zeros() < 7 } fn is_last(c: &u8) -> bool { c.leading_ones() == 0 } fn single_byte_rest(input: &[u8]) -> nom::IResult<&[u8], &[u8]> { combinator::verify(bytes::take(1u8), |c: &[u8]| { c.len() == 1 && is_first(&c[0]) && is_last(&c[0]) })(input) } fn multi_byte_rest(input: &[u8]) -> nom::IResult<&[u8], &[u8]> { combinator::recognize(sequence::tuple(( combinator::verify(number::u8, |c| is_first(c) && !is_last(c)), bytes::take_while(|c| !is_last(&c)), combinator::verify(number::u8, is_last), )))(input) } branch::alt((single_byte_rest, multi_byte_rest))(input) } fn single_byte_tag(input: &[u8]) -> nom::IResult<&[u8], &[u8]> { combinator::verify(bytes::take(1u8), |c: &[u8]| { c.len() == 1 && !is_multi_byte_tag_first(&c[0]) })(input) } pub(crate) fn tag(input: &[u8]) -> nom::IResult<&[u8], Tag> { combinator::map(branch::alt((multi_byte_tag, single_byte_tag)), Tag::from)(input) } #[cfg(test)] mod test { use super::tag; #[test] fn test_tag() { let (_, t) = tag(&[0x0f]).unwrap(); assert_eq!(t.0, &[0x0f]); let (_, t) = tag(&[0x0f, 0x4f]).unwrap(); assert_eq!(t.0, &[0x0f]); let (_, t) = tag(&[0x4f]).unwrap(); assert_eq!(t.0, &[0x4f]); let (_, t) = tag(&[0x5f, 0x1f]).unwrap(); assert_eq!(t.0, &[0x5f, 0x1f]); let (_, t) = tag(&[0x5f, 0x2d]).unwrap(); assert_eq!(t.0, &[0x5f, 0x2d]); let (_, t) = tag(&[0x5f, 0x35]).unwrap(); assert_eq!(t.0, &[0x5f, 0x35]); let (_, t) = tag(&[0x5f, 0x35, 0x35]).unwrap(); assert_eq!(t.0, &[0x5f, 0x35]); let (_, t) = tag(&[0x5f, 0x35, 0x2d]).unwrap(); assert_eq!(t.0, &[0x5f, 0x35]); assert!(tag(&[0x5f]).is_err()); } } openpgp-card-0.5.0/src/ocard/tlv/value.rs000064400000000000000000000026171046102023000163610ustar 00000000000000// SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Value in a TLV data structure use crate::ocard::data::complete; use crate::ocard::tlv::Tlv; /// A TLV "value" #[derive(Debug, Eq, PartialEq)] pub enum Value { /// A "constructed" value, consisting of a list of Tlv C(Vec), /// A "simple" value, consisting of binary data S(Vec), } impl Value { pub(crate) fn parse(data: &[u8], constructed: bool) -> nom::IResult<&[u8], Self> { match constructed { false => Ok((&[], Value::S(data.to_vec()))), true => { let mut c = vec![]; let mut input = data; while !input.is_empty() { let (rest, tlv) = Tlv::parse(input)?; input = rest; c.push(tlv); } Ok((&[], Value::C(c))) } } } pub fn from(data: &[u8], constructed: bool) -> Result { complete(Self::parse(data, constructed)) } pub fn serialize(&self) -> Vec { match self { Value::S(data) => data.clone(), Value::C(data) => { let mut s = vec![]; for t in data { s.extend(&t.serialize()); } s } } } } openpgp-card-0.5.0/src/ocard/tlv.rs000064400000000000000000000227121046102023000152430ustar 00000000000000// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 pub(crate) mod length; pub(crate) mod tag; pub(crate) mod value; use std::convert::TryFrom; use nom::{bytes::complete as bytes, combinator}; use crate::ocard::data::complete; use crate::ocard::tlv::{length::tlv_encode_length, tag::Tag, value::Value}; /// TLV (Tag-Length-Value) data structure. /// /// Many DOs (data objects) on OpenPGP cards are stored in TLV format. /// This struct handles serializing and deserializing TLV. #[derive(Debug, Eq, PartialEq)] pub struct Tlv { tag: Tag, value: Value, } impl Tlv { pub fn new>(tag: T, value: Value) -> Self { let tag = tag.into(); Self { tag, value } } /// Find the first occurrence of `tag` and return its value (if any) pub fn find>(&self, tag: T) -> Option<&Value> { let t: Tag = tag.clone().into(); if self.tag == t { Some(&self.value) } else { if let Value::C(inner) = &self.value { for tlv in inner { if let Some(found) = tlv.find(tag.clone()) { return Some(found); } } } None } } pub fn serialize(&self) -> Vec { let value = self.value.serialize(); let length = tlv_encode_length(value.len() as u16); let mut ser = Vec::new(); ser.extend(self.tag.get().iter()); ser.extend(length.iter()); ser.extend(value.iter()); ser } fn parse(input: &[u8]) -> nom::IResult<&[u8], Tlv> { // Read the tag byte(s) let (input, tag) = tag::tag(input)?; // Read the length field and get the corresponding number of bytes, // which contain the value of this tlv let (input, value) = combinator::flat_map(length::length, bytes::take)(input)?; // Parse the value bytes, as "simple" or "constructed", depending // on the tag. let (_, v) = Value::parse(value, tag.is_constructed())?; Ok((input, Self::new(tag, v))) } } impl TryFrom<&[u8]> for Tlv { type Error = crate::Error; fn try_from(input: &[u8]) -> Result { complete(Tlv::parse(input)) } } #[cfg(test)] mod test { use std::convert::TryFrom; use hex_literal::hex; use super::{Tlv, Value}; use crate::ocard::tags::Tags; use crate::Error; #[test] fn test_tlv0() { let cpkt = Tlv::new( Tags::CardholderPrivateKeyTemplate, Value::S(vec![ 0x91, 0x03, 0x92, 0x82, 0x01, 0x00, 0x93, 0x82, 0x01, 0x00, ]), ); assert_eq!( cpkt.serialize(), vec![0x7F, 0x48, 0x0A, 0x91, 0x03, 0x92, 0x82, 0x01, 0x00, 0x93, 0x82, 0x01, 0x00,] ); } #[test] fn test_tlv() -> Result<(), Error> { // From OpenPGP card spec § 7.2.6 let data = hex!("5B0B546573743C3C54657374695F2D0264655F350131").to_vec(); let (input, tlv) = Tlv::parse(&data).unwrap(); assert_eq!( tlv, Tlv::new( Tags::Name, Value::S(hex!("546573743C3C5465737469").to_vec()) ) ); let (input, tlv) = Tlv::parse(input).unwrap(); assert_eq!( tlv, Tlv::new(Tags::LanguagePref, Value::S(hex!("6465").to_vec())) ); let (input, tlv) = Tlv::parse(input).unwrap(); assert_eq!(tlv, Tlv::new(Tags::Sex, Value::S(hex!("31").to_vec()))); assert!(input.is_empty()); Ok(()) } #[test] fn test_tlv_yubi5() -> Result<(), Error> { // 'YubiKey 5 NFC' output for GET DATA on "Application Related Data" let data = hex!("6e8201374f10d27600012401030400061601918000005f520800730000e00590007f740381012073820110c00a7d000bfe080000ff0000c106010800001100c206010800001100c306010800001100da06010800001100c407ff7f7f7f030003c5500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd1000000000000000000000000000000000de0801000200030081027f660802020bfe02020bfed6020020d7020020d8020020d9020020"); let tlv = Tlv::try_from(&data[..])?; // check that after re-serializing, the data is still the same let serialized = tlv.serialize(); assert_eq!(serialized, data.to_vec()); // outermost layer contains all bytes as value let value = tlv.find(Tags::ApplicationRelatedData).unwrap(); assert_eq!(value.serialize(), hex!("4f10d27600012401030400061601918000005f520800730000e00590007f740381012073820110c00a7d000bfe080000ff0000c106010800001100c206010800001100c306010800001100da06010800001100c407ff7f7f7f030003c5500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd1000000000000000000000000000000000de0801000200030081027f660802020bfe02020bfed6020020d7020020d8020020d9020020")); // get and verify data for ecap tag let value = tlv.find(Tags::ExtendedCapabilities).unwrap(); assert_eq!(value.serialize(), hex!("7d000bfe080000ff0000")); let value = tlv.find(Tags::ApplicationIdentifier).unwrap(); assert_eq!(value.serialize(), hex!("d2760001240103040006160191800000")); let value = tlv.find(Tags::HistoricalBytes).unwrap(); assert_eq!(value.serialize(), hex!("00730000e0059000")); let value = tlv.find(Tags::GeneralFeatureManagement).unwrap(); assert_eq!(value.serialize(), hex!("810120")); let value = tlv.find(Tags::DiscretionaryDataObjects).unwrap(); assert_eq!(value.serialize(), hex!("c00a7d000bfe080000ff0000c106010800001100c206010800001100c306010800001100da06010800001100c407ff7f7f7f030003c5500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd1000000000000000000000000000000000de0801000200030081027f660802020bfe02020bfed6020020d7020020d8020020d9020020")); let value = tlv.find(Tags::ExtendedCapabilities).unwrap(); assert_eq!(value.serialize(), hex!("7d000bfe080000ff0000")); let value = tlv.find(Tags::AlgorithmAttributesSignature).unwrap(); assert_eq!(value.serialize(), hex!("010800001100")); let value = tlv.find(Tags::AlgorithmAttributesDecryption).unwrap(); assert_eq!(value.serialize(), hex!("010800001100")); let value = tlv.find(Tags::AlgorithmAttributesAuthentication).unwrap(); assert_eq!(value.serialize(), hex!("010800001100")); let value = tlv.find(Tags::AlgorithmAttributesAttestation).unwrap(); assert_eq!(value.serialize(), hex!("010800001100")); let value = tlv.find(Tags::PWStatusBytes).unwrap(); assert_eq!(value.serialize(), hex!("ff7f7f7f030003")); let value = tlv.find(Tags::Fingerprints).unwrap(); assert_eq!(value.serialize(), hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); let value = tlv.find(Tags::CaFingerprints).unwrap(); assert_eq!(value.serialize(), hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); let value = tlv.find(Tags::GenerationTimes).unwrap(); assert_eq!(value.serialize(), hex!("00000000000000000000000000000000")); let value = tlv.find(Tags::KeyInformation).unwrap(); assert_eq!(value.serialize(), hex!("0100020003008102")); let value = tlv.find(Tags::ExtendedLengthInformation).unwrap(); assert_eq!(value.serialize(), hex!("02020bfe02020bfe")); let value = tlv.find(Tags::UifSig).unwrap(); assert_eq!(value.serialize(), hex!("0020")); let value = tlv.find(Tags::UifDec).unwrap(); assert_eq!(value.serialize(), hex!("0020")); let value = tlv.find(Tags::UifAuth).unwrap(); assert_eq!(value.serialize(), hex!("0020")); let value = tlv.find(Tags::UifAttestation).unwrap(); assert_eq!(value.serialize(), hex!("0020")); Ok(()) } #[test] fn test_tlv_builder() { // NOTE: The data used in this example is similar to key upload, // but has been abridged and changed. It does not represent a // complete valid OpenPGP card DO! let a = Tlv::new( Tags::CardholderPrivateKeyTemplate, Value::S(vec![0x92, 0x03]), ); let b = Tlv::new(Tags::ConcatenatedKeyData, Value::S(vec![0x1, 0x2, 0x3])); let tlv = Tlv::new(Tags::ExtendedHeaderList, Value::C(vec![a, b])); assert_eq!( tlv.serialize(), &[0x4d, 0xb, 0x7f, 0x48, 0x2, 0x92, 0x3, 0x5f, 0x48, 0x3, 0x1, 0x2, 0x3] ); } } openpgp-card-0.5.0/src/state.rs000064400000000000000000000102011046102023000144540ustar 00000000000000// 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 crate::ocard::data::{ApplicationRelatedData, KdfDo}; use crate::{Cached, 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<'_, '_> {} /// 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: crate::ocard::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: crate::ocard::Transaction<'a>, // Cache of "application related data". // // FIXME: Should be automatically invalidated when changing data on the card! // (e.g. uploading keys, etc) ard: Cached, // Cache of the card's KdfDo // FIXME: invalidate when changed! kdf_do: Cached, // verify status of pw1 // FIXME: this mechanism needs more thought pub(crate) pw1: bool, // verify status of pw1 for signing // FIXME: this mechanism needs more thought pub(crate) pw1_sign: bool, // verify status of pw3 // FIXME: this mechanism needs more thought pub(crate) pw3: bool, } impl<'a> Transaction<'a> { pub(crate) fn new(opt: crate::ocard::Transaction<'a>, ard: ApplicationRelatedData) -> Self { Transaction { opt, ard: Cached::Value(ard), kdf_do: Cached::Uncached, pw1: false, pw1_sign: false, pw3: false, } } pub(crate) fn ard(&mut self) -> &ApplicationRelatedData { if matches!(self.ard, Cached::Uncached) { match self.opt.application_related_data() { Ok(ard) => { self.ard = Cached::Value(ard); } Err(_) => { self.ard = Cached::None; } } } match &self.ard { Cached::Value(ard) => ard, Cached::Uncached => unreachable!(), Cached::None => unreachable!(), } } pub(crate) fn kdf_do(&mut self) -> Option<&KdfDo> { if matches!(self.kdf_do, Cached::Uncached) { match self.opt.kdf_do() { Ok(kdf) => { self.kdf_do = Cached::Value(kdf.clone()); } Err(_) => { self.kdf_do = Cached::None; } } } match &self.kdf_do { Cached::None => None, Cached::Value(kdf) => Some(kdf), Cached::Uncached => unreachable!(), } } pub(crate) fn invalidate_cache(&mut self) { self.ard = Cached::Uncached; self.kdf_do = Cached::Uncached; } } /// 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>, }