From 2d1bf919d47bf43b1dc9724845c654ffcbfadcc8 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 29 Aug 2023 13:18:53 +0200 Subject: [PATCH] openpgp-card: rename Openpgp and OpenpgpTransaction, restructure modules --- card-functionality/src/tests.rs | 12 +- openpgp-card-sequoia/src/decryptor.rs | 6 +- openpgp-card-sequoia/src/lib.rs | 14 +- openpgp-card-sequoia/src/signer.rs | 12 +- openpgp-card-sequoia/src/state.rs | 5 +- openpgp-card/src/apdu/commands.rs | 7 +- openpgp-card/src/card_do.rs | 73 +- openpgp-card/src/card_do/cardholder.rs | 4 +- openpgp-card/src/keys.rs | 14 +- openpgp-card/src/lib.rs | 1461 +++++++++++++++++++----- openpgp-card/src/openpgp.rs | 1185 ------------------- openpgp-card/src/tags.rs | 259 +++++ openpgp-card/src/tlv.rs | 5 +- 13 files changed, 1537 insertions(+), 1520 deletions(-) delete mode 100644 openpgp-card/src/openpgp.rs create mode 100644 openpgp-card/src/tags.rs diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 8660e17..631f2e2 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -8,7 +8,7 @@ use std::string::FromUtf8Error; use anyhow::Result; use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::{KeyGenerationTime, Sex}; -use openpgp_card::{Error, KeyType, OpenPgp, StatusBytes}; +use openpgp_card::{Error, KeyType, StatusBytes}; use openpgp_card_sequoia::sq_util; use openpgp_card_sequoia::state::{Admin, Open, Transaction}; use openpgp_card_sequoia::util::{ @@ -136,7 +136,10 @@ fn check_key_upload_algo_attrs() -> Result<()> { Ok(()) } -pub fn test_print_caps(pgp: &mut OpenPgp, _param: &[&str]) -> Result { +pub fn test_print_caps( + pgp: &mut openpgp_card::Card, + _param: &[&str], +) -> Result { let mut pgpt = pgp.transaction()?; let ard = pgpt.application_related_data()?; @@ -156,7 +159,10 @@ pub fn test_print_caps(pgp: &mut OpenPgp, _param: &[&str]) -> Result Result { +pub fn test_print_algo_info( + pgp: &mut openpgp_card::Card, + _param: &[&str], +) -> Result { let mut pgpt = pgp.transaction()?; let ard = pgpt.application_related_data()?; diff --git a/openpgp-card-sequoia/src/decryptor.rs b/openpgp-card-sequoia/src/decryptor.rs index a41a743..65300b5 100644 --- a/openpgp-card-sequoia/src/decryptor.rs +++ b/openpgp-card-sequoia/src/decryptor.rs @@ -3,7 +3,7 @@ use anyhow::anyhow; use openpgp_card::crypto_data::Cryptogram; -use openpgp_card::OpenPgpTransaction; +use openpgp_card::Transaction; use sequoia_openpgp::crypto::mpi; use sequoia_openpgp::crypto::SessionKey; use sequoia_openpgp::packet; @@ -15,7 +15,7 @@ use crate::PublicKey; pub struct CardDecryptor<'a, 'app> { /// The OpenPGP card (authenticated to allow decryption operations) - ca: &'a mut OpenPgpTransaction<'app>, + ca: &'a mut Transaction<'app>, /// The matching public key for the card's decryption key public: PublicKey, @@ -26,7 +26,7 @@ pub struct CardDecryptor<'a, 'app> { impl<'a, 'app> CardDecryptor<'a, 'app> { pub(crate) fn with_pubkey( - ca: &'a mut OpenPgpTransaction<'app>, + ca: &'a mut Transaction<'app>, public: PublicKey, touch_prompt: &'a (dyn Fn() + Send + Sync), ) -> CardDecryptor<'a, 'app> { diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 8716763..b1afaba 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -143,11 +143,11 @@ use card_backend::{CardBackend, SmartcardError}; use openpgp_card::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; use openpgp_card::card_do::{ ApplicationIdentifier, CardholderRelatedData, ExtendedCapabilities, ExtendedLengthInfo, - Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, Lang, PWStatusBytes, + Fingerprint, HistoricalBytes, KeyGenerationTime, KeyInformation, KeySet, Lang, PWStatusBytes, SecuritySupportTemplate, Sex, TouchPolicy, UIF, }; use openpgp_card::crypto_data::PublicKeyMaterial; -use openpgp_card::{Error, KeySet, KeyType, OpenPgp, OpenPgpTransaction}; +use openpgp_card::{Error, KeyType}; use sequoia_openpgp::cert::prelude::ValidErasedKeyAmalgamation; use sequoia_openpgp::packet::key::SecretParts; use sequoia_openpgp::packet::{key, Key}; @@ -241,7 +241,7 @@ impl Card { where B: Into>, { - let pgp = OpenPgp::new(backend)?; + let pgp = openpgp_card::Card::new(backend)?; Ok(Card:: { state: Open { pgp }, @@ -265,7 +265,7 @@ impl Card { impl<'a> Card> { // Internal constructor - fn new(mut opt: OpenPgpTransaction<'a>) -> Result { + fn new(mut opt: openpgp_card::Transaction<'a>) -> Result { let ard = opt.application_related_data()?; Ok(Self { @@ -715,7 +715,7 @@ impl<'a> Card> { impl<'app, 'open> Card> { /// Helper fn to easily access underlying openpgp_card object - fn card(&mut self) -> &mut OpenPgpTransaction<'app> { + fn card(&mut self) -> &mut openpgp_card::Transaction<'app> { &mut self.state.tx.state.opt } @@ -767,7 +767,7 @@ impl<'app, 'open> Card> { impl<'app, 'open> Card> { /// Helper fn to easily access underlying openpgp_card object - fn card(&mut self) -> &mut OpenPgpTransaction<'app> { + fn card(&mut self) -> &mut openpgp_card::Transaction<'app> { &mut self.state.tx.state.opt } @@ -823,7 +823,7 @@ impl<'app, 'open> Card> { } /// Helper fn to easily access underlying openpgp_card object - fn card(&mut self) -> &mut OpenPgpTransaction<'app> { + fn card(&mut self) -> &mut openpgp_card::Transaction<'app> { &mut self.state.tx.state.opt } } diff --git a/openpgp-card-sequoia/src/signer.rs b/openpgp-card-sequoia/src/signer.rs index 079dc70..d377701 100644 --- a/openpgp-card-sequoia/src/signer.rs +++ b/openpgp-card-sequoia/src/signer.rs @@ -5,7 +5,7 @@ use std::convert::TryInto; use anyhow::anyhow; use openpgp_card::crypto_data::Hash; -use openpgp_card::OpenPgpTransaction; +use openpgp_card::Transaction; use sequoia_openpgp::crypto; use sequoia_openpgp::crypto::mpi; use sequoia_openpgp::types::{Curve, PublicKeyAlgorithm}; @@ -14,7 +14,7 @@ use crate::PublicKey; pub struct CardSigner<'a, 'app> { /// The OpenPGP card (authenticated to allow signing operations) - ca: &'a mut OpenPgpTransaction<'app>, + ca: &'a mut Transaction<'app>, /// The matching public key for the card's signing key public: PublicKey, @@ -28,7 +28,7 @@ pub struct CardSigner<'a, 'app> { impl<'a, 'app> CardSigner<'a, 'app> { pub(crate) fn with_pubkey( - ca: &'a mut OpenPgpTransaction<'app>, + ca: &'a mut Transaction<'app>, public: PublicKey, touch_prompt: &'a (dyn Fn() + Send + Sync), ) -> CardSigner<'a, 'app> { @@ -41,7 +41,7 @@ impl<'a, 'app> CardSigner<'a, 'app> { } pub(crate) fn with_pubkey_for_auth( - ca: &'a mut OpenPgpTransaction<'app>, + ca: &'a mut Transaction<'app>, public: PublicKey, touch_prompt: &'a (dyn Fn() + Send + Sync), ) -> CardSigner<'a, 'app> { @@ -84,9 +84,9 @@ impl<'a, 'app> crypto::Signer for CardSigner<'a, 'app> { }; let sig_fn = if !self.auth { - OpenPgpTransaction::signature_for_hash + Transaction::signature_for_hash } else { - OpenPgpTransaction::authenticate_for_hash + Transaction::authenticate_for_hash }; // Delegate a signing (or auth) operation to the OpenPGP card. diff --git a/openpgp-card-sequoia/src/state.rs b/openpgp-card-sequoia/src/state.rs index 2143f3f..c33e023 100644 --- a/openpgp-card-sequoia/src/state.rs +++ b/openpgp-card-sequoia/src/state.rs @@ -4,7 +4,6 @@ //! States of a card are modeled by the types `Open`, `Transaction`, `User`, `Sign`, `Admin`. use openpgp_card::card_do::ApplicationRelatedData; -use openpgp_card::{OpenPgp, OpenPgpTransaction}; use crate::Card; @@ -23,7 +22,7 @@ impl State for Admin<'_, '_> {} /// /// A transaction can be started on the card, in this state. pub struct Open { - pub(crate) pgp: OpenPgp, + pub(crate) pgp: openpgp_card::Card, } /// State of an OpenPGP card once a transaction has been started. @@ -34,7 +33,7 @@ pub struct Open { /// /// (Note that a factory-reset can be performed in this base state.) pub struct Transaction<'a> { - pub(crate) opt: OpenPgpTransaction<'a>, + pub(crate) opt: openpgp_card::Transaction<'a>, // Cache of "application related data". // diff --git a/openpgp-card/src/apdu/commands.rs b/openpgp-card/src/apdu/commands.rs index cb20ccf..dcd2c67 100644 --- a/openpgp-card/src/apdu/commands.rs +++ b/openpgp-card/src/apdu/commands.rs @@ -1,15 +1,16 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Pre-defined `Command` values for the OpenPGP card application use crate::apdu::command::Command; -use crate::{KeyType, ShortTag, Tags, OP_APP}; +use crate::tags::{ShortTag, Tags}; +use crate::{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, OP_APP.to_vec()) + Command::new(0x00, 0xA4, 0x04, 0x00, OPENPGP_APPLICATION.to_vec()) } /// 7.2.6 GET DATA diff --git a/openpgp-card/src/card_do.rs b/openpgp-card/src/card_do.rs index 2ab2b57..a54d349 100644 --- a/openpgp-card/src/card_do.rs +++ b/openpgp-card/src/card_do.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! OpenPGP card data objects (DO) @@ -9,7 +9,8 @@ use std::time::{Duration, UNIX_EPOCH}; use chrono::{DateTime, Utc}; -use crate::{algorithm::AlgorithmAttributes, tlv::Tlv, Error, KeySet, KeyType, Tags}; +use crate::tags::Tags; +use crate::{algorithm::AlgorithmAttributes, tlv::Tlv, Error, KeyType}; mod algo_attrs; mod algo_info; @@ -22,7 +23,7 @@ mod historical; mod key_generation_times; mod pw_status; -/// 4.4.3.1 Application Related Data +/// 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. @@ -274,7 +275,7 @@ impl ApplicationRelatedData { } } -/// Security support template (see spec pg. 24) +/// Security support template [Spec page 24] #[derive(Debug)] pub struct SecuritySupportTemplate { // Digital signature counter [3 bytes] @@ -288,7 +289,7 @@ impl SecuritySupportTemplate { } } -/// An OpenPGP key generation Time (see spec pg. 24) +/// An OpenPGP key generation Time [Spec page 24] #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct KeyGenerationTime(u32); @@ -309,7 +310,7 @@ impl Display for KeyGenerationTime { } } -/// User Interaction Flag (UIF) (see spec pg. 24) +/// User Interaction Flag (UIF) [Spec page 24] #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct UIF([u8; 2]); @@ -422,7 +423,7 @@ impl From for TouchPolicy { } } -/// "additional hardware for user interaction" (see spec 4.1.3.2) +/// "additional hardware for user interaction" [Spec section 4.1.3.2] pub struct Features(u8); impl From for Features { @@ -464,7 +465,7 @@ impl Display for Features { } } -/// 4.4.3.8 Key Information +/// Key Information [Spec section 4.4.3.8] pub struct KeyInformation(Vec); impl From> for KeyInformation { @@ -583,7 +584,7 @@ impl Display for KeyStatus { } } -/// 4.2.1 Application Identifier (AID) +/// Application Identifier (AID) [Spec section 4.2.1] #[derive(Debug, Eq, PartialEq)] pub struct ApplicationIdentifier { application: u8, @@ -602,7 +603,7 @@ impl Display for ApplicationIdentifier { } } -/// 6 Historical Bytes +/// Historical Bytes [Spec chapter 6] #[derive(Debug, PartialEq, Eq)] pub struct HistoricalBytes { /// category indicator byte @@ -618,7 +619,7 @@ pub struct HistoricalBytes { sib: u8, } -/// Card Capabilities (see 6 Historical Bytes) +/// Card Capabilities [Spec chapter 6 (Historical Bytes)] #[derive(Debug, PartialEq, Eq)] pub struct CardCapabilities { command_chaining: bool, @@ -642,7 +643,7 @@ impl Display for CardCapabilities { } } -/// Card service data (see 6 Historical Bytes) +/// Card service data [Spec chapter 6 (Historical Bytes)] #[derive(Debug, PartialEq, Eq)] pub struct CardServiceData { select_by_full_df_name: bool, // Application Selection by full DF name (AID) @@ -689,7 +690,7 @@ impl Display for CardServiceData { } } -/// 4.4.3.7 Extended Capabilities +/// Extended Capabilities [Spec section 4.4.3.7] #[derive(Debug, Eq, PartialEq)] pub struct ExtendedCapabilities { secure_messaging: bool, @@ -779,7 +780,7 @@ impl Display for ExtendedCapabilities { } } -/// 4.1.3.1 Extended length information +/// Extended length information [Spec section 4.1.3.1] #[derive(Debug, Eq, PartialEq)] pub struct ExtendedLengthInfo { max_command_bytes: u16, @@ -794,7 +795,7 @@ impl Display for ExtendedLengthInfo { } } -/// Cardholder Related Data (see spec pg. 22) +/// Cardholder Related Data [Spec page 22] #[derive(Debug, PartialEq, Eq)] pub struct CardholderRelatedData { name: Option>, @@ -819,7 +820,7 @@ impl Display for CardholderRelatedData { } } -/// 4.4.3.5 Sex +/// Sex [Spec section 4.4.3.5] /// /// Encoded in accordance with #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -867,7 +868,9 @@ impl From for Sex { } } -/// Individual language for Language Preferences (4.4.3.4), accessible via `CardholderRelatedData`. +/// Individual language for Language Preferences [Spec section 4.4.3.4] +/// +/// This field is accessible via `CardholderRelatedData`. /// /// Encoded according to #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -922,7 +925,7 @@ impl From<&[u8; 2]> for Lang { } } -/// PW status Bytes (see spec page 23) +/// PW status Bytes [Spec page 23] #[derive(Debug, PartialEq, Eq)] pub struct PWStatusBytes { pub(crate) pw1_cds_valid_once: bool, @@ -992,7 +995,7 @@ impl PWStatusBytes { } } -/// Fingerprint (see spec pg. 23) +/// Fingerprint [Spec page 23] #[derive(Clone, Eq, PartialEq)] pub struct Fingerprint([u8; 20]); @@ -1026,3 +1029,35 @@ pub(crate) fn complete(result: nom::IResult<&[u8], O>) -> Result { ))) } } + +/// 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() + } +} diff --git a/openpgp-card/src/card_do/cardholder.rs b/openpgp-card/src/card_do/cardholder.rs index 4a332ac..53aa83d 100644 --- a/openpgp-card/src/card_do/cardholder.rs +++ b/openpgp-card/src/card_do/cardholder.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Cardholder Related Data (see spec pg. 22) @@ -6,8 +6,8 @@ use std::convert::TryFrom; use crate::card_do::{CardholderRelatedData, Lang, Sex}; +use crate::tags::Tags; use crate::tlv::{value::Value, Tlv}; -use crate::Tags; impl CardholderRelatedData { pub fn name(&self) -> Option<&[u8]> { diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index 54513c4..9b58ead 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Generate and import keys @@ -14,9 +14,9 @@ use crate::crypto_data::{ CardUploadableKey, EccKey, EccPub, EccType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey, RSAPub, }; -use crate::openpgp::OpenPgpTransaction; +use crate::tags::Tags; use crate::tlv::{length::tlv_encode_length, value::Value, Tlv}; -use crate::{Error, KeyType, Tag, Tags}; +use crate::{Error, KeyType, Tag, Transaction}; /// Generate asymmetric key pair on the card. /// @@ -29,7 +29,7 @@ use crate::{Error, KeyType, Tag, Tags}; /// `fp_from_pub` calculates the fingerprint for a public key data object and /// creation timestamp pub(crate) fn gen_key_with_metadata( - card_tx: &mut OpenPgpTransaction, + card_tx: &mut Transaction, fp_from_pub: fn(&PublicKeyMaterial, KeyGenerationTime, KeyType) -> Result, key_type: KeyType, algo: Option<&AlgorithmAttributes>, @@ -121,7 +121,7 @@ fn tlv_to_pubkey(tlv: &Tlv, algo: &AlgorithmAttributes) -> Result Result { log::info!("OpenPgpTransaction: generate_asymmetric_key_pair"); @@ -145,7 +145,7 @@ pub(crate) fn generate_asymmetric_key_pair( /// /// (See 7.2.14 GENERATE ASYMMETRIC KEY PAIR) pub(crate) fn public_key( - card_tx: &mut OpenPgpTransaction, + card_tx: &mut Transaction, key_type: KeyType, ) -> Result { log::info!("OpenPgpTransaction: public_key"); @@ -173,7 +173,7 @@ pub(crate) fn public_key( /// 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 OpenPgpTransaction, + card_tx: &mut Transaction, key: Box, key_type: KeyType, algo_info: Option, diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 18904fb..bb6872b 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Client library for @@ -14,7 +14,9 @@ //! //! This library can't directly access cards by itself. Instead, users //! need to supply a backend that implements the [`card_backend::CardBackend`] -//! / [`card_backend::CardTransaction`] traits. The companion crate +//! / [`card_backend::CardTransaction`] traits. +//! +//! The companion crate //! [card-backend-pcsc](https://crates.io/crates/card-backend-pcsc) //! offers a backend that uses [PC/SC](https://en.wikipedia.org/wiki/PC/SC) to //! communicate with Smart Cards. @@ -23,8 +25,8 @@ //! crate offers a higher level wrapper based on the [Sequoia PGP](https://sequoia-pgp.org/) //! implementation. //! -//! See the [architecture diagram](https://gitlab.com/openpgp-card/openpgp-card#architecture) for -//! a visualization. +//! See the [architecture diagram](https://gitlab.com/openpgp-card/openpgp-card#architecture) +//! for an overview of the ecosystem around this crate. extern crate core; @@ -35,267 +37,29 @@ pub mod crypto_data; mod errors; pub(crate) mod keys; mod oid; -mod openpgp; +mod tags; mod tlv; +use std::convert::{TryFrom, TryInto}; + +use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; +use tags::{ShortTag, Tags}; + +use crate::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; +use crate::apdu::command::Command; +use crate::apdu::commands; +use crate::apdu::response::RawResponse; +use crate::card_do::{ + ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang, + PWStatusBytes, SecuritySupportTemplate, Sex, UIF, +}; +use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; pub use crate::errors::{Error, StatusBytes}; -pub use crate::openpgp::{OpenPgp, OpenPgpTransaction}; use crate::tlv::tag::Tag; +use crate::tlv::value::Value; +use crate::tlv::Tlv; -pub(crate) const OP_APP: &[u8] = &[0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]; - -/// 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)] -#[non_exhaustive] -#[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)] -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], - } - } -} +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)] @@ -304,6 +68,7 @@ pub enum KeyType { Signing, Decryption, Authentication, + Attestation, } @@ -348,34 +113,1170 @@ impl KeyType { } } -/// A KeySet binds together a triple of information about each Key on a card -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct KeySet { - signature: Option, - decryption: Option, - authentication: Option, +/// 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 [Card] object, including in long running programs. +/// All operations must be performed on a [Transaction] (which must be short lived). +pub struct Card { + card: Box, + card_caps: 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 Card { + /// Turn a [CardBackend] into an [Card] 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, + }; + + let caps = { + 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 (mut 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, + ); + + drop(tx); + + caps + }; + + log::trace!("init_card_caps to: {:x?}", caps); + op.card_caps = Some(caps); + + 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 + } + + /// Get an OpenPgpTransaction object. This starts a transaction on the underlying + /// CardBackend. + /// + /// Note: transactions on the Card cannot be long running, they will be reset within seconds + /// when idle. + /// + /// If the card has been reset, and `reselect_application` is set, then + /// that application will be `SELECT`ed after starting the transaction. + pub fn transaction(&mut self) -> Result { + let card_caps = &mut self.card_caps; + let tx = self.card.transaction(Some(OPENPGP_APPLICATION))?; + + if tx.was_reset() { + // FIXME + // Signal state invalidation? (PIN verification, ...) + } + + Ok(Transaction { tx, card_caps }) + } +} + +/// To perform commands on a [Card], 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 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, + ))) + } + + // --- login data (5e) --- + + /// Get URL (5f50) + pub fn url(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: url"); + + let resp = self.send_command(commands::url(), true)?; + + Ok(resp.data()?.to_vec()) + } + + /// Get Login Data (5e) + pub fn login_data(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: login_data"); + + let resp = self.send_command(commands::login_data(), true)?; + + Ok(resp.data()?.to_vec()) + } + + /// Get cardholder related data (65) + pub fn cardholder_related_data(&mut self) -> Result { + log::info!("OpenPgpTransaction: cardholder_related_data"); + + let crd = commands::cardholder_related_data(); + let resp = self.send_command(crd, true)?; + resp.check_ok()?; + + CardholderRelatedData::try_from(resp.data()?) + } + + /// Get security support template (7a) + pub fn security_support_template(&mut self) -> Result { + log::info!("OpenPgpTransaction: security_support_template"); + + let sst = commands::security_support_template(); + let resp = self.send_command(sst, true)?; + resp.check_ok()?; + + let tlv = Tlv::try_from(resp.data()?)?; + let res = tlv.find(Tag::from([0x93])).ok_or_else(|| { + Error::NotFound("Couldn't get SecuritySupportTemplate DO".to_string()) + })?; + + if let Value::S(data) = res { + let mut data = data.to_vec(); + if data.len() != 3 { + return Err(Error::ParseError(format!( + "Unexpected length {} for 'Digital signature counter' 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(), + )) } } -} -impl KeySet { - pub fn signature(&self) -> Option<&T> { - self.signature.as_ref() + /// 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"); + + let cmd = commands::cardholder_certificate(); + self.send_command(cmd, true)?.try_into() } - pub fn decryption(&self) -> Option<&T> { - self.decryption.as_ref() + /// 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"); + + let cmd = commands::get_next_cardholder_certificate(); + self.send_command(cmd, true)?.try_into() } - pub fn authentication(&self) -> Option<&T> { - self.authentication.as_ref() + /// 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)?; + resp.check_ok()?; + + let ai = AlgoInfo::try_from(resp.data()?)?; + Ok(Some(ai)) + } + + /// Get "Attestation Certificate (Yubico)" + pub fn attestation_certificate(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: attestation_certificate"); + + let resp = self.send_command(commands::attestation_certificate(), true)?; + + Ok(resp.data()?.into()) + } + + /// Firmware Version (YubiKey specific (?)) + pub fn firmware_version(&mut self) -> Result, Error> { + log::info!("OpenPgpTransaction: firmware_version"); + + let resp = self.send_command(commands::firmware_version(), true)?; + + Ok(resp.data()?.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 { + Ok(resp?.data()?.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. + /// + /// `yk_workaround`: YubiKey 5 up to (and including) firmware version 5.4.3 need a workaround + /// for this command. Set to `true` to apply this workaround. + /// (When sending the SELECT DATA command as defined in the card spec, without enabling the + /// workaround, bad YubiKey firmware versions (<= 5.4.3) return + /// [`IncorrectParametersCommandDataField`](StatusBytes::IncorrectParametersCommandDataField)) + /// + /// (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 [`firmware_version`](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 + /// [`IncorrectParametersCommandDataField`](StatusBytes::IncorrectParametersCommandDataField) + /// - for read operations: using + /// [`next_cardholder_certificate`](Transaction::next_cardholder_certificate) + /// instead of SELECT DATA) + pub fn select_data(&mut self, num: u8, tag: &[u8], yk_workaround: bool) -> 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(); + + if yk_workaround { + // 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)?.try_into()?; + + 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"); + + assert!((1..=4).contains(&num)); + + let cmd = commands::private_use_do(num); + let resp = self.send_command(cmd, true)?; + + Ok(resp.data()?.to_vec()) + } + + // ---------- + + /// 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"); + + // send 4 bad requests to verify pw1 + // [apdu 00 20 00 81 08 40 40 40 40 40 40 40 40] + for _ in 0..4 { + log::info!(" verify_pw1_81"); + let verify = commands::verify_pw1_81([0x40; 8].to_vec()); + let resp = self.send_command(verify, false)?; + if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied + || resp.status() == StatusBytes::AuthenticationMethodBlocked + || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) + { + return Err(Error::InternalError( + "Unexpected status for reset, at pw1.".into(), + )); + } + } + + // send 4 bad requests to verify pw3 + // [apdu 00 20 00 83 08 40 40 40 40 40 40 40 40] + for _ in 0..4 { + log::info!(" verify_pw3"); + let verify = commands::verify_pw3([0x40; 8].to_vec()); + let resp = self.send_command(verify, false)?; + + if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied + || resp.status() == StatusBytes::AuthenticationMethodBlocked + || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) + { + 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: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: verify_pw1_sign"); + + let verify = commands::verify_pw1_81(pin.to_vec()); + self.send_command(verify, 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![]); + self.send_command(verify, false)?.try_into() + } + + /// Verify PW1 (user). + /// (For operations except signing, mode 82). + pub fn verify_pw1_user(&mut self, pin: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: verify_pw1_user"); + + let verify = commands::verify_pw1_82(pin.to_vec()); + 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![]); + self.send_command(verify, false)?.try_into() + } + + /// Verify PW3 (admin). + pub fn verify_pw3(&mut self, pin: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: verify_pw3"); + + let verify = commands::verify_pw3(pin.to_vec()); + 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 reset the pin) + pub fn check_pw3(&mut self) -> Result<(), Error> { + log::info!("OpenPgpTransaction: check_pw3"); + + let verify = commands::verify_pw3(vec![]); + 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: &[u8], new: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: change_pw1"); + + let mut data = vec![]; + data.extend(old); + data.extend(new); + + let change = commands::change_pw1(data); + 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: &[u8], new: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: change_pw3"); + + let mut data = vec![]; + data.extend(old); + data.extend(new); + + let change = commands::change_pw3(data); + 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: &[u8], + resetting_code: Option<&[u8]>, + ) -> Result<(), Error> { + log::info!("OpenPgpTransaction: reset_retry_counter_pw1"); + + let reset = commands::reset_retry_counter_pw1(resetting_code, new_pw1); + self.send_command(reset, 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 `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)?; + resp.check_ok()?; + + Ok(resp.data().map(|d| d.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 [pso_decipher](Transaction::pso_decipher) + /// and then sets the *Decryption* key to be used for [internal_authenticate](Transaction::internal_authenticate). + /// + /// ```no_run + /// # use openpgp_card::{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 + /// 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 `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"); + + assert!((1..=4).contains(&num)); + + let cmd = commands::put_private_use_do(num, data); + let resp = self.send_command(cmd, true)?; + + Ok(resp.data()?.to_vec()) + } + + pub fn set_login(&mut self, login: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_login"); + let put_login_data = commands::put_login_data(login.to_vec()); + self.send_command(put_login_data, false)?.try_into() + } + + pub fn set_name(&mut self, name: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_name"); + + let put_name = commands::put_name(name.to_vec()); + self.send_command(put_name, 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| Into::>::into(l)) + .collect(); + + let put_lang = commands::put_lang(bytes); + self.send_command(put_lang, false)?.try_into() + } + + pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_sex"); + + let put_sex = commands::put_sex((&sex).into()); + self.send_command(put_sex, false)?.try_into() + } + + pub fn set_url(&mut self, url: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_url"); + + let put_url = commands::put_url(url.to_vec()); + self.send_command(put_url, 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 + /// (4.4.3.9 Algorithm Attributes) + pub fn set_algorithm_attributes( + &mut self, + key_type: KeyType, + algo: &AlgorithmAttributes, + ) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_algorithm_attributes"); + + // Command to PUT the algorithm attributes + let cmd = commands::put_data(key_type.algorithm_tag(), algo.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 fp_cmd = commands::put_data(key_type.fingerprint_put_tag(), fp.as_bytes().to_vec()); + + self.send_command(fp_cmd, false)?.try_into() + } + + pub fn set_ca_fingerprint_1(&mut self, fp: Fingerprint) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_ca_fingerprint_1"); + + let fp_cmd = commands::put_data(Tags::CaFingerprint1, fp.as_bytes().to_vec()); + self.send_command(fp_cmd, false)?.try_into() + } + + pub fn set_ca_fingerprint_2(&mut self, fp: Fingerprint) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_ca_fingerprint_2"); + + let fp_cmd = commands::put_data(Tags::CaFingerprint2, fp.as_bytes().to_vec()); + self.send_command(fp_cmd, false)?.try_into() + } + + pub fn set_ca_fingerprint_3(&mut self, fp: Fingerprint) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_ca_fingerprint_3"); + + let fp_cmd = commands::put_data(Tags::CaFingerprint3, fp.as_bytes().to_vec()); + self.send_command(fp_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() + .iter() + .skip_while(|&&e| e == 0) + .copied() + .collect(); + + let time_cmd = commands::put_data(key_type.timestamp_put_tag(), time_value); + + self.send_command(time_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: &[u8]) -> Result<(), Error> { + log::info!("OpenPgpTransaction: set_resetting_code"); + + let cmd = commands::put_data(Tags::ResettingCode, resetting_code.to_vec()); + 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 fp_cmd = commands::put_data(Tags::PsoEncDecKey, key.to_vec()); + + self.send_command(fp_cmd, false)?.try_into() + } + + // FIXME: optional DO for PSO:ENC/DEC with AES + + /// Set UIF for PSO:CDS + pub fn set_uif_pso_cds(&mut self, uif: &UIF) -> 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: &UIF) -> 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: &UIF) -> 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: &UIF) -> 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 + + // FIXME: KDF DO + + // 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> { + // An error is ok - it's fine if a card doesn't offer a list of + // supported algorithms + let algo_info = self.algorithm_information().unwrap_or(None); + + keys::key_import(self, key, key_type, algo_info) + } + + /// Generate a key on the card. + /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) + /// + /// If the `algorithm_attributes` parameter is Some, then this algorithm will be set on + /// the card for "key_type". + /// + /// Note: `algorithm_attributes` needs to precisely specify the RSA bitsize of e (if + /// applicable), and import format, with values that the current card + /// supports. + pub fn generate_key( + &mut self, + fp_from_pub: fn( + &PublicKeyMaterial, + KeyGenerationTime, + KeyType, + ) -> Result, + key_type: KeyType, + algorithm_attributes: Option<&AlgorithmAttributes>, + ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { + keys::gen_key_with_metadata(self, fp_from_pub, key_type, algorithm_attributes) + } + + /// Generate a key on the card. + /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) + /// + /// This is a wrapper around generate_key() which allows + /// using the simplified `AlgoSimple` algorithm selector enum. + /// + /// Note: AlgoSimple doesn't specify card specific details (such as + /// bitsize of e for RSA, and import format). This function determines + /// these values based on information from the card. + pub fn generate_key_simple( + &mut self, + fp_from_pub: fn( + &PublicKeyMaterial, + KeyGenerationTime, + KeyType, + ) -> Result, + key_type: KeyType, + simple: AlgoSimple, + ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { + let ard = self.application_related_data()?; + let algo_info = if let Ok(ai) = self.algorithm_information() { + ai + } else { + None + }; + + let algo = simple.determine_algo_attributes(key_type, &ard, algo_info)?; + + Self::generate_key(self, fp_from_pub, key_type, Some(&algo)) + } + + /// 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::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(), } } diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs deleted file mode 100644 index 1d88def..0000000 --- a/openpgp-card/src/openpgp.rs +++ /dev/null @@ -1,1185 +0,0 @@ -// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use std::convert::{TryFrom, TryInto}; - -use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError}; - -use crate::algorithm::{AlgoInfo, AlgoSimple, AlgorithmAttributes}; -use crate::apdu::command::Command; -use crate::apdu::commands; -use crate::apdu::response::RawResponse; -use crate::card_do::{ - ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang, - PWStatusBytes, SecuritySupportTemplate, Sex, UIF, -}; -use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; -use crate::tlv::{value::Value, Tlv}; -use crate::{apdu, keys, Error, KeyType, StatusBytes, Tag, Tags, OP_APP}; - -/// An OpenPGP card access 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. All operations must be performed on -/// a short lived `OpenPgpTransaction`. -pub struct OpenPgp { - card: Box, - card_caps: Option, -} - -impl OpenPgp { - /// Turn a [card_backend::CardBackend] into an [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, - }; - - let caps = { - 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, - ); - - drop(tx); - - caps - }; - - log::trace!("init_card_caps to: {:x?}", caps); - op.card_caps = Some(caps); - - 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 - } - - /// Get an OpenPgpTransaction object. This starts a transaction on the underlying - /// CardBackend. - /// - /// Note: transactions on the Card cannot be long running, they will be reset within seconds - /// when idle. - /// - /// If the card has been reset, and `reselect_application` is set, then - /// that application will be `SELECT`ed after starting the transaction. - pub fn transaction(&mut self) -> Result { - let card_caps = &mut self.card_caps; - let tx = self.card.transaction(Some(OP_APP))?; - - if tx.was_reset() { - // FIXME - // Signal state invalidation? (PIN verification, ...) - } - - Ok(OpenPgpTransaction { tx, card_caps }) - } -} - -/// 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. -/// -/// An OpenPgpTransaction 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 OpenPgpTransaction<'a> { - tx: Box, - card_caps: &'a mut Option, -} - -impl<'a> OpenPgpTransaction<'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, - ))) - } - - // --- login data (5e) --- - - /// Get URL (5f50) - pub fn url(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: url"); - - let resp = self.send_command(commands::url(), true)?; - - Ok(resp.data()?.to_vec()) - } - - /// Get Login Data (5e) - pub fn login_data(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: login_data"); - - let resp = self.send_command(commands::login_data(), true)?; - - Ok(resp.data()?.to_vec()) - } - - /// Get cardholder related data (65) - pub fn cardholder_related_data(&mut self) -> Result { - log::info!("OpenPgpTransaction: cardholder_related_data"); - - let crd = commands::cardholder_related_data(); - let resp = self.send_command(crd, true)?; - resp.check_ok()?; - - CardholderRelatedData::try_from(resp.data()?) - } - - /// Get security support template (7a) - pub fn security_support_template(&mut self) -> Result { - log::info!("OpenPgpTransaction: security_support_template"); - - let sst = commands::security_support_template(); - let resp = self.send_command(sst, true)?; - resp.check_ok()?; - - let tlv = Tlv::try_from(resp.data()?)?; - let res = tlv.find(Tag::from([0x93])).ok_or_else(|| { - Error::NotFound("Couldn't get SecuritySupportTemplate DO".to_string()) - })?; - - if let Value::S(data) = res { - let mut data = data.to_vec(); - if data.len() != 3 { - return Err(Error::ParseError(format!( - "Unexpected length {} for 'Digital signature counter' 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"); - - let cmd = commands::cardholder_certificate(); - self.send_command(cmd, 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"); - - let cmd = commands::get_next_cardholder_certificate(); - self.send_command(cmd, true)?.try_into() - } - - /// 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)?; - resp.check_ok()?; - - let ai = AlgoInfo::try_from(resp.data()?)?; - Ok(Some(ai)) - } - - /// Get "Attestation Certificate (Yubico)" - pub fn attestation_certificate(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: attestation_certificate"); - - let resp = self.send_command(commands::attestation_certificate(), true)?; - - Ok(resp.data()?.into()) - } - - /// Firmware Version (YubiKey specific (?)) - pub fn firmware_version(&mut self) -> Result, Error> { - log::info!("OpenPgpTransaction: firmware_version"); - - let resp = self.send_command(commands::firmware_version(), true)?; - - Ok(resp.data()?.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 { - Ok(resp?.data()?.into()) - } - } - - /// SELECT DATA ("select a DO in the current template"). - /// - /// This command currently only applies to - /// [`cardholder_certificate`](OpenPgpTransaction::cardholder_certificate) and - /// [`set_cardholder_certificate`](OpenPgpTransaction::set_cardholder_certificate) - /// in OpenPGP card. - /// - /// `yk_workaround`: YubiKey 5 up to (and including) firmware version 5.4.3 need a workaround - /// for this command. Set to `true` to apply this workaround. - /// (When sending the SELECT DATA command as defined in the card spec, without enabling the - /// workaround, bad YubiKey firmware versions (<= 5.4.3) return - /// [`IncorrectParametersCommandDataField`](StatusBytes::IncorrectParametersCommandDataField)) - /// - /// (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 [`firmware_version`](OpenPgpTransaction::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 - /// [`IncorrectParametersCommandDataField`](StatusBytes::IncorrectParametersCommandDataField) - /// - for read operations: using - /// [`next_cardholder_certificate`](OpenPgpTransaction::next_cardholder_certificate) - /// instead of SELECT DATA) - pub fn select_data(&mut self, num: u8, tag: &[u8], yk_workaround: bool) -> 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(); - - if yk_workaround { - // 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)?.try_into()?; - - 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"); - - assert!((1..=4).contains(&num)); - - let cmd = commands::private_use_do(num); - let resp = self.send_command(cmd, true)?; - - Ok(resp.data()?.to_vec()) - } - - // ---------- - - /// 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"); - - // send 4 bad requests to verify pw1 - // [apdu 00 20 00 81 08 40 40 40 40 40 40 40 40] - for _ in 0..4 { - log::info!(" verify_pw1_81"); - let verify = commands::verify_pw1_81([0x40; 8].to_vec()); - let resp = self.send_command(verify, false)?; - if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied - || resp.status() == StatusBytes::AuthenticationMethodBlocked - || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) - { - return Err(Error::InternalError( - "Unexpected status for reset, at pw1.".into(), - )); - } - } - - // send 4 bad requests to verify pw3 - // [apdu 00 20 00 83 08 40 40 40 40 40 40 40 40] - for _ in 0..4 { - log::info!(" verify_pw3"); - let verify = commands::verify_pw3([0x40; 8].to_vec()); - let resp = self.send_command(verify, false)?; - - if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied - || resp.status() == StatusBytes::AuthenticationMethodBlocked - || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) - { - 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: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: verify_pw1_sign"); - - let verify = commands::verify_pw1_81(pin.to_vec()); - self.send_command(verify, 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![]); - self.send_command(verify, false)?.try_into() - } - - /// Verify PW1 (user). - /// (For operations except signing, mode 82). - pub fn verify_pw1_user(&mut self, pin: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: verify_pw1_user"); - - let verify = commands::verify_pw1_82(pin.to_vec()); - 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![]); - self.send_command(verify, false)?.try_into() - } - - /// Verify PW3 (admin). - pub fn verify_pw3(&mut self, pin: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: verify_pw3"); - - let verify = commands::verify_pw3(pin.to_vec()); - 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 reset the pin) - pub fn check_pw3(&mut self) -> Result<(), Error> { - log::info!("OpenPgpTransaction: check_pw3"); - - let verify = commands::verify_pw3(vec![]); - 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: &[u8], new: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: change_pw1"); - - let mut data = vec![]; - data.extend(old); - data.extend(new); - - let change = commands::change_pw1(data); - 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: &[u8], new: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: change_pw3"); - - let mut data = vec![]; - data.extend(old); - data.extend(new); - - let change = commands::change_pw3(data); - 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: &[u8], - resetting_code: Option<&[u8]>, - ) -> Result<(), Error> { - log::info!("OpenPgpTransaction: reset_retry_counter_pw1"); - - let reset = commands::reset_retry_counter_pw1(resetting_code, new_pw1); - self.send_command(reset, 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 `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)?; - resp.check_ok()?; - - Ok(resp.data().map(|d| d.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 [pso_decipher](OpenPgpTransaction::pso_decipher) - /// and then sets the *Decryption* key to be used for [internal_authenticate](OpenPgpTransaction::internal_authenticate). - /// - /// ```no_run - /// # use openpgp_card::{KeyType, OpenPgpTransaction}; - /// # let mut tx: OpenPgpTransaction<'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 - /// 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 `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"); - - assert!((1..=4).contains(&num)); - - let cmd = commands::put_private_use_do(num, data); - let resp = self.send_command(cmd, true)?; - - Ok(resp.data()?.to_vec()) - } - - pub fn set_login(&mut self, login: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_login"); - let put_login_data = commands::put_login_data(login.to_vec()); - self.send_command(put_login_data, false)?.try_into() - } - - pub fn set_name(&mut self, name: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_name"); - - let put_name = commands::put_name(name.to_vec()); - self.send_command(put_name, 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| Into::>::into(l)) - .collect(); - - let put_lang = commands::put_lang(bytes); - self.send_command(put_lang, false)?.try_into() - } - - pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_sex"); - - let put_sex = commands::put_sex((&sex).into()); - self.send_command(put_sex, false)?.try_into() - } - - pub fn set_url(&mut self, url: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_url"); - - let put_url = commands::put_url(url.to_vec()); - self.send_command(put_url, 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 - /// (4.4.3.9 Algorithm Attributes) - pub fn set_algorithm_attributes( - &mut self, - key_type: KeyType, - algo: &AlgorithmAttributes, - ) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_algorithm_attributes"); - - // Command to PUT the algorithm attributes - let cmd = commands::put_data(key_type.algorithm_tag(), algo.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 fp_cmd = commands::put_data(key_type.fingerprint_put_tag(), fp.as_bytes().to_vec()); - - self.send_command(fp_cmd, false)?.try_into() - } - - pub fn set_ca_fingerprint_1(&mut self, fp: Fingerprint) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_ca_fingerprint_1"); - - let fp_cmd = commands::put_data(Tags::CaFingerprint1, fp.as_bytes().to_vec()); - self.send_command(fp_cmd, false)?.try_into() - } - - pub fn set_ca_fingerprint_2(&mut self, fp: Fingerprint) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_ca_fingerprint_2"); - - let fp_cmd = commands::put_data(Tags::CaFingerprint2, fp.as_bytes().to_vec()); - self.send_command(fp_cmd, false)?.try_into() - } - - pub fn set_ca_fingerprint_3(&mut self, fp: Fingerprint) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_ca_fingerprint_3"); - - let fp_cmd = commands::put_data(Tags::CaFingerprint3, fp.as_bytes().to_vec()); - self.send_command(fp_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() - .iter() - .skip_while(|&&e| e == 0) - .copied() - .collect(); - - let time_cmd = commands::put_data(key_type.timestamp_put_tag(), time_value); - - self.send_command(time_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: &[u8]) -> Result<(), Error> { - log::info!("OpenPgpTransaction: set_resetting_code"); - - let cmd = commands::put_data(Tags::ResettingCode, resetting_code.to_vec()); - 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 fp_cmd = commands::put_data(Tags::PsoEncDecKey, key.to_vec()); - - self.send_command(fp_cmd, false)?.try_into() - } - - // FIXME: optional DO for PSO:ENC/DEC with AES - - /// Set UIF for PSO:CDS - pub fn set_uif_pso_cds(&mut self, uif: &UIF) -> 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: &UIF) -> 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: &UIF) -> 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: &UIF) -> 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 - - // FIXME: KDF DO - - // 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> { - // An error is ok - it's fine if a card doesn't offer a list of - // supported algorithms - let algo_info = self.algorithm_information().unwrap_or(None); - - keys::key_import(self, key, key_type, algo_info) - } - - /// Generate a key on the card. - /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) - /// - /// If the `algorithm_attributes` parameter is Some, then this algorithm will be set on - /// the card for "key_type". - /// - /// Note: `algorithm_attributes` needs to precisely specify the RSA bitsize of e (if - /// applicable), and import format, with values that the current card - /// supports. - pub fn generate_key( - &mut self, - fp_from_pub: fn( - &PublicKeyMaterial, - KeyGenerationTime, - KeyType, - ) -> Result, - key_type: KeyType, - algorithm_attributes: Option<&AlgorithmAttributes>, - ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - keys::gen_key_with_metadata(self, fp_from_pub, key_type, algorithm_attributes) - } - - /// Generate a key on the card. - /// (7.2.14 GENERATE ASYMMETRIC KEY PAIR) - /// - /// This is a wrapper around generate_key() which allows - /// using the simplified `AlgoSimple` algorithm selector enum. - /// - /// Note: AlgoSimple doesn't specify card specific details (such as - /// bitsize of e for RSA, and import format). This function determines - /// these values based on information from the card. - pub fn generate_key_simple( - &mut self, - fp_from_pub: fn( - &PublicKeyMaterial, - KeyGenerationTime, - KeyType, - ) -> Result, - key_type: KeyType, - simple: AlgoSimple, - ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - let ard = self.application_related_data()?; - let algo_info = if let Ok(ai) = self.algorithm_information() { - ai - } else { - None - }; - - let algo = simple.determine_algo_attributes(key_type, &ard, algo_info)?; - - Self::generate_key(self, fp_from_pub, key_type, Some(&algo)) - } - - /// 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::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(), - } -} diff --git a/openpgp-card/src/tags.rs b/openpgp-card/src/tags.rs new file mode 100644 index 0000000..bd8b1af --- /dev/null +++ b/openpgp-card/src/tags.rs @@ -0,0 +1,259 @@ +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::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)] +#[non_exhaustive] +#[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 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], + } + } +} diff --git a/openpgp-card/src/tlv.rs b/openpgp-card/src/tlv.rs index b661f93..2a10d87 100644 --- a/openpgp-card/src/tlv.rs +++ b/openpgp-card/src/tlv.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 pub(crate) mod length; @@ -87,7 +87,8 @@ mod test { use hex_literal::hex; use super::{Tlv, Value}; - use crate::{Error, Tags}; + use crate::tags::Tags; + use crate::Error; #[test] fn test_tlv0() {