From 96167f6530014366a715599b8569ca912c87ad09 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 22 Feb 2022 17:55:19 +0100 Subject: [PATCH] Move OpenPGP card functionality into OpenPgp/OpenPgpTransaction. This separates backend access (implemented in CardBackend and CardTransaction) from OpenPGP card operations. Fixes #7 --- card-functionality/src/list-cards.rs | 7 +- card-functionality/src/tests.rs | 246 +++--- card-functionality/src/util.rs | 6 +- openpgp-card-examples/src/bin/decrypt.rs | 6 +- openpgp-card-examples/src/bin/detach-sign.rs | 10 +- openpgp-card-sequoia/src/card.rs | 98 +-- openpgp-card-sequoia/src/decryptor.rs | 26 +- openpgp-card-sequoia/src/lib.rs | 30 +- openpgp-card-sequoia/src/main.rs | 19 +- openpgp-card-sequoia/src/signer.rs | 30 +- openpgp-card-sequoia/src/util.rs | 6 +- openpgp-card/src/keys.rs | 45 +- openpgp-card/src/lib.rs | 742 +----------------- openpgp-card/src/openpgp.rs | 772 +++++++++++++++++++ tools/src/bin/opgpcard-pin/main.rs | 11 +- tools/src/bin/opgpcard/main.rs | 35 +- tools/src/bin/opgpcard/util.rs | 4 +- 17 files changed, 1098 insertions(+), 995 deletions(-) create mode 100644 openpgp-card/src/openpgp.rs diff --git a/card-functionality/src/list-cards.rs b/card-functionality/src/list-cards.rs index 56f3142..a47c19c 100644 --- a/card-functionality/src/list-cards.rs +++ b/card-functionality/src/list-cards.rs @@ -3,7 +3,7 @@ use anyhow::Result; -use openpgp_card::CardBackend; +use openpgp_card::OpenPgp; use openpgp_card_pcsc::PcscBackend; use openpgp_card_sequoia::card::Open; @@ -11,9 +11,8 @@ fn main() -> Result<()> { println!("The following OpenPGP cards are connected to your system:"); for mut card in PcscBackend::cards(None)? { - let mut txc = card.transaction()?; - - let open = Open::new(&mut *txc)?; + let mut pgp = OpenPgp::new(&mut card); + let open = Open::new(pgp.transaction()?)?; println!(" {}", open.application_identifier()?.ident()); } diff --git a/card-functionality/src/tests.rs b/card-functionality/src/tests.rs index d7d643d..1e88b04 100644 --- a/card-functionality/src/tests.rs +++ b/card-functionality/src/tests.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -use anyhow::{anyhow, Result}; +use anyhow::Result; use std::convert::TryFrom; use std::str::FromStr; use std::string::FromUtf8Error; @@ -12,10 +12,9 @@ use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::serialize::SerializeInto; use sequoia_openpgp::Cert; -use openpgp_card; use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::{KeyGenerationTime, Sex}; -use openpgp_card::{CardTransaction, Error, KeyType, StatusBytes}; +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}; @@ -50,10 +49,10 @@ pub enum TestError { } /// Run after each "upload keys", if key *was* uploaded (?) -pub fn test_decrypt( - card_tx: &mut (dyn CardTransaction + Send + Sync), - param: &[&str], -) -> Result { +pub fn test_decrypt(card: &mut dyn CardBackend, param: &[&str]) -> Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + assert_eq!( param.len(), 2, @@ -63,11 +62,11 @@ pub fn test_decrypt( let cert = Cert::from_str(param[0])?; let msg = param[1].to_string(); - card_tx.verify_pw1("123456")?; + pgpt.verify_pw1("123456")?; let p = StandardPolicy::new(); - let res = openpgp_card_sequoia::util::decrypt(card_tx, &cert, msg.into_bytes(), &p)?; + let res = openpgp_card_sequoia::util::decrypt(&mut pgpt, &cert, msg.into_bytes(), &p)?; let plain = String::from_utf8_lossy(&res); assert_eq!(plain, "Hello world!\n"); @@ -76,18 +75,18 @@ pub fn test_decrypt( } /// Run after each "upload keys", if key *was* uploaded (?) -pub fn test_sign( - card_tx: &mut (dyn CardTransaction + Send + Sync), - param: &[&str], -) -> Result { +pub fn test_sign(card: &mut dyn CardBackend, param: &[&str]) -> Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + assert_eq!(param.len(), 1, "test_sign needs a filename for 'cert'"); - card_tx.verify_pw1_for_signing("123456")?; + pgpt.verify_pw1_for_signing("123456")?; let cert = Cert::from_str(param[0])?; let msg = "Hello world, I am signed."; - let sig = openpgp_card_sequoia::util::sign(card_tx, &cert, &mut msg.as_bytes())?; + let sig = openpgp_card_sequoia::util::sign(&mut pgpt, &cert, &mut msg.as_bytes())?; // validate sig assert!(util::verify_sig(&cert, msg.as_bytes(), sig.as_bytes())?); @@ -96,10 +95,10 @@ pub fn test_sign( } fn check_key_upload_metadata( - card_tx: &mut (dyn CardTransaction + Send + Sync), + pgpt: &mut OpenPgpTransaction, meta: &[(String, KeyGenerationTime)], ) -> Result<()> { - let ard = card_tx.application_related_data()?; + let ard = pgpt.application_related_data()?; // check fingerprints let card_fp = ard.fingerprints()?; @@ -140,10 +139,13 @@ fn check_key_upload_algo_attrs() -> Result<()> { } pub fn test_print_caps( - card_tx: &mut (dyn CardTransaction + Send + Sync), + card: &mut dyn CardBackend, _param: &[&str], ) -> Result { - let ard = card_tx.application_related_data()?; + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + + let ard = pgpt.application_related_data()?; let aid = ard.application_id()?; println!("aid: {:#x?}", aid); @@ -161,17 +163,20 @@ pub fn test_print_caps( } pub fn test_print_algo_info( - card_tx: &mut (dyn CardTransaction + Send + Sync), + card: &mut dyn CardBackend, _param: &[&str], ) -> Result { - let ard = card_tx.application_related_data()?; + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + + let ard = pgpt.application_related_data()?; let dec = ard.algorithm_attributes(KeyType::Decryption)?; println!("Current algorithm for the decrypt slot: {}", dec); println!(); - let algo = card_tx.algorithm_information(); + let algo = pgpt.algorithm_information(); if let Ok(Some(algo)) = algo { println!("Card algorithm list:\n{}", algo); } @@ -180,25 +185,28 @@ pub fn test_print_algo_info( } pub fn test_upload_keys( - card_tx: &mut (dyn CardTransaction + Send + Sync), + card: &mut dyn CardBackend, param: &[&str], ) -> Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + assert_eq!( param.len(), 1, "test_upload_keys needs a filename for 'cert'" ); - card_tx.verify_pw3("12345678")?; + pgpt.verify_pw3("12345678")?; let cert = Cert::from_file(param[0])?; let p = StandardPolicy::new(); - let meta = util::upload_subkeys(card_tx, &cert, &p) + let meta = util::upload_subkeys(&mut pgpt, &cert, &p) .map_err(|e| TestError::KeyUploadError(param[0].to_string(), e))?; - check_key_upload_metadata(card_tx, &meta)?; + check_key_upload_metadata(&mut pgpt, &meta)?; // FIXME: implement check_key_upload_algo_attrs()?; @@ -207,11 +215,11 @@ pub fn test_upload_keys( } /// Generate keys for each of the three KeyTypes -pub fn test_keygen( - card_tx: &mut (dyn CardTransaction + Send + Sync), - param: &[&str], -) -> Result { - card_tx.verify_pw3("12345678")?; +pub fn test_keygen(card: &mut dyn CardBackend, param: &[&str]) -> Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + + pgpt.verify_pw3("12345678")?; // Generate all three subkeys on card let algo = param[0]; @@ -219,20 +227,20 @@ pub fn test_keygen( let alg = AlgoSimple::try_from(algo)?; println!(" Generate subkey for Signing"); - let (pkm, ts) = card_tx.generate_key_simple(public_to_fingerprint, KeyType::Signing, alg)?; + 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)?; println!(" Generate subkey for Decryption"); - let (pkm, ts) = card_tx.generate_key_simple(public_to_fingerprint, KeyType::Decryption, alg)?; + 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)?; println!(" Generate subkey for Authentication"); let (pkm, ts) = - card_tx.generate_key_simple(public_to_fingerprint, KeyType::Authentication, alg)?; + pgpt.generate_key_simple(public_to_fingerprint, KeyType::Authentication, alg)?; let key_aut = public_key_material_to_key(&pkm, KeyType::Authentication, ts)?; // Generate a Cert for this set of generated keys - let mut open = Open::new(card_tx)?; + let mut open = Open::new(pgpt)?; let cert = make_cert( &mut open, key_sig, @@ -249,16 +257,16 @@ pub fn test_keygen( } /// Construct public key based on data from the card -pub fn test_get_pub( - card_tx: &mut (dyn CardTransaction + Send + Sync), - _param: &[&str], -) -> Result { - let ard = card_tx.application_related_data()?; +pub fn test_get_pub(card: &mut dyn CardBackend, _param: &[&str]) -> Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + + let ard = pgpt.application_related_data()?; let key_gen = ard.key_generation_times()?; // -- - let sig = card_tx.public_key(KeyType::Signing)?; + 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)?; @@ -266,7 +274,7 @@ pub fn test_get_pub( // -- - let dec = card_tx.public_key(KeyType::Decryption)?; + 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)?; @@ -274,7 +282,7 @@ pub fn test_get_pub( // -- - let auth = card_tx.public_key(KeyType::Authentication)?; + 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)?; @@ -288,11 +296,11 @@ pub fn test_get_pub( Ok(vec![]) } -pub fn test_reset( - card_tx: &mut (dyn CardTransaction + Send + Sync), - _param: &[&str], -) -> Result { - let _res = card_tx.factory_reset()?; +pub fn test_reset(card: &mut dyn CardBackend, _param: &[&str]) -> Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + + let _res = pgpt.factory_reset()?; Ok(vec![]) } @@ -302,25 +310,28 @@ pub fn test_reset( /// Returns an empty TestOutput, throws errors for unexpected Status codes /// and for unequal field values. pub fn test_set_user_data( - card_tx: &mut (dyn CardTransaction + Send + Sync), + card: &mut dyn CardBackend, _param: &[&str], ) -> Result { - card_tx.verify_pw3("12345678")?; + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + + pgpt.verify_pw3("12345678")?; // name - card_tx.set_name(b"Bar< Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + let out = vec![]; println!(); - let d = card_tx.private_use_do(1)?; + let d = pgpt.private_use_do(1)?; println!("data 1 {:?}", d); - card_tx.verify_pw1("123456")?; + pgpt.verify_pw1("123456")?; - card_tx.set_private_use_do(1, "Foo bar1!".as_bytes().to_vec())?; - card_tx.set_private_use_do(3, "Foo bar3!".as_bytes().to_vec())?; + pgpt.set_private_use_do(1, "Foo bar1!".as_bytes().to_vec())?; + pgpt.set_private_use_do(3, "Foo bar3!".as_bytes().to_vec())?; - card_tx.verify_pw3("12345678")?; + pgpt.verify_pw3("12345678")?; - card_tx.set_private_use_do(2, "Foo bar2!".as_bytes().to_vec())?; - card_tx.set_private_use_do(4, "Foo bar4!".as_bytes().to_vec())?; + pgpt.set_private_use_do(2, "Foo bar2!".as_bytes().to_vec())?; + pgpt.set_private_use_do(4, "Foo bar4!".as_bytes().to_vec())?; - let d = card_tx.private_use_do(1)?; + let d = pgpt.private_use_do(1)?; println!("data 1 {:?}", d); - let d = card_tx.private_use_do(2)?; + let d = pgpt.private_use_do(2)?; println!("data 2 {:?}", d); - let d = card_tx.private_use_do(3)?; + let d = pgpt.private_use_do(3)?; println!("data 3 {:?}", d); - let d = card_tx.private_use_do(4)?; + let d = pgpt.private_use_do(4)?; println!("data 4 {:?}", d); Ok(out) } // pub fn test_cardholder_cert( -// card_tx: &mut (dyn CardTransaction + Send + Sync), +// card_tx: &mut CardApp, // _param: &[&str], // ) -> Result { // let mut out = vec![]; @@ -429,24 +443,27 @@ pub fn test_private_data( // } pub fn test_pw_status( - card_tx: &mut (dyn CardTransaction + Send + Sync), + card: &mut dyn CardBackend, _param: &[&str], ) -> Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + let out = vec![]; - let ard = card_tx.application_related_data()?; + let ard = pgpt.application_related_data()?; let mut pws = ard.pw_status_bytes()?; println!("pws {:?}", pws); - card_tx.verify_pw3("12345678")?; + pgpt.verify_pw3("12345678")?; pws.set_pw1_cds_valid_once(false); pws.set_pw1_pin_block(true); - card_tx.set_pw_status_bytes(&pws, false)?; + pgpt.set_pw_status_bytes(&pws, false)?; - let ard = card_tx.application_related_data()?; + let ard = pgpt.application_related_data()?; let pws = ard.pw_status_bytes()?; println!("pws {:?}", pws); @@ -456,10 +473,10 @@ pub fn test_pw_status( /// Outputs: /// - verify pw3 (check) -> Status /// - verify pw1 (check) -> Status -pub fn test_verify( - card_tx: &mut (dyn CardTransaction + Send + Sync), - _param: &[&str], -) -> Result { +pub fn test_verify(card: &mut dyn CardBackend, _param: &[&str]) -> Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + // Steps: // // - try to set name without verify, assert result is not ok @@ -475,7 +492,7 @@ pub fn test_verify( let mut out = vec![]; // try to set name without verify, assert result is not ok! - let res = card_tx.set_name("Notverified< { // e.g. yubikey5 returns an error status! out.push(TestResult::Status(s)); @@ -496,14 +513,14 @@ pub fn test_verify( Ok(_) => out.push(TestResult::StatusOk), } - card_tx.set_name(b"Admin< { // e.g. yubikey5 returns an error status! out.push(TestResult::Status(s)); @@ -514,37 +531,40 @@ pub fn test_verify( Ok(_) => out.push(TestResult::StatusOk), } - card_tx.set_name(b"There< Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + let out = vec![]; // first do admin-less pw1 on gnuk // (NOTE: Gnuk requires a key to be loaded before allowing pw changes!) println!("change pw1"); - card_tx.change_pw1("123456", "abcdef00")?; + pgpt.change_pw1("123456", "abcdef00")?; // also set admin pw, which means pw1 is now only user-pw again, on gnuk println!("change pw3"); // ca.change_pw3("abcdef00", "abcdefgh")?; // gnuk - card_tx.change_pw3("12345678", "abcdefgh")?; + pgpt.change_pw3("12345678", "abcdefgh")?; println!("change pw1"); - card_tx.change_pw1("abcdef00", "abcdef")?; // gnuk + pgpt.change_pw1("abcdef00", "abcdef")?; // gnuk // ca.change_pw1("123456", "abcdef")?; println!("verify bad pw1"); - match card_tx.verify_pw1("123456ab") { + match pgpt.verify_pw1("123456ab") { Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => { // this is expected } @@ -555,10 +575,10 @@ pub fn test_change_pw( } println!("verify good pw1"); - card_tx.verify_pw1("abcdef")?; + pgpt.verify_pw1("abcdef")?; println!("verify bad pw3"); - match card_tx.verify_pw3("00000000") { + match pgpt.verify_pw3("00000000") { Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => { // this is expected } @@ -569,34 +589,37 @@ pub fn test_change_pw( } println!("verify good pw3"); - card_tx.verify_pw3("abcdefgh")?; + pgpt.verify_pw3("abcdefgh")?; println!("change pw3 back to default"); - card_tx.change_pw3("abcdefgh", "12345678")?; + pgpt.change_pw3("abcdefgh", "12345678")?; println!("change pw1 back to default"); - card_tx.change_pw1("abcdef", "123456")?; + pgpt.change_pw1("abcdef", "123456")?; Ok(out) } pub fn test_reset_retry_counter( - card_tx: &mut (dyn CardTransaction + Send + Sync), + card: &mut dyn CardBackend, _param: &[&str], ) -> Result { + let mut pgp = OpenPgp::new(card); + let mut pgpt = pgp.transaction()?; + let out = vec![]; // set pw3, then pw1 (to bring gnuk into non-admin mode) println!("set pw3"); - card_tx.change_pw3("12345678", "12345678")?; + pgpt.change_pw3("12345678", "12345678")?; println!("set pw1"); - card_tx.change_pw1("123456", "123456")?; + pgpt.change_pw1("123456", "123456")?; println!("break pw1"); - let _ = card_tx.verify_pw1("wrong0"); - let _ = card_tx.verify_pw1("wrong0"); - let _ = card_tx.verify_pw1("wrong0"); - let res = card_tx.verify_pw1("wrong0"); + let _ = pgpt.verify_pw1("wrong0"); + let _ = pgpt.verify_pw1("wrong0"); + let _ = pgpt.verify_pw1("wrong0"); + let res = pgpt.verify_pw1("wrong0"); match res { Err(Error::CardStatus(StatusBytes::AuthenticationMethodBlocked)) => { @@ -615,23 +638,23 @@ pub fn test_reset_retry_counter( } println!("verify pw3"); - card_tx.verify_pw3("12345678")?; + pgpt.verify_pw3("12345678")?; println!("set resetting code"); - card_tx.set_resetting_code("abcdefgh".as_bytes().to_vec())?; + pgpt.set_resetting_code("abcdefgh".as_bytes().to_vec())?; println!("reset retry counter"); // ca.reset_retry_counter_pw1("abcdef".as_bytes().to_vec(), None)?; - let _res = card_tx.reset_retry_counter_pw1( + let _res = pgpt.reset_retry_counter_pw1( "abcdef".as_bytes().to_vec(), Some("abcdefgh".as_bytes().to_vec()), ); println!("verify good pw1"); - card_tx.verify_pw1("abcdef")?; + pgpt.verify_pw1("abcdef")?; println!("verify bad pw1"); - match card_tx.verify_pw1("00000000") { + match pgpt.verify_pw1("00000000") { Err(Error::CardStatus(StatusBytes::SecurityStatusNotSatisfied)) => { // this is expected } @@ -646,11 +669,10 @@ pub fn test_reset_retry_counter( pub fn run_test( tc: &mut TestCardData, - t: fn(&mut (dyn CardTransaction + Send + Sync), &[&str]) -> Result, + t: fn(&mut (dyn CardBackend), &[&str]) -> Result, param: &[&str], ) -> Result { let mut card = tc.get_card()?; - let mut txc = card.transaction().map_err(|e| anyhow!(e))?; - t(&mut *txc, param) + t(&mut *card, param) } diff --git a/card-functionality/src/util.rs b/card-functionality/src/util.rs index 0dae88a..b55509c 100644 --- a/card-functionality/src/util.rs +++ b/card-functionality/src/util.rs @@ -14,14 +14,14 @@ use sequoia_openpgp::serialize::stream::{Armorer, Encryptor, LiteralWriter, Mess use sequoia_openpgp::Cert; use openpgp_card::card_do::KeyGenerationTime; -use openpgp_card::{CardTransaction, KeyType}; +use openpgp_card::{KeyType, OpenPgpTransaction}; use openpgp_card_sequoia::sq_util; use openpgp_card_sequoia::util::vka_as_uploadable_key; pub const SP: &StandardPolicy = &StandardPolicy::new(); pub(crate) fn upload_subkeys( - card_tx: &mut dyn CardTransaction, + pgpt: &mut OpenPgpTransaction, cert: &Cert, policy: &dyn Policy, ) -> Result> { @@ -46,7 +46,7 @@ pub(crate) fn upload_subkeys( // upload key let cuk = vka_as_uploadable_key(vka, None); - card_tx.key_import(cuk, *kt)?; + pgpt.key_import(cuk, *kt)?; } } diff --git a/openpgp-card-examples/src/bin/decrypt.rs b/openpgp-card-examples/src/bin/decrypt.rs index b804952..36e32d4 100644 --- a/openpgp-card-examples/src/bin/decrypt.rs +++ b/openpgp-card-examples/src/bin/decrypt.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2021 Wiktor Kwapisiewicz // SPDX-License-Identifier: MIT OR Apache-2.0 -use openpgp_card::CardBackend; +use openpgp_card::OpenPgp; use openpgp_card_pcsc::PcscBackend; use openpgp_card_sequoia::card::Open; @@ -23,9 +23,9 @@ fn main() -> Result<(), Box> { let cert_file = &args[2]; let mut card = PcscBackend::open_by_ident(card_ident, None)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - let mut open = Open::new(&mut *txc)?; + let mut open = Open::new(pgp.transaction()?)?; let pin = std::fs::read_to_string(pin_file)?; diff --git a/openpgp-card-examples/src/bin/detach-sign.rs b/openpgp-card-examples/src/bin/detach-sign.rs index 140e180..cce00b4 100644 --- a/openpgp-card-examples/src/bin/detach-sign.rs +++ b/openpgp-card-examples/src/bin/detach-sign.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2021 Wiktor Kwapisiewicz // SPDX-License-Identifier: MIT OR Apache-2.0 -use openpgp_card::CardBackend; +use openpgp_card::OpenPgp; use openpgp_card_pcsc::PcscBackend; use openpgp_card_sequoia::card::Open; @@ -23,18 +23,18 @@ fn main() -> Result<(), Box> { let cert_file = &args[2]; let mut card = PcscBackend::open_by_ident(card_ident, None)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - let mut open = Open::new(&mut *txc)?; + let mut open = Open::new(pgp.transaction()?)?; let pin = std::fs::read_to_string(pin_file)?; open.verify_user_for_signing(&pin)?; - let mut user = open.signing_card().unwrap(); + let mut sign = open.signing_card().unwrap(); let cert = Cert::from_file(cert_file)?; - let s = user.signer(&cert)?; + let s = sign.signer(&cert)?; let stdout = std::io::stdout(); diff --git a/openpgp-card-sequoia/src/card.rs b/openpgp-card-sequoia/src/card.rs index dac7b63..ae75d36 100644 --- a/openpgp-card-sequoia/src/card.rs +++ b/openpgp-card-sequoia/src/card.rs @@ -16,7 +16,7 @@ use openpgp_card::card_do::{ ExtendedLengthInfo, Fingerprint, HistoricalBytes, KeyGenerationTime, Lang, PWStatusBytes, SecuritySupportTemplate, Sex, }; -use openpgp_card::{CardTransaction, Error, KeySet, KeyType}; +use openpgp_card::{Error, KeySet, KeyType, OpenPgpTransaction}; use crate::decryptor::CardDecryptor; use crate::signer::CardSigner; @@ -27,7 +27,7 @@ use openpgp_card::crypto_data::PublicKeyMaterial; /// Representation of an opened OpenPGP card in its base state (i.e. no /// passwords have been verified, default authorization applies). pub struct Open<'a> { - card_tx: &'a mut (dyn CardTransaction + Send + Sync), + opt: OpenPgpTransaction<'a>, // Cache of "application related data". // @@ -46,27 +46,28 @@ pub struct Open<'a> { } impl<'a> Open<'a> { - pub fn new(card_tx: &'a mut (dyn CardTransaction + Send + Sync)) -> Result { - let ard = card_tx.application_related_data()?; + pub fn new(mut ca: OpenPgpTransaction<'a>) -> Result { + let ard = ca.application_related_data()?; Ok(Self { - card_tx, + opt: ca, ard, pw1: false, pw1_sign: false, pw3: false, }) } + pub fn feature_pinpad_verify(&mut self) -> bool { - self.card_tx.feature_pinpad_verify() + self.opt.feature_pinpad_verify() } pub fn feature_pinpad_modify(&mut self) -> bool { - self.card_tx.feature_pinpad_modify() + self.opt.feature_pinpad_modify() } pub fn verify_user(&mut self, pin: &str) -> Result<(), Error> { - let _ = self.card_tx.verify_pw1(pin)?; + let _ = self.opt.verify_pw1(pin)?; self.pw1 = true; Ok(()) } @@ -74,13 +75,13 @@ impl<'a> Open<'a> { pub fn verify_user_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> { prompt(); - let _ = self.card_tx.verify_pw1_pinpad()?; + let _ = self.opt.verify_pw1_pinpad()?; self.pw1 = true; Ok(()) } pub fn verify_user_for_signing(&mut self, pin: &str) -> Result<(), Error> { - let _ = self.card_tx.verify_pw1_for_signing(pin)?; + let _ = self.opt.verify_pw1_for_signing(pin)?; // FIXME: depending on card mode, pw1_sign is only usable once @@ -91,7 +92,7 @@ impl<'a> Open<'a> { pub fn verify_user_for_signing_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> { prompt(); - let _ = self.card_tx.verify_pw1_for_signing_pinpad()?; + let _ = self.opt.verify_pw1_for_signing_pinpad()?; // FIXME: depending on card mode, pw1_sign is only usable once @@ -100,7 +101,7 @@ impl<'a> Open<'a> { } pub fn verify_admin(&mut self, pin: &str) -> Result<(), Error> { - let _ = self.card_tx.verify_pw3(pin)?; + let _ = self.opt.verify_pw3(pin)?; self.pw3 = true; Ok(()) } @@ -108,7 +109,7 @@ impl<'a> Open<'a> { pub fn verify_admin_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> { prompt(); - let _ = self.card_tx.verify_pw3_pinpad()?; + let _ = self.opt.verify_pw3_pinpad()?; self.pw3 = true; Ok(()) } @@ -117,41 +118,41 @@ impl<'a> Open<'a> { /// /// NOTE: on some cards this functionality seems broken. pub fn check_user_verified(&mut self) -> Result<(), Error> { - self.card_tx.check_pw1() + self.opt.check_pw1() } /// Ask the card if the admin password has been successfully verified. /// /// NOTE: on some cards this functionality seems broken. pub fn check_admin_verified(&mut self) -> Result<(), Error> { - self.card_tx.check_pw3() + self.opt.check_pw3() } pub fn change_user_pin(&mut self, old: &str, new: &str) -> Result<(), Error> { - self.card_tx.change_pw1(old, new) + self.opt.change_pw1(old, new) } pub fn change_user_pin_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> { prompt(); - self.card_tx.change_pw1_pinpad() + self.opt.change_pw1_pinpad() } pub fn reset_user_pin(&mut self, rst: &str, new: &str) -> Result<(), Error> { - self.card_tx + self.opt .reset_retry_counter_pw1(new.into(), Some(rst.into())) } pub fn change_admin_pin(&mut self, old: &str, new: &str) -> Result<(), Error> { - self.card_tx.change_pw3(old, new) + self.opt.change_pw3(old, new) } pub fn change_admin_pin_pinpad(&mut self, prompt: &dyn Fn()) -> Result<(), Error> { prompt(); - self.card_tx.change_pw3_pinpad() + self.opt.change_pw3_pinpad() } /// Get a view of the card authenticated for "User" commands. - pub fn user_card<'b>(&'a mut self) -> Option> { + pub fn user_card<'b>(&'b mut self) -> Option> { if self.pw1 { Some(User { oc: self }) } else { @@ -259,17 +260,17 @@ impl<'a> Open<'a> { // --- URL (5f50) --- pub fn url(&mut self) -> Result { - Ok(String::from_utf8_lossy(&self.card_tx.url()?).to_string()) + Ok(String::from_utf8_lossy(&self.opt.url()?).to_string()) } // --- cardholder related data (65) --- pub fn cardholder_related_data(&mut self) -> Result { - self.card_tx.cardholder_related_data() + self.opt.cardholder_related_data() } // --- security support template (7a) --- pub fn security_support_template(&mut self) -> Result { - self.card_tx.security_support_template() + self.opt.security_support_template() } // DO "Algorithm Information" (0xFA) @@ -283,25 +284,25 @@ impl<'a> Open<'a> { return Ok(None); } - self.card_tx.algorithm_information() + self.opt.algorithm_information() } /// Firmware Version, YubiKey specific (?) pub fn firmware_version(&mut self) -> Result> { - self.card_tx.firmware_version() + self.opt.firmware_version() } // ---------- pub fn public_key(&mut self, key_type: KeyType) -> Result { - self.card_tx.public_key(key_type).map_err(|e| e.into()) + self.opt.public_key(key_type).map_err(|e| e.into()) } // ---------- /// Delete all state on this OpenPGP card pub fn factory_reset(&mut self) -> Result<()> { - self.card_tx.factory_reset() + self.opt.factory_reset() } } @@ -311,9 +312,9 @@ pub struct User<'app, 'open> { oc: &'open mut Open<'app>, } -impl User<'_, '_> { - pub fn decryptor(&mut self, cert: &Cert) -> Result { - CardDecryptor::new(self.oc.card_tx, cert) +impl<'app, 'open> User<'app, 'open> { + pub fn decryptor(&mut self, cert: &Cert) -> Result, Error> { + CardDecryptor::new(&mut self.oc.opt, cert) } } @@ -323,19 +324,19 @@ pub struct Sign<'app, 'open> { oc: &'open mut Open<'app>, } -impl Sign<'_, '_> { - pub fn signer(&mut self, cert: &Cert) -> std::result::Result { +impl<'app, 'open> Sign<'app, 'open> { + pub fn signer(&mut self, cert: &Cert) -> std::result::Result, Error> { // FIXME: depending on the setting in "PW1 Status byte", only one // signature can be made after verification for signing - CardSigner::new(self.oc.card_tx, cert) + CardSigner::new(&mut self.oc.opt, cert) } - pub fn signer_from_pubkey(&mut self, pubkey: PublicKey) -> CardSigner { + pub fn signer_from_pubkey(&mut self, pubkey: PublicKey) -> CardSigner<'_, 'app> { // FIXME: depending on the setting in "PW1 Status byte", only one // signature can be made after verification for signing - CardSigner::with_pubkey(self.oc.card_tx, pubkey) + CardSigner::with_pubkey(&mut self.oc.opt, pubkey) } } @@ -361,7 +362,7 @@ impl Admin<'_, '_> { return Err(anyhow!("Invalid char in name").into()); }; - self.oc.card_tx.set_name(name.as_bytes()) + self.oc.opt.set_name(name.as_bytes()) } pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> { @@ -369,11 +370,11 @@ impl Admin<'_, '_> { return Err(anyhow!("lang too long").into()); } - self.oc.card_tx.set_lang(lang) + self.oc.opt.set_lang(lang) } pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { - self.oc.card_tx.set_sex(sex) + self.oc.opt.set_sex(sex) } pub fn set_url(&mut self, url: &str) -> Result<(), Error> { @@ -390,18 +391,18 @@ impl Admin<'_, '_> { // or if it's within the acceptable length: // send the url update to the card. - self.oc.card_tx.set_url(url.as_bytes()) + self.oc.opt.set_url(url.as_bytes()) } else { Err(anyhow!("URL too long").into()) } } pub fn set_resetting_code(&mut self, pin: &str) -> Result<(), Error> { - self.oc.card_tx.set_resetting_code(pin.into()) + self.oc.opt.set_resetting_code(pin.into()) } pub fn reset_user_pin(&mut self, new: &str) -> Result<(), Error> { - self.oc.card_tx.reset_retry_counter_pw1(new.into(), None) + self.oc.opt.reset_retry_counter_pw1(new.into(), None) } /// Upload a ValidErasedKeyAmalgamation to the card as a specific KeyType. @@ -414,7 +415,7 @@ impl Admin<'_, '_> { password: Option, ) -> Result<(), Error> { let key = vka_as_uploadable_key(vka, password); - self.oc.card_tx.key_import(key, key_type) + self.oc.opt.key_import(key, key_type) } pub fn generate_key_simple( @@ -423,14 +424,13 @@ impl Admin<'_, '_> { algo: Option, ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { match algo { - Some(algo) => { - self.oc - .card_tx - .generate_key_simple(public_to_fingerprint, key_type, algo) - } + Some(algo) => self + .oc + .opt + .generate_key_simple(public_to_fingerprint, key_type, algo), None => self .oc - .card_tx + .opt .generate_key(public_to_fingerprint, key_type, None), } } diff --git a/openpgp-card-sequoia/src/decryptor.rs b/openpgp-card-sequoia/src/decryptor.rs index 573cbb4..ff1a48b 100644 --- a/openpgp-card-sequoia/src/decryptor.rs +++ b/openpgp-card-sequoia/src/decryptor.rs @@ -13,30 +13,30 @@ use openpgp::Cert; use sequoia_openpgp as openpgp; use openpgp_card::crypto_data::Cryptogram; -use openpgp_card::{CardTransaction, Error}; +use openpgp_card::{Error, OpenPgpTransaction}; use crate::sq_util; use crate::PublicKey; -pub struct CardDecryptor<'a> { +pub struct CardDecryptor<'a, 'app> { /// The OpenPGP card (authenticated to allow decryption operations) - card_tx: &'a mut dyn CardTransaction, + ca: &'a mut OpenPgpTransaction<'app>, /// The matching public key for the card's decryption key public: PublicKey, } -impl<'a> CardDecryptor<'a> { +impl<'a, 'app> CardDecryptor<'a, 'app> { /// Try to create a CardDecryptor. /// /// An Error is returned if no match between the card's decryption /// key and a (sub)key of `cert` can be made. pub fn new( - card_tx: &'a mut dyn CardTransaction, + ca: &'a mut OpenPgpTransaction<'app>, cert: &Cert, - ) -> Result, Error> { + ) -> Result, Error> { // Get the fingerprint for the decryption key from the card. - let ard = card_tx.application_related_data()?; + let ard = ca.application_related_data()?; let fps = ard.fingerprints()?; let fp = fps.decryption(); @@ -46,7 +46,7 @@ impl<'a> CardDecryptor<'a> { if let Some(eka) = sq_util::get_subkey_by_fingerprint(cert, &fp)? { let public = eka.key().clone(); - Ok(Self { card_tx, public }) + Ok(Self { ca, public }) } else { Err(Error::InternalError(anyhow!( "Failed to find (sub)key {} in cert", @@ -61,7 +61,7 @@ impl<'a> CardDecryptor<'a> { } } -impl<'a> crypto::Decryptor for CardDecryptor<'a> { +impl<'a, 'app> crypto::Decryptor for CardDecryptor<'a, 'app> { fn public(&self) -> &PublicKey { &self.public } @@ -80,7 +80,7 @@ impl<'a> crypto::Decryptor for CardDecryptor<'a> { match (ciphertext, self.public.mpis()) { (mpi::Ciphertext::RSA { c: ct }, mpi::PublicKey::RSA { .. }) => { let dm = Cryptogram::RSA(ct.value()); - let dec = self.card_tx.decipher(dm)?; + let dec = self.ca.decipher(dm)?; let sk = openpgp::crypto::SessionKey::from(&dec[..]); Ok(sk) @@ -101,7 +101,7 @@ impl<'a> crypto::Decryptor for CardDecryptor<'a> { }; // Decryption operation on the card - let mut dec = self.card_tx.decipher(dm)?; + let mut dec = self.ca.decipher(dm)?; // Specifically handle return value format like Gnuk's // (Gnuk returns a leading '0x04' byte and @@ -129,7 +129,7 @@ impl<'a> crypto::Decryptor for CardDecryptor<'a> { } } -impl<'a> DecryptionHelper for CardDecryptor<'a> { +impl<'a, 'app> DecryptionHelper for CardDecryptor<'a, 'app> { fn decrypt( &mut self, pkesks: &[packet::PKESK], @@ -159,7 +159,7 @@ impl<'a> DecryptionHelper for CardDecryptor<'a> { } } -impl<'a> VerificationHelper for CardDecryptor<'a> { +impl VerificationHelper for CardDecryptor<'_, '_> { fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result> { Ok(vec![]) } diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 9e5a2f2..76c7e11 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -13,13 +13,13 @@ //! //! ```no_run //! use openpgp_card_pcsc::PcscBackend; -//! use openpgp_card::CardBackend; +//! use openpgp_card::OpenPgp; //! use openpgp_card_sequoia::card::Open; //! //! # fn main() -> Result<(), Box> { //! for mut card in PcscBackend::cards(None)? { -//! let mut txc = card.transaction()?; -//! let open = Open::new(&mut *txc)?; +//! let mut pgp = OpenPgp::new(&mut card); +//! let mut open = Open::new(pgp.transaction()?)?; //! println!("Found OpenPGP card with ident '{}'", //! open.application_identifier()?.ident()); //! } @@ -31,13 +31,13 @@ //! //! ```no_run //! use openpgp_card_pcsc::PcscBackend; -//! use openpgp_card::CardBackend; +//! use openpgp_card::OpenPgp; //! use openpgp_card_sequoia::card::Open; //! //! # fn main() -> Result<(), Box> { //! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?; -//! let mut txc = card.transaction()?; -//! let mut open = Open::new(&mut *txc)?; +//! let mut pgp = OpenPgp::new(&mut card); +//! let mut open = Open::new(pgp.transaction()?)?; //! # Ok(()) //! # } //! ``` @@ -53,15 +53,15 @@ //! //! ```no_run //! use openpgp_card_pcsc::PcscBackend; -//! use openpgp_card::CardBackend; +//! use openpgp_card::OpenPgp; //! use openpgp_card_sequoia::card::Open; //! //! # fn main() -> Result<(), Box> { //! // Open card via PCSC //! use sequoia_openpgp::policy::StandardPolicy; //! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?; -//! let mut txc = card.transaction()?; -//! let mut open = Open::new(&mut *txc)?; +//! let mut pgp = OpenPgp::new(&mut card); +//! let mut open = Open::new(pgp.transaction()?)?; //! //! // Get authorization for user access to the card with password //! open.verify_user("123456")?; @@ -96,15 +96,15 @@ //! //! ```no_run //! use openpgp_card_pcsc::PcscBackend; -//! use openpgp_card::CardBackend; +//! use openpgp_card::OpenPgp; //! use openpgp_card_sequoia::card::Open; //! //! # fn main() -> Result<(), Box> { //! // Open card via PCSC //! use sequoia_openpgp::policy::StandardPolicy; //! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?; -//! let mut txc = card.transaction()?; -//! let mut open = Open::new(&mut *txc)?; +//! let mut pgp = OpenPgp::new(&mut card); +//! let mut open = Open::new(pgp.transaction()?)?; //! //! // Get authorization for signing access to the card with password //! open.verify_user_for_signing("123456")?; @@ -129,14 +129,14 @@ //! //! ```no_run //! use openpgp_card_pcsc::PcscBackend; -//! use openpgp_card::CardBackend; +//! use openpgp_card::OpenPgp; //! use openpgp_card_sequoia::card::Open; //! //! # fn main() -> Result<(), Box> { //! // Open card via PCSC //! let mut card = PcscBackend::open_by_ident("abcd:12345678", None)?; -//! let mut txc = card.transaction()?; -//! let mut open = Open::new(&mut *txc)?; +//! let mut pgp = OpenPgp::new(&mut card); +//! let mut open = Open::new(pgp.transaction()?)?; //! //! // Get authorization for admin access to the card with password //! open.verify_admin("12345678")?; diff --git a/openpgp-card-sequoia/src/main.rs b/openpgp-card-sequoia/src/main.rs index 1701f1a..1d5bcd0 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -10,7 +10,7 @@ use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::Cert; use openpgp_card::card_do::Sex; -use openpgp_card::{CardBackend, KeyType}; +use openpgp_card::{KeyType, OpenPgp}; use openpgp_card_pcsc::PcscBackend; use openpgp_card_sequoia::card::Open; @@ -36,9 +36,9 @@ fn main() -> Result<(), Box> { if let Ok(test_card_ident) = test_card_ident { let mut card = PcscBackend::open_by_ident(&test_card_ident, None)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - let mut open = Open::new(&mut *txc)?; + let mut open = Open::new(pgp.transaction()?)?; // card metadata @@ -141,9 +141,9 @@ fn main() -> Result<(), Box> { // Open fresh Card for decrypt // ----------------------------- let mut card = PcscBackend::open_by_ident(&test_card_ident, None)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - let mut open = Open::new(&mut *txc)?; + let mut open = Open::new(pgp.transaction()?)?; // Check that we're still using the expected card let app_id = open.application_identifier()?; @@ -181,9 +181,9 @@ fn main() -> Result<(), Box> { // Open fresh Card for signing // ----------------------------- let mut card = PcscBackend::open_by_ident(&test_card_ident, None)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - let mut open = Open::new(&mut *txc)?; + let mut open = Open::new(pgp.transaction()?)?; // Sign open.verify_user_for_signing("123456")?; @@ -213,9 +213,10 @@ fn main() -> Result<(), Box> { println!("The following OpenPGP cards are connected to your system:"); for mut card in PcscBackend::cards(None)? { - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); + + let open = Open::new(pgp.transaction()?)?; - let open = Open::new(&mut *txc)?; println!(" {}", open.application_identifier()?.ident()); } } diff --git a/openpgp-card-sequoia/src/signer.rs b/openpgp-card-sequoia/src/signer.rs index a4e43a7..cd0854e 100644 --- a/openpgp-card-sequoia/src/signer.rs +++ b/openpgp-card-sequoia/src/signer.rs @@ -11,30 +11,30 @@ use openpgp::types::{Curve, PublicKeyAlgorithm}; use sequoia_openpgp as openpgp; use openpgp_card::crypto_data::Hash; -use openpgp_card::{CardTransaction, Error}; +use openpgp_card::{Error, OpenPgpTransaction}; use crate::sq_util; use crate::PublicKey; -pub struct CardSigner<'a> { +pub struct CardSigner<'a, 'app> { /// The OpenPGP card (authenticated to allow signing operations) - card_tx: &'a mut (dyn CardTransaction + Send + Sync), + ca: &'a mut OpenPgpTransaction<'app>, /// The matching public key for the card's signing key public: PublicKey, } -impl<'a> CardSigner<'a> { +impl<'a, 'app> CardSigner<'a, 'app> { /// Try to create a CardSigner. /// /// An Error is returned if no match between the card's signing /// key and a (sub)key of `cert` can be made. pub fn new( - card_tx: &'a mut (dyn CardTransaction + Send + Sync), + ca: &'a mut OpenPgpTransaction<'app>, cert: &openpgp::Cert, - ) -> Result, Error> { + ) -> Result, Error> { // Get the fingerprint for the signing key from the card. - let ard = card_tx.application_related_data()?; + let ard = ca.application_related_data()?; let fps = ard.fingerprints()?; let fp = fps.signature(); @@ -44,7 +44,7 @@ impl<'a> CardSigner<'a> { if let Some(eka) = sq_util::get_subkey_by_fingerprint(cert, &fp)? { let key = eka.key().clone(); - Ok(Self::with_pubkey(card_tx, key)) + Ok(Self::with_pubkey(ca, key)) } else { Err(Error::InternalError(anyhow!( "Failed to find (sub)key {} in cert", @@ -59,14 +59,14 @@ impl<'a> CardSigner<'a> { } pub(crate) fn with_pubkey( - card_tx: &'a mut (dyn CardTransaction + Send + Sync), + ca: &'a mut OpenPgpTransaction<'app>, public: PublicKey, - ) -> CardSigner<'a> { - CardSigner { card_tx, public } + ) -> CardSigner<'a, 'app> { + CardSigner { ca, public } } } -impl<'a> crypto::Signer for CardSigner<'a> { +impl<'a, 'app> crypto::Signer for CardSigner<'a, 'app> { fn public(&self) -> &PublicKey { &self.public } @@ -111,7 +111,7 @@ impl<'a> crypto::Signer for CardSigner<'a> { } }; - let sig = self.card_tx.signature_for_hash(hash)?; + let sig = self.ca.signature_for_hash(hash)?; let mpi = mpi::MPI::new(&sig[..]); Ok(mpi::Signature::RSA { s: mpi }) @@ -119,7 +119,7 @@ impl<'a> crypto::Signer for CardSigner<'a> { (PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => { let hash = Hash::EdDSA(digest); - let sig = self.card_tx.signature_for_hash(hash)?; + let sig = self.ca.signature_for_hash(hash)?; let r = mpi::MPI::new(&sig[..32]); let s = mpi::MPI::new(&sig[32..]); @@ -134,7 +134,7 @@ impl<'a> crypto::Signer for CardSigner<'a> { _ => Hash::ECDSA(digest), }; - let sig = self.card_tx.signature_for_hash(hash)?; + let sig = self.ca.signature_for_hash(hash)?; let len_2 = sig.len() / 2; let r = mpi::MPI::new(&sig[..len_2]); diff --git a/openpgp-card-sequoia/src/util.rs b/openpgp-card-sequoia/src/util.rs index 39e0fbb..ab0f49a 100644 --- a/openpgp-card-sequoia/src/util.rs +++ b/openpgp-card-sequoia/src/util.rs @@ -27,7 +27,7 @@ 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::{CardTransaction, Error, KeyType}; +use openpgp_card::{Error, KeyType, OpenPgpTransaction}; use crate::card::Open; use crate::privkey::SequoiaKey; @@ -283,7 +283,7 @@ pub fn vka_as_uploadable_key( /// FIXME: this fn is used in card_functionality, but should be removed pub fn sign( - card_tx: &mut (dyn CardTransaction + Send + Sync), + card_tx: &'_ mut OpenPgpTransaction<'_>, cert: &Cert, input: &mut dyn io::Read, ) -> Result { @@ -307,7 +307,7 @@ pub fn sign( /// FIXME: this fn is used in card_functionality, but should be removed pub fn decrypt( - card_tx: &mut dyn CardTransaction, + card_tx: &'_ mut OpenPgpTransaction<'_>, cert: &Cert, msg: Vec, p: &dyn Policy, diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index d28796d..855369d 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -15,9 +15,9 @@ use crate::crypto_data::{ CardUploadableKey, EccKey, EccPub, EccType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey, RSAPub, }; +use crate::openpgp::OpenPgpTransaction; use crate::tlv::{length::tlv_encode_length, value::Value, Tlv}; -use crate::{apdu, KeyType}; -use crate::{CardTransaction, Error}; +use crate::{apdu, Error, KeyType}; /// Generate asymmetric key pair on the card. /// @@ -29,15 +29,12 @@ use crate::{CardTransaction, Error}; /// /// `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 C, +pub(crate) fn gen_key_with_metadata( + card_tx: &mut OpenPgpTransaction, fp_from_pub: fn(&PublicKeyMaterial, KeyGenerationTime, KeyType) -> Result, key_type: KeyType, algo: Option<&Algo>, -) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> -where - C: CardTransaction + ?Sized, -{ +) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { // Set algo on card if it's Some if let Some(target_algo) = algo { // FIXME: caching @@ -125,18 +122,15 @@ fn tlv_to_pubkey(tlv: &Tlv, algo: &Algo) -> Result { /// /// This runs the low level key generation primitive on the card. /// (This does not set algorithm attributes, creation time or fingerprint) -pub(crate) fn generate_asymmetric_key_pair( - card_tx: &mut C, +pub(crate) fn generate_asymmetric_key_pair( + card_tx: &mut OpenPgpTransaction, key_type: KeyType, -) -> Result -where - C: CardTransaction + ?Sized, -{ +) -> Result { // generate key let crt = control_reference_template(key_type)?; let gen_key_cmd = commands::gen_key(crt.serialize().to_vec()); - let resp = apdu::send_command(card_tx, gen_key_cmd, true)?; + let resp = apdu::send_command(card_tx.tx(), gen_key_cmd, true)?; resp.check_ok()?; let tlv = Tlv::try_from(resp.data()?)?; @@ -150,10 +144,10 @@ where /// in the card or imported") /// /// (See 7.2.14 GENERATE ASYMMETRIC KEY PAIR) -pub(crate) fn public_key(card_tx: &mut C, key_type: KeyType) -> Result -where - C: CardTransaction + ?Sized, -{ +pub(crate) fn public_key( + card_tx: &mut OpenPgpTransaction, + key_type: KeyType, +) -> Result { // get current algo let ard = card_tx.application_related_data()?; // FIXME: caching let algo = ard.algorithm_attributes(key_type)?; @@ -162,7 +156,7 @@ where let crt = control_reference_template(key_type)?; let get_pub_key_cmd = commands::get_pub_key(crt.serialize().to_vec()); - let resp = apdu::send_command(card_tx, get_pub_key_cmd, true)?; + let resp = apdu::send_command(card_tx.tx(), get_pub_key_cmd, true)?; resp.check_ok()?; let tlv = Tlv::try_from(resp.data()?)?; @@ -176,15 +170,12 @@ where /// If the key is suitable for `key_type`, an Error is returned (either /// caused by checks before attempting to upload the key to the card, or by /// an error that the card reports during an attempt to upload the key). -pub(crate) fn key_import( - card_tx: &mut C, +pub(crate) fn key_import( + card_tx: &mut OpenPgpTransaction, key: Box, key_type: KeyType, algo_info: Option, -) -> Result<(), Error> -where - C: CardTransaction + ?Sized, -{ +) -> Result<(), Error> { // FIXME: caching? let ard = card_tx.application_related_data()?; @@ -220,7 +211,7 @@ where card_tx.set_algorithm_attributes(key_type, &algo)?; } - apdu::send_command(card_tx, key_cmd, false)?.check_ok()?; + apdu::send_command(card_tx.tx(), key_cmd, false)?.check_ok()?; card_tx.set_fingerprint(fp, key_type)?; card_tx.set_creation_time(key.timestamp(), key_type)?; diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index f0de92e..d9cbc09 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -31,27 +31,24 @@ pub mod card_do; pub mod crypto_data; mod errors; pub(crate) mod keys; +mod openpgp; mod tlv; pub use crate::errors::{Error, SmartcardError, StatusBytes}; +pub use crate::openpgp::{OpenPgp, OpenPgpTransaction}; -use anyhow::{anyhow, Result}; -use std::convert::TryFrom; +use anyhow::Result; use std::convert::TryInto; use std::ops::{Deref, DerefMut}; -use crate::algorithm::{Algo, AlgoInfo, AlgoSimple}; use crate::apdu::commands; -use crate::apdu::response::RawResponse; -use crate::card_do::{ - ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang, - PWStatusBytes, SecuritySupportTemplate, Sex, -}; -use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; -use crate::tlv::tag::Tag; -use crate::tlv::value::Value; -use crate::tlv::Tlv; +use crate::card_do::ApplicationRelatedData; +use crate::tlv::{tag::Tag, value::Value, Tlv}; +/// The CardBackend trait defines a connection with an OpenPGP card via a +/// backend implementation (e.g. via the pcsc backend in the crate +/// [openpgp-card-pcsc](https://crates.io/crates/openpgp-card-pcsc)), +/// A CardBackend is only used to get access to a `CardTransaction` object. #[blanket::blanket(derive(Box))] pub trait CardBackend { fn transaction(&mut self) -> Result, Error>; @@ -61,8 +58,6 @@ pub trait CardBackend { /// backend implementation (e.g. the pcsc backend in the crate /// [openpgp-card-pcsc](https://crates.io/crates/openpgp-card-pcsc)), /// after opening a transaction from a CardBackend. -/// -/// CardTransaction exposes low-level access to OpenPGP card functionality. #[blanket::blanket(derive(Box))] pub trait CardTransaction { /// Transmit the command data in `cmd` to the card. @@ -110,6 +105,21 @@ pub trait CardTransaction { apdu::send_command(self, select_openpgp, false)?.try_into() } + /// 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.) + fn application_related_data(&mut self) -> Result { + let ad = commands::application_related_data(); + let resp = apdu::send_command(self, ad, true)?; + let value = Value::from(resp.data()?, true)?; + + log::debug!(" ARD value: {:x?}", value); + + Ok(ApplicationRelatedData(Tlv::new(Tag::from([0x6E]), value))) + } + /// Get a CardApp based on a CardTransaction. /// /// It is expected that SELECT has already been performed on the card @@ -168,673 +178,6 @@ pub trait CardTransaction { Ok(()) } - - // --- 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.) - fn application_related_data(&mut self) -> Result { - let ad = commands::application_related_data(); - let resp = apdu::send_command(self, ad, true)?; - let value = Value::from(resp.data()?, true)?; - - log::debug!(" ARD value: {:x?}", value); - - Ok(ApplicationRelatedData(Tlv::new(Tag::from([0x6E]), value))) - } - - // #[allow(dead_code)] - // fn ca_fingerprints() { - // unimplemented!() - // } - // - // #[allow(dead_code)] - // fn key_information() { - // unimplemented!() - // } - // - // #[allow(dead_code)] - // fn uif_pso_cds() { - // unimplemented!() - // } - // - // #[allow(dead_code)] - // fn uif_pso_dec() { - // unimplemented!() - // } - // - // #[allow(dead_code)] - // fn uif_pso_aut() { - // unimplemented!() - // } - // - // #[allow(dead_code)] - // fn uif_attestation() { - // unimplemented!() - // } - - // --- login data (5e) --- - - /// Get URL (5f50) - fn url(&mut self) -> Result> { - let resp = apdu::send_command(self, commands::url(), true)?; - - Ok(resp.data()?.to_vec()) - } - - /// Get cardholder related data (65) - fn cardholder_related_data(&mut self) -> Result { - let crd = commands::cardholder_related_data(); - let resp = apdu::send_command(self, crd, true)?; - resp.check_ok()?; - - CardholderRelatedData::try_from(resp.data()?) - } - - /// Get security support template (7a) - fn security_support_template(&mut self) -> Result { - let sst = commands::security_support_template(); - let resp = apdu::send_command(self, sst, true)?; - resp.check_ok()?; - - let tlv = Tlv::try_from(resp.data()?)?; - let res = tlv - .find(&[0x93].into()) - .ok_or_else(|| anyhow!("Couldn't get SecuritySupportTemplate DO"))?; - - if let Value::S(data) = res { - let mut data = data.to_vec(); - assert_eq!(data.len(), 3); - - 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(anyhow!("Failed to process SecuritySupportTemplate")) - } - } - - /// 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). - #[allow(dead_code)] - fn cardholder_certificate(&mut self) -> Result, Error> { - let cmd = commands::cardholder_certificate(); - apdu::send_command(self, cmd, true)?.try_into() - } - - /// Get "Algorithm Information" - fn algorithm_information(&mut self) -> Result> { - let resp = apdu::send_command(self, commands::algo_info(), true)?; - resp.check_ok()?; - - let ai = AlgoInfo::try_from(resp.data()?)?; - Ok(Some(ai)) - } - - /// Firmware Version (YubiKey specific (?)) - fn firmware_version(&mut self) -> Result> { - let resp = apdu::send_command(self, commands::firmware_version(), true)?; - - Ok(resp.data()?.into()) - } - - /// Set identity (Nitrokey Start specific (?)). - /// [see: - /// - /// ] - fn set_identity(&mut self, id: u8) -> Result> { - let resp = apdu::send_command(self, 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", - /// e.g. for cardholder certificate) - fn select_data(&mut self, num: u8, tag: &[u8]) -> Result, Error> { - let tlv = Tlv::new( - [0x60], - Value::C(vec![Tlv::new([0x5c], Value::S(tag.to_vec()))]), - ); - - let data = tlv.serialize(); - - let cmd = commands::select_data(num, data); - apdu::send_command(self, cmd, true)?.try_into() - } - - // --- optional private DOs (0101 - 0104) --- - - /// Get data from "private use" DO. - /// - /// `num` must be between 1 and 4. - fn private_use_do(&mut self, num: u8) -> Result> { - assert!((1..=4).contains(&num)); - - let cmd = commands::private_use_do(num); - let resp = apdu::send_command(self, cmd, true)?; - - Ok(resp.data()?.to_vec()) - } - - /// Set data of "private use" DO. - /// - /// `num` must be between 1 and 4. - /// - /// Access condition: - /// - 1/3 need PW1 (82) - /// - 2/4 need PW3 - fn set_private_use_do(&mut self, num: u8, data: Vec) -> Result> { - assert!((1..=4).contains(&num)); - - let cmd = commands::put_private_use_do(num, data); - let resp = apdu::send_command(self, 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). - fn factory_reset(&mut self) -> Result<()> { - // 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 { - let verify = commands::verify_pw1_81([0x40; 8].to_vec()); - let resp = apdu::send_command(self, verify, false)?; - if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied - || resp.status() == StatusBytes::AuthenticationMethodBlocked - || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) - { - return Err(anyhow!("Unexpected status for reset, at pw1.")); - } - } - - // 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 { - let verify = commands::verify_pw3([0x40; 8].to_vec()); - let resp = apdu::send_command(self, verify, false)?; - - if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied - || resp.status() == StatusBytes::AuthenticationMethodBlocked - || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) - { - return Err(anyhow!("Unexpected status for reset, at pw3.")); - } - } - - // terminate_df [apdu 00 e6 00 00] - let term = commands::terminate_df(); - let resp = apdu::send_command(self, term, false)?; - resp.check_ok()?; - - // activate_file [apdu 00 44 00 00] - let act = commands::activate_file(); - let resp = apdu::send_command(self, act, false)?; - resp.check_ok()?; - - 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. - fn verify_pw1_for_signing(&mut self, pin: &str) -> Result<(), Error> { - let verify = commands::verify_pw1_81(pin.as_bytes().to_vec()); - apdu::send_command(self, 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. - fn verify_pw1_for_signing_pinpad(&mut self) -> Result<(), Error> { - let res = self.pinpad_verify(0x81)?; - 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) - fn check_pw1_for_signing(&mut self) -> Result<(), Error> { - let verify = commands::verify_pw1_81(vec![]); - apdu::send_command(self, verify, false)?.try_into() - } - - /// Verify PW1 (user). - /// (For operations except signing, mode 82). - fn verify_pw1(&mut self, pin: &str) -> Result<(), Error> { - let verify = commands::verify_pw1_82(pin.as_bytes().to_vec()); - apdu::send_command(self, 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. - - fn verify_pw1_pinpad(&mut self) -> Result<(), Error> { - let res = self.pinpad_verify(0x82)?; - 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) - fn check_pw1(&mut self) -> Result<(), Error> { - let verify = commands::verify_pw1_82(vec![]); - apdu::send_command(self, verify, false)?.try_into() - } - - /// Verify PW3 (admin). - fn verify_pw3(&mut self, pin: &str) -> Result<(), Error> { - let verify = commands::verify_pw3(pin.as_bytes().to_vec()); - apdu::send_command(self, verify, false)?.try_into() - } - - /// Verify PW3 (admin) using a pinpad on the card reader. If no usable - /// pinpad is found, an error is returned. - fn verify_pw3_pinpad(&mut self) -> Result<(), Error> { - let res = self.pinpad_verify(0x83)?; - 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) - fn check_pw3(&mut self) -> Result<(), Error> { - let verify = commands::verify_pw3(vec![]); - apdu::send_command(self, verify, false)?.try_into() - } - - /// Change the value of PW1 (user password). - /// - /// The current value of PW1 must be presented in `old` for authorization. - fn change_pw1(&mut self, old: &str, new: &str) -> Result<(), Error> { - let mut data = vec![]; - data.extend(old.as_bytes()); - data.extend(new.as_bytes()); - - let change = commands::change_pw1(data); - apdu::send_command(self, change, false)?.try_into() - } - - /// Change the value of PW1 (user password) using a pinpad on the - /// card reader. If no usable pinpad is found, an error is returned. - fn change_pw1_pinpad(&mut self) -> Result<(), Error> { - let res = self.pinpad_modify(0x81)?; - 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. - fn change_pw3(&mut self, old: &str, new: &str) -> Result<(), Error> { - let mut data = vec![]; - data.extend(old.as_bytes()); - data.extend(new.as_bytes()); - - let change = commands::change_pw3(data); - apdu::send_command(self, 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. - fn change_pw3_pinpad(&mut self) -> Result<(), Error> { - let res = self.pinpad_modify(0x83)?; - 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. - fn reset_retry_counter_pw1( - &mut self, - new_pw1: Vec, - resetting_code: Option>, - ) -> Result<(), Error> { - let reset = commands::reset_retry_counter_pw1(resetting_code, new_pw1); - apdu::send_command(self, 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`) - 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([0x86], Value::S(eph.to_vec())); - - // Public Key DO - let pkdo = Tlv::new([0x7f, 0x49], Value::C(vec![epk])); - - // Cipher DO - let cdo = Tlv::new([0xa6], 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) - fn pso_decipher(&mut self, data: Vec) -> Result, Error> { - // The OpenPGP card is already connected and PW1 82 has been verified - let dec_cmd = commands::decryption(data); - let resp = apdu::send_command(self, dec_cmd, true)?; - resp.check_ok()?; - - Ok(resp.data().map(|d| d.to_vec())?) - } - - // --- 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. - 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) - fn pso_compute_digital_signature(&mut self, data: Vec) -> Result, Error> { - let cds_cmd = commands::signature(data); - - let resp = apdu::send_command(self, 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. - 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) - fn internal_authenticate(&mut self, data: Vec) -> Result, Error> { - let ia_cmd = commands::internal_authenticate(data); - let resp = apdu::send_command(self, ia_cmd, true)?; - - Ok(resp.data().map(|d| d.to_vec())?) - } - - // --- admin --- - - fn set_name(&mut self, name: &[u8]) -> Result<(), Error> { - let put_name = commands::put_name(name.to_vec()); - apdu::send_command(self, put_name, false)?.try_into() - } - - fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> { - let bytes: Vec = lang - .iter() - .map(|&l| Into::>::into(l)) - .flatten() - .collect(); - - let put_lang = commands::put_lang(bytes); - apdu::send_command(self, put_lang, false)?.try_into() - } - - fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { - let put_sex = commands::put_sex((&sex).into()); - apdu::send_command(self, put_sex, false)?.try_into() - } - - fn set_url(&mut self, url: &[u8]) -> Result<(), Error> { - let put_url = commands::put_url(url.to_vec()); - apdu::send_command(self, put_url, false)?.try_into() - } - - fn set_creation_time( - &mut self, - time: KeyGenerationTime, - key_type: KeyType, - ) -> Result<(), Error> { - // 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); - - apdu::send_command(self, time_cmd, false)?.try_into() - } - - fn set_fingerprint(&mut self, fp: Fingerprint, key_type: KeyType) -> Result<(), Error> { - let fp_cmd = commands::put_data(&[key_type.fingerprint_put_tag()], fp.as_bytes().to_vec()); - - apdu::send_command(self, fp_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) - fn set_pw_status_bytes(&mut self, pw_status: &PWStatusBytes, long: bool) -> Result<(), Error> { - let data = pw_status.serialize_for_put(long); - - let cmd = commands::put_pw_status(data); - apdu::send_command(self, cmd, false)?.try_into() - } - - /// Set cardholder certificate (for AUT, DEC or SIG). - /// - /// Call select_data() before calling this fn, to select a particular - /// certificate (if the card supports multiple certificates). - fn set_cardholder_certificate(&mut self, data: Vec) -> Result<(), Error> { - let cmd = commands::put_cardholder_certificate(data); - apdu::send_command(self, cmd, false)?.try_into() - } - - /// Set algorithm attributes - /// (4.4.3.9 Algorithm Attributes) - fn set_algorithm_attributes(&mut self, key_type: KeyType, algo: &Algo) -> Result<(), Error> { - // Command to PUT the algorithm attributes - let cmd = commands::put_data(&[key_type.algorithm_tag()], algo.to_data_object()?); - - apdu::send_command(self, cmd, false)?.try_into() - } - - /// Set resetting code - /// (4.3.4 Resetting Code) - fn set_resetting_code(&mut self, resetting_code: Vec) -> Result<(), Error> { - let cmd = commands::put_data(&[0xd3], resetting_code); - apdu::send_command(self, cmd, false)?.try_into() - } - - /// Import an existing private key to the card. - /// (This implicitly sets the algorithm info, fingerprint and timestamp) - fn key_import( - &mut self, - key: Box, - key_type: KeyType, - ) -> Result<(), Error> { - let algo_info = self.algorithm_information(); - - // An error is ok - it's fine if a card doesn't offer a list of - // supported algorithms - let algo_info = algo_info.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 `algo` parameter is Some, then this algorithm will be set on - /// the card for "key_type". - /// - /// Note: `algo` needs to precisely specify the RSA bitsize of e (if - /// applicable), and import format, with values that the current card - /// supports. - fn generate_key( - &mut self, - fp_from_pub: fn( - &PublicKeyMaterial, - KeyGenerationTime, - KeyType, - ) -> Result, - key_type: KeyType, - algo: Option<&Algo>, - ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { - keys::gen_key_with_metadata(self, fp_from_pub, key_type, algo) - } - - /// 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. - 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(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. - fn public_key(&mut self, key_type: KeyType) -> Result { - keys::public_key(self, key_type) - } } impl<'a> Deref for dyn CardTransaction + Send + Sync + 'a { @@ -850,40 +193,15 @@ impl<'a> DerefMut for dyn CardTransaction + Send + Sync + 'a { } } -fn digestinfo(hash: Hash) -> Vec { - match hash { - Hash::SHA256(_) | Hash::SHA384(_) | Hash::SHA512(_) => { - let tlv = Tlv::new( - [0x30], - Value::C(vec![ - Tlv::new( - [0x30], - Value::C(vec![ - Tlv::new( - [0x06], - // unwrapping is ok, for SHA* - Value::S(hash.oid().unwrap().to_vec()), - ), - Tlv::new([0x05], Value::S(vec![])), - ]), - ), - Tlv::new([0x04], Value::S(hash.digest().to_vec())), - ]), - ); - - tlv.serialize() - } - Hash::EdDSA(d) => d.to_vec(), - Hash::ECDSA(d) => d.to_vec(), - } -} - -/// Configuration of the capabilities of a card. +/// Information about the capabilities of a card. /// -/// This configuration is used to determine e.g. if chaining or extended +/// This configuration can is used to determine e.g. if chaining or extended /// length can be used when communicating with the card. /// -/// (This configuration is retrieved from card metadata, specifically from +/// CardCaps data is used internally and can be used by card backends, it is unlikely to be +/// useful for users of this library. +/// +/// (The information is retrieved from card metadata, specifically from /// "Card Capabilities", "Extended length information" and "PWStatus") #[derive(Clone, Copy, Debug)] pub struct CardCaps { diff --git a/openpgp-card/src/openpgp.rs b/openpgp-card/src/openpgp.rs new file mode 100644 index 0000000..693004a --- /dev/null +++ b/openpgp-card/src/openpgp.rs @@ -0,0 +1,772 @@ +// SPDX-FileCopyrightText: 2021-2022 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::{anyhow, Result}; +use std::convert::{TryFrom, TryInto}; + +use crate::algorithm::{Algo, AlgoInfo, AlgoSimple}; +use crate::apdu::commands; +use crate::apdu::response::RawResponse; +use crate::card_do::{ + ApplicationRelatedData, CardholderRelatedData, Fingerprint, KeyGenerationTime, Lang, + PWStatusBytes, SecuritySupportTemplate, Sex, +}; +use crate::crypto_data::{CardUploadableKey, Cryptogram, Hash, PublicKeyMaterial}; +use crate::tlv::{value::Value, Tlv}; +use crate::{ + apdu, keys, CardBackend, CardTransaction, Error, KeyType, SmartcardError, StatusBytes, +}; + +/// An OpenPGP card access object, backed by a CardBackend implementation. +/// +/// Most users will probably want to use the `PcscCard` backend from the `openpgp-card-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<'a> { + card: &'a mut dyn CardBackend, +} + +impl<'a> OpenPgp<'a> { + pub fn new(card: &'a mut dyn CardBackend) -> Self { + 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. + pub fn transaction(&mut self) -> Result { + Ok(OpenPgpTransaction { + tx: self.card.transaction()?, + }) + } +} + +/// 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, +} + +impl<'a> OpenPgpTransaction<'a> { + pub(crate) fn tx(&mut self) -> &mut dyn CardTransaction { + self.tx.as_mut() + } + + // --- 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 { + self.tx.application_related_data() + } + + // #[allow(dead_code)] + // fn ca_fingerprints() { + // unimplemented!() + // } + // + // #[allow(dead_code)] + // fn key_information() { + // unimplemented!() + // } + // + // #[allow(dead_code)] + // fn uif_pso_cds() { + // unimplemented!() + // } + // + // #[allow(dead_code)] + // fn uif_pso_dec() { + // unimplemented!() + // } + // + // #[allow(dead_code)] + // fn uif_pso_aut() { + // unimplemented!() + // } + // + // #[allow(dead_code)] + // fn uif_attestation() { + // unimplemented!() + // } + + // --- login data (5e) --- + + /// Get URL (5f50) + pub fn url(&mut self) -> Result> { + let resp = apdu::send_command(self.tx(), commands::url(), true)?; + + Ok(resp.data()?.to_vec()) + } + + /// Get cardholder related data (65) + pub fn cardholder_related_data(&mut self) -> Result { + let crd = commands::cardholder_related_data(); + let resp = apdu::send_command(self.tx(), crd, true)?; + resp.check_ok()?; + + CardholderRelatedData::try_from(resp.data()?) + } + + /// Get security support template (7a) + pub fn security_support_template(&mut self) -> Result { + let sst = commands::security_support_template(); + let resp = apdu::send_command(self.tx(), sst, true)?; + resp.check_ok()?; + + let tlv = Tlv::try_from(resp.data()?)?; + let res = tlv + .find(&[0x93].into()) + .ok_or_else(|| anyhow!("Couldn't get SecuritySupportTemplate DO"))?; + + if let Value::S(data) = res { + let mut data = data.to_vec(); + assert_eq!(data.len(), 3); + + 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(anyhow!("Failed to process SecuritySupportTemplate")) + } + } + + /// 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). + #[allow(dead_code)] + pub fn cardholder_certificate(&mut self) -> Result, Error> { + let cmd = commands::cardholder_certificate(); + apdu::send_command(self.tx(), cmd, true)?.try_into() + } + + /// Get "Algorithm Information" + pub fn algorithm_information(&mut self) -> Result> { + let resp = apdu::send_command(self.tx(), commands::algo_info(), true)?; + resp.check_ok()?; + + let ai = AlgoInfo::try_from(resp.data()?)?; + Ok(Some(ai)) + } + + /// Firmware Version (YubiKey specific (?)) + pub fn firmware_version(&mut self) -> Result> { + let resp = apdu::send_command(self.tx(), commands::firmware_version(), true)?; + + Ok(resp.data()?.into()) + } + + /// Set identity (Nitrokey Start specific (?)). + /// [see: + /// + /// ] + pub fn set_identity(&mut self, id: u8) -> Result> { + let resp = apdu::send_command(self.tx(), 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", + /// e.g. for cardholder certificate) + pub fn select_data(&mut self, num: u8, tag: &[u8]) -> Result, Error> { + let tlv = Tlv::new( + [0x60], + Value::C(vec![Tlv::new([0x5c], Value::S(tag.to_vec()))]), + ); + + let data = tlv.serialize(); + + let cmd = commands::select_data(num, data); + apdu::send_command(self.tx(), cmd, true)?.try_into() + } + + // --- 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> { + assert!((1..=4).contains(&num)); + + let cmd = commands::private_use_do(num); + let resp = apdu::send_command(self.tx(), cmd, true)?; + + Ok(resp.data()?.to_vec()) + } + + /// 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> { + assert!((1..=4).contains(&num)); + + let cmd = commands::put_private_use_do(num, data); + let resp = apdu::send_command(self.tx(), 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<()> { + // 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 { + let verify = commands::verify_pw1_81([0x40; 8].to_vec()); + let resp = apdu::send_command(self.tx(), verify, false)?; + if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied + || resp.status() == StatusBytes::AuthenticationMethodBlocked + || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) + { + return Err(anyhow!("Unexpected status for reset, at pw1.")); + } + } + + // 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 { + let verify = commands::verify_pw3([0x40; 8].to_vec()); + let resp = apdu::send_command(self.tx(), verify, false)?; + + if !(resp.status() == StatusBytes::SecurityStatusNotSatisfied + || resp.status() == StatusBytes::AuthenticationMethodBlocked + || matches!(resp.status(), StatusBytes::PasswordNotChecked(_))) + { + return Err(anyhow!("Unexpected status for reset, at pw3.")); + } + } + + // terminate_df [apdu 00 e6 00 00] + let term = commands::terminate_df(); + let resp = apdu::send_command(self.tx(), term, false)?; + resp.check_ok()?; + + // activate_file [apdu 00 44 00 00] + let act = commands::activate_file(); + let resp = apdu::send_command(self.tx(), act, false)?; + resp.check_ok()?; + + 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_for_signing(&mut self, pin: &str) -> Result<(), Error> { + let verify = commands::verify_pw1_81(pin.as_bytes().to_vec()); + apdu::send_command(self.tx(), 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_for_signing_pinpad(&mut self) -> Result<(), Error> { + let res = self.tx().pinpad_verify(0x81)?; + 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_for_signing(&mut self) -> Result<(), Error> { + let verify = commands::verify_pw1_81(vec![]); + apdu::send_command(self.tx(), verify, false)?.try_into() + } + + /// Verify PW1 (user). + /// (For operations except signing, mode 82). + pub fn verify_pw1(&mut self, pin: &str) -> Result<(), Error> { + let verify = commands::verify_pw1_82(pin.as_bytes().to_vec()); + apdu::send_command(self.tx(), 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_pinpad(&mut self) -> Result<(), Error> { + let res = self.tx().pinpad_verify(0x82)?; + 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(&mut self) -> Result<(), Error> { + let verify = commands::verify_pw1_82(vec![]); + apdu::send_command(self.tx(), verify, false)?.try_into() + } + + /// Verify PW3 (admin). + pub fn verify_pw3(&mut self, pin: &str) -> Result<(), Error> { + let verify = commands::verify_pw3(pin.as_bytes().to_vec()); + apdu::send_command(self.tx(), 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> { + let res = self.tx().pinpad_verify(0x83)?; + 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> { + let verify = commands::verify_pw3(vec![]); + apdu::send_command(self.tx(), 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: &str, new: &str) -> Result<(), Error> { + let mut data = vec![]; + data.extend(old.as_bytes()); + data.extend(new.as_bytes()); + + let change = commands::change_pw1(data); + apdu::send_command(self.tx(), change, false)?.try_into() + } + + /// Change the value of PW1 (user password) 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> { + let res = self.tx().pinpad_modify(0x81)?; + 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: &str, new: &str) -> Result<(), Error> { + let mut data = vec![]; + data.extend(old.as_bytes()); + data.extend(new.as_bytes()); + + let change = commands::change_pw3(data); + apdu::send_command(self.tx(), 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> { + let res = self.tx().pinpad_modify(0x83)?; + 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: Vec, + resetting_code: Option>, + ) -> Result<(), Error> { + let reset = commands::reset_retry_counter_pw1(resetting_code, new_pw1); + apdu::send_command(self.tx(), 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([0x86], Value::S(eph.to_vec())); + + // Public Key DO + let pkdo = Tlv::new([0x7f, 0x49], Value::C(vec![epk])); + + // Cipher DO + let cdo = Tlv::new([0xa6], 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> { + // The OpenPGP card is already connected and PW1 82 has been verified + let dec_cmd = commands::decryption(data); + let resp = apdu::send_command(self.tx(), dec_cmd, true)?; + resp.check_ok()?; + + Ok(resp.data().map(|d| d.to_vec())?) + } + + // --- 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> { + let cds_cmd = commands::signature(data); + + let resp = apdu::send_command(self.tx(), 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> { + let ia_cmd = commands::internal_authenticate(data); + let resp = apdu::send_command(self.tx(), ia_cmd, true)?; + + Ok(resp.data().map(|d| d.to_vec())?) + } + + // --- admin --- + + pub fn set_name(&mut self, name: &[u8]) -> Result<(), Error> { + let put_name = commands::put_name(name.to_vec()); + apdu::send_command(self.tx(), put_name, false)?.try_into() + } + + pub fn set_lang(&mut self, lang: &[Lang]) -> Result<(), Error> { + let bytes: Vec = lang + .iter() + .map(|&l| Into::>::into(l)) + .flatten() + .collect(); + + let put_lang = commands::put_lang(bytes); + apdu::send_command(self.tx(), put_lang, false)?.try_into() + } + + pub fn set_sex(&mut self, sex: Sex) -> Result<(), Error> { + let put_sex = commands::put_sex((&sex).into()); + apdu::send_command(self.tx(), put_sex, false)?.try_into() + } + + pub fn set_url(&mut self, url: &[u8]) -> Result<(), Error> { + let put_url = commands::put_url(url.to_vec()); + apdu::send_command(self.tx(), put_url, false)?.try_into() + } + + pub fn set_creation_time( + &mut self, + time: KeyGenerationTime, + key_type: KeyType, + ) -> Result<(), Error> { + // 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); + + apdu::send_command(self.tx(), time_cmd, false)?.try_into() + } + + pub fn set_fingerprint(&mut self, fp: Fingerprint, key_type: KeyType) -> Result<(), Error> { + let fp_cmd = commands::put_data(&[key_type.fingerprint_put_tag()], fp.as_bytes().to_vec()); + + apdu::send_command(self.tx(), fp_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> { + let data = pw_status.serialize_for_put(long); + + let cmd = commands::put_pw_status(data); + apdu::send_command(self.tx(), cmd, false)?.try_into() + } + + /// Set cardholder certificate (for AUT, DEC or SIG). + /// + /// Call select_data() before calling this fn, to select a particular + /// certificate (if the card supports multiple certificates). + pub fn set_cardholder_certificate(&mut self, data: Vec) -> Result<(), Error> { + let cmd = commands::put_cardholder_certificate(data); + apdu::send_command(self.tx(), cmd, false)?.try_into() + } + + /// Set algorithm attributes + /// (4.4.3.9 Algorithm Attributes) + pub fn set_algorithm_attributes( + &mut self, + key_type: KeyType, + algo: &Algo, + ) -> Result<(), Error> { + // Command to PUT the algorithm attributes + let cmd = commands::put_data(&[key_type.algorithm_tag()], algo.to_data_object()?); + + apdu::send_command(self.tx(), cmd, false)?.try_into() + } + + /// Set resetting code + /// (4.3.4 Resetting Code) + pub fn set_resetting_code(&mut self, resetting_code: Vec) -> Result<(), Error> { + let cmd = commands::put_data(&[0xd3], resetting_code); + apdu::send_command(self.tx(), cmd, false)?.try_into() + } + + /// Import an existing private key to the card. + /// (This implicitly sets the algorithm info, fingerprint and timestamp) + pub fn key_import( + &mut self, + key: Box, + key_type: KeyType, + ) -> Result<(), Error> { + let algo_info = self.algorithm_information(); + + // An error is ok - it's fine if a card doesn't offer a list of + // supported algorithms + let algo_info = algo_info.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 `algo` parameter is Some, then this algorithm will be set on + /// the card for "key_type". + /// + /// Note: `algo` 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, + algo: Option<&Algo>, + ) -> Result<(PublicKeyMaterial, KeyGenerationTime), Error> { + keys::gen_key_with_metadata(self, fp_from_pub, key_type, algo) + } + + /// 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(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( + [0x30], + Value::C(vec![ + Tlv::new( + [0x30], + Value::C(vec![ + Tlv::new( + [0x06], + // unwrapping is ok, for SHA* + Value::S(hash.oid().unwrap().to_vec()), + ), + Tlv::new([0x05], Value::S(vec![])), + ]), + ), + Tlv::new([0x04], Value::S(hash.digest().to_vec())), + ]), + ); + + tlv.serialize() + } + Hash::EdDSA(d) => d.to_vec(), + Hash::ECDSA(d) => d.to_vec(), + } +} diff --git a/tools/src/bin/opgpcard-pin/main.rs b/tools/src/bin/opgpcard-pin/main.rs index 0819676..34c1845 100644 --- a/tools/src/bin/opgpcard-pin/main.rs +++ b/tools/src/bin/opgpcard-pin/main.rs @@ -4,7 +4,7 @@ use anyhow::Result; use structopt::StructOpt; -use openpgp_card::{CardBackend, Error, StatusBytes}; +use openpgp_card::{Error, OpenPgp, StatusBytes}; use openpgp_card_pcsc::PcscBackend; use openpgp_card_sequoia::card::Open; @@ -16,12 +16,13 @@ fn main() -> Result<(), Box> { let cli = cli::Cli::from_args(); let mut card = PcscBackend::open_by_ident(&cli.ident, None)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - let pinpad_verify = txc.feature_pinpad_verify(); - let pinpad_modify = txc.feature_pinpad_modify(); + let pgpt = pgp.transaction()?; + let pinpad_verify = pgpt.feature_pinpad_verify(); + let pinpad_modify = pgpt.feature_pinpad_modify(); - let mut open = Open::new(&mut *txc)?; + let mut open = Open::new(pgpt)?; match cli.cmd { cli::Command::SetUserPin {} => { diff --git a/tools/src/bin/opgpcard/main.rs b/tools/src/bin/opgpcard/main.rs index 6178df5..0912e2a 100644 --- a/tools/src/bin/opgpcard/main.rs +++ b/tools/src/bin/opgpcard/main.rs @@ -13,8 +13,7 @@ use sequoia_openpgp::Cert; use openpgp_card::algorithm::AlgoSimple; use openpgp_card::card_do::Sex; -use openpgp_card::{CardBackend, KeyType}; - +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::{sq_util, PublicKey}; @@ -71,9 +70,9 @@ fn main() -> Result<(), Box> { cmd, } => { let mut card = util::open_card(&ident)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - let mut open = Open::new(&mut *txc)?; + let mut open = Open::new(pgp.transaction()?)?; match cmd { cli::AdminCommand::Name { name } => { @@ -134,9 +133,8 @@ fn list_cards() -> Result<()> { println!("Available OpenPGP cards:"); for mut card in cards { - let mut txc = card.transaction()?; - - let open = Open::new(&mut *txc)?; + let mut pgp = OpenPgp::new(&mut card); + let open = Open::new(pgp.transaction()?)?; println!(" {}", open.application_identifier()?.ident()); } } else { @@ -147,9 +145,10 @@ fn list_cards() -> Result<()> { fn set_identity(ident: &str, id: u8) -> Result<(), Box> { let mut card = util::open_card(ident)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - txc.set_identity(id)?; + let mut pgpt = pgp.transaction()?; + pgpt.set_identity(id)?; Ok(()) } @@ -166,9 +165,8 @@ fn print_status(ident: Option, verbose: bool) -> Result<()> { } }; - let mut txc = card.transaction()?; - - let mut open = Open::new(&mut *txc)?; + let mut pgp = OpenPgp::new(&mut *card); + let mut open = Open::new(pgp.transaction()?)?; let ident = open.application_identifier()?.ident(); @@ -326,9 +324,9 @@ fn decrypt( let input = util::open_or_stdin(input.as_deref())?; let mut card = util::open_card(ident)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - let mut open = Open::new(&mut *txc)?; + let mut open = Open::new(pgp.transaction()?)?; let mut user = util::verify_to_user(&mut open, pin_file)?; let d = user.decryptor(&cert)?; @@ -352,9 +350,9 @@ fn sign_detached( let mut input = util::open_or_stdin(input.as_deref())?; let mut card = util::open_card(ident)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - let mut open = Open::new(&mut *txc)?; + let mut open = Open::new(pgp.transaction()?)?; let mut sign = util::verify_to_sign(&mut open, pin_file)?; let s = sign.signer(&cert)?; @@ -371,9 +369,10 @@ fn sign_detached( fn factory_reset(ident: &str) -> Result<()> { println!("Resetting Card {}", ident); let mut card = util::open_card(ident)?; - let mut txc = card.transaction()?; + let mut pgp = OpenPgp::new(&mut card); - Open::new(&mut *txc)?.factory_reset() + let mut open = Open::new(pgp.transaction()?)?; + open.factory_reset() } fn key_import_yolo(mut admin: Admin, key: &Cert) -> Result<()> { diff --git a/tools/src/bin/opgpcard/util.rs b/tools/src/bin/opgpcard/util.rs index 83d6760..1f7e415 100644 --- a/tools/src/bin/opgpcard/util.rs +++ b/tools/src/bin/opgpcard/util.rs @@ -19,7 +19,7 @@ pub(crate) fn open_card(ident: &str) -> Result { } pub(crate) fn verify_to_user<'app, 'open>( - open: &'app mut Open<'app>, + open: &'open mut Open<'app>, pin_file: Option, ) -> Result, Box> { if let Some(path) = pin_file { @@ -37,7 +37,7 @@ pub(crate) fn verify_to_user<'app, 'open>( } pub(crate) fn verify_to_sign<'app, 'open>( - open: &'app mut Open<'app>, + open: &'open mut Open<'app>, pin_file: Option, ) -> Result, Box> { if let Some(path) = pin_file {