diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index 09dc27f..f8c835d 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -14,7 +14,7 @@ use openpgp_card; use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::{KeyGenerationTime, Sex}; use openpgp_card::{CardApp, Error, KeyType, StatusBytes}; -use openpgp_card_sequoia::{ +use openpgp_card_sequoia::util::{ make_cert, public_key_material_to_key, public_to_fingerprint, }; @@ -64,7 +64,8 @@ pub fn test_decrypt( ca.verify_pw1("123456")?; - let res = openpgp_card_sequoia::decrypt(&mut ca, &cert, msg.into_bytes())?; + let res = + openpgp_card_sequoia::util::decrypt(&mut ca, &cert, msg.into_bytes())?; let plain = String::from_utf8_lossy(&res); assert_eq!(plain, "Hello world!\n"); @@ -84,7 +85,8 @@ pub fn test_sign( let cert = Cert::from_str(param[0])?; let msg = "Hello world, I am signed."; - let sig = openpgp_card_sequoia::sign(&mut ca, &cert, &mut msg.as_bytes())?; + let sig = + openpgp_card_sequoia::util::sign(&mut ca, &cert, &mut msg.as_bytes())?; // validate sig assert!(util::verify_sig(&cert, msg.as_bytes(), sig.as_bytes())?); @@ -257,11 +259,7 @@ pub fn test_get_pub( let sig = ca.get_pub_key(KeyType::Signing)?; let ts = key_gen.signature().unwrap().get().into(); - let key = openpgp_card_sequoia::public_key_material_to_key( - &sig, - KeyType::Signing, - ts, - )?; + let key = public_key_material_to_key(&sig, KeyType::Signing, ts)?; println!(" sig key data from card -> {:x?}", key); @@ -269,11 +267,7 @@ pub fn test_get_pub( let dec = ca.get_pub_key(KeyType::Decryption)?; let ts = key_gen.decryption().unwrap().get().into(); - let key = openpgp_card_sequoia::public_key_material_to_key( - &dec, - KeyType::Decryption, - ts, - )?; + let key = public_key_material_to_key(&dec, KeyType::Decryption, ts)?; println!(" dec key data from card -> {:x?}", key); @@ -281,11 +275,7 @@ pub fn test_get_pub( let auth = ca.get_pub_key(KeyType::Authentication)?; let ts = key_gen.authentication().unwrap().get().into(); - let key = openpgp_card_sequoia::public_key_material_to_key( - &auth, - KeyType::Authentication, - ts, - )?; + let key = public_key_material_to_key(&auth, KeyType::Authentication, ts)?; println!(" auth key data from card -> {:x?}", key); diff --git a/card-functionality/src/util.rs b/card-functionality/src/util.rs index d128e51..60071dd 100644 --- a/card-functionality/src/util.rs +++ b/card-functionality/src/util.rs @@ -20,7 +20,7 @@ use sequoia_openpgp::Cert; use openpgp_card::card_do::KeyGenerationTime; use openpgp_card::{CardApp, KeyType}; -use openpgp_card_sequoia::vka_as_uploadable_key; +use openpgp_card_sequoia::util::vka_as_uploadable_key; pub const SP: &StandardPolicy = &StandardPolicy::new(); diff --git a/openpgp-card-sequoia/src/card.rs b/openpgp-card-sequoia/src/card.rs new file mode 100644 index 0000000..e75cfbb --- /dev/null +++ b/openpgp-card-sequoia/src/card.rs @@ -0,0 +1,350 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Perform operations on a card. Different states of a card are modeled by +//! different types, such as `Open`, `User`, `Sign`, `Admin`. + +use anyhow::{anyhow, Result}; +use std::ops::{Deref, DerefMut}; + +use sequoia_openpgp::policy::Policy; +use sequoia_openpgp::Cert; + +use openpgp_card::algorithm::{Algo, AlgoInfo}; +use openpgp_card::card_do::{ + ApplicationIdentifier, ApplicationRelatedData, CardholderRelatedData, + ExCapFeatures, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint, + HistoricalBytes, PWStatusBytes, SecuritySupportTemplate, Sex, +}; +use openpgp_card::crypto_data::CardUploadableKey; +use openpgp_card::{CardApp, CardClientBox, Error, KeySet, KeyType, Response}; + +use crate::decryptor::CardDecryptor; +use crate::signer::CardSigner; + +/// Representation of an opened OpenPGP card in its base state (i.e. no +/// passwords have been verified, default authorization applies). +pub struct Open { + card_app: CardApp, + + // Cache of "application related data". + // + // FIXME: Should be invalidated when changing data on the card! + // (e.g. uploading keys, etc) + ard: ApplicationRelatedData, +} + +impl Open { + /// Set up connection to a CardClient (read and cache "application + /// related data"). + /// + /// The OpenPGP applet must already be opened in the CardClient. + pub fn open_card(ccb: CardClientBox) -> Result { + // read and cache "application related data" + let mut card_app = CardApp::from(ccb); + + let ard = card_app.get_application_related_data()?; + + card_app.init_caps(&ard)?; + + Ok(Self { card_app, ard }) + } + + // --- application data --- + + /// Load "application related data". + /// + /// This is done once, after opening the OpenPGP card applet + /// (the data is stored in the OpenPGPCard object). + fn get_app_data(&mut self) -> Result { + self.card_app.get_application_related_data() + } + + pub fn get_application_id(&self) -> Result { + self.ard.get_application_id() + } + + pub fn get_historical(&self) -> Result { + self.ard.get_historical() + } + + pub fn get_extended_length_information( + &self, + ) -> Result> { + self.ard.get_extended_length_information() + } + + pub fn get_general_feature_management() -> Option { + unimplemented!() + } + + pub fn get_discretionary_data_objects() { + unimplemented!() + } + + pub fn get_extended_capabilities( + &self, + ) -> Result { + self.ard.get_extended_capabilities() + } + + pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result { + self.ard.get_algorithm_attributes(key_type) + } + + /// PW status Bytes + pub fn get_pw_status_bytes(&self) -> Result { + self.ard.get_pw_status_bytes() + } + + pub fn get_fingerprints(&self) -> Result, Error> { + self.ard.get_fingerprints() + } + + pub fn get_ca_fingerprints(&self) { + unimplemented!() + } + + pub fn get_key_generation_times() { + unimplemented!() + } + + pub fn get_key_information() { + unimplemented!() + } + + pub fn get_uif_pso_cds() { + unimplemented!() + } + + pub fn get_uif_pso_dec() { + unimplemented!() + } + + pub fn get_uif_pso_aut() { + unimplemented!() + } + pub fn get_uif_attestation() { + unimplemented!() + } + + // --- optional private DOs (0101 - 0104) --- + + // --- login data (5e) --- + + // --- URL (5f50) --- + + pub fn get_url(&mut self) -> Result { + self.card_app.get_url() + } + + // --- cardholder related data (65) --- + pub fn get_cardholder_related_data( + &mut self, + ) -> Result { + self.card_app.get_cardholder_related_data() + } + + // --- security support template (7a) --- + pub fn get_security_support_template( + &mut self, + ) -> Result { + self.card_app.get_security_support_template() + } + + // DO "Algorithm Information" (0xFA) + pub fn list_supported_algo(&mut self) -> Result> { + // The DO "Algorithm Information" (Tag FA) shall be present if + // Algorithm attributes can be changed + let ec = self.get_extended_capabilities()?; + if !ec.features().contains(&ExCapFeatures::AlgoAttrsChangeable) { + // Algorithm attributes can not be changed, + // list_supported_algo is not supported + return Ok(None); + } + + self.card_app.get_algo_info() + } + + // ---------- + + /// Delete all state on this OpenPGP card + pub fn factory_reset(&mut self) -> Result<()> { + self.card_app.factory_reset() + } + + pub fn verify_pw1_for_signing(mut self, pin: &str) -> Result { + assert!(pin.len() >= 6); // FIXME: Err + + if self.card_app.verify_pw1_for_signing(pin).is_ok() { + Ok(Sign { oc: self }) + } else { + Err(self) + } + } + + pub fn check_pw1(&mut self) -> Result { + self.card_app.check_pw1() + } + + pub fn verify_pw1(mut self, pin: &str) -> Result { + assert!(pin.len() >= 6); // FIXME: Err + + if self.card_app.verify_pw1(pin).is_ok() { + Ok(User { oc: self }) + } else { + Err(self) + } + } + + pub fn check_pw3(&mut self) -> Result { + self.card_app.check_pw3() + } + + pub fn verify_pw3(mut self, pin: &str) -> Result { + assert!(pin.len() >= 8); // FIXME: Err + + if self.card_app.verify_pw3(pin).is_ok() { + Ok(Admin { oc: self }) + } else { + Err(self) + } + } +} + +/// An OpenPGP card after successful verification of PW1 in mode 82 +/// (verification for operations other than signing) +pub struct User { + oc: Open, +} + +/// Allow access to fn of OpenPGPCard, through OpenPGPCardUser. +impl Deref for User { + type Target = Open; + + fn deref(&self) -> &Self::Target { + &self.oc + } +} + +/// Allow access to fn of CardBase, through CardUser. +impl DerefMut for User { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.oc + } +} + +impl User { + pub fn decryptor( + &mut self, + cert: &Cert, + policy: &dyn Policy, + ) -> Result { + CardDecryptor::new(&mut self.card_app, cert, policy) + } +} + +/// An OpenPGP card after successful verification of PW1 in mode 81 +/// (verification for signing) +pub struct Sign { + oc: Open, +} + +/// Allow access to fn of CardBase, through CardSign. +impl Deref for Sign { + type Target = Open; + + fn deref(&self) -> &Self::Target { + &self.oc + } +} + +/// Allow access to fn of CardBase, through CardSign. +impl DerefMut for Sign { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.oc + } +} + +// FIXME: depending on the setting in "PW1 Status byte", only one +// signature can be made after verification for signing +impl Sign { + pub fn signer( + &mut self, + cert: &Cert, + policy: &dyn Policy, + ) -> std::result::Result { + CardSigner::new(&mut self.card_app, cert, policy) + } +} + +/// An OpenPGP card after successful verification of PW3 ("Admin privileges") +pub struct Admin { + oc: Open, +} + +/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin. +impl Deref for Admin { + type Target = Open; + + fn deref(&self) -> &Self::Target { + &self.oc + } +} + +/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin. +impl DerefMut for Admin { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.oc + } +} + +impl Admin { + pub fn set_name(&mut self, name: &str) -> Result { + if name.len() >= 40 { + return Err(anyhow!("name too long").into()); + } + + // All chars must be in ASCII7 + if name.chars().any(|c| !c.is_ascii()) { + return Err(anyhow!("Invalid char in name").into()); + }; + + self.card_app.set_name(name) + } + + pub fn set_lang(&mut self, lang: &str) -> Result { + if lang.len() > 8 { + return Err(anyhow!("lang too long").into()); + } + + self.card_app.set_lang(lang) + } + + pub fn set_sex(&mut self, sex: Sex) -> Result { + self.card_app.set_sex(sex) + } + + pub fn set_url(&mut self, url: &str) -> Result { + if url.chars().any(|c| !c.is_ascii()) { + return Err(anyhow!("Invalid char in url").into()); + } + + // Check for max len + let ec = self.get_extended_capabilities()?; + + if url.len() < ec.max_len_special_do() as usize { + self.card_app.set_url(url) + } else { + Err(anyhow!("URL too long").into()) + } + } + + pub fn upload_key( + &mut self, + key: Box, + key_type: KeyType, + ) -> Result<(), Error> { + self.card_app.key_import(key, key_type) + } +} diff --git a/openpgp-card-sequoia/src/decryptor.rs b/openpgp-card-sequoia/src/decryptor.rs index 13b9082..bd73f0b 100644 --- a/openpgp-card-sequoia/src/decryptor.rs +++ b/openpgp-card-sequoia/src/decryptor.rs @@ -20,7 +20,7 @@ use openpgp_card::{CardApp, Error}; use crate::PublicKey; -pub(crate) struct CardDecryptor<'a> { +pub struct CardDecryptor<'a> { /// The OpenPGP card (authenticated to allow decryption operations) ca: &'a mut CardApp, diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 62bd224..0302d5d 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -1,890 +1,18 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -//! This is a higher-level wrapper around the openpgp-card crate. +//! A higher-level wrapper around the openpgp-card crate. //! It uses sequoia_openpgp for OpenPGP operations. -use anyhow::{anyhow, Context, Result}; -use std::convert::TryFrom; -use std::convert::TryInto; -use std::io; -use std::ops::{Deref, DerefMut}; -use std::time::SystemTime; - -use openpgp::armor; -use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation; -use openpgp::crypto::mpi; -use openpgp::crypto::mpi::{ProtectedMPI, MPI}; -use openpgp::packet::key::KeyRole; -use openpgp::packet::key::{Key4, PublicParts}; -use openpgp::packet::key::{PrimaryRole, SubordinateRole}; -use openpgp::packet::key::{SecretParts, UnspecifiedRole}; -use openpgp::packet::signature::SignatureBuilder; -use openpgp::packet::UserID; use openpgp::packet::{key, Key}; -use openpgp::parse::{stream::DecryptorBuilder, Parse}; -use openpgp::policy::StandardPolicy; -use openpgp::serialize::stream::{Message, Signer}; -use openpgp::types::Timestamp; -use openpgp::types::{KeyFlags, PublicKeyAlgorithm, SignatureType}; -use openpgp::{Cert, Packet}; use sequoia_openpgp as openpgp; -use openpgp_card::algorithm::{Algo, AlgoInfo, Curve}; -use openpgp_card::card_do::{ - ApplicationIdentifier, ApplicationRelatedData, CardholderRelatedData, - ExCapFeatures, ExtendedCapabilities, ExtendedLengthInfo, Fingerprint, - HistoricalBytes, KeyGenerationTime, PWStatusBytes, - SecuritySupportTemplate, Sex, -}; -use openpgp_card::crypto_data::{ - CardUploadableKey, Cryptogram, EccKey, EccType, Hash, PrivateKeyMaterial, - PublicKeyMaterial, RSAKey, -}; -use openpgp_card::{CardApp, CardClientBox, Error, KeySet, KeyType, Response}; - -use crate::signer::CardSigner; - +pub mod card; mod decryptor; -pub mod signer; +mod privkey; +mod signer; +pub mod sq_util; +pub mod util; -/// Shorthand for public key data +/// Shorthand for Sequoia public key data (a single public (sub)key) pub(crate) type PublicKey = Key; - -/// A SequoiaKey represents the private cryptographic key material of an -/// OpenPGP (sub)key to be uploaded to an OpenPGP card. -struct SequoiaKey { - key: openpgp::packet::Key, - public: mpi::PublicKey, - password: Option, -} - -impl SequoiaKey { - /// A `SequoiaKey` wraps a Sequoia PGP private (sub)key data - /// (i.e. a ValidErasedKeyAmalgamation) in a form that can be uploaded - /// by the openpgp-card crate. - fn new( - vka: ValidErasedKeyAmalgamation, - password: Option, - ) -> Self { - let public = vka.parts_as_public().mpis().clone(); - - Self { - key: vka.key().clone(), - public, - password, - } - } -} - -/// Helper fn: get a CardUploadableKey for a ValidErasedKeyAmalgamation -pub fn vka_as_uploadable_key( - vka: ValidErasedKeyAmalgamation, - password: Option, -) -> Box { - let sqk = SequoiaKey::new(vka, password); - Box::new(sqk) -} - -/// Helper fn: get a Key for a PublicKeyMaterial -pub fn public_key_material_to_key( - pkm: &PublicKeyMaterial, - key_type: KeyType, - time: KeyGenerationTime, -) -> Result> { - let time = Timestamp::from(time.get()).into(); - - match pkm { - PublicKeyMaterial::R(rsa) => { - let k4 = Key4::import_public_rsa(rsa.v(), rsa.n(), Some(time))?; - - Ok(k4.into()) - } - PublicKeyMaterial::E(ecc) => { - let algo = ecc.algo().clone(); // FIXME? - if let Algo::Ecc(algo_ecc) = algo { - let curve = match algo_ecc.curve() { - Curve::NistP256r1 => openpgp::types::Curve::NistP256, - Curve::NistP384r1 => openpgp::types::Curve::NistP384, - Curve::NistP521r1 => openpgp::types::Curve::NistP521, - Curve::Ed25519 => openpgp::types::Curve::Ed25519, - Curve::Cv25519 => openpgp::types::Curve::Cv25519, - c => unimplemented!("unhandled curve: {:?}", c), - }; - - match key_type { - KeyType::Authentication | KeyType::Signing => { - if algo_ecc.curve() == Curve::Ed25519 { - // EdDSA - let k4 = - Key4::import_public_ed25519(ecc.data(), time)?; - - Ok(Key::from(k4)) - } else { - // ECDSA - let k4 = Key4::new( - time, - PublicKeyAlgorithm::ECDSA, - mpi::PublicKey::ECDSA { - curve, - q: mpi::MPI::new(ecc.data()), - }, - )?; - - Ok(k4.into()) - } - } - KeyType::Decryption => { - if algo_ecc.curve() == Curve::Cv25519 { - // EdDSA - let k4 = Key4::import_public_cv25519( - ecc.data(), - None, - None, - time, - )?; - - Ok(k4.into()) - } else { - // FIXME: just defining `hash` and `sym` is not - // ok when a cert already exists - - // ECDH - let k4 = Key4::new( - time, - PublicKeyAlgorithm::ECDH, - mpi::PublicKey::ECDH { - curve, - q: mpi::MPI::new(ecc.data()), - hash: Default::default(), - sym: Default::default(), - }, - )?; - - Ok(k4.into()) - } - } - _ => unimplemented!("Unsupported KeyType"), - } - } else { - panic!("unexpected algo {:?}", algo); - } - } - _ => unimplemented!("Unexpected PublicKeyMaterial type"), - } -} - -/// Create a Cert from the three subkeys on a card. -/// (Calling this multiple times will result in different Certs!) -/// -/// FIXME: make dec/auth keys optional -/// -/// FIXME: accept optional metadata for user_id(s)? -pub fn make_cert( - ca: &mut CardApp, - key_sig: Key, - key_dec: Key, - key_aut: Key, -) -> Result { - let mut pp = vec![]; - - // 1) use the signing key as primary key - let pri = PrimaryRole::convert_key(key_sig.clone()); - pp.push(Packet::from(pri)); - - // 2) add decryption key as subkey - let sub_dec = SubordinateRole::convert_key(key_dec); - pp.push(Packet::from(sub_dec.clone())); - - // Temporary version of the cert - let cert = Cert::try_from(pp.clone())?; - - // 3) make binding, sign with card -> add - { - let signing_builder = - SignatureBuilder::new(SignatureType::SubkeyBinding) - .set_signature_creation_time(SystemTime::now())? - .set_key_validity_period(std::time::Duration::new(0, 0))? - .set_key_flags( - KeyFlags::empty() - .set_storage_encryption() - .set_transport_encryption(), - )?; - - // Allow signing on the card - ca.verify_pw1_for_signing("123456")?; - - // Card-backed signer for bindings - let mut card_signer = CardSigner::with_pubkey(ca, key_sig.clone()); - - let signing_bsig: Packet = sub_dec - .bind(&mut card_signer, &cert, signing_builder)? - .into(); - - pp.push(signing_bsig); - } - - // 4) add auth subkey - let sub_aut = SubordinateRole::convert_key(key_aut); - pp.push(Packet::from(sub_aut.clone())); - - // 5) make, sign binding -> add - { - let signing_builder = - SignatureBuilder::new(SignatureType::SubkeyBinding) - .set_signature_creation_time(SystemTime::now())? - .set_key_validity_period(std::time::Duration::new(0, 0))? - .set_key_flags(KeyFlags::empty().set_authentication())?; - - // Allow signing on the card - ca.verify_pw1_for_signing("123456")?; - - // Card-backed signer for bindings - let mut card_signer = CardSigner::with_pubkey(ca, key_sig.clone()); - - let signing_bsig: Packet = sub_aut - .bind(&mut card_signer, &cert, signing_builder)? - .into(); - - pp.push(signing_bsig); - } - - // 6) add user id from name / email - let cardholder = ca.get_cardholder_related_data()?; - - // FIXME: process name field? accept email as argument?! - let uid: UserID = - cardholder.name().expect("expecting name on card").into(); - - pp.push(uid.clone().into()); - - // 7) make, sign binding -> add - { - let signing_builder = - SignatureBuilder::new(SignatureType::PositiveCertification) - .set_signature_creation_time(SystemTime::now())? - .set_key_validity_period(std::time::Duration::new(0, 0))? - .set_key_flags( - // Flags for primary key - KeyFlags::empty().set_signing().set_certification(), - )?; - - // Allow signing on the card - ca.verify_pw1_for_signing("123456")?; - - // Card-backed signer for bindings - let mut card_signer = CardSigner::with_pubkey(ca, key_sig); - - let signing_bsig: Packet = - uid.bind(&mut card_signer, &cert, signing_builder)?.into(); - - pp.push(signing_bsig); - } - - Cert::try_from(pp) -} - -/// Implement the `CardUploadableKey` trait that openpgp-card uses to -/// upload (sub)keys to a card. -impl CardUploadableKey for SequoiaKey { - fn get_key(&self) -> Result { - // Decrypt key with password, if set - let key = match &self.password { - None => self.key.clone(), - Some(pw) => self.key.clone().decrypt_secret( - &openpgp::crypto::Password::from(pw.as_str()), - )?, - }; - - // Get private cryptographic material - let unenc = if let Some( - openpgp::packet::key::SecretKeyMaterial::Unencrypted(ref u), - ) = key.optional_secret() - { - u - } else { - panic!("can't get private key material"); - }; - - let secret_key_material = unenc.map(|mpis| mpis.clone()); - - match (self.public.clone(), secret_key_material) { - ( - mpi::PublicKey::RSA { e, n }, - mpi::SecretKeyMaterial::RSA { d: _, p, q, u: _ }, - ) => { - let sq_rsa = SqRSA::new(e, n, p, q); - - Ok(PrivateKeyMaterial::R(Box::new(sq_rsa))) - } - ( - mpi::PublicKey::ECDH { curve, .. }, - mpi::SecretKeyMaterial::ECDH { scalar }, - ) => { - let sq_ecc = - SqEccKey::new(curve.oid().to_vec(), scalar, EccType::ECDH); - - Ok(PrivateKeyMaterial::E(Box::new(sq_ecc))) - } - ( - mpi::PublicKey::ECDSA { curve, .. }, - mpi::SecretKeyMaterial::ECDSA { scalar }, - ) => { - let sq_ecc = SqEccKey::new( - curve.oid().to_vec(), - scalar, - EccType::ECDSA, - ); - - Ok(PrivateKeyMaterial::E(Box::new(sq_ecc))) - } - ( - mpi::PublicKey::EdDSA { curve, .. }, - mpi::SecretKeyMaterial::EdDSA { scalar }, - ) => { - let sq_ecc = SqEccKey::new( - curve.oid().to_vec(), - scalar, - EccType::EdDSA, - ); - - Ok(PrivateKeyMaterial::E(Box::new(sq_ecc))) - } - (p, s) => { - unimplemented!( - "Unexpected algorithms: public {:?}, \ - secret {:?}", - p, - s - ); - } - } - } - - /// Number of non-leap seconds since January 1, 1970 0:00:00 UTC - /// (aka "UNIX timestamp") - fn get_ts(&self) -> KeyGenerationTime { - let ts: Timestamp = Timestamp::try_from(self.key.creation_time()) - .expect("Creation time cannot be converted into u32 timestamp"); - let ts: u32 = ts.into(); - - ts.into() - } - - fn get_fp(&self) -> Result { - let fp = self.key.fingerprint(); - fp.as_bytes().try_into() - } -} - -/// RSA-specific data-structure to hold private (sub)key material for upload -/// with the `openpgp-card` crate. -struct SqRSA { - e: MPI, - n: MPI, - p: ProtectedMPI, - q: ProtectedMPI, -} - -impl SqRSA { - fn new(e: MPI, n: MPI, p: ProtectedMPI, q: ProtectedMPI) -> Self { - Self { e, n, p, q } - } -} - -impl RSAKey for SqRSA { - fn get_e(&self) -> &[u8] { - self.e.value() - } - - fn get_n(&self) -> &[u8] { - self.n.value() - } - - fn get_p(&self) -> &[u8] { - self.p.value() - } - - fn get_q(&self) -> &[u8] { - self.q.value() - } -} - -/// ECC-specific data-structure to hold private (sub)key material for upload -/// with the `openpgp-card` crate. -struct SqEccKey { - oid: Vec, - scalar: ProtectedMPI, - ecc_type: EccType, -} - -impl SqEccKey { - fn new(oid: Vec, scalar: ProtectedMPI, ecc_type: EccType) -> Self { - SqEccKey { - oid, - scalar, - ecc_type, - } - } -} - -impl EccKey for SqEccKey { - fn get_oid(&self) -> &[u8] { - &self.oid - } - - fn get_scalar(&self) -> &[u8] { - self.scalar.value() - } - - fn get_type(&self) -> EccType { - self.ecc_type - } -} - -/// Convenience fn to select and upload a (sub)key from a Cert, as a given -/// KeyType. If multiple suitable (sub)keys are found, the first one is -/// used. -/// -/// FIXME: picking the (sub)key to upload should probably done with -/// more intent. -pub fn upload_from_cert_yolo( - oca: &mut CardAdmin, - cert: &openpgp::Cert, - key_type: KeyType, - password: Option, -) -> Result<(), Box> { - let policy = StandardPolicy::new(); - - // Find all suitable (sub)keys for key_type. - let mut valid_ka = cert - .keys() - .with_policy(&policy, None) - .secret() - .alive() - .revoked(false); - valid_ka = match key_type { - KeyType::Decryption => valid_ka.for_storage_encryption(), - KeyType::Signing => valid_ka.for_signing(), - KeyType::Authentication => valid_ka.for_authentication(), - _ => return Err(anyhow!("Unexpected KeyType").into()), - }; - - // FIXME: for now, we just pick the first (sub)key from the list - if let Some(vka) = valid_ka.next() { - upload_key(oca, vka, key_type, password).map_err(|e| e.into()) - } else { - Err(anyhow!("No suitable (sub)key found").into()) - } -} - -/// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType. -/// -/// The caller needs to make sure that `vka` is suitable for `key_type`. -pub fn upload_key( - oca: &mut CardAdmin, - vka: ValidErasedKeyAmalgamation, - key_type: KeyType, - password: Option, -) -> Result<(), Error> { - let sqk = SequoiaKey::new(vka, password); - - oca.upload_key(Box::new(sqk), key_type) -} - -pub fn decrypt( - ca: &mut CardApp, - cert: &openpgp::Cert, - msg: Vec, -) -> Result> { - let mut decrypted = Vec::new(); - { - let reader = io::BufReader::new(&msg[..]); - - let p = StandardPolicy::new(); - let d = decryptor::CardDecryptor::new(ca, cert, &p)?; - - let db = DecryptorBuilder::from_reader(reader)?; - let mut decryptor = db.with_policy(&p, None, d)?; - - // Read all data from decryptor and store in decrypted - io::copy(&mut decryptor, &mut decrypted)?; - } - - Ok(decrypted) -} - -pub fn sign( - ca: &mut CardApp, - cert: &openpgp::Cert, - input: &mut dyn io::Read, -) -> Result { - let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?; - { - let p = StandardPolicy::new(); - let s = signer::CardSigner::new(ca, cert, &p)?; - - let message = Message::new(&mut armorer); - let mut message = Signer::new(message, s).detached().build()?; - - // Process input data, via message - io::copy(input, &mut message)?; - - message.finalize()?; - } - - let buffer = armorer.finalize()?; - - String::from_utf8(buffer).context("Failed to convert signature to utf8") -} - -/// Mapping function to get a fingerprint from "PublicKeyMaterial + -/// timestamp + KeyType" (intended for use with `CardApp.generate_key()`). -pub fn public_to_fingerprint( - pkm: &PublicKeyMaterial, - time: KeyGenerationTime, - kt: KeyType, -) -> Result { - // Transform PublicKeyMaterial into a Sequoia Key - let key = public_key_material_to_key(pkm, kt, time)?; - - // Get fingerprint from the Sequoia Key - let fp = key.fingerprint(); - fp.as_bytes().try_into() -} - -// -------- - -/// Representation of an opened OpenPGP card in its basic, freshly opened, -/// state (i.e. no passwords have been verified, default privileges apply). -pub struct CardBase { - card_app: CardApp, - - // Cache of "application related data". - // - // FIXME: Should be invalidated when changing data on the card! - // (e.g. uploading keys, etc) - ard: ApplicationRelatedData, -} - -impl CardBase { - pub fn new(card_app: CardApp, ard: ApplicationRelatedData) -> Self { - Self { card_app, ard } - } - - /// Get a reference to the internal CardApp object (for use in tests) - pub fn get_card_app(&mut self) -> &mut CardApp { - &mut self.card_app - } - - /// Set up connection (cache "application related data") to a - /// CardClient, on which the openpgp applet has already been opened. - pub fn open_card(ccb: CardClientBox) -> Result { - // read and cache "application related data" - let mut card_app = CardApp::from(ccb); - - let ard = card_app.get_application_related_data()?; - - card_app.init_caps(&ard)?; - - Ok(Self { card_app, ard }) - } - - // --- application data --- - - /// Load "application related data". - /// - /// This is done once, after opening the OpenPGP card applet - /// (the data is stored in the OpenPGPCard object). - fn get_app_data(&mut self) -> Result { - self.card_app.get_application_related_data() - } - - pub fn get_application_id(&self) -> Result { - self.ard.get_application_id() - } - - pub fn get_historical(&self) -> Result { - self.ard.get_historical() - } - - pub fn get_extended_length_information( - &self, - ) -> Result> { - self.ard.get_extended_length_information() - } - - pub fn get_general_feature_management() -> Option { - unimplemented!() - } - - pub fn get_discretionary_data_objects() { - unimplemented!() - } - - pub fn get_extended_capabilities( - &self, - ) -> Result { - self.ard.get_extended_capabilities() - } - - pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result { - self.ard.get_algorithm_attributes(key_type) - } - - /// PW status Bytes - pub fn get_pw_status_bytes(&self) -> Result { - self.ard.get_pw_status_bytes() - } - - pub fn get_fingerprints(&self) -> Result, Error> { - self.ard.get_fingerprints() - } - - pub fn get_ca_fingerprints(&self) { - unimplemented!() - } - - pub fn get_key_generation_times() { - unimplemented!() - } - - pub fn get_key_information() { - unimplemented!() - } - - pub fn get_uif_pso_cds() { - unimplemented!() - } - - pub fn get_uif_pso_dec() { - unimplemented!() - } - - pub fn get_uif_pso_aut() { - unimplemented!() - } - pub fn get_uif_attestation() { - unimplemented!() - } - - // --- optional private DOs (0101 - 0104) --- - - // --- login data (5e) --- - - // --- URL (5f50) --- - - pub fn get_url(&mut self) -> Result { - self.card_app.get_url() - } - - // --- cardholder related data (65) --- - pub fn get_cardholder_related_data( - &mut self, - ) -> Result { - self.card_app.get_cardholder_related_data() - } - - // --- security support template (7a) --- - pub fn get_security_support_template( - &mut self, - ) -> Result { - self.card_app.get_security_support_template() - } - - // DO "Algorithm Information" (0xFA) - pub fn list_supported_algo(&mut self) -> Result> { - // The DO "Algorithm Information" (Tag FA) shall be present if - // Algorithm attributes can be changed - let ec = self.get_extended_capabilities()?; - if !ec.features().contains(&ExCapFeatures::AlgoAttrsChangeable) { - // Algorithm attributes can not be changed, - // list_supported_algo is not supported - return Ok(None); - } - - self.card_app.get_algo_info() - } - - // ---------- - - /// Delete all state on this OpenPGP card - pub fn factory_reset(&mut self) -> Result<()> { - self.card_app.factory_reset() - } - - pub fn verify_pw1_for_signing( - mut self, - pin: &str, - ) -> Result { - assert!(pin.len() >= 6); // FIXME: Err - - if self.card_app.verify_pw1_for_signing(pin).is_ok() { - Ok(CardSign { oc: self }) - } else { - Err(self) - } - } - - pub fn check_pw1(&mut self) -> Result { - self.card_app.check_pw1() - } - - pub fn verify_pw1(mut self, pin: &str) -> Result { - assert!(pin.len() >= 6); // FIXME: Err - - if self.card_app.verify_pw1(pin).is_ok() { - Ok(CardUser { oc: self }) - } else { - Err(self) - } - } - - pub fn check_pw3(&mut self) -> Result { - self.card_app.check_pw3() - } - - pub fn verify_pw3(mut self, pin: &str) -> Result { - assert!(pin.len() >= 8); // FIXME: Err - - if self.card_app.verify_pw3(pin).is_ok() { - Ok(CardAdmin { oc: self }) - } else { - Err(self) - } - } -} - -/// An OpenPGP card after successful verification of PW1 in mode 82 -/// (verification for operations other than signing) -pub struct CardUser { - oc: CardBase, -} - -/// Allow access to fn of OpenPGPCard, through OpenPGPCardUser. -impl Deref for CardUser { - type Target = CardBase; - - fn deref(&self) -> &Self::Target { - &self.oc - } -} - -/// Allow access to fn of CardBase, through CardUser. -impl DerefMut for CardUser { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.oc - } -} - -impl CardUser { - /// Decrypt the ciphertext in `dm`, on the card. - pub fn decipher(&mut self, dm: Cryptogram) -> Result, Error> { - self.card_app.decipher(dm) - } -} - -/// An OpenPGP card after successful verification of PW1 in mode 81 -/// (verification for signing) -pub struct CardSign { - oc: CardBase, -} - -/// Allow access to fn of CardBase, through CardSign. -impl Deref for CardSign { - type Target = CardBase; - - fn deref(&self) -> &Self::Target { - &self.oc - } -} - -/// Allow access to fn of CardBase, through CardSign. -impl DerefMut for CardSign { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.oc - } -} - -// FIXME: depending on the setting in "PW1 Status byte", only one -// signature can be made after verification for signing -impl CardSign { - /// Sign the message in `hash`, on the card. - pub fn signature_for_hash( - &mut self, - hash: Hash, - ) -> Result, Error> { - self.card_app.signature_for_hash(hash) - } -} - -/// An OpenPGP card after successful verification of PW3 ("Admin privileges") -pub struct CardAdmin { - oc: CardBase, -} - -/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin. -impl Deref for CardAdmin { - type Target = CardBase; - - fn deref(&self) -> &Self::Target { - &self.oc - } -} - -/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin. -impl DerefMut for CardAdmin { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.oc - } -} - -impl CardAdmin { - pub fn set_name(&mut self, name: &str) -> Result { - if name.len() >= 40 { - return Err(anyhow!("name too long").into()); - } - - // All chars must be in ASCII7 - if name.chars().any(|c| !c.is_ascii()) { - return Err(anyhow!("Invalid char in name").into()); - }; - - self.card_app.set_name(name) - } - - pub fn set_lang(&mut self, lang: &str) -> Result { - if lang.len() > 8 { - return Err(anyhow!("lang too long").into()); - } - - self.card_app.set_lang(lang) - } - - pub fn set_sex(&mut self, sex: Sex) -> Result { - self.card_app.set_sex(sex) - } - - pub fn set_url(&mut self, url: &str) -> Result { - if url.chars().any(|c| !c.is_ascii()) { - return Err(anyhow!("Invalid char in url").into()); - } - - // Check for max len - let ec = self.get_extended_capabilities()?; - - if url.len() < ec.max_len_special_do() as usize { - self.card_app.set_url(url) - } else { - Err(anyhow!("URL too long").into()) - } - } - - pub fn upload_key( - &mut self, - key: Box, - key_type: KeyType, - ) -> Result<(), Error> { - self.card_app.key_import(key, key_type) - } -} diff --git a/openpgp-card-sequoia/src/main.rs b/openpgp-card-sequoia/src/main.rs index 4a633ca..c8b6dc4 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -6,6 +6,7 @@ use std::env; use std::error::Error; use sequoia_openpgp::parse::Parse; +use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::Cert; use openpgp_card::card_do::Sex; @@ -13,7 +14,8 @@ use openpgp_card::{CardApp, KeyType}; use openpgp_card_pcsc::PcscClient; // use openpgp_card_scdc::ScdClient; -use openpgp_card_sequoia::CardBase; +use openpgp_card_sequoia::card::Open; +use openpgp_card_sequoia::sq_util::{decryption_helper, sign_helper}; // Filename of test key and test message to use: @@ -38,7 +40,7 @@ fn main() -> Result<(), Box> { if let Ok(test_card_ident) = test_card_ident { println!("** get card"); let mut oc = - CardBase::open_card(PcscClient::open_by_ident(&test_card_ident)?)?; + Open::open_card(PcscClient::open_by_ident(&test_card_ident)?)?; // let mut oc = CardBase::open_card(ScdClient::open_by_serial( // None, @@ -123,14 +125,14 @@ fn main() -> Result<(), Box> { let cert = Cert::from_file(TEST_KEY_PATH)?; - openpgp_card_sequoia::upload_from_cert_yolo( + openpgp_card_sequoia::util::upload_from_cert_yolo( &mut oc_admin, &cert, KeyType::Decryption, None, )?; - openpgp_card_sequoia::upload_from_cert_yolo( + openpgp_card_sequoia::util::upload_from_cert_yolo( &mut oc_admin, &cert, KeyType::Signing, @@ -153,7 +155,7 @@ fn main() -> Result<(), Box> { // ----------------------------- let mut oc = - CardBase::open_card(PcscClient::open_by_ident(&test_card_ident)?)?; + Open::open_card(PcscClient::open_by_ident(&test_card_ident)?)?; // let mut oc = CardBase::open_card(ScdClient::open_by_serial( // None, @@ -181,11 +183,11 @@ fn main() -> Result<(), Box> { println!("{:?}", msg); - let res = openpgp_card_sequoia::decrypt( - &mut oc_user.get_card_app(), - &cert, - msg.into_bytes(), - )?; + let sp = StandardPolicy::new(); + + let d = oc_user.decryptor(&cert, &sp)?; + + let res = decryption_helper(d, msg.into_bytes(), &sp)?; let plain = String::from_utf8_lossy(&res); println!("decrypted plaintext: {}", plain); @@ -199,7 +201,7 @@ fn main() -> Result<(), Box> { // Open fresh Card for signing // ----------------------------- let oc = - CardBase::open_card(PcscClient::open_by_ident(&test_card_ident)?)?; + Open::open_card(PcscClient::open_by_ident(&test_card_ident)?)?; // let oc = CardBase::open_card(ScdClient::open_by_serial( // None, @@ -214,11 +216,10 @@ fn main() -> Result<(), Box> { let cert = Cert::from_file(TEST_KEY_PATH)?; let text = "Hello world, I am signed."; - let res = openpgp_card_sequoia::sign( - oc_user.get_card_app(), - &cert, - &mut text.as_bytes(), - ); + + let signer = oc_user.signer(&cert, &StandardPolicy::new())?; + + let res = sign_helper(signer, &mut text.as_bytes()); println!("res sign {:?}", res); diff --git a/openpgp-card-sequoia/src/privkey.rs b/openpgp-card-sequoia/src/privkey.rs new file mode 100644 index 0000000..63ce569 --- /dev/null +++ b/openpgp-card-sequoia/src/privkey.rs @@ -0,0 +1,205 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::convert::TryFrom; +use std::convert::TryInto; + +use anyhow::Result; +use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation; +use openpgp::crypto::{mpi, mpi::ProtectedMPI, mpi::MPI}; +use openpgp::packet::{ + key, + key::{SecretParts, UnspecifiedRole}, + Key, +}; +use openpgp::types::Timestamp; +use sequoia_openpgp as openpgp; + +use openpgp_card::card_do::{Fingerprint, KeyGenerationTime}; +use openpgp_card::crypto_data::{ + CardUploadableKey, EccKey, EccType, PrivateKeyMaterial, RSAKey, +}; +use openpgp_card::Error; + +/// A SequoiaKey represents the private cryptographic key material of an +/// OpenPGP (sub)key to be uploaded to an OpenPGP card. +pub(crate) struct SequoiaKey { + key: Key, + public: mpi::PublicKey, + password: Option, +} + +impl SequoiaKey { + /// A `SequoiaKey` wraps a Sequoia PGP private (sub)key data + /// (i.e. a ValidErasedKeyAmalgamation) in a form that can be uploaded + /// by the openpgp-card crate. + pub(crate) fn new( + vka: ValidErasedKeyAmalgamation, + password: Option, + ) -> Self { + let public = vka.parts_as_public().mpis().clone(); + + Self { + key: vka.key().clone(), + public, + password, + } + } +} + +/// Implement the `CardUploadableKey` trait that openpgp-card uses to +/// upload (sub)keys to a card. +impl CardUploadableKey for SequoiaKey { + fn get_key(&self) -> Result { + // Decrypt key with password, if set + let key = match &self.password { + None => self.key.clone(), + Some(pw) => self.key.clone().decrypt_secret( + &openpgp::crypto::Password::from(pw.as_str()), + )?, + }; + + // Get private cryptographic material + let unenc = if let Some(key::SecretKeyMaterial::Unencrypted(ref u)) = + key.optional_secret() + { + u + } else { + panic!("can't get private key material"); + }; + + let secret_key_material = unenc.map(|mpis| mpis.clone()); + + match (self.public.clone(), secret_key_material) { + ( + mpi::PublicKey::RSA { e, n }, + mpi::SecretKeyMaterial::RSA { d: _, p, q, u: _ }, + ) => { + let sq_rsa = SqRSA::new(e, n, p, q); + + Ok(PrivateKeyMaterial::R(Box::new(sq_rsa))) + } + ( + mpi::PublicKey::ECDH { curve, .. }, + mpi::SecretKeyMaterial::ECDH { scalar }, + ) => { + let sq_ecc = + SqEccKey::new(curve.oid().to_vec(), scalar, EccType::ECDH); + + Ok(PrivateKeyMaterial::E(Box::new(sq_ecc))) + } + ( + mpi::PublicKey::ECDSA { curve, .. }, + mpi::SecretKeyMaterial::ECDSA { scalar }, + ) => { + let sq_ecc = SqEccKey::new( + curve.oid().to_vec(), + scalar, + EccType::ECDSA, + ); + + Ok(PrivateKeyMaterial::E(Box::new(sq_ecc))) + } + ( + mpi::PublicKey::EdDSA { curve, .. }, + mpi::SecretKeyMaterial::EdDSA { scalar }, + ) => { + let sq_ecc = SqEccKey::new( + curve.oid().to_vec(), + scalar, + EccType::EdDSA, + ); + + Ok(PrivateKeyMaterial::E(Box::new(sq_ecc))) + } + (p, s) => { + unimplemented!( + "Unexpected algorithms: public {:?}, \ + secret {:?}", + p, + s + ); + } + } + } + + /// Number of non-leap seconds since January 1, 1970 0:00:00 UTC + /// (aka "UNIX timestamp") + fn get_ts(&self) -> KeyGenerationTime { + let ts: Timestamp = Timestamp::try_from(self.key.creation_time()) + .expect("Creation time cannot be converted into u32 timestamp"); + let ts: u32 = ts.into(); + + ts.into() + } + + fn get_fp(&self) -> Result { + let fp = self.key.fingerprint(); + fp.as_bytes().try_into() + } +} + +/// RSA-specific data-structure to hold private (sub)key material for upload +/// with the `openpgp-card` crate. +struct SqRSA { + e: MPI, + n: MPI, + p: ProtectedMPI, + q: ProtectedMPI, +} + +impl SqRSA { + fn new(e: MPI, n: MPI, p: ProtectedMPI, q: ProtectedMPI) -> Self { + Self { e, n, p, q } + } +} + +impl RSAKey for SqRSA { + fn get_e(&self) -> &[u8] { + self.e.value() + } + + fn get_n(&self) -> &[u8] { + self.n.value() + } + + fn get_p(&self) -> &[u8] { + self.p.value() + } + + fn get_q(&self) -> &[u8] { + self.q.value() + } +} + +/// ECC-specific data-structure to hold private (sub)key material for upload +/// with the `openpgp-card` crate. +struct SqEccKey { + oid: Vec, + scalar: ProtectedMPI, + ecc_type: EccType, +} + +impl SqEccKey { + fn new(oid: Vec, scalar: ProtectedMPI, ecc_type: EccType) -> Self { + SqEccKey { + oid, + scalar, + ecc_type, + } + } +} + +impl EccKey for SqEccKey { + fn get_oid(&self) -> &[u8] { + &self.oid + } + + fn get_scalar(&self) -> &[u8] { + self.scalar.value() + } + + fn get_type(&self) -> EccType { + self.ecc_type + } +} diff --git a/openpgp-card-sequoia/src/signer.rs b/openpgp-card-sequoia/src/signer.rs index 34f52d5..19ad19b 100644 --- a/openpgp-card-sequoia/src/signer.rs +++ b/openpgp-card-sequoia/src/signer.rs @@ -65,13 +65,12 @@ impl<'a> CardSigner<'a> { } } else { Err(Error::InternalError(anyhow!( - "Failed to get the signing key's Fingerprint \ - from the card" + "Failed to get the signing key's Fingerprint from the card" ))) } } - pub fn with_pubkey( + pub(crate) fn with_pubkey( ca: &'a mut CardApp, public: PublicKey, ) -> CardSigner<'a> { @@ -84,17 +83,18 @@ impl<'a> crypto::Signer for CardSigner<'a> { &self.public } - /// Delegate a signing operation to the OpenPGP card. - /// - /// This fn prepares the data structures that openpgp-card needs to - /// perform the signing operation. - /// - /// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) fn sign( &mut self, hash_algo: openpgp::types::HashAlgorithm, digest: &[u8], ) -> openpgp::Result { + // Delegate a signing operation to the OpenPGP card. + // + // This fn prepares the data structures that openpgp-card needs to + // perform the signing operation. + // + // (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) + match (self.public.pk_algo(), self.public.mpis()) { #[allow(deprecated)] (PublicKeyAlgorithm::RSASign, mpi::PublicKey::RSA { .. }) diff --git a/openpgp-card-sequoia/src/sq_util.rs b/openpgp-card-sequoia/src/sq_util.rs new file mode 100644 index 0000000..0b451a0 --- /dev/null +++ b/openpgp-card-sequoia/src/sq_util.rs @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Simple wrappers for performing very specific tasks with Sequoia PGP + +use anyhow::{Context, Result}; +use std::io; + +use openpgp::armor; +use openpgp::crypto; +use openpgp::parse::{ + stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper}, + Parse, +}; +use openpgp::policy::Policy; +use openpgp::serialize::stream::{Message, Signer}; +use sequoia_openpgp as openpgp; + +pub fn sign_helper(s: S, input: &mut dyn io::Read) -> Result +where + S: crypto::Signer + Send + Sync, +{ + let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?; + { + let message = Message::new(&mut armorer); + let mut message = Signer::new(message, s).detached().build()?; + + // Process input data, via message + io::copy(input, &mut message)?; + + message.finalize()?; + } + + let buffer = armorer.finalize()?; + + String::from_utf8(buffer).context("Failed to convert signature to utf8") +} + +pub fn decryption_helper( + d: D, + msg: Vec, + p: &dyn Policy, +) -> Result> +where + D: crypto::Decryptor + VerificationHelper + DecryptionHelper, +{ + let mut decrypted = Vec::new(); + { + let reader = io::BufReader::new(&msg[..]); + + let db = DecryptorBuilder::from_reader(reader)?; + let mut decryptor = db.with_policy(p, None, d)?; + + // Read all data from decryptor and store in decrypted + io::copy(&mut decryptor, &mut decrypted)?; + } + + Ok(decrypted) +} diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs new file mode 100644 index 0000000..9398cf3 --- /dev/null +++ b/openpgp-card-sequoia/src/util.rs @@ -0,0 +1,356 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::convert::TryFrom; +use std::convert::TryInto; +use std::io; +use std::time::SystemTime; + +use anyhow::{anyhow, Context, Result}; + +use openpgp::armor; +use openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation; +use openpgp::crypto::mpi; +use openpgp::packet::{ + key::{Key4, KeyRole, PrimaryRole, SecretParts, SubordinateRole}, + signature::SignatureBuilder, + Key, UserID, +}; +use openpgp::parse::{stream::DecryptorBuilder, Parse}; +use openpgp::policy::StandardPolicy; +use openpgp::serialize::stream::{Message, Signer}; +use openpgp::types::{KeyFlags, PublicKeyAlgorithm, SignatureType, Timestamp}; +use openpgp::{Cert, Packet}; +use sequoia_openpgp as openpgp; + +use openpgp_card::algorithm::{Algo, Curve}; +use openpgp_card::card_do::{Fingerprint, KeyGenerationTime}; +use openpgp_card::crypto_data::{CardUploadableKey, PublicKeyMaterial}; +use openpgp_card::{CardApp, Error, KeyType}; + +use crate::card::Admin; +use crate::privkey::SequoiaKey; +use crate::signer::CardSigner; +use crate::{decryptor, signer, PublicKey}; + +/// Create a Cert from the three subkeys on a card. +/// (Calling this multiple times will result in different Certs!) +/// +/// FIXME: make dec/auth keys optional +/// +/// FIXME: accept optional metadata for user_id(s)? +pub fn make_cert( + ca: &mut CardApp, + key_sig: PublicKey, + key_dec: PublicKey, + key_aut: PublicKey, +) -> Result { + let mut pp = vec![]; + + // 1) use the signing key as primary key + let pri = PrimaryRole::convert_key(key_sig.clone()); + pp.push(Packet::from(pri)); + + // 2) add decryption key as subkey + let sub_dec = SubordinateRole::convert_key(key_dec); + pp.push(Packet::from(sub_dec.clone())); + + // Temporary version of the cert + let cert = Cert::try_from(pp.clone())?; + + // 3) make binding, sign with card -> add + { + let signing_builder = + SignatureBuilder::new(SignatureType::SubkeyBinding) + .set_signature_creation_time(SystemTime::now())? + .set_key_validity_period(std::time::Duration::new(0, 0))? + .set_key_flags( + KeyFlags::empty() + .set_storage_encryption() + .set_transport_encryption(), + )?; + + // Allow signing on the card + ca.verify_pw1_for_signing("123456")?; + + // Card-backed signer for bindings + let mut card_signer = CardSigner::with_pubkey(ca, key_sig.clone()); + + let signing_bsig: Packet = sub_dec + .bind(&mut card_signer, &cert, signing_builder)? + .into(); + + pp.push(signing_bsig); + } + + // 4) add auth subkey + let sub_aut = SubordinateRole::convert_key(key_aut); + pp.push(Packet::from(sub_aut.clone())); + + // 5) make, sign binding -> add + { + let signing_builder = + SignatureBuilder::new(SignatureType::SubkeyBinding) + .set_signature_creation_time(SystemTime::now())? + .set_key_validity_period(std::time::Duration::new(0, 0))? + .set_key_flags(KeyFlags::empty().set_authentication())?; + + // Allow signing on the card + ca.verify_pw1_for_signing("123456")?; + + // Card-backed signer for bindings + let mut card_signer = CardSigner::with_pubkey(ca, key_sig.clone()); + + let signing_bsig: Packet = sub_aut + .bind(&mut card_signer, &cert, signing_builder)? + .into(); + + pp.push(signing_bsig); + } + + // 6) add user id from name / email + let cardholder = ca.get_cardholder_related_data()?; + + // FIXME: process name field? accept email as argument?! + let uid: UserID = + cardholder.name().expect("expecting name on card").into(); + + pp.push(uid.clone().into()); + + // 7) make, sign binding -> add + { + let signing_builder = + SignatureBuilder::new(SignatureType::PositiveCertification) + .set_signature_creation_time(SystemTime::now())? + .set_key_validity_period(std::time::Duration::new(0, 0))? + .set_key_flags( + // Flags for primary key + KeyFlags::empty().set_signing().set_certification(), + )?; + + // Allow signing on the card + ca.verify_pw1_for_signing("123456")?; + + // Card-backed signer for bindings + let mut card_signer = CardSigner::with_pubkey(ca, key_sig); + + let signing_bsig: Packet = + uid.bind(&mut card_signer, &cert, signing_builder)?.into(); + + pp.push(signing_bsig); + } + + Cert::try_from(pp) +} + +/// Helper fn: get a CardUploadableKey for a ValidErasedKeyAmalgamation +pub fn vka_as_uploadable_key( + vka: ValidErasedKeyAmalgamation, + password: Option, +) -> Box { + let sqk = SequoiaKey::new(vka, password); + Box::new(sqk) +} + +/// Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial +pub fn public_key_material_to_key( + pkm: &PublicKeyMaterial, + key_type: KeyType, + time: KeyGenerationTime, +) -> Result { + let time = Timestamp::from(time.get()).into(); + + match pkm { + PublicKeyMaterial::R(rsa) => { + let k4 = Key4::import_public_rsa(rsa.v(), rsa.n(), Some(time))?; + + Ok(k4.into()) + } + PublicKeyMaterial::E(ecc) => { + let algo = ecc.algo().clone(); // FIXME? + if let Algo::Ecc(algo_ecc) = algo { + let curve = match algo_ecc.curve() { + Curve::NistP256r1 => openpgp::types::Curve::NistP256, + Curve::NistP384r1 => openpgp::types::Curve::NistP384, + Curve::NistP521r1 => openpgp::types::Curve::NistP521, + Curve::Ed25519 => openpgp::types::Curve::Ed25519, + Curve::Cv25519 => openpgp::types::Curve::Cv25519, + c => unimplemented!("unhandled curve: {:?}", c), + }; + + match key_type { + KeyType::Authentication | KeyType::Signing => { + if algo_ecc.curve() == Curve::Ed25519 { + // EdDSA + let k4 = + Key4::import_public_ed25519(ecc.data(), time)?; + + Ok(Key::from(k4)) + } else { + // ECDSA + let k4 = Key4::new( + time, + PublicKeyAlgorithm::ECDSA, + mpi::PublicKey::ECDSA { + curve, + q: mpi::MPI::new(ecc.data()), + }, + )?; + + Ok(k4.into()) + } + } + KeyType::Decryption => { + if algo_ecc.curve() == Curve::Cv25519 { + // EdDSA + let k4 = Key4::import_public_cv25519( + ecc.data(), + None, + None, + time, + )?; + + Ok(k4.into()) + } else { + // FIXME: just defining `hash` and `sym` is not + // ok when a cert already exists + + // ECDH + let k4 = Key4::new( + time, + PublicKeyAlgorithm::ECDH, + mpi::PublicKey::ECDH { + curve, + q: mpi::MPI::new(ecc.data()), + hash: Default::default(), + sym: Default::default(), + }, + )?; + + Ok(k4.into()) + } + } + _ => unimplemented!("Unsupported KeyType"), + } + } else { + panic!("unexpected algo {:?}", algo); + } + } + _ => unimplemented!("Unexpected PublicKeyMaterial type"), + } +} + +/// Mapping function to get a fingerprint from "PublicKeyMaterial + +/// timestamp + KeyType" (intended for use with `CardApp.generate_key()`). +pub fn public_to_fingerprint( + pkm: &PublicKeyMaterial, + time: KeyGenerationTime, + kt: KeyType, +) -> Result { + // Transform PublicKeyMaterial into a Sequoia Key + let key = public_key_material_to_key(pkm, kt, time)?; + + // Get fingerprint from the Sequoia Key + let fp = key.fingerprint(); + fp.as_bytes().try_into() +} + +/// FIXME: this is used in main.rs for testing, but should probably not exist. +/// +/// Convenience fn to select and upload a (sub)key from a Cert, as a given +/// KeyType. If multiple suitable (sub)keys are found, the first one is +/// used. +/// +/// (sub)keys for upload should probably always be explicitly picked by +/// client software. +pub fn upload_from_cert_yolo( + oca: &mut Admin, + cert: &Cert, + key_type: KeyType, + password: Option, +) -> Result<(), Box> { + let policy = StandardPolicy::new(); + + // Find all suitable (sub)keys for key_type. + let mut valid_ka = cert + .keys() + .with_policy(&policy, None) + .secret() + .alive() + .revoked(false); + valid_ka = match key_type { + KeyType::Decryption => valid_ka.for_storage_encryption(), + KeyType::Signing => valid_ka.for_signing(), + KeyType::Authentication => valid_ka.for_authentication(), + _ => return Err(anyhow!("Unexpected KeyType").into()), + }; + + // FIXME: for now, we just pick the first (sub)key from the list + if let Some(vka) = valid_ka.next() { + upload_key(oca, vka, key_type, password).map_err(|e| e.into()) + } else { + Err(anyhow!("No suitable (sub)key found").into()) + } +} + +/// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType. +/// +/// The caller needs to make sure that `vka` is suitable for `key_type`. +pub fn upload_key( + oca: &mut Admin, + vka: ValidErasedKeyAmalgamation, + key_type: KeyType, + password: Option, +) -> Result<(), Error> { + let sqk = SequoiaKey::new(vka, password); + + oca.upload_key(Box::new(sqk), key_type) +} + +/// FIXME: this fn is used in card_functionality, but should be removed +pub fn sign( + ca: &mut CardApp, + cert: &Cert, + input: &mut dyn io::Read, +) -> Result { + let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?; + { + let p = StandardPolicy::new(); + let s = signer::CardSigner::new(ca, cert, &p)?; + + let message = Message::new(&mut armorer); + let mut message = Signer::new(message, s).detached().build()?; + + // Process input data, via message + io::copy(input, &mut message)?; + + message.finalize()?; + } + + let buffer = armorer.finalize()?; + + String::from_utf8(buffer).context("Failed to convert signature to utf8") +} + +/// FIXME: this fn is used in card_functionality, but should be removed +pub fn decrypt( + ca: &mut CardApp, + cert: &Cert, + msg: Vec, +) -> Result> { + let mut decrypted = Vec::new(); + { + let reader = io::BufReader::new(&msg[..]); + + let p = StandardPolicy::new(); + let d = decryptor::CardDecryptor::new(ca, cert, &p)?; + + let db = DecryptorBuilder::from_reader(reader)?; + let mut decryptor = db.with_policy(&p, None, d)?; + + // Read all data from decryptor and store in decrypted + io::copy(&mut decryptor, &mut decrypted)?; + } + + Ok(decrypted) +}