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..c746ff3 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -14,6 +14,7 @@ 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" } chrono = "0.4" anyhow = "1" thiserror = "1" diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 6cfcfde..6d825f1 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -282,7 +282,7 @@ pub fn decrypt( } pub fn sign( - ocu: &mut CardSign, + 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 47da491..6604788 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -8,7 +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; // Filename of test key and test message to use: @@ -21,6 +22,8 @@ use openpgp_card::{CardBase, KeyType}; 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"; + fn main() -> Result<(), Box> { env_logger::init(); @@ -29,7 +32,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 +144,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,18 +185,19 @@ 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 oc = ScdClient::open_scdc(SOCKET)?; // 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( - &mut oc_user, + oc_user, &cert, &mut text.as_bytes(), ); @@ -213,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-sequoia/src/signer.rs b/openpgp-card-sequoia/src/signer.rs index 3939480..c99c791 100644 --- a/openpgp-card-sequoia/src/signer.rs +++ b/openpgp-card-sequoia/src/signer.rs @@ -16,26 +16,26 @@ use openpgp_card::Hash; use crate::PublicKey; -pub(crate) struct CardSigner<'a> { +pub(crate) struct CardSigner { /// The OpenPGP card (authenticated to allow signing operations) - ocu: &'a mut CardSign, + ocu: CardSign, /// 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: CardSign, 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.get_fingerprints()?; let fp = fps.signature(); if let Some(fp) = fp { @@ -58,7 +58,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 +75,7 @@ impl<'a> CardSigner<'a> { } } -impl<'a> crypto::Signer for CardSigner<'a> { +impl<'a> crypto::Signer for CardSigner { fn public(&self) -> &PublicKey { &self.public } diff --git a/openpgp-card/src/apdu/mod.rs b/openpgp-card/src/apdu/mod.rs index c10f432..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; +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>, @@ -179,18 +179,32 @@ fn send_command_low_level( } } -pub trait CardClient { - fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result>; -} - pub struct PcscClient { card: Card, } 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 85675ee..1608cf6 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -13,35 +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::apdu::CardClient; -use crate::Hash; +use crate::tlv::{tag::Tag, Tlv, TlvEntry}; use crate::{ - apdu, key_upload, parse, tlv, CardCaps, CardUploadableKey, DecryptMe, - KeyType, Sex, + apdu, key_upload, parse, tlv, CardCaps, CardClientBox, CardUploadableKey, + DecryptMe, Hash, KeyType, Sex, }; -pub(crate) struct CardApp { - card_client: Box, +pub struct CardApp { + 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, @@ -55,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 edc9832..76c080c 100644 --- a/openpgp-card/src/key_upload.rs +++ b/openpgp-card/src/key_upload.rs @@ -3,19 +3,17 @@ use anyhow::{anyhow, Result}; -use crate::apdu::command::Command; -use crate::apdu::{commands, CardClient}; +use crate::apdu::{command::Command, 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, 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 +368,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 0098aca..a93bbbd 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -13,28 +13,48 @@ 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>; +} + +pub type CardClientBox = Box; + /// 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,12 +248,16 @@ pub struct CardBase { } impl CardBase { - /// Get all cards that can be opened as an OpenPGP card applet - pub fn list_cards() -> Result> { + 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 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(); @@ -245,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!( "{:?}", @@ -254,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 { @@ -271,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!( "{:?}", @@ -282,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/Cargo.toml b/scdc/Cargo.toml new file mode 100644 index 0000000..fd5f34e --- /dev/null +++ b/scdc/Cargo.toml @@ -0,0 +1,19 @@ +# 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" +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" +log = "0.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..c88a3fe --- /dev/null +++ b/scdc/src/lib.rs @@ -0,0 +1,106 @@ +// 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; +use sequoia_ipc::assuan::{Client, Response}; +use std::sync::Mutex; +use tokio::runtime::Runtime; + +use openpgp_card::errors::OpenpgpCardError; +use openpgp_card::{CardBase, CardClient, CardClientBox}; + +lazy_static! { + pub(crate) static ref RT: Mutex = + Mutex::new(tokio::runtime::Runtime::new().unwrap()); +} + +pub struct ScdClient { + client: Client, +} + +impl ScdClient { + /// Create a CardBase object that uses an scdaemon instance as its + /// backend. + pub fn open_scdc(socket: &str) -> Result { + let card_client = ScdClient::new(socket)?; + let card_client_box = Box::new(card_client) as CardClientBox; + + 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))?; + Ok(Self { client }) + } + + 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()) { + 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(self.client.next()) {} + + return Ok(()); + } + } + + Err(anyhow!("Card not found")) + } +} + +impl CardClient for ScdClient { + fn transmit(&mut self, cmd: &[u8], _: usize) -> Result> { + let hex = hex::encode(cmd); + + let send = format!("APDU {}\n", hex); + println!("send: '{}'", send); + self.client.send(send)?; + + let mut rt = RT.lock().unwrap(); + + while let Some(response) = rt.block_on(self.client.next()) { + log::trace!("res: {:x?}", response); + if let Err(_) = response { + unimplemented!(); + } + + if let Ok(Response::Data { partial }) = response { + let res = partial; + + // drop remaining lines + while let Some(drop) = rt.block_on(self.client.next()) { + log::trace!("drop: {:x?}", drop); + } + + return Ok(res); + } + } + + Err(anyhow!("no response found")) + } +}