From 2343bd8310386697bee71147fac74d5018025de2 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 10 Jul 2021 21:58:13 +0200 Subject: [PATCH 01/18] Initial scdc experimentation. --- Cargo.toml | 1 + openpgp-card-sequoia/Cargo.toml | 2 + openpgp-card-sequoia/src/main.rs | 22 ++++-- openpgp-card/src/apdu/mod.rs | 6 +- openpgp-card/src/card_app.rs | 7 +- openpgp-card/src/key_upload.rs | 6 +- openpgp-card/src/lib.rs | 28 +++++++- scdc/Cargo.toml | 15 ++++ scdc/src/lib.rs | 115 +++++++++++++++++++++++++++++++ 9 files changed, 181 insertions(+), 21 deletions(-) create mode 100644 scdc/Cargo.toml create mode 100644 scdc/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 45c5ddb..e48f5a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ members = [ "openpgp-card", "openpgp-card-sequoia", + "scdc", ] diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index 4fb0e30..118e899 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -14,6 +14,8 @@ documentation = "https://docs.rs/crate/openpgp-card-sequoia" [dependencies] sequoia-openpgp = "1.3" openpgp-card = { path = "../openpgp-card", version = "0.0.1" } +openpgp-card-scdc = { path = "../scdc" } +tokio = "0.2" chrono = "0.4" anyhow = "1" thiserror = "1" diff --git a/openpgp-card-sequoia/src/main.rs b/openpgp-card-sequoia/src/main.rs index 47da491..0796aa0 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -9,18 +9,22 @@ use sequoia_openpgp::parse::Parse; use sequoia_openpgp::Cert; use openpgp_card::{CardBase, KeyType}; +use openpgp_card_scdc::ScdClient; // Filename of test key and test message to use: -// const TEST_KEY_PATH: &str = "example/test4k.sec"; -// const TEST_ENC_MSG: &str = "example/encrypted_to_rsa4k.asc"; +const TEST_KEY_PATH: &str = "example/test4k.sec"; +const TEST_ENC_MSG: &str = "example/encrypted_to_rsa4k.asc"; // const TEST_KEY_PATH: &str = "example/nist521.sec"; // const TEST_ENC_MSG: &str = "example/encrypted_to_nist521.asc"; -const TEST_KEY_PATH: &str = "example/test25519.sec"; -const TEST_ENC_MSG: &str = "example/encrypted_to_25519.asc"; +// const TEST_KEY_PATH: &str = "example/test25519.sec"; +// const TEST_ENC_MSG: &str = "example/encrypted_to_25519.asc"; +const SOCKET: &str = "/run/user/1000/gnupg/S.scdaemon"; + +// #[tokio::main] fn main() -> Result<(), Box> { env_logger::init(); @@ -29,7 +33,8 @@ fn main() -> Result<(), Box> { if let Ok(test_card_ident) = test_card_ident { println!("** get card"); - let mut oc = CardBase::open_by_ident(&test_card_ident)?; + // let mut oc = CardBase::open_by_ident(&test_card_ident)?; + let mut oc = ScdClient::open_scdc(SOCKET)?; // card metadata @@ -140,7 +145,9 @@ fn main() -> Result<(), Box> { // Open fresh Card for decrypt // ----------------------------- - let mut oc = CardBase::open_by_ident(&test_card_ident)?; + // let mut oc = CardBase::open_by_ident(&test_card_ident)?; + let mut oc = ScdClient::open_scdc(SOCKET)?; + let app_id = oc.get_aid()?; // Check that we're still using the expected card @@ -179,7 +186,8 @@ fn main() -> Result<(), Box> { // ----------------------------- // Open fresh Card for signing // ----------------------------- - let oc = CardBase::open_by_ident(&test_card_ident)?; + // let oc = CardBase::open_by_ident(&test_card_ident)?; + let mut oc = ScdClient::open_scdc(SOCKET)?; // Sign match oc.verify_pw1_for_signing("123456") { diff --git a/openpgp-card/src/apdu/mod.rs b/openpgp-card/src/apdu/mod.rs index c10f432..c91923b 100644 --- a/openpgp-card/src/apdu/mod.rs +++ b/openpgp-card/src/apdu/mod.rs @@ -12,7 +12,7 @@ use std::convert::TryFrom; use crate::apdu::command::Command; use crate::apdu::response::Response; use crate::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError}; -use crate::CardCaps; +use crate::{CardCaps, CardClient}; #[derive(Clone, Copy, PartialEq)] pub(crate) enum Le { @@ -179,10 +179,6 @@ fn send_command_low_level( } } -pub trait CardClient { - fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result>; -} - pub struct PcscClient { card: Card, } diff --git a/openpgp-card/src/card_app.rs b/openpgp-card/src/card_app.rs index 85675ee..7bca175 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -28,14 +28,13 @@ use crate::errors::OpenpgpCardError; use crate::tlv::tag::Tag; use crate::tlv::TlvEntry; -use crate::apdu::CardClient; use crate::Hash; use crate::{ - apdu, key_upload, parse, tlv, CardCaps, CardUploadableKey, DecryptMe, - KeyType, Sex, + apdu, key_upload, parse, tlv, CardCaps, CardClient, CardUploadableKey, + DecryptMe, KeyType, Sex, }; -pub(crate) struct CardApp { +pub struct CardApp { card_client: Box, card_caps: Option, } diff --git a/openpgp-card/src/key_upload.rs b/openpgp-card/src/key_upload.rs index edc9832..c07fb0d 100644 --- a/openpgp-card/src/key_upload.rs +++ b/openpgp-card/src/key_upload.rs @@ -4,13 +4,13 @@ use anyhow::{anyhow, Result}; use crate::apdu::command::Command; -use crate::apdu::{commands, CardClient}; +use crate::apdu::commands; use crate::card_app::CardApp; use crate::errors::OpenpgpCardError; use crate::parse::algo_attrs::{Algo, RsaAttrs}; use crate::parse::algo_info::AlgoInfo; use crate::tlv::{tag::Tag, Tlv, TlvEntry}; -use crate::{apdu, CardCaps}; +use crate::{apdu, CardCaps, CardClient}; use crate::{ tlv, CardUploadableKey, EccKey, EccType, KeyType, PrivateKeyMaterial, RSAKey, @@ -26,6 +26,8 @@ pub(crate) fn upload_key( key_type: KeyType, algo_list: Option, ) -> Result<(), OpenpgpCardError> { + println!("upload key"); + let (algo_cmd, key_cmd) = match key.get_key()? { PrivateKeyMaterial::R(rsa_key) => { // RSA bitsize diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 0098aca..1caa72a 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -13,28 +13,46 @@ use parse::{ }; use tlv::Tlv; -use crate::apdu::{CardClient, PcscClient}; +use crate::apdu::PcscClient; use crate::card_app::CardApp; use crate::errors::{OpenpgpCardError, SmartcardError}; use std::ops::{Deref, DerefMut}; mod apdu; mod card; -mod card_app; +pub mod card_app; pub mod errors; mod key_upload; mod parse; mod tlv; +pub trait CardClient { + fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result>; +} + /// Information about the capabilities of the card. /// (feature configuration from card metadata) #[derive(Clone, Copy)] -pub(crate) struct CardCaps { +pub struct CardCaps { pub(crate) ext_support: bool, pub(crate) chaining_support: bool, pub(crate) max_cmd_bytes: u16, } +impl CardCaps { + pub fn new( + ext_support: bool, + chaining_support: bool, + max_cmd_bytes: u16, + ) -> CardCaps { + Self { + ext_support, + chaining_support, + max_cmd_bytes, + } + } +} + /// Container for a hash value. /// These hash values can be signed by the card. pub enum Hash<'a> { @@ -228,6 +246,10 @@ pub struct CardBase { } impl CardBase { + pub fn new(card_app: CardApp, ard: Tlv) -> Self { + Self { card_app, ard } + } + /// Get all cards that can be opened as an OpenPGP card applet pub fn list_cards() -> Result> { let cards = card::get_cards().map_err(|err| anyhow!(err))?; diff --git a/scdc/Cargo.toml b/scdc/Cargo.toml new file mode 100644 index 0000000..be910cf --- /dev/null +++ b/scdc/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "openpgp-card-scdc" +description = "Experimental SCDaemon Client, intended for use in the openpgp-card crate" +version = "0.0.1" +edition = "2018" + +[dependencies] +openpgp-card = { path = "../openpgp-card" } +sequoia-ipc = { path = "../../sequoia/ipc" } +libc = "0.2" +hex = "0.4" +anyhow = "1" +futures = "0.3" +tokio = "0.2" +lazy_static = "1.4" \ No newline at end of file diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs new file mode 100644 index 0000000..9dd9bc9 --- /dev/null +++ b/scdc/src/lib.rs @@ -0,0 +1,115 @@ +use anyhow::{anyhow, Result}; +use futures::StreamExt; +use sequoia_ipc::assuan::{Client, Response}; +use std::sync::{Arc, Mutex}; +use tokio::runtime::Runtime; + +use lazy_static::lazy_static; +use openpgp_card::card_app::CardApp; +use openpgp_card::errors::OpenpgpCardError; +use openpgp_card::{CardBase, CardCaps, CardClient}; + +lazy_static! { + pub(crate) static ref RT: Mutex = + { Mutex::new(tokio::runtime::Runtime::new().unwrap()) }; +} + +pub struct ScdClient { + client: Arc>, +} + +impl ScdClient { + /// Create a CardBase object that uses an scdaemon instance as its + /// backend. + pub fn open_scdc(socket: &str) -> Result { + println!("open_scdc"); + + let card_client = ScdClient::new(socket)?; + + let ccb = Box::new(card_client) as Box; + + println!("get ard"); + + // read and cache "application related data" + let mut card_app = CardApp::new(ccb); + let ard = card_app.get_app_data()?; + + println!("got ard"); + + // Determine chaining/extended length support from card + // metadata and cache this information in CardApp (as a + // CardCaps) + + let mut ext_support = false; + let mut chaining_support = false; + + if let Ok(hist) = CardApp::get_historical(&ard) { + if let Some(cc) = hist.get_card_capabilities() { + chaining_support = cc.get_command_chaining(); + ext_support = cc.get_extended_lc_le(); + } + } + + let max_cmd_bytes = if let Ok(Some(eli)) = + CardApp::get_extended_length_information(&ard) + { + eli.max_command_bytes + } else { + 255 + }; + + let caps = CardCaps::new(ext_support, chaining_support, max_cmd_bytes); + let card_app = card_app.set_caps(caps); + + Ok(CardBase::new(card_app, ard)) + } + + pub fn new(socket: &str) -> Result { + let client = RT.lock().unwrap().block_on(Client::connect(socket))?; + let client = Arc::new(Mutex::new(client)); + Ok(Self { client }) + } + + async fn transmit_async(&mut self, cmd: &[u8]) -> Result> { + let hex = hex::encode(cmd); + + let mut client = self.client.lock().unwrap(); + + let mut res = None; + + { + let send = format!("APDU {}\n", hex); + println!("send: '{}'", send); + client.send(send)?; + + while let Some(response) = client.next().await { + println!("res: {:?}", response); + if let Ok(Response::Data { partial }) = response { + res = Some(partial); + + // drop remaining lines + while let Some(_) = client.next().await {} + + break; + } + } + } + + match res { + Some(s) => Ok(s), + None => Err(anyhow!("no response found")), + } + } +} + +impl CardClient for ScdClient { + fn transmit(&mut self, cmd: &[u8], _buf_size: usize) -> Result> { + let mut res = None; + + { + res = Some(RT.lock().unwrap().block_on(self.transmit_async(cmd))); + } + + res.unwrap() + } +} From 1187e816d0322c9b54da832337f2df1907ef5c0c Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sat, 10 Jul 2021 22:09:11 +0200 Subject: [PATCH 02/18] Cleanup --- openpgp-card-sequoia/Cargo.toml | 1 - openpgp-card-sequoia/src/main.rs | 1 - openpgp-card/src/key_upload.rs | 2 - scdc/src/lib.rs | 66 ++++++++++++-------------------- 4 files changed, 25 insertions(+), 45 deletions(-) diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index 118e899..c746ff3 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -15,7 +15,6 @@ documentation = "https://docs.rs/crate/openpgp-card-sequoia" sequoia-openpgp = "1.3" openpgp-card = { path = "../openpgp-card", version = "0.0.1" } openpgp-card-scdc = { path = "../scdc" } -tokio = "0.2" chrono = "0.4" anyhow = "1" thiserror = "1" diff --git a/openpgp-card-sequoia/src/main.rs b/openpgp-card-sequoia/src/main.rs index 0796aa0..899ee27 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -24,7 +24,6 @@ const TEST_ENC_MSG: &str = "example/encrypted_to_rsa4k.asc"; const SOCKET: &str = "/run/user/1000/gnupg/S.scdaemon"; -// #[tokio::main] fn main() -> Result<(), Box> { env_logger::init(); diff --git a/openpgp-card/src/key_upload.rs b/openpgp-card/src/key_upload.rs index c07fb0d..0bd733f 100644 --- a/openpgp-card/src/key_upload.rs +++ b/openpgp-card/src/key_upload.rs @@ -26,8 +26,6 @@ pub(crate) fn upload_key( key_type: KeyType, algo_list: Option, ) -> Result<(), OpenpgpCardError> { - println!("upload key"); - let (algo_cmd, key_cmd) = match key.get_key()? { PrivateKeyMaterial::R(rsa_key) => { // RSA bitsize diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index 9dd9bc9..fb15cc6 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -1,17 +1,17 @@ use anyhow::{anyhow, Result}; use futures::StreamExt; +use lazy_static::lazy_static; use sequoia_ipc::assuan::{Client, Response}; use std::sync::{Arc, Mutex}; use tokio::runtime::Runtime; -use lazy_static::lazy_static; use openpgp_card::card_app::CardApp; use openpgp_card::errors::OpenpgpCardError; use openpgp_card::{CardBase, CardCaps, CardClient}; lazy_static! { pub(crate) static ref RT: Mutex = - { Mutex::new(tokio::runtime::Runtime::new().unwrap()) }; + Mutex::new(tokio::runtime::Runtime::new().unwrap()); } pub struct ScdClient { @@ -22,20 +22,14 @@ impl ScdClient { /// Create a CardBase object that uses an scdaemon instance as its /// backend. pub fn open_scdc(socket: &str) -> Result { - println!("open_scdc"); - let card_client = ScdClient::new(socket)?; - - let ccb = Box::new(card_client) as Box; - - println!("get ard"); + let card_client_box = + Box::new(card_client) as Box; // read and cache "application related data" - let mut card_app = CardApp::new(ccb); + let mut card_app = CardApp::new(card_client_box); let ard = card_app.get_app_data()?; - println!("got ard"); - // Determine chaining/extended length support from card // metadata and cache this information in CardApp (as a // CardCaps) @@ -59,6 +53,7 @@ impl ScdClient { }; let caps = CardCaps::new(ext_support, chaining_support, max_cmd_bytes); + let card_app = card_app.set_caps(caps); Ok(CardBase::new(card_app, ard)) @@ -69,47 +64,36 @@ impl ScdClient { let client = Arc::new(Mutex::new(client)); Ok(Self { client }) } +} - async fn transmit_async(&mut self, cmd: &[u8]) -> Result> { +impl CardClient for ScdClient { + fn transmit(&mut self, cmd: &[u8], _: usize) -> Result> { let hex = hex::encode(cmd); let mut client = self.client.lock().unwrap(); - let mut res = None; + let send = format!("APDU {}\n", hex); + println!("send: '{}'", send); + client.send(send)?; - { - let send = format!("APDU {}\n", hex); - println!("send: '{}'", send); - client.send(send)?; + let mut rt = RT.lock().unwrap(); - while let Some(response) = client.next().await { - println!("res: {:?}", response); - if let Ok(Response::Data { partial }) = response { - res = Some(partial); + while let Some(response) = rt.block_on(client.next()) { + println!("res: {:x?}", response); + if let Ok(Response::Data { partial }) = response { + let res = partial; - // drop remaining lines - while let Some(_) = client.next().await {} - - break; + // drop remaining lines + while let Some(drop) = rt.block_on(client.next()) { + println!("drop: {:x?}", drop); } + + println!(); + + return Ok(res); } } - match res { - Some(s) => Ok(s), - None => Err(anyhow!("no response found")), - } - } -} - -impl CardClient for ScdClient { - fn transmit(&mut self, cmd: &[u8], _buf_size: usize) -> Result> { - let mut res = None; - - { - res = Some(RT.lock().unwrap().block_on(self.transmit_async(cmd))); - } - - res.unwrap() + Err(anyhow!("no response found")) } } From de0645ef0e30deb35226a03eb874b47faf4298bb Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 12 Jul 2021 12:15:31 +0200 Subject: [PATCH 03/18] refactor opening of cards --- openpgp-card-sequoia/src/main.rs | 4 +- openpgp-card/src/apdu/mod.rs | 28 ++++++++-- openpgp-card/src/card_app.rs | 24 ++++----- openpgp-card/src/key_upload.rs | 5 +- openpgp-card/src/lib.rs | 91 ++++++++++++++------------------ scdc/src/lib.rs | 42 +++------------ 6 files changed, 84 insertions(+), 110 deletions(-) diff --git a/openpgp-card-sequoia/src/main.rs b/openpgp-card-sequoia/src/main.rs index 899ee27..027fded 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -186,7 +186,7 @@ fn main() -> Result<(), Box> { // Open fresh Card for signing // ----------------------------- // let oc = CardBase::open_by_ident(&test_card_ident)?; - let mut oc = ScdClient::open_scdc(SOCKET)?; + let oc = ScdClient::open_scdc(SOCKET)?; // Sign match oc.verify_pw1_for_signing("123456") { @@ -220,7 +220,7 @@ fn main() -> Result<(), Box> { println!("The following OpenPGP cards are connected to your system:"); - let cards = openpgp_card::CardBase::list_cards()?; + let cards = openpgp_card::CardBase::list_cards_pcsc()?; for c in cards { println!(" '{}'", c.get_aid()?.ident()); } diff --git a/openpgp-card/src/apdu/mod.rs b/openpgp-card/src/apdu/mod.rs index c91923b..ef633dd 100644 --- a/openpgp-card/src/apdu/mod.rs +++ b/openpgp-card/src/apdu/mod.rs @@ -5,14 +5,14 @@ pub mod command; pub mod commands; pub mod response; -use anyhow::Result; +use anyhow::{anyhow, Result}; use pcsc::Card; use std::convert::TryFrom; use crate::apdu::command::Command; use crate::apdu::response::Response; use crate::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError}; -use crate::{CardCaps, CardClient}; +use crate::{CardBase, CardCaps, CardClient, CardClientBox}; #[derive(Clone, Copy, PartialEq)] pub(crate) enum Le { @@ -26,7 +26,7 @@ pub(crate) enum Le { /// If the reply is truncated, this fn assembles all the parts and returns /// them as one aggregated Response. pub(crate) fn send_command( - card_client: &mut Box, + card_client: &mut CardClientBox, cmd: Command, expect_reply: bool, card_caps: Option<&CardCaps>, @@ -70,7 +70,7 @@ pub(crate) fn send_command( /// If the response is chained, this fn only returns one chunk, the caller /// needs take care of chained responses fn send_command_low_level( - card_client: &mut Box, + card_client: &mut CardClientBox, cmd: Command, expect_reply: bool, card_caps: Option<&CardCaps>, @@ -184,9 +184,27 @@ pub struct PcscClient { } impl PcscClient { - pub fn new(card: Card) -> Self { + fn new(card: Card) -> Self { Self { card } } + + /// Take a PCSC Card object and try to open the OpenPGP card applet. + /// If successful, wrap and return the resulting CardClient as a + /// CardBase object (which involves caching the "application related + /// data"). + pub fn open(card: Card) -> Result { + let card_client = PcscClient::new(card); + let mut ccb = Box::new(card_client) as CardClientBox; + + let select_openpgp = commands::select_openpgp(); + let resp = send_command(&mut ccb, select_openpgp, false, None)?; + + if resp.is_ok() { + CardBase::open_card(ccb) + } else { + Err(anyhow!("Couldn't open OpenPGP application").into()) + } + } } impl CardClient for PcscClient { diff --git a/openpgp-card/src/card_app.rs b/openpgp-card/src/card_app.rs index 7bca175..1608cf6 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -13,34 +13,28 @@ use std::borrow::BorrowMut; use std::convert::TryFrom; use anyhow::{anyhow, Result}; -use pcsc::*; -use apdu::{commands, response::Response}; -use parse::{ +use crate::apdu::{commands, response::Response}; +use crate::errors::OpenpgpCardError; +use crate::parse::{ algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId, cardholder::CardHolder, extended_cap::ExtendedCap, extended_length_info::ExtendedLengthInfo, fingerprint, historical::Historical, pw_status::PWStatus, KeySet, }; -use tlv::Tlv; - -use crate::errors::OpenpgpCardError; -use crate::tlv::tag::Tag; -use crate::tlv::TlvEntry; - -use crate::Hash; +use crate::tlv::{tag::Tag, Tlv, TlvEntry}; use crate::{ - apdu, key_upload, parse, tlv, CardCaps, CardClient, CardUploadableKey, - DecryptMe, KeyType, Sex, + apdu, key_upload, parse, tlv, CardCaps, CardClientBox, CardUploadableKey, + DecryptMe, Hash, KeyType, Sex, }; pub struct CardApp { - card_client: Box, + card_client: CardClientBox, card_caps: Option, } impl CardApp { - pub fn new(card_client: Box) -> Self { + pub fn new(card_client: CardClientBox) -> Self { Self { card_client, card_caps: None, @@ -54,7 +48,7 @@ impl CardApp { } } - pub fn card(&mut self) -> &mut Box { + pub fn card(&mut self) -> &mut CardClientBox { &mut self.card_client } diff --git a/openpgp-card/src/key_upload.rs b/openpgp-card/src/key_upload.rs index 0bd733f..27aea12 100644 --- a/openpgp-card/src/key_upload.rs +++ b/openpgp-card/src/key_upload.rs @@ -10,12 +10,11 @@ use crate::errors::OpenpgpCardError; use crate::parse::algo_attrs::{Algo, RsaAttrs}; use crate::parse::algo_info::AlgoInfo; use crate::tlv::{tag::Tag, Tlv, TlvEntry}; -use crate::{apdu, CardCaps, CardClient}; +use crate::{apdu, CardCaps, CardClientBox}; use crate::{ tlv, CardUploadableKey, EccKey, EccType, KeyType, PrivateKeyMaterial, RSAKey, }; -use pcsc::Card; /// Upload an explicitly selected Key to the card as a specific KeyType. /// @@ -370,7 +369,7 @@ fn ecc_algo_attrs_cmd( } fn copy_key_to_card( - card_client: &mut Box, + card_client: &mut CardClientBox, key_type: KeyType, ts: u64, fp: Vec, diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 1caa72a..a93bbbd 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -30,6 +30,8 @@ pub trait CardClient { fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result>; } +pub type CardClientBox = Box; + /// Information about the capabilities of the card. /// (feature configuration from card metadata) #[derive(Clone, Copy)] @@ -250,12 +252,12 @@ impl CardBase { Self { card_app, ard } } - /// Get all cards that can be opened as an OpenPGP card applet - pub fn list_cards() -> Result> { + /// Get all cards that can be opened as an OpenPGP card applet via pcsc + pub fn list_cards_pcsc() -> Result> { let cards = card::get_cards().map_err(|err| anyhow!(err))?; let ocs: Vec<_> = cards .into_iter() - .map(Self::open_card) + .map(PcscClient::open) .map(|oc| oc.ok()) .flatten() .collect(); @@ -267,7 +269,7 @@ impl CardBase { /// /// The ident is constructed as a concatenation of manufacturer /// id, a colon, and the card serial. Example: "1234:5678ABCD". - pub fn open_by_ident(ident: &str) -> Result { + pub fn open_by_ident_pcsc(ident: &str) -> Result { let cards = card::get_cards().map_err(|e| { OpenpgpCardError::Smartcard(SmartcardError::Error(format!( "{:?}", @@ -276,7 +278,7 @@ impl CardBase { })?; for card in cards { - let res = Self::open_card(card); + let res = PcscClient::open(card); if let Ok(opened_card) = res { let res = opened_card.get_aid(); if let Ok(aid) = res { @@ -293,7 +295,7 @@ impl CardBase { } /// Open connection to some card and select the openpgp applet - pub fn open_yolo() -> Result { + pub fn open_yolo_pcsc() -> Result { let mut cards = card::get_cards().map_err(|e| { OpenpgpCardError::Smartcard(SmartcardError::Error(format!( "{:?}", @@ -304,57 +306,46 @@ impl CardBase { // randomly use the first card in the list let card = cards.swap_remove(0); - Self::open_card(card) + PcscClient::open(card) } - /// Open connection to a specific card and select the openpgp applet - fn open_card(card: Card) -> Result { - let select_openpgp = commands::select_openpgp(); + /// Set up connection (cache "application related data") to a + /// CardClient, on which the openpgp applet has already been opened. + pub fn open_card(ccb: CardClientBox) -> Result { + // read and cache "application related data" + let mut card_app = CardApp::new(ccb); + let ard = card_app.get_app_data()?; - let card_client = PcscClient::new(card); - let mut ccb = - Box::new(card_client) as Box; + // Determine chaining/extended length support from card + // metadata and cache this information in CardApp (as a + // CardCaps) - let resp = apdu::send_command(&mut ccb, select_openpgp, false, None)?; + let mut ext_support = false; + let mut chaining_support = false; - if resp.is_ok() { - // read and cache "application related data" - let mut card_app = CardApp::new(ccb); - let ard = card_app.get_app_data()?; - - // Determine chaining/extended length support from card - // metadata and cache this information in CardApp (as a - // CardCaps) - - let mut ext_support = false; - let mut chaining_support = false; - - if let Ok(hist) = CardApp::get_historical(&ard) { - if let Some(cc) = hist.get_card_capabilities() { - chaining_support = cc.get_command_chaining(); - ext_support = cc.get_extended_lc_le(); - } + if let Ok(hist) = CardApp::get_historical(&ard) { + if let Some(cc) = hist.get_card_capabilities() { + chaining_support = cc.get_command_chaining(); + ext_support = cc.get_extended_lc_le(); } - - let max_cmd_bytes = if let Ok(Some(eli)) = - CardApp::get_extended_length_information(&ard) - { - eli.max_command_bytes - } else { - 255 - }; - - let caps = CardCaps { - ext_support, - chaining_support, - max_cmd_bytes, - }; - let card_app = card_app.set_caps(caps); - - Ok(Self { card_app, ard }) - } else { - Err(anyhow!("Couldn't open OpenPGP application").into()) } + + let max_cmd_bytes = if let Ok(Some(eli)) = + CardApp::get_extended_length_information(&ard) + { + eli.max_command_bytes + } else { + 255 + }; + + let caps = CardCaps { + ext_support, + chaining_support, + max_cmd_bytes, + }; + let card_app = card_app.set_caps(caps); + + Ok(Self { card_app, ard }) } // --- application data --- diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index fb15cc6..89347ce 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -5,9 +5,8 @@ use sequoia_ipc::assuan::{Client, Response}; use std::sync::{Arc, Mutex}; use tokio::runtime::Runtime; -use openpgp_card::card_app::CardApp; use openpgp_card::errors::OpenpgpCardError; -use openpgp_card::{CardBase, CardCaps, CardClient}; +use openpgp_card::{CardBase, CardClient, CardClientBox}; lazy_static! { pub(crate) static ref RT: Mutex = @@ -23,40 +22,9 @@ impl ScdClient { /// backend. pub fn open_scdc(socket: &str) -> Result { let card_client = ScdClient::new(socket)?; - let card_client_box = - Box::new(card_client) as Box; + let card_client_box = Box::new(card_client) as CardClientBox; - // read and cache "application related data" - let mut card_app = CardApp::new(card_client_box); - let ard = card_app.get_app_data()?; - - // Determine chaining/extended length support from card - // metadata and cache this information in CardApp (as a - // CardCaps) - - let mut ext_support = false; - let mut chaining_support = false; - - if let Ok(hist) = CardApp::get_historical(&ard) { - if let Some(cc) = hist.get_card_capabilities() { - chaining_support = cc.get_command_chaining(); - ext_support = cc.get_extended_lc_le(); - } - } - - let max_cmd_bytes = if let Ok(Some(eli)) = - CardApp::get_extended_length_information(&ard) - { - eli.max_command_bytes - } else { - 255 - }; - - let caps = CardCaps::new(ext_support, chaining_support, max_cmd_bytes); - - let card_app = card_app.set_caps(caps); - - Ok(CardBase::new(card_app, ard)) + CardBase::open_card(card_client_box) } pub fn new(socket: &str) -> Result { @@ -80,6 +48,10 @@ impl CardClient for ScdClient { while let Some(response) = rt.block_on(client.next()) { println!("res: {:x?}", response); + if let Err(_) = response { + unimplemented!(); + } + if let Ok(Response::Data { partial }) = response { let res = partial; From 3872b585d8cca101de6f108ac0e2962a87fbcdcb Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 12 Jul 2021 12:25:58 +0200 Subject: [PATCH 04/18] Add SPDX copyright headers --- scdc/Cargo.toml | 3 +++ scdc/src/lib.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/scdc/Cargo.toml b/scdc/Cargo.toml index be910cf..163e873 100644 --- a/scdc/Cargo.toml +++ b/scdc/Cargo.toml @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021 Heiko Schaefer +# SPDX-License-Identifier: MIT OR Apache-2.0 + [package] name = "openpgp-card-scdc" description = "Experimental SCDaemon Client, intended for use in the openpgp-card crate" diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index 89347ce..d081b14 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + use anyhow::{anyhow, Result}; use futures::StreamExt; use lazy_static::lazy_static; From 89745c02681d092ab6516781be446e65eb939efa Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Mon, 12 Jul 2021 13:54:59 +0200 Subject: [PATCH 05/18] Experiment: wrap Arc> in CardSigner --- openpgp-card-sequoia/src/lib.rs | 3 +- openpgp-card-sequoia/src/main.rs | 3 +- openpgp-card-sequoia/src/signer.rs | 66 ++++++++++++++++-------------- openpgp-card/src/lib.rs | 2 +- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 6cfcfde..e735239 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -24,6 +24,7 @@ use openpgp_card::{ errors::OpenpgpCardError, CardAdmin, CardSign, CardUploadableKey, CardUser, EccKey, EccType, KeyType, PrivateKeyMaterial, RSAKey, }; +use std::sync::{Arc, Mutex}; mod decryptor; mod signer; @@ -282,7 +283,7 @@ pub fn decrypt( } pub fn sign( - ocu: &mut CardSign, + ocu: Arc>, cert: &sequoia_openpgp::Cert, input: &mut dyn io::Read, ) -> Result { diff --git a/openpgp-card-sequoia/src/main.rs b/openpgp-card-sequoia/src/main.rs index 027fded..02a0b12 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -10,6 +10,7 @@ use sequoia_openpgp::Cert; use openpgp_card::{CardBase, KeyType}; use openpgp_card_scdc::ScdClient; +use std::sync::{Arc, Mutex}; // Filename of test key and test message to use: @@ -197,7 +198,7 @@ fn main() -> Result<(), Box> { let text = "Hello world, I am signed."; let res = openpgp_card_sequoia::sign( - &mut oc_user, + Arc::new(Mutex::new(oc_user)), &cert, &mut text.as_bytes(), ); diff --git a/openpgp-card-sequoia/src/signer.rs b/openpgp-card-sequoia/src/signer.rs index 3939480..8781ccb 100644 --- a/openpgp-card-sequoia/src/signer.rs +++ b/openpgp-card-sequoia/src/signer.rs @@ -15,27 +15,28 @@ use openpgp_card::CardSign; use openpgp_card::Hash; use crate::PublicKey; +use std::sync::{Arc, Mutex}; -pub(crate) struct CardSigner<'a> { +pub(crate) struct CardSigner { /// The OpenPGP card (authenticated to allow signing operations) - ocu: &'a mut CardSign, + ocu: Arc>, /// The matching public key for the card's signing key public: PublicKey, } -impl<'a> CardSigner<'a> { +impl CardSigner { /// 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( - ocs: &'a mut CardSign, + cs: Arc>, cert: &openpgp::Cert, policy: &dyn Policy, - ) -> Result, OpenpgpCardError> { + ) -> Result { // Get the fingerprint for the signing key from the card. - let fps = ocs.get_fingerprints()?; + let fps = cs.lock().unwrap().get_fingerprints()?; let fp = fps.signature(); if let Some(fp) = fp { @@ -58,7 +59,7 @@ impl<'a> CardSigner<'a> { let public = keys[0].clone(); Ok(CardSigner { - ocu: ocs, + ocu: cs, public: public.role_as_unspecified().clone(), }) } else { @@ -75,7 +76,7 @@ impl<'a> CardSigner<'a> { } } -impl<'a> crypto::Signer for CardSigner<'a> { +impl<'a> crypto::Signer for CardSigner { fn public(&self) -> &PublicKey { &self.public } @@ -98,28 +99,22 @@ impl<'a> crypto::Signer for CardSigner<'a> { PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { .. }, ) => { - let sig = match hash_algo { - openpgp::types::HashAlgorithm::SHA256 => { - let hash = - Hash::SHA256(digest.try_into().map_err(|_| { - anyhow!("invalid slice length") - })?); - self.ocu.signature_for_hash(hash)? - } - openpgp::types::HashAlgorithm::SHA384 => { - let hash = - Hash::SHA384(digest.try_into().map_err(|_| { - anyhow!("invalid slice length") - })?); - self.ocu.signature_for_hash(hash)? - } - openpgp::types::HashAlgorithm::SHA512 => { - let hash = - Hash::SHA512(digest.try_into().map_err(|_| { - anyhow!("invalid slice length") - })?); - self.ocu.signature_for_hash(hash)? - } + let hash = match hash_algo { + openpgp::types::HashAlgorithm::SHA256 => Hash::SHA256( + digest + .try_into() + .map_err(|_| anyhow!("invalid slice length"))?, + ), + openpgp::types::HashAlgorithm::SHA384 => Hash::SHA384( + digest + .try_into() + .map_err(|_| anyhow!("invalid slice length"))?, + ), + openpgp::types::HashAlgorithm::SHA512 => Hash::SHA512( + digest + .try_into() + .map_err(|_| anyhow!("invalid slice length"))?, + ), _ => { return Err(anyhow!( "Unsupported hash algorithm for RSA {:?}", @@ -128,12 +123,21 @@ impl<'a> crypto::Signer for CardSigner<'a> { } }; + let cs = self.ocu.clone(); + let mut cs = cs.lock().unwrap(); + + let sig = cs.signature_for_hash(hash)?; + let mpi = mpi::MPI::new(&sig[..]); Ok(mpi::Signature::RSA { s: mpi }) } (PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => { let hash = Hash::EdDSA(digest); - let sig = self.ocu.signature_for_hash(hash)?; + + let cs = self.ocu.clone(); + let mut cs = cs.lock().unwrap(); + + let sig = cs.signature_for_hash(hash)?; let r = mpi::MPI::new(&sig[..32]); let s = mpi::MPI::new(&sig[32..]); diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index a93bbbd..770165e 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -30,7 +30,7 @@ pub trait CardClient { fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result>; } -pub type CardClientBox = Box; +pub type CardClientBox = Box; /// Information about the capabilities of the card. /// (feature configuration from card metadata) From 31e577c89675e1333d2f05be5b9de527d94d02b2 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 13 Jul 2021 00:08:45 +0200 Subject: [PATCH 06/18] Refactoring low level API for card-functionality tests --- openpgp-card/src/apdu/mod.rs | 16 +++++++++++++--- openpgp-card/src/card_app.rs | 12 ++++++++++++ openpgp-card/src/lib.rs | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/openpgp-card/src/apdu/mod.rs b/openpgp-card/src/apdu/mod.rs index ef633dd..4de8ac6 100644 --- a/openpgp-card/src/apdu/mod.rs +++ b/openpgp-card/src/apdu/mod.rs @@ -11,6 +11,8 @@ use std::convert::TryFrom; use crate::apdu::command::Command; use crate::apdu::response::Response; +use crate::card; +use crate::card_app::CardApp; use crate::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError}; use crate::{CardBase, CardCaps, CardClient, CardClientBox}; @@ -188,6 +190,14 @@ impl PcscClient { Self { card } } + pub fn list_cards() -> Result> { + Ok(card::get_cards() + .map_err(|err| anyhow!(err))? + .into_iter() + .map(PcscClient::new) + .collect()) + } + /// Take a PCSC Card object and try to open the OpenPGP card applet. /// If successful, wrap and return the resulting CardClient as a /// CardBase object (which involves caching the "application related @@ -196,11 +206,11 @@ impl PcscClient { let card_client = PcscClient::new(card); let mut ccb = Box::new(card_client) as CardClientBox; - let select_openpgp = commands::select_openpgp(); - let resp = send_command(&mut ccb, select_openpgp, false, None)?; + let mut ca = CardApp::new(ccb); + let resp = ca.select()?; if resp.is_ok() { - CardBase::open_card(ccb) + CardBase::open_card(ca.take_card()) } else { Err(anyhow!("Couldn't open OpenPGP application").into()) } diff --git a/openpgp-card/src/card_app.rs b/openpgp-card/src/card_app.rs index 1608cf6..b10a2aa 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -41,6 +41,10 @@ impl CardApp { } } + pub(crate) fn take_card(self) -> CardClientBox { + self.card_client + } + pub fn set_caps(self, card_caps: CardCaps) -> Self { Self { card_client: self.card_client, @@ -56,6 +60,14 @@ impl CardApp { self.card_caps.as_ref() } + // --- select --- + + /// "Select" the OpenPGP card application + pub fn select(&mut self) -> Result { + let select_openpgp = commands::select_openpgp(); + apdu::send_command(&mut self.card_client, select_openpgp, false, None) + } + // --- application data --- /// Load "application related data". diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 770165e..c7b40fe 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -18,7 +18,7 @@ use crate::card_app::CardApp; use crate::errors::{OpenpgpCardError, SmartcardError}; use std::ops::{Deref, DerefMut}; -mod apdu; +pub mod apdu; mod card; pub mod card_app; pub mod errors; From 3bba67fbcc425a859d72e61131585497088c481d Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 13 Jul 2021 00:09:50 +0200 Subject: [PATCH 07/18] First draft of low-level card functionality testing --- Cargo.toml | 1 + card-functionality/Cargo.toml | 13 +++++++ card-functionality/src/main.rs | 67 ++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 card-functionality/Cargo.toml create mode 100644 card-functionality/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index e48f5a5..205f074 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ members = [ "openpgp-card", "openpgp-card-sequoia", "scdc", + "card-functionality", ] diff --git a/card-functionality/Cargo.toml b/card-functionality/Cargo.toml new file mode 100644 index 0000000..2430f20 --- /dev/null +++ b/card-functionality/Cargo.toml @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2021 Heiko Schaefer +# SPDX-License-Identifier: MIT OR Apache-2.0 + +[package] +name = "card-functionality" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +openpgp-card = { path = "../openpgp-card" } +anyhow = "1" \ No newline at end of file diff --git a/card-functionality/src/main.rs b/card-functionality/src/main.rs new file mode 100644 index 0000000..e059f3c --- /dev/null +++ b/card-functionality/src/main.rs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::Result; +use std::env; + +use openpgp_card::apdu::PcscClient; +use openpgp_card::card_app::CardApp; +use openpgp_card::CardClientBox; + +fn test(mut ca: CardApp) -> Result<()> { + let res = ca.verify_pw3("12345678"); + println!("res verify pw3 {:x?}", res); + + let check = ca.check_pw3(); + println!("has pw3 been verified yet? {:x?}", check); + + let res = ca.set_name("Admin< Result<()> { + let cards = PcscClient::list_cards()?; + + // Ident of the OpenPGP Card that will be used for tests. + let test_card_ident = + env::var("TEST_CARD_IDENT").expect("TEST_CARD_IDENT is not set"); + + for card in cards { + let card_client = Box::new(card) as CardClientBox; + + let mut ca = CardApp::new(card_client); + + let res = ca.select()?; + res.check_ok()?; + + let ard = ca.get_app_data()?; + let app_id = CardApp::get_aid(&ard)?; + + println!("Opened Card: ident {}", app_id.ident()); + if app_id.ident() == test_card_ident { + println!("Running Test on {}", app_id.ident()); + + test(ca)?; + } + } + + Ok(()) +} From 6601d2d09b4c424f4b7875018ecc5c7e4193046e Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 13 Jul 2021 20:36:45 +0200 Subject: [PATCH 08/18] - return data from tests in a Vec - run a test on a set of cards --- card-functionality/src/main.rs | 116 ++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 39 deletions(-) diff --git a/card-functionality/src/main.rs b/card-functionality/src/main.rs index e059f3c..ec702e0 100644 --- a/card-functionality/src/main.rs +++ b/card-functionality/src/main.rs @@ -1,52 +1,71 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -use anyhow::Result; +use anyhow::{anyhow, Result}; use std::env; use openpgp_card::apdu::PcscClient; use openpgp_card::card_app::CardApp; use openpgp_card::CardClientBox; +use std::collections::HashMap; -fn test(mut ca: CardApp) -> Result<()> { - let res = ca.verify_pw3("12345678"); - println!("res verify pw3 {:x?}", res); - - let check = ca.check_pw3(); - println!("has pw3 been verified yet? {:x?}", check); - - let res = ca.set_name("Admin< Result<()> { - let cards = PcscClient::list_cards()?; +type TestOutput = Vec; - // Ident of the OpenPGP Card that will be used for tests. - let test_card_ident = - env::var("TEST_CARD_IDENT").expect("TEST_CARD_IDENT is not set"); +/// outputs: +/// - verify pw3 + pin -> Status +/// - verify pw3 (check) -> Status +/// - set name -> Status +/// - get name -> Text(name) +/// - verify pw1 + pin -> Status +/// - verify pw1 (check) -> Status +/// - set name -> Status +/// - get name -> Text(name) +fn test_verify(ca: &mut CardApp) -> Result { + let mut out = vec![]; - for card in cards { + let res = ca.verify_pw3("12345678")?; + out.push(TestResult::Status(res.status())); + + let check = ca.check_pw3()?; + out.push(TestResult::Status(check.status())); + + let res = ca.set_name("Admin< Result, +) -> Result> { + let mut out = HashMap::new(); + + for card in PcscClient::list_cards()? { let card_client = Box::new(card) as CardClientBox; - let mut ca = CardApp::new(card_client); let res = ca.select()?; @@ -55,13 +74,32 @@ fn main() -> Result<()> { let ard = ca.get_app_data()?; let app_id = CardApp::get_aid(&ard)?; - println!("Opened Card: ident {}", app_id.ident()); - if app_id.ident() == test_card_ident { - println!("Running Test on {}", app_id.ident()); + if cards.contains(&app_id.ident().as_str()) { + println!("Running Test on {}:", app_id.ident()); - test(ca)?; + let res = t(&mut ca); + println!("{:x?}", res); + + out.insert(app_id.ident(), res?); } } + Ok(out) +} + +fn main() -> Result<()> { + // Ident of the OpenPGP Card that will be used for tests. + let test_card_ident = + env::var("TEST_CARD_IDENT").expect("TEST_CARD_IDENT is not set"); + + // list of card idents to runs the tests on + let cards = vec![ + "0006:16019180", // Yubikey 5 + "0005:0000A835", // FLOSS Card 3.4 + "FFFE:57183146", // Rysim Gnuk (green) + ]; + + let _verify_res = run_test(&cards, test_verify)?; + Ok(()) } From 6c7ce6228cd7b08b4fa072b805f047b3c98f52e5 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Tue, 13 Jul 2021 22:27:44 +0200 Subject: [PATCH 09/18] Fix mistake in mapping of u8 value to Sex --- openpgp-card/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index c7b40fe..bb47181 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -145,7 +145,7 @@ pub enum DecryptMe<'a> { ECDH(&'a [u8]), } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Sex { NotKnown, Male, @@ -167,9 +167,9 @@ impl Sex { impl From for Sex { fn from(s: u8) -> Self { match s { - 31 => Sex::Male, - 32 => Sex::Female, - 39 => Sex::NotApplicable, + 0x31 => Sex::Male, + 0x32 => Sex::Female, + 0x39 => Sex::NotApplicable, _ => Sex::NotKnown, } } From 499e128b4e4301bdb5b203803eba394bd96412e4 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 14 Jul 2021 00:06:30 +0200 Subject: [PATCH 10/18] Minimize the output data: assert data that is always expected - only return Status bytes that diverge between cards. Added a test that sets and checks name, lang, sex, url data. --- card-functionality/src/main.rs | 86 +++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 21 deletions(-) diff --git a/card-functionality/src/main.rs b/card-functionality/src/main.rs index ec702e0..26f1de4 100644 --- a/card-functionality/src/main.rs +++ b/card-functionality/src/main.rs @@ -1,13 +1,12 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -use anyhow::{anyhow, Result}; -use std::env; +use anyhow::Result; +use std::collections::HashMap; use openpgp_card::apdu::PcscClient; use openpgp_card::card_app::CardApp; -use openpgp_card::CardClientBox; -use std::collections::HashMap; +use openpgp_card::{CardClientBox, Sex}; #[derive(Debug)] enum TestResult { @@ -17,43 +16,91 @@ enum TestResult { type TestOutput = Vec; -/// outputs: -/// - verify pw3 + pin -> Status +/// Sets name, lang, sex, url; then reads the fields from the card and +/// compares the values with the expected values. +/// +/// Returns an empty TestOutput, throws errors for unexpected Status codes +/// and for unequal field values. +fn test_set_user_data(ca: &mut CardApp) -> Result { + let res = ca.verify_pw3("12345678")?; + res.check_ok()?; + + // name + let res = ca.set_name("Bar< Status -/// - set name -> Status -/// - get name -> Text(name) -/// - verify pw1 + pin -> Status /// - verify pw1 (check) -> Status -/// - set name -> Status -/// - get name -> Text(name) fn test_verify(ca: &mut CardApp) -> Result { + // Steps: + // + // - try to set name without verify, assert result is not ok + // - verify pw3 + pin -> Status + // - verify pw3 (check) -> Status + // - set name -> Status + // - get name -> Text(name) + // - verify pw1 + pin -> Status + // - verify pw1 (check) -> Status + // - set name -> Status + // - get name -> Text(name) + let mut out = vec![]; + // try to set name without verify, assert result is not ok! + let res = ca.set_name("Notverified< Result<()> { - // Ident of the OpenPGP Card that will be used for tests. - let test_card_ident = - env::var("TEST_CARD_IDENT").expect("TEST_CARD_IDENT is not set"); - // list of card idents to runs the tests on let cards = vec![ "0006:16019180", // Yubikey 5 @@ -100,6 +143,7 @@ fn main() -> Result<()> { ]; let _verify_res = run_test(&cards, test_verify)?; + let _userdata_res = run_test(&cards, test_set_user_data)?; Ok(()) } From 42c4f14d57924d8ad16b7548a51ed00162fe9735 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 14 Jul 2021 02:09:35 +0200 Subject: [PATCH 11/18] - Add documentation - Add placeholders for more tests --- card-functionality/src/main.rs | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/card-functionality/src/main.rs b/card-functionality/src/main.rs index 26f1de4..040b00a 100644 --- a/card-functionality/src/main.rs +++ b/card-functionality/src/main.rs @@ -1,6 +1,30 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 +//! These tests rely mainly on the card-app abstraction layer in +//! openpgp-card. However, for crypto-operations, higher level APIs and +//! Sequoia PGP are used. +//! +//! The main purpose of this test suite is to be able to test the behavior +//! of different OpenPGP card implementation. +//! +//! These tests assert (and fail) in cases where a certain behavior is +//! expected from all cards, and a card doesn't conform. +//! However, in some aspects, card behavior is expected to diverge, and +//! it's not ok for us to just fail and reject the card's output. +//! Even when it contradicts the OpenPGP card spec. +//! +//! For such cases, these tests return a TestOutput, which is a +//! Vec, to document the return values of the card in question. +//! +//! e.g.: the Yubikey 5 fails to handle the VERIFY command with empty data +//! (see OpenPGP card spec, 7.2.2: "If the command is called +//! without data, the actual access status of the addressed password is +//! returned or the access status is set to 'not verified'"). +//! +//! The Yubikey 5 erroneously returns Status 0x6a80 ("Incorrect parameters in +//! the command data field"). + use anyhow::Result; use std::collections::HashMap; @@ -16,6 +40,53 @@ enum TestResult { type TestOutput = Vec; +/// run after each "upload keys", if key *was* uploaded (?) +fn test_decrypt() { + // FIXME + unimplemented!() +} + +/// run after each "upload keys", if key *was* uploaded (?) +fn test_sign() { + // FIXME + unimplemented!() +} + +fn test_upload_keys_general() { + // FIXME + + // check fingerprint + // get_algorithm_attributes + // get_key_generation_times +} + +fn test_upload_keys_rsa() { + // FIXME + unimplemented!() + + // upload key + + // test upload general - checks +} + +fn test_upload_keys_25519() { + // FIXME + unimplemented!() + + // check if card supports 25519, if not that's ok, return this + // information and don't try upload. + + // upload key + + // test upload general - checks +} + +fn test_keygen() { + // FIXME + // (implementation of this functionality is still missing in openpgp-card) + unimplemented!() +} + /// Sets name, lang, sex, url; then reads the fields from the card and /// compares the values with the expected values. /// From 38fb6bd970d17b26b7efae19ccb49afdc89e4760 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 14 Jul 2021 18:02:11 +0200 Subject: [PATCH 12/18] Add a helper method to construct a CardUploadableKey from a ValidErasedKeyAmalgamation --- openpgp-card-sequoia/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index e735239..b2b20e3 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -58,6 +58,14 @@ impl SequoiaKey { } } +pub fn vka_as_uploadable_key( + vka: ValidErasedKeyAmalgamation, + password: Option, +) -> Box { + let sqk = SequoiaKey::new(vka, password); + Box::new(sqk) +} + /// Implement the `CardUploadableKey` trait that openpgp-card uses to /// upload (sub)keys to a card. impl CardUploadableKey for SequoiaKey { From 7afe2f52c2381e69fb7b33e1d1c06f21c50b9c6b Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 14 Jul 2021 18:03:07 +0200 Subject: [PATCH 13/18] Refactor card initialisation --- openpgp-card/src/card_app.rs | 33 +++++++++++++++++++++++++++++++++ openpgp-card/src/lib.rs | 30 ++---------------------------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/openpgp-card/src/card_app.rs b/openpgp-card/src/card_app.rs index b10a2aa..ea7c05b 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -45,6 +45,39 @@ impl CardApp { self.card_client } + /// Read capabilities from the card, and set them in the CardApp + pub fn init_caps(mut self, ard: &Tlv) -> Result { + // Determine chaining/extended length support from card + // metadata and cache this information in CardApp (as a + // CardCaps) + + let mut ext_support = false; + let mut chaining_support = false; + + if let Ok(hist) = CardApp::get_historical(&ard) { + if let Some(cc) = hist.get_card_capabilities() { + chaining_support = cc.get_command_chaining(); + ext_support = cc.get_extended_lc_le(); + } + } + + let max_cmd_bytes = if let Ok(Some(eli)) = + CardApp::get_extended_length_information(&ard) + { + eli.max_command_bytes + } else { + 255 + }; + + let caps = CardCaps { + ext_support, + chaining_support, + max_cmd_bytes, + }; + + Ok(self.set_caps(caps)) + } + pub fn set_caps(self, card_caps: CardCaps) -> Self { Self { card_client: self.card_client, diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index bb47181..6750e9a 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -314,36 +314,10 @@ impl CardBase { pub fn open_card(ccb: CardClientBox) -> Result { // read and cache "application related data" let mut card_app = CardApp::new(ccb); + let ard = card_app.get_app_data()?; - // Determine chaining/extended length support from card - // metadata and cache this information in CardApp (as a - // CardCaps) - - let mut ext_support = false; - let mut chaining_support = false; - - if let Ok(hist) = CardApp::get_historical(&ard) { - if let Some(cc) = hist.get_card_capabilities() { - chaining_support = cc.get_command_chaining(); - ext_support = cc.get_extended_lc_le(); - } - } - - let max_cmd_bytes = if let Ok(Some(eli)) = - CardApp::get_extended_length_information(&ard) - { - eli.max_command_bytes - } else { - 255 - }; - - let caps = CardCaps { - ext_support, - chaining_support, - max_cmd_bytes, - }; - let card_app = card_app.set_caps(caps); + card_app = card_app.init_caps(&ard)?; Ok(Self { card_app, ard }) } From c4457576335d6937defff56a8cc5f3cdd61d30ba Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 14 Jul 2021 21:59:33 +0200 Subject: [PATCH 14/18] Implement get_key_generation_times() --- openpgp-card/src/card_app.rs | 19 +++++- openpgp-card/src/key_upload.rs | 1 + .../src/parse/key_generation_times.rs | 61 +++++++++++++++++++ openpgp-card/src/parse/mod.rs | 1 + 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 openpgp-card/src/parse/key_generation_times.rs diff --git a/openpgp-card/src/card_app.rs b/openpgp-card/src/card_app.rs index ea7c05b..941bde5 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -16,11 +16,12 @@ use anyhow::{anyhow, Result}; use crate::apdu::{commands, response::Response}; use crate::errors::OpenpgpCardError; +use crate::parse::key_generation_times::KeyGeneration; use crate::parse::{ algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId, cardholder::CardHolder, extended_cap::ExtendedCap, extended_length_info::ExtendedLengthInfo, fingerprint, - historical::Historical, pw_status::PWStatus, KeySet, + historical::Historical, key_generation_times, pw_status::PWStatus, KeySet, }; use crate::tlv::{tag::Tag, Tlv, TlvEntry}; use crate::{ @@ -236,8 +237,20 @@ impl CardApp { unimplemented!() } - pub fn get_key_generation_times() { - unimplemented!() + pub fn get_key_generation_times( + ard: &Tlv, + ) -> Result, OpenpgpCardError> { + let kg = ard.find(&Tag::from([0xCD])); + + if let Some(kg) = kg { + let kg = key_generation_times::from(&kg.serialize())?; + + log::debug!("Key generation: {:x?}", kg); + + Ok(kg) + } else { + Err(anyhow!("Failed to get key generation times.").into()) + } } pub fn get_key_information() { diff --git a/openpgp-card/src/key_upload.rs b/openpgp-card/src/key_upload.rs index 27aea12..c24099c 100644 --- a/openpgp-card/src/key_upload.rs +++ b/openpgp-card/src/key_upload.rs @@ -387,6 +387,7 @@ fn copy_key_to_card( .copied() .collect(); + // Generation date/time let time_cmd = commands::put_data(&[key_type.get_timestamp_put_tag()], time_value); diff --git a/openpgp-card/src/parse/key_generation_times.rs b/openpgp-card/src/parse/key_generation_times.rs new file mode 100644 index 0000000..b1ce671 --- /dev/null +++ b/openpgp-card/src/parse/key_generation_times.rs @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::anyhow; +use nom::{ + bytes::complete as bytes, combinator, number::complete as number, sequence, +}; +use std::fmt; + +use crate::errors::OpenpgpCardError; +use crate::parse::KeySet; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct KeyGeneration(u32); + +impl From for KeyGeneration { + fn from(data: u32) -> Self { + Self(data) + } +} + +fn gen_time(input: &[u8]) -> nom::IResult<&[u8], u32> { + (number::be_u32)(input) +} + +fn key_generation(input: &[u8]) -> nom::IResult<&[u8], Option> { + combinator::map(gen_time, |kg| match kg { + 0 => None, + kg => Some(KeyGeneration(kg)), + })(input) +} + +fn key_generation_set( + input: &[u8], +) -> nom::IResult<&[u8], KeySet> { + combinator::into(sequence::tuple(( + key_generation, + key_generation, + key_generation, + )))(input) +} + +pub fn from(input: &[u8]) -> Result, OpenpgpCardError> { + // List of generation dates/times of key pairs, binary. + // 4 bytes, Big Endian each for Sig, Dec and Aut. Each + // value shall be seconds since Jan 1, 1970. Default + // value is 00000000 (not specified). + + log::trace!( + "Key generation times from input: {:x?}, len {}", + input, + input.len() + ); + + // The input may be longer than 3 key generation times, don't fail if it + // hasn't been completely consumed. + self::key_generation_set(input) + .map(|res| res.1) + .map_err(|err| anyhow!("Parsing failed: {:?}", err)) + .map_err(OpenpgpCardError::InternalError) +} diff --git a/openpgp-card/src/parse/mod.rs b/openpgp-card/src/parse/mod.rs index 2da0124..1b01b13 100644 --- a/openpgp-card/src/parse/mod.rs +++ b/openpgp-card/src/parse/mod.rs @@ -12,6 +12,7 @@ pub mod extended_cap; pub mod extended_length_info; pub mod fingerprint; pub mod historical; +pub mod key_generation_times; pub mod pw_status; use anyhow::{anyhow, Error}; From ec8c15cab3b487caa64294a6e3007b18dc25e635 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Wed, 14 Jul 2021 22:31:00 +0200 Subject: [PATCH 15/18] Add conversion implementations --- openpgp-card/src/parse/key_generation_times.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openpgp-card/src/parse/key_generation_times.rs b/openpgp-card/src/parse/key_generation_times.rs index b1ce671..b5c5ce0 100644 --- a/openpgp-card/src/parse/key_generation_times.rs +++ b/openpgp-card/src/parse/key_generation_times.rs @@ -9,10 +9,25 @@ use std::fmt; use crate::errors::OpenpgpCardError; use crate::parse::KeySet; +use chrono::{DateTime, NaiveDateTime, Utc}; #[derive(Clone, Eq, PartialEq, Debug)] pub struct KeyGeneration(u32); +impl From for DateTime { + fn from(kg: KeyGeneration) -> Self { + let naive_datetime = NaiveDateTime::from_timestamp(kg.0 as i64, 0); + + DateTime::from_utc(naive_datetime, Utc) + } +} + +impl From<&KeyGeneration> for u32 { + fn from(kg: &KeyGeneration) -> Self { + kg.0 + } +} + impl From for KeyGeneration { fn from(data: u32) -> Self { Self(data) From 32dbecbda2006a4b743e7080be414ae268dc381e Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 15 Jul 2021 13:13:56 +0200 Subject: [PATCH 16/18] Test code for key uploading --- card-functionality/Cargo.toml | 5 +- card-functionality/data/rsa2k.sec | 83 ++++++++++++++++++++ card-functionality/src/main.rs | 125 +++++++++++++++++++++++++----- card-functionality/src/util.rs | 72 +++++++++++++++++ 4 files changed, 266 insertions(+), 19 deletions(-) create mode 100644 card-functionality/data/rsa2k.sec create mode 100644 card-functionality/src/util.rs diff --git a/card-functionality/Cargo.toml b/card-functionality/Cargo.toml index 2430f20..d2b8a5f 100644 --- a/card-functionality/Cargo.toml +++ b/card-functionality/Cargo.toml @@ -10,4 +10,7 @@ edition = "2018" [dependencies] openpgp-card = { path = "../openpgp-card" } -anyhow = "1" \ No newline at end of file +openpgp-card-sequoia = { path = "../openpgp-card-sequoia" } +sequoia-openpgp = "1.3" +anyhow = "1" +env_logger = "0.8" \ No newline at end of file diff --git a/card-functionality/data/rsa2k.sec b/card-functionality/data/rsa2k.sec new file mode 100644 index 0000000..a9398f5 --- /dev/null +++ b/card-functionality/data/rsa2k.sec @@ -0,0 +1,83 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +7aYYBzgV/of68E1HMFmY7A== +=JojA +-----END PGP PRIVATE KEY BLOCK----- diff --git a/card-functionality/src/main.rs b/card-functionality/src/main.rs index 040b00a..198e3de 100644 --- a/card-functionality/src/main.rs +++ b/card-functionality/src/main.rs @@ -28,9 +28,14 @@ use anyhow::Result; use std::collections::HashMap; +use sequoia_openpgp::parse::Parse; +use sequoia_openpgp::Cert; + use openpgp_card::apdu::PcscClient; use openpgp_card::card_app::CardApp; -use openpgp_card::{CardClientBox, Sex}; +use openpgp_card::{CardClientBox, KeyType, Sex}; + +mod util; #[derive(Debug)] enum TestResult { @@ -40,33 +45,81 @@ enum TestResult { type TestOutput = Vec; -/// run after each "upload keys", if key *was* uploaded (?) +/// Map: Card ident -> TestOutput +type TestsOutput = HashMap; + +/// Run after each "upload keys", if key *was* uploaded (?) fn test_decrypt() { // FIXME unimplemented!() } -/// run after each "upload keys", if key *was* uploaded (?) +/// Run after each "upload keys", if key *was* uploaded (?) fn test_sign() { // FIXME unimplemented!() } -fn test_upload_keys_general() { - // FIXME +fn check_key_upload_metadata( + ca: &mut CardApp, + meta: &[(String, u32)], +) -> Result<()> { + let ard = ca.get_app_data()?; + + // check fingerprints + let card_fp = CardApp::get_fingerprints(&ard)?; + + let sig = card_fp.signature().expect("signature fingerprint"); + assert_eq!(format!("{:X}", sig), meta[0].0); + + let dec = card_fp.decryption().expect("decryption fingerprint"); + assert_eq!(format!("{:X}", dec), meta[1].0); + + let auth = card_fp + .authentication() + .expect("authentication fingerprint"); + assert_eq!(format!("{:X}", auth), meta[2].0); - // check fingerprint - // get_algorithm_attributes // get_key_generation_times + let card_kg = CardApp::get_key_generation_times(&ard)?; + + let sig: u32 = + card_kg.signature().expect("signature creation time").into(); + assert_eq!(sig, meta[0].1); + + let dec: u32 = card_kg + .decryption() + .expect("decryption creation time") + .into(); + assert_eq!(dec, meta[1].1); + + let auth: u32 = card_kg + .authentication() + .expect("authentication creation time") + .into(); + assert_eq!(auth, meta[2].1); + + Ok(()) } -fn test_upload_keys_rsa() { +fn check_key_upload_algo_attrs() -> Result<()> { + // get_algorithm_attributes // FIXME - unimplemented!() - // upload key + Ok(()) +} - // test upload general - checks +fn test_upload_keys_rsa_2k(ca: &mut CardApp) -> Result { + let verify = ca.verify_pw3("12345678")?; + verify.check_ok()?; + + let cert = Cert::from_file("data/rsa2k.sec")?; + let meta = util::upload_subkeys(ca, &cert)?; + + check_key_upload_metadata(ca, &meta)?; + check_key_upload_algo_attrs()?; + + Ok(vec![]) } fn test_upload_keys_25519() { @@ -87,6 +140,11 @@ fn test_keygen() { unimplemented!() } +fn test_reset(ca: &mut CardApp) -> Result { + let res = ca.factory_reset()?; + Ok(vec![]) +} + /// Sets name, lang, sex, url; then reads the fields from the card and /// compares the values with the expected values. /// @@ -179,16 +237,22 @@ fn test_verify(ca: &mut CardApp) -> Result { fn run_test( cards: &[&str], t: fn(&mut CardApp) -> Result, -) -> Result> { +) -> Result { let mut out = HashMap::new(); for card in PcscClient::list_cards()? { let card_client = Box::new(card) as CardClientBox; + let mut ca = CardApp::new(card_client); + // Select OpenPGP applet let res = ca.select()?; res.check_ok()?; + // Set Card Capabilities (chaining, command length, ..) + let ard = ca.get_app_data()?; + ca = ca.init_caps(&ard)?; + let ard = ca.get_app_data()?; let app_id = CardApp::get_aid(&ard)?; @@ -196,7 +260,6 @@ fn run_test( println!("Running Test on {}:", app_id.ident()); let res = t(&mut ca); - println!("{:x?}", res); out.insert(app_id.ident(), res?); } @@ -206,15 +269,41 @@ fn run_test( } fn main() -> Result<()> { + env_logger::init(); + // list of card idents to runs the tests on let cards = vec![ - "0006:16019180", // Yubikey 5 - "0005:0000A835", // FLOSS Card 3.4 - "FFFE:57183146", // Rysim Gnuk (green) + "0006:16019180", /* Yubikey 5 */ + "0005:0000A835", /* FLOSS Card 3.4 */ + "FFFE:57183146", /* Gnuk Rysim (green) */ + + // "FFFE:4231EB6E", /* Gnuk FST */ ]; - let _verify_res = run_test(&cards, test_verify)?; - let _userdata_res = run_test(&cards, test_set_user_data)?; + // println!("reset"); + // let _ = run_test(&cards, test_reset)?; + // + // println!("verify"); + // let verify_out = run_test(&cards, test_verify)?; + // println!("{:x?}", verify_out); + // + // println!("set user data"); + // let userdata_out = run_test(&cards, test_set_user_data)?; + // println!("{:x?}", userdata_out); + + // upload RSA keys + println!("upload RSA2k key"); + let upload_out = run_test(&cards, test_upload_keys_rsa_2k)?; + println!("{:x?}", upload_out); + + // sign + // decrypt + + // upload 25519 keys + // sign + // decrypt + + // upload some key with pw Ok(()) } diff --git a/card-functionality/src/util.rs b/card-functionality/src/util.rs new file mode 100644 index 0000000..2286104 --- /dev/null +++ b/card-functionality/src/util.rs @@ -0,0 +1,72 @@ +use anyhow::{anyhow, Result}; + +use sequoia_openpgp::cert::amalgamation::key::ValidKeyAmalgamation; +use sequoia_openpgp::packet::key::{SecretParts, UnspecifiedRole}; +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 std::time::SystemTime; + +pub const SP: &StandardPolicy = &StandardPolicy::new(); + +pub(crate) fn upload_subkeys( + ca: &mut CardApp, + cert: &Cert, +) -> Result> { + let mut out = vec![]; + let mut gentime = 0; + + for kt in [ + KeyType::Signing, + KeyType::Decryption, + KeyType::Authentication, + ] { + let vka = get_subkey(cert, kt)?; + + // store fingerprint as return-value + let fp = vka.fingerprint().to_hex(); + // store key creation time as return-value + let creation = vka + .creation_time() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() as u32; + + out.push((fp, creation)); + + // upload key + let cuk = vka_as_uploadable_key(vka, None); + let res = ca.upload_key(cuk, kt)?; + } + + Ok(out) +} + +fn get_subkey( + cert: &Cert, + key_type: KeyType, +) -> Result> { + // Find all suitable (sub)keys for key_type. + let mut valid_ka = cert + .keys() + .with_policy(SP, None) + .secret() + .alive() + .revoked(false); + valid_ka = match key_type { + KeyType::Decryption => valid_ka.for_storage_encryption(), + KeyType::Signing => valid_ka.for_signing(), + KeyType::Authentication => valid_ka.for_authentication(), + _ => return Err(anyhow!("Unexpected KeyType")), + }; + + // FIXME: for now, we just pick the first (sub)key from the list + if let Some(vka) = valid_ka.next() { + Ok(vka) + } else { + Err(anyhow!("No suitable (sub)key found")) + } +} From 68cad4c147fc51c5897e9c0a120df825b4141ad0 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 15 Jul 2021 13:22:33 +0200 Subject: [PATCH 17/18] Implement ScdClient::open_scdc_by_serial() to open a specific card through scdaemon --- openpgp-card-sequoia/src/main.rs | 7 +++++- scdc/src/lib.rs | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/openpgp-card-sequoia/src/main.rs b/openpgp-card-sequoia/src/main.rs index 02a0b12..97f4dac 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -31,10 +31,15 @@ fn main() -> Result<(), Box> { // Ident of the OpenPGP Card that will be used for tests. let test_card_ident = env::var("TEST_CARD_IDENT"); + // "serial" for opening a specific card through scdaemon + let test_card_serial = env::var("TEST_CARD_SERIAL")?; + if let Ok(test_card_ident) = test_card_ident { println!("** get card"); // let mut oc = CardBase::open_by_ident(&test_card_ident)?; - let mut oc = ScdClient::open_scdc(SOCKET)?; + // let mut oc = ScdClient::open_scdc(SOCKET)?; + let mut oc = + ScdClient::open_scdc_by_serial(SOCKET, &test_card_serial)?; // card metadata diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index d081b14..a8119ee 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -30,11 +30,51 @@ impl ScdClient { CardBase::open_card(card_client_box) } + /// Create a CardBase object that uses an scdaemon instance as its + /// backend, asking for a specific card by `serial`. + pub fn open_scdc_by_serial( + socket: &str, + serial: &str, + ) -> Result { + let mut card_client = ScdClient::new(socket)?; + + card_client.select_card(serial)?; + + let card_client_box = Box::new(card_client) as CardClientBox; + + CardBase::open_card(card_client_box) + } + pub fn new(socket: &str) -> Result { let client = RT.lock().unwrap().block_on(Client::connect(socket))?; let client = Arc::new(Mutex::new(client)); Ok(Self { client }) } + + /// SERIALNO --demand=D27600012401030400050000A8350000 + fn select_card(&mut self, serial: &str) -> Result<()> { + let mut client = self.client.lock().unwrap(); + + let send = format!("SERIALNO --demand={}\n", serial); + client.send(send)?; + + let mut rt = RT.lock().unwrap(); + + while let Some(response) = rt.block_on(client.next()) { + if let Err(_) = response { + return Err(anyhow!("Card not found")); + } + + if let Ok(Response::Status { .. }) = response { + // drop remaining lines + while let Some(drop) = rt.block_on(client.next()) {} + + return Ok(()); + } + } + + Err(anyhow!("Card not found")) + } } impl CardClient for ScdClient { From b8bd87bd7e7f2467fd6953459dd76ab581e100e7 Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Fri, 16 Jul 2021 14:15:03 +0200 Subject: [PATCH 18/18] Remove Arc> attempts, rely on assuan::Client now being Send+Sync --- openpgp-card-sequoia/src/lib.rs | 3 +-- openpgp-card-sequoia/src/main.rs | 7 +++---- openpgp-card-sequoia/src/signer.rs | 17 +++++------------ openpgp-card/src/lib.rs | 2 +- scdc/src/lib.rs | 21 ++++++++------------- 5 files changed, 18 insertions(+), 32 deletions(-) diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index b2b20e3..9bd73f2 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -24,7 +24,6 @@ use openpgp_card::{ errors::OpenpgpCardError, CardAdmin, CardSign, CardUploadableKey, CardUser, EccKey, EccType, KeyType, PrivateKeyMaterial, RSAKey, }; -use std::sync::{Arc, Mutex}; mod decryptor; mod signer; @@ -291,7 +290,7 @@ pub fn decrypt( } pub fn sign( - ocu: Arc>, + ocu: CardSign, cert: &sequoia_openpgp::Cert, input: &mut dyn io::Read, ) -> Result { diff --git a/openpgp-card-sequoia/src/main.rs b/openpgp-card-sequoia/src/main.rs index 97f4dac..62fdd12 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -8,9 +8,8 @@ use anyhow::Result; use sequoia_openpgp::parse::Parse; use sequoia_openpgp::Cert; -use openpgp_card::{CardBase, KeyType}; +use openpgp_card::KeyType; use openpgp_card_scdc::ScdClient; -use std::sync::{Arc, Mutex}; // Filename of test key and test message to use: @@ -196,14 +195,14 @@ fn main() -> Result<(), Box> { // Sign match oc.verify_pw1_for_signing("123456") { - Ok(mut oc_user) => { + Ok(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( - Arc::new(Mutex::new(oc_user)), + oc_user, &cert, &mut text.as_bytes(), ); diff --git a/openpgp-card-sequoia/src/signer.rs b/openpgp-card-sequoia/src/signer.rs index 8781ccb..79c4570 100644 --- a/openpgp-card-sequoia/src/signer.rs +++ b/openpgp-card-sequoia/src/signer.rs @@ -15,11 +15,10 @@ use openpgp_card::CardSign; use openpgp_card::Hash; use crate::PublicKey; -use std::sync::{Arc, Mutex}; pub(crate) struct CardSigner { /// The OpenPGP card (authenticated to allow signing operations) - ocu: Arc>, + ocu: CardSign, /// The matching public key for the card's signing key public: PublicKey, @@ -31,12 +30,12 @@ impl 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: Arc>, + cs: CardSign, cert: &openpgp::Cert, policy: &dyn Policy, ) -> Result { // Get the fingerprint for the signing key from the card. - let fps = cs.lock().unwrap().get_fingerprints()?; + let fps = cs.get_fingerprints()?; let fp = fps.signature(); if let Some(fp) = fp { @@ -123,10 +122,7 @@ impl<'a> crypto::Signer for CardSigner { } }; - let cs = self.ocu.clone(); - let mut cs = cs.lock().unwrap(); - - let sig = cs.signature_for_hash(hash)?; + let sig = self.ocu.signature_for_hash(hash)?; let mpi = mpi::MPI::new(&sig[..]); Ok(mpi::Signature::RSA { s: mpi }) @@ -134,10 +130,7 @@ impl<'a> crypto::Signer for CardSigner { (PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { .. }) => { let hash = Hash::EdDSA(digest); - let cs = self.ocu.clone(); - let mut cs = cs.lock().unwrap(); - - let sig = cs.signature_for_hash(hash)?; + let sig = self.ocu.signature_for_hash(hash)?; let r = mpi::MPI::new(&sig[..32]); let s = mpi::MPI::new(&sig[32..]); diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 6750e9a..3224d64 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -30,7 +30,7 @@ pub trait CardClient { fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result>; } -pub type CardClientBox = Box; +pub type CardClientBox = Box; /// Information about the capabilities of the card. /// (feature configuration from card metadata) diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index a8119ee..06c45c4 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Result}; use futures::StreamExt; use lazy_static::lazy_static; use sequoia_ipc::assuan::{Client, Response}; -use std::sync::{Arc, Mutex}; +use std::sync::Mutex; use tokio::runtime::Runtime; use openpgp_card::errors::OpenpgpCardError; @@ -17,7 +17,7 @@ lazy_static! { } pub struct ScdClient { - client: Arc>, + client: Client, } impl ScdClient { @@ -47,27 +47,24 @@ impl ScdClient { pub fn new(socket: &str) -> Result { let client = RT.lock().unwrap().block_on(Client::connect(socket))?; - let client = Arc::new(Mutex::new(client)); Ok(Self { client }) } /// SERIALNO --demand=D27600012401030400050000A8350000 fn select_card(&mut self, serial: &str) -> Result<()> { - let mut client = self.client.lock().unwrap(); - let send = format!("SERIALNO --demand={}\n", serial); - client.send(send)?; + self.client.send(send)?; let mut rt = RT.lock().unwrap(); - while let Some(response) = rt.block_on(client.next()) { + while let Some(response) = rt.block_on(self.client.next()) { if let Err(_) = response { return Err(anyhow!("Card not found")); } if let Ok(Response::Status { .. }) = response { // drop remaining lines - while let Some(drop) = rt.block_on(client.next()) {} + while let Some(_drop) = rt.block_on(self.client.next()) {} return Ok(()); } @@ -81,15 +78,13 @@ impl CardClient for ScdClient { fn transmit(&mut self, cmd: &[u8], _: usize) -> Result> { let hex = hex::encode(cmd); - let mut client = self.client.lock().unwrap(); - let send = format!("APDU {}\n", hex); println!("send: '{}'", send); - client.send(send)?; + self.client.send(send)?; let mut rt = RT.lock().unwrap(); - while let Some(response) = rt.block_on(client.next()) { + while let Some(response) = rt.block_on(self.client.next()) { println!("res: {:x?}", response); if let Err(_) = response { unimplemented!(); @@ -99,7 +94,7 @@ impl CardClient for ScdClient { let res = partial; // drop remaining lines - while let Some(drop) = rt.block_on(client.next()) { + while let Some(drop) = rt.block_on(self.client.next()) { println!("drop: {:x?}", drop); }