This commit is contained in:
Heiko Schaefer 2021-07-18 12:00:00 +02:00
parent aef6c781ed
commit 97d4880118
7 changed files with 154 additions and 33 deletions

View file

@ -11,6 +11,7 @@ edition = "2018"
[dependencies] [dependencies]
openpgp-card = { path = "../openpgp-card" } openpgp-card = { path = "../openpgp-card" }
openpgp-card-sequoia = { path = "../openpgp-card-sequoia" } openpgp-card-sequoia = { path = "../openpgp-card-sequoia" }
openpgp-card-scdc = { path = "../scdc" }
sequoia-openpgp = "1.3" sequoia-openpgp = "1.3"
anyhow = "1" anyhow = "1"
env_logger = "0.8" env_logger = "0.8"

View file

@ -33,7 +33,8 @@ use sequoia_openpgp::Cert;
use openpgp_card::apdu::PcscClient; use openpgp_card::apdu::PcscClient;
use openpgp_card::card_app::CardApp; use openpgp_card::card_app::CardApp;
use openpgp_card::{CardBase, CardClientBox, Sex}; use openpgp_card::{CardClientBox, Sex};
use openpgp_card_scdc::ScdClient;
mod util; mod util;
@ -72,9 +73,21 @@ fn test_decrypt(mut ca: &mut CardApp, param: &[&str]) -> Result<TestOutput> {
} }
/// Run after each "upload keys", if key *was* uploaded (?) /// Run after each "upload keys", if key *was* uploaded (?)
fn test_sign() { fn test_sign(mut ca: &mut CardApp, param: &[&str]) -> Result<TestOutput> {
// FIXME assert_eq!(param.len(), 1, "test_sign needs a filename for 'cert'");
unimplemented!()
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( fn check_key_upload_metadata(
@ -251,7 +264,7 @@ fn test_verify(ca: &mut CardApp, _param: &[&str]) -> Result<TestOutput> {
Ok(out) Ok(out)
} }
fn run_test( fn run_test_pcsc(
cards: &[&str], cards: &[&str],
t: fn(&mut CardApp, &[&str]) -> Result<TestOutput>, t: fn(&mut CardApp, &[&str]) -> Result<TestOutput>,
param: &[&str], param: &[&str],
@ -260,7 +273,6 @@ fn run_test(
for card in PcscClient::list_cards()? { for card in PcscClient::list_cards()? {
let card_client = Box::new(card) as CardClientBox; let card_client = Box::new(card) as CardClientBox;
let mut ca = CardApp::new(card_client); let mut ca = CardApp::new(card_client);
// Select OpenPGP applet // Select OpenPGP applet
@ -271,11 +283,47 @@ fn run_test(
let ard = ca.get_app_data()?; let ard = ca.get_app_data()?;
ca = ca.init_caps(&ard)?; ca = ca.init_caps(&ard)?;
let ard = ca.get_app_data()?;
let app_id = CardApp::get_aid(&ard)?; let app_id = CardApp::get_aid(&ard)?;
if cards.contains(&app_id.ident().as_str()) { 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<TestOutput>,
param: &[&str],
) -> Result<TestsOutput> {
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); let res = t(&mut ca, param);
@ -290,7 +338,7 @@ fn main() -> Result<()> {
env_logger::init(); env_logger::init();
// list of card idents to runs the tests on // list of card idents to runs the tests on
let cards = vec![ let pcsc_cards = vec![
"0006:16019180", /* Yubikey 5 */ "0006:16019180", /* Yubikey 5 */
"0005:0000A835", /* FLOSS Card 3.4 */ "0005:0000A835", /* FLOSS Card 3.4 */
"FFFE:57183146", /* Gnuk Rysim (green) */ "FFFE:57183146", /* Gnuk Rysim (green) */
@ -298,6 +346,15 @@ fn main() -> Result<()> {
// "FFFE:4231EB6E", /* Gnuk FST */ // "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"); // println!("reset");
// let _ = run_test(&cards, test_reset)?; // let _ = run_test(&cards, test_reset)?;
// //
@ -314,19 +371,26 @@ fn main() -> Result<()> {
("data/rsa4k.sec", "data/encrypted_to_rsa4k.asc"), ("data/rsa4k.sec", "data/encrypted_to_rsa4k.asc"),
] { ] {
// upload keys // upload keys
println!("upload key"); println!("Upload key '{}'", key);
let upload_out = run_test(&cards, test_upload_keys, &vec![key])?; let upload_out =
run_test_pcsc(&pcsc_cards, test_upload_keys, &vec![key])?;
println!("{:x?}", upload_out); println!("{:x?}", upload_out);
println!();
// FIXME: if this card doesn't support the key type, skip the // FIXME: if this card doesn't support the key type, skip the
// following tests? // following tests?
// decrypt // decrypt
println!("decrypt"); println!("Decrypt");
let dec_out = run_test(&cards, test_decrypt, &vec![key, ciphertext])?; let dec_out =
run_test_pcsc(&cards, test_decrypt, &vec![key, ciphertext])?;
println!("{:x?}", dec_out); println!("{:x?}", dec_out);
// sign // 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 // upload some key with pw

View file

@ -3,14 +3,20 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use sequoia_openpgp as openpgp;
use sequoia_openpgp::cert::amalgamation::key::ValidKeyAmalgamation; use sequoia_openpgp::cert::amalgamation::key::ValidKeyAmalgamation;
use sequoia_openpgp::packet::key::{SecretParts, UnspecifiedRole}; use sequoia_openpgp::packet::key::{SecretParts, UnspecifiedRole};
use sequoia_openpgp::parse::Parse;
use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::policy::StandardPolicy;
use sequoia_openpgp::Cert; use sequoia_openpgp::Cert;
use openpgp_card::card_app::CardApp; use openpgp_card::card_app::CardApp;
use openpgp_card::KeyType; use openpgp_card::KeyType;
use openpgp_card_sequoia::vka_as_uploadable_key; use openpgp_card_sequoia::vka_as_uploadable_key;
use sequoia_openpgp::parse::stream::{
DetachedVerifierBuilder, MessageLayer, MessageStructure,
VerificationHelper,
};
use std::time::SystemTime; use std::time::SystemTime;
pub const SP: &StandardPolicy = &StandardPolicy::new(); pub const SP: &StandardPolicy = &StandardPolicy::new();
@ -72,3 +78,48 @@ fn get_subkey(
Err(anyhow!("No suitable (sub)key found")) 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<Vec<openpgp::Cert>> {
// 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<bool> {
let vh = VHelper::new(cert);
let mut dv = DetachedVerifierBuilder::from_bytes(&sig[..])?
.with_policy(SP, None, vh)?;
Ok(dv.verify_bytes(msg).is_ok())
}

View file

@ -22,8 +22,8 @@ use sequoia_openpgp as openpgp;
use openpgp_card::card_app::CardApp; use openpgp_card::card_app::CardApp;
use openpgp_card::{ use openpgp_card::{
errors::OpenpgpCardError, CardAdmin, CardSign, CardUploadableKey, EccKey, errors::OpenpgpCardError, CardAdmin, CardUploadableKey, EccKey, EccType,
EccType, KeyType, PrivateKeyMaterial, RSAKey, KeyType, PrivateKeyMaterial, RSAKey,
}; };
mod decryptor; mod decryptor;
@ -292,14 +292,14 @@ pub fn decrypt(
} }
pub fn sign( pub fn sign(
ocu: CardSign, ca: &mut CardApp,
cert: &sequoia_openpgp::Cert, cert: &sequoia_openpgp::Cert,
input: &mut dyn io::Read, input: &mut dyn io::Read,
) -> Result<String> { ) -> Result<String> {
let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?; let mut armorer = armor::Writer::new(vec![], armor::Kind::Signature)?;
{ {
let p = StandardPolicy::new(); 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 message = Message::new(&mut armorer);
let mut message = Signer::new(message, s).detached().build()?; let mut message = Signer::new(message, s).detached().build()?;

View file

@ -195,14 +195,14 @@ fn main() -> Result<(), Box<dyn Error>> {
// Sign // Sign
match oc.verify_pw1_for_signing("123456") { match oc.verify_pw1_for_signing("123456") {
Ok(oc_user) => { Ok(mut oc_user) => {
println!("pw1 81 verify ok"); println!("pw1 81 verify ok");
let cert = Cert::from_file(TEST_KEY_PATH)?; let cert = Cert::from_file(TEST_KEY_PATH)?;
let text = "Hello world, I am signed."; let text = "Hello world, I am signed.";
let res = openpgp_card_sequoia::sign( let res = openpgp_card_sequoia::sign(
oc_user, oc_user.get_card_app(),
&cert, &cert,
&mut text.as_bytes(), &mut text.as_bytes(),
); );

View file

@ -10,32 +10,33 @@ use openpgp::policy::Policy;
use openpgp::types::PublicKeyAlgorithm; use openpgp::types::PublicKeyAlgorithm;
use sequoia_openpgp as openpgp; use sequoia_openpgp as openpgp;
use openpgp_card::card_app::CardApp;
use openpgp_card::errors::OpenpgpCardError; use openpgp_card::errors::OpenpgpCardError;
use openpgp_card::CardSign;
use openpgp_card::Hash; use openpgp_card::Hash;
use crate::PublicKey; use crate::PublicKey;
pub(crate) struct CardSigner { pub(crate) struct CardSigner<'a> {
/// The OpenPGP card (authenticated to allow signing operations) /// The OpenPGP card (authenticated to allow signing operations)
ocu: CardSign, ca: &'a mut CardApp,
/// The matching public key for the card's signing key /// The matching public key for the card's signing key
public: PublicKey, public: PublicKey,
} }
impl CardSigner { impl<'a> CardSigner<'a> {
/// Try to create a CardSigner. /// Try to create a CardSigner.
/// ///
/// An Error is returned if no match between the card's signing /// An Error is returned if no match between the card's signing
/// key and a (sub)key of `cert` can be made. /// key and a (sub)key of `cert` can be made.
pub fn new( pub fn new(
cs: CardSign, ca: &'a mut CardApp,
cert: &openpgp::Cert, cert: &openpgp::Cert,
policy: &dyn Policy, policy: &dyn Policy,
) -> Result<CardSigner, OpenpgpCardError> { ) -> Result<CardSigner<'a>, OpenpgpCardError> {
// Get the fingerprint for the signing key from the card. // 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(); let fp = fps.signature();
if let Some(fp) = fp { if let Some(fp) = fp {
@ -58,7 +59,7 @@ impl CardSigner {
let public = keys[0].clone(); let public = keys[0].clone();
Ok(CardSigner { Ok(CardSigner {
ocu: cs, ca,
public: public.role_as_unspecified().clone(), public: public.role_as_unspecified().clone(),
}) })
} else { } else {
@ -75,7 +76,7 @@ impl CardSigner {
} }
} }
impl<'a> crypto::Signer for CardSigner { impl<'a> crypto::Signer for CardSigner<'a> {
fn public(&self) -> &PublicKey { fn public(&self) -> &PublicKey {
&self.public &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[..]); let mpi = mpi::MPI::new(&sig[..]);
Ok(mpi::Signature::RSA { s: mpi }) Ok(mpi::Signature::RSA { s: mpi })
@ -130,7 +131,7 @@ impl<'a> crypto::Signer for CardSigner {
(PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => { (PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => {
let hash = Hash::EdDSA(digest); 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 r = mpi::MPI::new(&sig[..32]);
let s = mpi::MPI::new(&sig[32..]); let s = mpi::MPI::new(&sig[32..]);

View file

@ -50,20 +50,24 @@ impl ScdClient {
Ok(Self { client }) 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); let send = format!("SERIALNO --demand={}\n", serial);
self.client.send(send)?; self.client.send(send)?;
let mut rt = RT.lock().unwrap(); let mut rt = RT.lock().unwrap();
while let Some(response) = rt.block_on(self.client.next()) { while let Some(response) = rt.block_on(self.client.next()) {
log::trace!("select res: {:x?}", response);
if response.is_err() { if response.is_err() {
return Err(anyhow!("Card not found")); return Err(anyhow!("Card not found"));
} }
if let Ok(Response::Status { .. }) = response { if let Ok(Response::Status { .. }) = response {
// drop remaining lines // 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(()); return Ok(());
} }
@ -78,7 +82,7 @@ impl CardClient for ScdClient {
let hex = hex::encode(cmd); let hex = hex::encode(cmd);
let send = format!("APDU {}\n", hex); let send = format!("APDU {}\n", hex);
println!("send: '{}'", send); log::trace!("send: '{}'", send);
self.client.send(send)?; self.client.send(send)?;
let mut rt = RT.lock().unwrap(); let mut rt = RT.lock().unwrap();