diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index e96f87f..09ea7a5 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use anyhow::Result; @@ -10,13 +10,16 @@ use thiserror; use sequoia_openpgp::parse::Parse; use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::serialize::SerializeInto; +use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use sequoia_openpgp::Cert; use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::{KeyGenerationTime, Sex}; use openpgp_card::{CardBackend, Error, KeyType, OpenPgp, OpenPgpTransaction, StatusBytes}; use openpgp_card_sequoia::card::Open; -use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key, public_to_fingerprint}; +use openpgp_card_sequoia::util::{ + make_cert, public_key_material_and_fp_to_key, public_key_material_to_key, +}; use crate::cards::TestCardData; use crate::util; @@ -226,9 +229,11 @@ pub fn test_keygen( param: &[&str], ) -> Result { let mut pgp = OpenPgp::new(card); - let mut pgpt = pgp.transaction()?; + let pgpt = pgp.transaction()?; - pgpt.verify_pw3(b"12345678")?; + let mut open = Open::new(pgpt)?; + open.verify_admin(b"12345678")?; + let mut admin = open.admin_card().expect("Couldn't get Admin card"); // Generate all three subkeys on card let algo = param[0]; @@ -236,20 +241,24 @@ pub fn test_keygen( let alg = AlgoSimple::try_from(algo)?; println!(" Generate subkey for Signing"); - let (pkm, ts) = pgpt.generate_key_simple(public_to_fingerprint, KeyType::Signing, alg)?; - let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, ts)?; + let (pkm, ts) = admin.generate_key_simple(KeyType::Signing, Some(alg))?; + let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, &ts, None, None)?; println!(" Generate subkey for Decryption"); - let (pkm, ts) = pgpt.generate_key_simple(public_to_fingerprint, KeyType::Decryption, alg)?; - let key_dec = public_key_material_to_key(&pkm, KeyType::Decryption, ts)?; + let (pkm, ts) = admin.generate_key_simple(KeyType::Decryption, Some(alg))?; + let key_dec = public_key_material_to_key( + &pkm, + KeyType::Decryption, + &ts, + Some(HashAlgorithm::SHA256), + Some(SymmetricAlgorithm::AES128), + )?; println!(" Generate subkey for Authentication"); - let (pkm, ts) = - pgpt.generate_key_simple(public_to_fingerprint, KeyType::Authentication, alg)?; - let key_aut = public_key_material_to_key(&pkm, KeyType::Authentication, ts)?; + let (pkm, ts) = admin.generate_key_simple(KeyType::Authentication, Some(alg))?; + let key_aut = public_key_material_to_key(&pkm, KeyType::Authentication, &ts, None, None)?; // Generate a Cert for this set of generated keys - let mut open = Open::new(pgpt)?; let cert = make_cert( &mut open, key_sig, @@ -274,37 +283,44 @@ pub fn test_get_pub( let mut pgpt = pgp.transaction()?; let ard = pgpt.application_related_data()?; - let key_gen = ard.key_generation_times()?; + let times = ard.key_generation_times()?; + let fps = ard.fingerprints()?; // -- let sig = pgpt.public_key(KeyType::Signing)?; - let ts = key_gen.signature().unwrap().get().into(); - let key = public_key_material_to_key(&sig, KeyType::Signing, ts)?; + let ts = times.signature().unwrap().get().into(); + let key = + public_key_material_and_fp_to_key(&sig, KeyType::Signing, &ts, fps.signature().unwrap())?; println!(" sig key data from card -> {:x?}", key); // -- let dec = pgpt.public_key(KeyType::Decryption)?; - let ts = key_gen.decryption().unwrap().get().into(); - let key = public_key_material_to_key(&dec, KeyType::Decryption, ts)?; + let ts = times.decryption().unwrap().get().into(); + let key = public_key_material_and_fp_to_key( + &dec, + KeyType::Decryption, + &ts, + fps.decryption().unwrap(), + )?; println!(" dec key data from card -> {:x?}", key); // -- let auth = pgpt.public_key(KeyType::Authentication)?; - let ts = key_gen.authentication().unwrap().get().into(); - let key = public_key_material_to_key(&auth, KeyType::Authentication, ts)?; + let ts = times.authentication().unwrap().get().into(); + let key = public_key_material_and_fp_to_key( + &auth, + KeyType::Authentication, + &ts, + fps.authentication().unwrap(), + )?; println!(" auth key data from card -> {:x?}", key); - // FIXME: assert that key FP is equal to FP from card - - // ca.generate_key(fp, KeyType::Decryption)?; - // ca.generate_key(fp, KeyType::Authentication)?; - Ok(vec![]) } diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index d954552..3b54722 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -5,7 +5,7 @@ name = "openpgp-card-sequoia" description = "Wrapper of openpgp-card for use with Sequoia PGP" license = "MIT OR Apache-2.0" -version = "0.0.9" +version = "0.0.10" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/hkos/openpgp-card" diff --git a/openpgp-card-sequoia/src/card.rs b/openpgp-card-sequoia/src/card.rs index 434309a..66bf735 100644 --- a/openpgp-card-sequoia/src/card.rs +++ b/openpgp-card-sequoia/src/card.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Perform operations on a card. Different states of a card are modeled by @@ -6,6 +6,7 @@ use sequoia_openpgp::cert::amalgamation::key::ValidErasedKeyAmalgamation; use sequoia_openpgp::packet::key::SecretParts; +use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use sequoia_openpgp::Cert; use openpgp_card::algorithm::{Algo, AlgoInfo, AlgoSimple}; @@ -415,20 +416,32 @@ impl Admin<'_, '_> { self.oc.opt.key_import(key, key_type) } + /// Wrapper fn for `public_to_fingerprint` that uses SHA256/AES128 as default parameters. + /// + /// FIXME: This is a hack. + /// These parameters should probably be automatically determined based on the algorithm used? + fn ptf( + pkm: &PublicKeyMaterial, + time: KeyGenerationTime, + key_type: KeyType, + ) -> Result { + public_to_fingerprint( + pkm, + &time, + key_type, + Some(HashAlgorithm::SHA256), + Some(SymmetricAlgorithm::AES128), + ) + } + pub fn generate_key_simple( &mut self, key_type: KeyType, algo: Option, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { match algo { - Some(algo) => self - .oc - .opt - .generate_key_simple(public_to_fingerprint, key_type, algo), - None => self - .oc - .opt - .generate_key(public_to_fingerprint, key_type, None), + Some(algo) => self.oc.opt.generate_key_simple(Self::ptf, key_type, algo), + None => self.oc.opt.generate_key(Self::ptf, key_type, None), } } } diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs index 90d957f..c71e60b 100644 --- a/openpgp-card-sequoia/src/util.rs +++ b/openpgp-card-sequoia/src/util.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Odds and ends, will most likely be restructured. @@ -24,6 +24,7 @@ use openpgp::serialize::stream::{Message, Signer}; use openpgp::types::{KeyFlags, PublicKeyAlgorithm, SignatureType, Timestamp}; use openpgp::{Cert, Packet}; use sequoia_openpgp as openpgp; +use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use openpgp_card::algorithm::{Algo, Curve}; use openpgp_card::card_do::{Fingerprint, KeyGenerationTime}; @@ -173,11 +174,77 @@ pub fn make_cert<'app>( Cert::try_from(pp) } -/// Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial +/// Meta-Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial, timestamp and +/// card-Fingerprint. +/// +/// For ECC decryption keys, possible values for the parameters `hash` and `sym` will be tested. +/// Once a key with matching fingerprint is found in this way, it is considered the correct key, +/// and returned. +/// +/// The Fingerprint of the retrieved PublicKey is always validated against the `Fingerprint` as +/// stored on the card. If the fingerprints doesn't match, an Error is returned. +pub fn public_key_material_and_fp_to_key( + pkm: &PublicKeyMaterial, + key_type: KeyType, + time: &KeyGenerationTime, + fingerprint: &Fingerprint, +) -> Result { + // Possible hash/sym parameters based on statistics over 2019-12 SKS dump: + // https://gitlab.com/sequoia-pgp/sequoia/-/issues/838#note_909813463 + + // We try these parameters in descending order of occurrence and return the PublicKey + // once the Fingerprint matches. + + let param: &[_] = match (pkm, key_type) { + (PublicKeyMaterial::E(_), KeyType::Decryption) => &[ + ( + Some(HashAlgorithm::SHA256), + Some(SymmetricAlgorithm::AES128), + ), + ( + Some(HashAlgorithm::SHA512), + Some(SymmetricAlgorithm::AES256), + ), + ( + Some(HashAlgorithm::SHA384), + Some(SymmetricAlgorithm::AES256), + ), + ( + Some(HashAlgorithm::SHA384), + Some(SymmetricAlgorithm::AES192), + ), + ( + Some(HashAlgorithm::SHA256), + Some(SymmetricAlgorithm::AES256), + ), + ], + _ => &[(None, None)], + }; + + for (hash, sym) in param { + if let Ok(key) = public_key_material_to_key(pkm, key_type, time, *hash, *sym) { + // check FP + if key.fingerprint().as_bytes() == fingerprint.as_bytes() { + // return if match + return Ok(key); + } + } + } + + Err(Error::InternalError( + "Couldn't find key with matching fingerprint".to_string(), + )) +} + +/// Helper fn: get a Sequoia PublicKey from an openpgp-card PublicKeyMaterial. +/// +/// For ECC decryption keys, `hash` and `sym` can be optionally specified. pub fn public_key_material_to_key( pkm: &PublicKeyMaterial, key_type: KeyType, - time: KeyGenerationTime, + time: &KeyGenerationTime, + hash: Option, + sym: Option, ) -> Result { let time = Timestamp::from(time.get()).into(); @@ -236,11 +303,8 @@ pub fn public_key_material_to_key( } KeyType::Decryption => { if algo_ecc.curve() == Curve::Cv25519 { - // FIXME: not setting `hash` and `sym` is not - // ok when a cert already exists - // EdDSA - let k4 = Key4::import_public_cv25519(ecc.data(), None, None, time) + let k4 = Key4::import_public_cv25519(ecc.data(), hash, sym, time) .map_err(|e| { Error::InternalError(format!( "sequoia Key4::import_public_cv25519 failed: {:?}", @@ -250,9 +314,6 @@ pub fn public_key_material_to_key( Ok(k4.into()) } else { - // FIXME: just defining `hash` and `sym` is not - // ok when a cert already exists - // ECDH let k4 = Key4::new( time, @@ -260,8 +321,8 @@ pub fn public_key_material_to_key( mpi::PublicKey::ECDH { curve, q: mpi::MPI::new(ecc.data()), - hash: Default::default(), - sym: Default::default(), + hash: hash.unwrap_or_default(), + sym: sym.unwrap_or_default(), }, ) .map_err(|e| { @@ -286,13 +347,17 @@ pub fn public_key_material_to_key( /// Mapping function to get a fingerprint from "PublicKeyMaterial + /// timestamp + KeyType" (intended for use with `CardApp.generate_key()`). -pub fn public_to_fingerprint( +/// +/// For ECC decryption keys, `hash` and `sym` can be optionally specified. +pub(crate) fn public_to_fingerprint( pkm: &PublicKeyMaterial, - time: KeyGenerationTime, + time: &KeyGenerationTime, kt: KeyType, + hash: Option, + sym: Option, ) -> Result { // Transform PublicKeyMaterial into a Sequoia Key - let key = public_key_material_to_key(pkm, kt, time)?; + let key = public_key_material_to_key(pkm, kt, time, hash, sym)?; // Get fingerprint from the Sequoia Key let fp = key.fingerprint(); diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 069e472..92547f6 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -5,7 +5,7 @@ name = "openpgp-card-tools" description = "CLI tools for OpenPGP cards" license = "MIT OR Apache-2.0" -version = "0.0.6" +version = "0.0.7" authors = ["Heiko Schaefer "] edition = "2018" repository = "https://gitlab.com/hkos/openpgp-card" @@ -15,7 +15,7 @@ documentation = "https://docs.rs/crate/openpgp-card-tools" sequoia-openpgp = "1.3" openpgp-card = { path = "../openpgp-card", version = "0.2" } openpgp-card-pcsc = { path = "../pcsc", version = "0.2" } -openpgp-card-sequoia = { path = "../openpgp-card-sequoia", version = "0.0.9" } +openpgp-card-sequoia = { path = "../openpgp-card-sequoia", version = "0.0.10" } sshkeys = "0.3.2" rpassword = "5" anyhow = "1" diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 9489599..e82ebce 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use anyhow::{anyhow, Result}; @@ -15,9 +15,12 @@ use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::Sex; use openpgp_card::{CardBackend, KeyType, OpenPgp}; use openpgp_card_sequoia::card::{Admin, Open}; -use openpgp_card_sequoia::util::{make_cert, public_key_material_to_key}; +use openpgp_card_sequoia::util::{ + make_cert, public_key_material_and_fp_to_key, public_key_material_to_key, +}; use openpgp_card_sequoia::{sq_util, PublicKey}; +use sequoia_openpgp::types::{HashAlgorithm, SymmetricAlgorithm}; use std::io::Write; mod cli; @@ -403,27 +406,36 @@ fn print_pubkey(ident: Option, user_pin: Option) -> Result<()> let pkm = open.public_key(KeyType::Signing)?; let times = open.key_generation_times()?; + let fps = open.fingerprints()?; - let key_sig = public_key_material_to_key( + let key_sig = public_key_material_and_fp_to_key( &pkm, KeyType::Signing, - *times.signature().expect("Signature time is unset"), + times.signature().expect("Signature time is unset"), + fps.signature().expect("Signature fingerprint is unset"), )?; let mut key_dec = None; if let Ok(pkm) = open.public_key(KeyType::Decryption) { if let Some(ts) = times.decryption() { - key_dec = Some(public_key_material_to_key(&pkm, KeyType::Decryption, *ts)?); + key_dec = Some(public_key_material_and_fp_to_key( + &pkm, + KeyType::Decryption, + ts, + fps.decryption().expect("Decryption fingerprint is unset"), + )?); } } let mut key_aut = None; if let Ok(pkm) = open.public_key(KeyType::Authentication) { if let Some(ts) = times.authentication() { - key_aut = Some(public_key_material_to_key( + key_aut = Some(public_key_material_and_fp_to_key( &pkm, KeyType::Authentication, - *ts, + ts, + fps.authentication() + .expect("Authentication fingerprint is unset"), )?); } } @@ -663,14 +675,20 @@ fn gen_subkeys( // We begin by generating the signing subkey, which is mandatory. println!(" Generate subkey for Signing"); let (pkm, ts) = admin.generate_key_simple(KeyType::Signing, algo)?; - let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, ts)?; + let key_sig = public_key_material_to_key(&pkm, KeyType::Signing, &ts, None, None)?; // make decryption subkey (unless disabled), with the same algorithm as // the sig key let key_dec = if decrypt { println!(" Generate subkey for Decryption"); let (pkm, ts) = admin.generate_key_simple(KeyType::Decryption, algo)?; - Some(public_key_material_to_key(&pkm, KeyType::Decryption, ts)?) + Some(public_key_material_to_key( + &pkm, + KeyType::Decryption, + &ts, + Some(HashAlgorithm::SHA256), // FIXME + Some(SymmetricAlgorithm::AES128), // FIXME + )?) } else { None }; @@ -684,7 +702,9 @@ fn gen_subkeys( Some(public_key_material_to_key( &pkm, KeyType::Authentication, - ts, + &ts, + None, + None, )?) } else { None