From 97d48801184a5dbcd2b4370c6ecb6c6dc3c78d3a Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 18 Jul 2021 12:00:00 +0200 Subject: [PATCH] Signing --- card-functionality/Cargo.toml | 1 + card-functionality/src/main.rs | 90 +++++++++++++++++++++++++----- card-functionality/src/util.rs | 51 +++++++++++++++++ openpgp-card-sequoia/src/lib.rs | 8 +-- openpgp-card-sequoia/src/main.rs | 4 +- openpgp-card-sequoia/src/signer.rs | 23 ++++---- scdc/src/lib.rs | 10 +++- 7 files changed, 154 insertions(+), 33 deletions(-) diff --git a/card-functionality/Cargo.toml b/card-functionality/Cargo.toml index d2b8a5f..26e4c41 100644 --- a/card-functionality/Cargo.toml +++ b/card-functionality/Cargo.toml @@ -11,6 +11,7 @@ edition = "2018" [dependencies] openpgp-card = { path = "../openpgp-card" } openpgp-card-sequoia = { path = "../openpgp-card-sequoia" } +openpgp-card-scdc = { path = "../scdc" } sequoia-openpgp = "1.3" anyhow = "1" env_logger = "0.8" \ No newline at end of file diff --git a/card-functionality/src/main.rs b/card-functionality/src/main.rs index e6a78ca..6e2c42b 100644 --- a/card-functionality/src/main.rs +++ b/card-functionality/src/main.rs @@ -33,7 +33,8 @@ use sequoia_openpgp::Cert; use openpgp_card::apdu::PcscClient; use openpgp_card::card_app::CardApp; -use openpgp_card::{CardBase, CardClientBox, Sex}; +use openpgp_card::{CardClientBox, Sex}; +use openpgp_card_scdc::ScdClient; mod util; @@ -72,9 +73,21 @@ fn test_decrypt(mut ca: &mut CardApp, param: &[&str]) -> Result { } /// Run after each "upload keys", if key *was* uploaded (?) -fn test_sign() { - // FIXME - unimplemented!() +fn test_sign(mut ca: &mut CardApp, param: &[&str]) -> Result { + assert_eq!(param.len(), 1, "test_sign needs a filename for 'cert'"); + + let res = ca.verify_pw1_for_signing("123456")?; + res.check_ok()?; + + let cert = Cert::from_file(param[0])?; + + let msg = "Hello world, I am signed."; + let sig = openpgp_card_sequoia::sign(&mut ca, &cert, &mut msg.as_bytes())?; + + // validate sig + assert!(util::verify_sig(&cert, msg.as_bytes(), sig.as_bytes())?); + + Ok(vec![]) } fn check_key_upload_metadata( @@ -251,7 +264,7 @@ fn test_verify(ca: &mut CardApp, _param: &[&str]) -> Result { Ok(out) } -fn run_test( +fn run_test_pcsc( cards: &[&str], t: fn(&mut CardApp, &[&str]) -> Result, param: &[&str], @@ -260,7 +273,6 @@ fn run_test( for card in PcscClient::list_cards()? { let card_client = Box::new(card) as CardClientBox; - let mut ca = CardApp::new(card_client); // Select OpenPGP applet @@ -271,11 +283,47 @@ fn run_test( let ard = ca.get_app_data()?; ca = ca.init_caps(&ard)?; - let ard = ca.get_app_data()?; let app_id = CardApp::get_aid(&ard)?; if cards.contains(&app_id.ident().as_str()) { - println!("Running Test on {}:", app_id.ident()); + // println!("Running Test on {}:", app_id.ident()); + + let res = t(&mut ca, param); + + out.insert(app_id.ident(), res?); + } + } + + Ok(out) +} + +fn run_test_scdc( + cards: &[&str], + t: fn(&mut CardApp, &[&str]) -> Result, + param: &[&str], +) -> Result { + let mut out = HashMap::new(); + + const SOCKET: &str = "/run/user/1000/gnupg/S.scdaemon"; + + for serial in cards { + let mut card = ScdClient::new(SOCKET)?; + card.select_card(serial)?; + + let card_client = Box::new(card) as CardClientBox; + + let mut ca = CardApp::new(card_client); + + // Set Card Capabilities (chaining, command length, ..) + let ard = ca.get_app_data()?; + ca = ca.init_caps(&ard)?; + + println!("XXX"); + + let app_id = CardApp::get_aid(&ard)?; + + if cards.contains(&app_id.ident().as_str()) { + // println!("Running Test on {}:", app_id.ident()); let res = t(&mut ca, param); @@ -290,7 +338,7 @@ fn main() -> Result<()> { env_logger::init(); // list of card idents to runs the tests on - let cards = vec![ + let pcsc_cards = vec![ "0006:16019180", /* Yubikey 5 */ "0005:0000A835", /* FLOSS Card 3.4 */ "FFFE:57183146", /* Gnuk Rysim (green) */ @@ -298,6 +346,15 @@ fn main() -> Result<()> { // "FFFE:4231EB6E", /* Gnuk FST */ ]; + // list of scdc card serial to runs the tests on + let scdc_cards = vec![ + // "D2760001240103040006160191800000", /* Yubikey 5 */ + "D27600012401030400050000A8350000", /* FLOSS Card 3.4 */ + "D276000124010200FFFE571831460000", /* Gnuk Rysim (green) */ + + // "D276000124010200FFFE4231EB6E0000", /* Gnuk FST */ + ]; + // println!("reset"); // let _ = run_test(&cards, test_reset)?; // @@ -314,19 +371,26 @@ fn main() -> Result<()> { ("data/rsa4k.sec", "data/encrypted_to_rsa4k.asc"), ] { // upload keys - println!("upload key"); - let upload_out = run_test(&cards, test_upload_keys, &vec![key])?; + println!("Upload key '{}'", key); + let upload_out = + run_test_pcsc(&pcsc_cards, test_upload_keys, &vec![key])?; println!("{:x?}", upload_out); + println!(); // FIXME: if this card doesn't support the key type, skip the // following tests? // decrypt - println!("decrypt"); - let dec_out = run_test(&cards, test_decrypt, &vec![key, ciphertext])?; + println!("Decrypt"); + let dec_out = + run_test_pcsc(&cards, test_decrypt, &vec![key, ciphertext])?; println!("{:x?}", dec_out); // sign + println!("Sign"); + let sign_out = run_test_pcsc(&pcsc_cards, test_sign, &vec![key])?; + println!("{:x?}", sign_out); + println!(); } // upload some key with pw diff --git a/card-functionality/src/util.rs b/card-functionality/src/util.rs index f11a948..e621e88 100644 --- a/card-functionality/src/util.rs +++ b/card-functionality/src/util.rs @@ -3,14 +3,20 @@ use anyhow::{anyhow, Result}; +use sequoia_openpgp as openpgp; use sequoia_openpgp::cert::amalgamation::key::ValidKeyAmalgamation; use sequoia_openpgp::packet::key::{SecretParts, UnspecifiedRole}; +use sequoia_openpgp::parse::Parse; use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::Cert; use openpgp_card::card_app::CardApp; use openpgp_card::KeyType; use openpgp_card_sequoia::vka_as_uploadable_key; +use sequoia_openpgp::parse::stream::{ + DetachedVerifierBuilder, MessageLayer, MessageStructure, + VerificationHelper, +}; use std::time::SystemTime; pub const SP: &StandardPolicy = &StandardPolicy::new(); @@ -72,3 +78,48 @@ fn get_subkey( Err(anyhow!("No suitable (sub)key found")) } } + +/// Perform signature verification for one Cert and a simple signature +/// over a message. +struct VHelper<'a> { + cert: &'a Cert, +} + +impl<'a> VHelper<'a> { + fn new(cert: &'a Cert) -> Self { + Self { cert } + } +} + +impl<'a> VerificationHelper for VHelper<'a> { + fn get_certs( + &mut self, + _ids: &[openpgp::KeyHandle], + ) -> openpgp::Result> { + // Hand out our single Cert + Ok(vec![self.cert.clone()]) + } + + fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> { + // We are interested in signatures over the data (level 0 signatures) + if let Some(MessageLayer::SignatureGroup { results }) = + structure.into_iter().next() + { + match results.into_iter().next() { + Some(Ok(_)) => Ok(()), // Good signature. + Some(Err(e)) => Err(openpgp::Error::from(e).into()), + None => Err(anyhow::anyhow!("No signature")), + } + } else { + Err(anyhow::anyhow!("Unexpected message structure")) + } + } +} + +pub fn verify_sig(cert: &Cert, msg: &[u8], sig: &[u8]) -> Result { + let vh = VHelper::new(cert); + let mut dv = DetachedVerifierBuilder::from_bytes(&sig[..])? + .with_policy(SP, None, vh)?; + + Ok(dv.verify_bytes(msg).is_ok()) +} diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 50a404c..5b4baee 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -22,8 +22,8 @@ use sequoia_openpgp as openpgp; use openpgp_card::card_app::CardApp; use openpgp_card::{ - errors::OpenpgpCardError, CardAdmin, CardSign, CardUploadableKey, EccKey, - EccType, KeyType, PrivateKeyMaterial, RSAKey, + errors::OpenpgpCardError, CardAdmin, CardUploadableKey, EccKey, EccType, + KeyType, PrivateKeyMaterial, RSAKey, }; mod decryptor; @@ -292,14 +292,14 @@ pub fn decrypt( } pub fn sign( - ocu: CardSign, + ca: &mut CardApp, cert: &sequoia_openpgp::Cert, input: &mut dyn io::Read, ) -> Result { let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?; { let p = StandardPolicy::new(); - let s = signer::CardSigner::new(ocu, cert, &p)?; + let s = signer::CardSigner::new(ca, cert, &p)?; let message = Message::new(&mut armorer); let mut message = Signer::new(message, s).detached().build()?; diff --git a/openpgp-card-sequoia/src/main.rs b/openpgp-card-sequoia/src/main.rs index 8b9fa32..1f1a9fe 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -195,14 +195,14 @@ fn main() -> Result<(), Box> { // Sign match oc.verify_pw1_for_signing("123456") { - Ok(oc_user) => { + Ok(mut oc_user) => { println!("pw1 81 verify ok"); let cert = Cert::from_file(TEST_KEY_PATH)?; let text = "Hello world, I am signed."; let res = openpgp_card_sequoia::sign( - oc_user, + oc_user.get_card_app(), &cert, &mut text.as_bytes(), ); diff --git a/openpgp-card-sequoia/src/signer.rs b/openpgp-card-sequoia/src/signer.rs index 79c4570..e197caf 100644 --- a/openpgp-card-sequoia/src/signer.rs +++ b/openpgp-card-sequoia/src/signer.rs @@ -10,32 +10,33 @@ use openpgp::policy::Policy; use openpgp::types::PublicKeyAlgorithm; use sequoia_openpgp as openpgp; +use openpgp_card::card_app::CardApp; use openpgp_card::errors::OpenpgpCardError; -use openpgp_card::CardSign; use openpgp_card::Hash; use crate::PublicKey; -pub(crate) struct CardSigner { +pub(crate) struct CardSigner<'a> { /// The OpenPGP card (authenticated to allow signing operations) - ocu: CardSign, + ca: &'a mut CardApp, /// The matching public key for the card's signing key public: PublicKey, } -impl CardSigner { +impl<'a> CardSigner<'a> { /// 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( - cs: CardSign, + ca: &'a mut CardApp, cert: &openpgp::Cert, policy: &dyn Policy, - ) -> Result { + ) -> Result, OpenpgpCardError> { // Get the fingerprint for the signing key from the card. - let fps = cs.get_fingerprints()?; + let ard = ca.get_app_data()?; + let fps = CardApp::get_fingerprints(&ard)?; let fp = fps.signature(); if let Some(fp) = fp { @@ -58,7 +59,7 @@ impl CardSigner { let public = keys[0].clone(); Ok(CardSigner { - ocu: cs, + ca, public: public.role_as_unspecified().clone(), }) } else { @@ -75,7 +76,7 @@ impl CardSigner { } } -impl<'a> crypto::Signer for CardSigner { +impl<'a> crypto::Signer for CardSigner<'a> { fn public(&self) -> &PublicKey { &self.public } @@ -122,7 +123,7 @@ impl<'a> crypto::Signer for CardSigner { } }; - let sig = self.ocu.signature_for_hash(hash)?; + let sig = self.ca.signature_for_hash(hash)?; let mpi = mpi::MPI::new(&sig[..]); Ok(mpi::Signature::RSA { s: mpi }) @@ -130,7 +131,7 @@ impl<'a> crypto::Signer for CardSigner { (PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => { let hash = Hash::EdDSA(digest); - let sig = self.ocu.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..]); diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index c946b3b..a4f9eb9 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -50,20 +50,24 @@ impl ScdClient { Ok(Self { client }) } - fn select_card(&mut self, serial: &str) -> Result<()> { + pub fn select_card(&mut self, serial: &str) -> Result<()> { let send = format!("SERIALNO --demand={}\n", serial); self.client.send(send)?; let mut rt = RT.lock().unwrap(); while let Some(response) = rt.block_on(self.client.next()) { + log::trace!("select res: {:x?}", response); + if response.is_err() { return Err(anyhow!("Card not found")); } if let Ok(Response::Status { .. }) = response { // drop remaining lines - while let Some(_drop) = rt.block_on(self.client.next()) {} + while let Some(_drop) = rt.block_on(self.client.next()) { + log::trace!("select drop: {:x?}", _drop); + } return Ok(()); } @@ -78,7 +82,7 @@ impl CardClient for ScdClient { let hex = hex::encode(cmd); let send = format!("APDU {}\n", hex); - println!("send: '{}'", send); + log::trace!("send: '{}'", send); self.client.send(send)?; let mut rt = RT.lock().unwrap();