diff --git a/Cargo.toml b/Cargo.toml index 205f074..96401de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "openpgp-card", "openpgp-card-sequoia", + "pcsc", "scdc", "card-functionality", ] diff --git a/card-functionality/Cargo.toml b/card-functionality/Cargo.toml index 73c0cdf..e7ab11f 100644 --- a/card-functionality/Cargo.toml +++ b/card-functionality/Cargo.toml @@ -27,6 +27,7 @@ path = "src/other.rs" openpgp-card = { path = "../openpgp-card" } openpgp-card-sequoia = { path = "../openpgp-card-sequoia" } openpgp-card-scdc = { path = "../scdc" } +openpgp-card-pcsc = { path = "../pcsc" } sequoia-openpgp = "1.3" anyhow = "1" thiserror = "1.0" diff --git a/card-functionality/src/cards.rs b/card-functionality/src/cards.rs index 048aee5..a829084 100644 --- a/card-functionality/src/cards.rs +++ b/card-functionality/src/cards.rs @@ -8,9 +8,8 @@ use anyhow::{anyhow, Result}; use serde_derive::Deserialize; use std::collections::BTreeMap; -use openpgp_card::apdu::PcscClient; use openpgp_card::card_app::CardApp; -use openpgp_card::CardClientBox; +use openpgp_card_pcsc::PcscClient; use openpgp_card_scdc::ScdClient; #[derive(Debug, Deserialize)] @@ -98,14 +97,9 @@ impl TestCard { let res = ScdClient::shutdown_scd(None); log::trace!(" Attempt to shutdown scd: {:?}", res); - for card in PcscClient::list_cards()? { - let card_client = Box::new(card) as CardClientBox; + for card_client in PcscClient::list_cards()? { 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()?; let app_id = CardApp::get_aid(&ard)?; diff --git a/openpgp-card-sequoia/Cargo.toml b/openpgp-card-sequoia/Cargo.toml index c746ff3..9b2ac69 100644 --- a/openpgp-card-sequoia/Cargo.toml +++ b/openpgp-card-sequoia/Cargo.toml @@ -15,6 +15,7 @@ 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" } +openpgp-card-pcsc = { path = "../pcsc" } chrono = "0.4" anyhow = "1" thiserror = "1" diff --git a/openpgp-card-sequoia/src/lib.rs b/openpgp-card-sequoia/src/lib.rs index 6191a7d..b60455f 100644 --- a/openpgp-card-sequoia/src/lib.rs +++ b/openpgp-card-sequoia/src/lib.rs @@ -9,6 +9,7 @@ use std::convert::TryFrom; use std::convert::TryInto; use std::error::Error; use std::io; +use std::ops::{Deref, DerefMut}; use std::time::SystemTime; use openpgp::armor; @@ -30,13 +31,18 @@ use openpgp::types::{KeyFlags, PublicKeyAlgorithm, SignatureType}; use openpgp::{Cert, Packet}; use sequoia_openpgp as openpgp; -use crate::signer::CardSigner; -use openpgp_card::card_app::CardApp; +use openpgp_card::apdu::response::Response; +use openpgp_card::card_app::{CardApp, ARD}; use openpgp_card::{ - errors::OpenpgpCardError, Algo, CardAdmin, CardUploadableKey, Curve, - EccKey, EccType, KeyType, PrivateKeyMaterial, PublicKeyMaterial, RSAKey, + errors::OpenpgpCardError, Algo, AlgoInfo, ApplicationId, CardClientBox, + CardHolder, CardUploadableKey, Curve, DecryptMe, EccKey, EccType, + ExtendedCap, ExtendedLengthInfo, Features, Fingerprint, Hash, Historical, + KeySet, KeyType, PWStatus, PrivateKeyMaterial, PublicKeyMaterial, RSAKey, + Sex, }; +use crate::signer::CardSigner; + mod decryptor; pub mod signer; @@ -543,3 +549,379 @@ pub fn public_to_fingerprint( assert_eq!(fp.len(), 20); Ok(fp.try_into()?) } + +// -------- + +/// Representation of an opened OpenPGP card in its basic, freshly opened, +/// state (i.e. no passwords have been verified, default privileges apply). +pub struct CardBase { + card_app: CardApp, + + // Cache of "application related data". + // + // FIXME: Should be invalidated when changing data on the card! + // (e.g. uploading keys, etc) + ard: ARD, +} + +impl CardBase { + pub fn new(card_app: CardApp, ard: ARD) -> Self { + Self { card_app, ard } + } + + /// Get a reference to the internal CardApp object (for use in tests) + pub fn get_card_app(&mut self) -> &mut CardApp { + &mut self.card_app + } + + /// 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()?; + + card_app.init_caps(&ard)?; + + Ok(Self { card_app, ard }) + } + + // --- application data --- + + /// Load "application related data". + /// + /// This is done once, after opening the OpenPGP card applet + /// (the data is stored in the OpenPGPCard object). + fn get_app_data(&mut self) -> Result { + self.card_app.get_app_data() + } + + pub fn get_aid(&self) -> Result { + CardApp::get_aid(&self.ard) + } + + pub fn get_historical(&self) -> Result { + CardApp::get_historical(&self.ard) + } + + pub fn get_extended_length_information( + &self, + ) -> Result> { + CardApp::get_extended_length_information(&self.ard) + } + + pub fn get_general_feature_management() -> Option { + unimplemented!() + } + + pub fn get_discretionary_data_objects() { + unimplemented!() + } + + pub fn get_extended_capabilities( + &self, + ) -> Result { + CardApp::get_extended_capabilities(&self.ard) + } + + pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result { + CardApp::get_algorithm_attributes(&self.ard, key_type) + } + + /// PW status Bytes + pub fn get_pw_status_bytes(&self) -> Result { + CardApp::get_pw_status_bytes(&self.ard) + } + + pub fn get_fingerprints( + &self, + ) -> Result, OpenpgpCardError> { + CardApp::get_fingerprints(&self.ard) + } + + pub fn get_ca_fingerprints(&self) { + unimplemented!() + } + + pub fn get_key_generation_times() { + unimplemented!() + } + + pub fn get_key_information() { + unimplemented!() + } + + pub fn get_uif_pso_cds() { + unimplemented!() + } + + pub fn get_uif_pso_dec() { + unimplemented!() + } + + pub fn get_uif_pso_aut() { + unimplemented!() + } + pub fn get_uif_attestation() { + unimplemented!() + } + + // --- optional private DOs (0101 - 0104) --- + + // --- login data (5e) --- + + // --- URL (5f50) --- + + pub fn get_url(&mut self) -> Result { + self.card_app.get_url() + } + + // --- cardholder related data (65) --- + pub fn get_cardholder_related_data(&mut self) -> Result { + self.card_app.get_cardholder_related_data() + } + + // --- security support template (7a) --- + pub fn get_security_support_template(&mut self) -> Result> { + self.card_app.get_security_support_template() + } + + // DO "Algorithm Information" (0xFA) + pub fn list_supported_algo(&mut self) -> Result> { + // The DO "Algorithm Information" (Tag FA) shall be present if + // Algorithm attributes can be changed + let ec = self.get_extended_capabilities()?; + if !ec.features.contains(&Features::AlgoAttrsChangeable) { + // Algorithm attributes can not be changed, + // list_supported_algo is not supported + return Ok(None); + } + + self.card_app.list_supported_algo() + } + + // ---------- + + /// Delete all state on this OpenPGP card + pub fn factory_reset(&mut self) -> Result<()> { + self.card_app.factory_reset() + } + + pub fn verify_pw1_for_signing( + mut self, + pin: &str, + ) -> Result { + assert!(pin.len() >= 6); // FIXME: Err + + let res = self.card_app.verify_pw1_for_signing(pin); + + if let Ok(resp) = res { + if resp.is_ok() { + return Ok(CardSign { oc: self }); + } + } + + Err(self) + } + + pub fn check_pw1(&mut self) -> Result { + self.card_app.check_pw1() + } + + pub fn verify_pw1(mut self, pin: &str) -> Result { + assert!(pin.len() >= 6); // FIXME: Err + + let res = self.card_app.verify_pw1(pin); + + if let Ok(resp) = res { + if resp.is_ok() { + return Ok(CardUser { oc: self }); + } + } + + Err(self) + } + + pub fn check_pw3(&mut self) -> Result { + self.card_app.check_pw3() + } + + pub fn verify_pw3(mut self, pin: &str) -> Result { + assert!(pin.len() >= 8); // FIXME: Err + + let res = self.card_app.verify_pw3(pin); + + if let Ok(resp) = res { + if resp.is_ok() { + return Ok(CardAdmin { oc: self }); + } + } + + Err(self) + } +} + +/// An OpenPGP card after successful verification of PW1 in mode 82 +/// (verification for operations other than signing) +pub struct CardUser { + oc: CardBase, +} + +/// Allow access to fn of OpenPGPCard, through OpenPGPCardUser. +impl Deref for CardUser { + type Target = CardBase; + + fn deref(&self) -> &Self::Target { + &self.oc + } +} + +/// Allow access to fn of CardBase, through CardUser. +impl DerefMut for CardUser { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.oc + } +} + +impl CardUser { + /// Decrypt the ciphertext in `dm`, on the card. + pub fn decrypt( + &mut self, + dm: DecryptMe, + ) -> Result, OpenpgpCardError> { + self.card_app.decrypt(dm) + } + + /// Run decryption operation on the smartcard + /// (7.2.11 PSO: DECIPHER) + pub(crate) fn pso_decipher( + &mut self, + data: Vec, + ) -> Result, OpenpgpCardError> { + self.card_app.pso_decipher(data) + } +} + +/// An OpenPGP card after successful verification of PW1 in mode 81 +/// (verification for signing) +pub struct CardSign { + oc: CardBase, +} + +/// Allow access to fn of CardBase, through CardSign. +impl Deref for CardSign { + type Target = CardBase; + + fn deref(&self) -> &Self::Target { + &self.oc + } +} + +/// Allow access to fn of CardBase, through CardSign. +impl DerefMut for CardSign { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.oc + } +} + +// FIXME: depending on the setting in "PW1 Status byte", only one +// signature can be made after verification for signing +impl CardSign { + /// Sign the message in `hash`, on the card. + pub fn signature_for_hash( + &mut self, + hash: Hash, + ) -> Result, OpenpgpCardError> { + self.card_app.signature_for_hash(hash) + } + + /// Run signing operation on the smartcard + /// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) + pub(crate) fn compute_digital_signature( + &mut self, + data: Vec, + ) -> Result, OpenpgpCardError> { + self.card_app.compute_digital_signature(data) + } +} + +/// An OpenPGP card after successful verification of PW3 ("Admin privileges") +pub struct CardAdmin { + oc: CardBase, +} + +/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin. +impl Deref for CardAdmin { + type Target = CardBase; + + fn deref(&self) -> &Self::Target { + &self.oc + } +} + +/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin. +impl DerefMut for CardAdmin { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.oc + } +} + +impl CardAdmin { + pub fn set_name( + &mut self, + name: &str, + ) -> Result { + if name.len() >= 40 { + return Err(anyhow!("name too long").into()); + } + + // All chars must be in ASCII7 + if name.chars().any(|c| !c.is_ascii()) { + return Err(anyhow!("Invalid char in name").into()); + }; + + self.card_app.set_name(name) + } + + pub fn set_lang( + &mut self, + lang: &str, + ) -> Result { + if lang.len() > 8 { + return Err(anyhow!("lang too long").into()); + } + + self.card_app.set_lang(lang) + } + + pub fn set_sex(&mut self, sex: Sex) -> Result { + self.card_app.set_sex(sex) + } + + pub fn set_url( + &mut self, + url: &str, + ) -> Result { + if url.chars().any(|c| !c.is_ascii()) { + return Err(anyhow!("Invalid char in url").into()); + } + + // Check for max len + let ec = self.get_extended_capabilities()?; + + if url.len() < ec.max_len_special_do as usize { + self.card_app.set_url(url) + } else { + Err(anyhow!("URL too long").into()) + } + } + + pub fn upload_key( + &mut self, + key: Box, + key_type: KeyType, + ) -> Result<(), OpenpgpCardError> { + self.card_app.upload_key(key, key_type) + } +} diff --git a/openpgp-card-sequoia/src/main.rs b/openpgp-card-sequoia/src/main.rs index 5ab03f7..b8de20e 100644 --- a/openpgp-card-sequoia/src/main.rs +++ b/openpgp-card-sequoia/src/main.rs @@ -8,9 +8,13 @@ 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 openpgp_card::card_app::CardApp; +use openpgp_card_pcsc::PcscClient; +use openpgp_card_sequoia::CardBase; + // Filename of test key and test message to use: const TEST_KEY_PATH: &str = "example/test4k.sec"; @@ -230,9 +234,15 @@ fn main() -> Result<(), Box> { println!("The following OpenPGP cards are connected to your system:"); - let cards = openpgp_card::CardBase::list_cards_pcsc()?; + let cards = PcscClient::list_cards()?; for c in cards { - println!(" '{}'", c.get_aid()?.ident()); + let mut ca = CardApp::new(c); + + let ard = ca.get_app_data()?; + let app_id = CardApp::get_aid(&ard)?; + + let ident = app_id.ident(); + println!(" '{}'", ident); } } diff --git a/openpgp-card/Cargo.toml b/openpgp-card/Cargo.toml index f22725c..273e1bb 100644 --- a/openpgp-card/Cargo.toml +++ b/openpgp-card/Cargo.toml @@ -12,7 +12,6 @@ repository = "https://gitlab.com/hkos/openpgp-card" documentation = "https://docs.rs/crate/openpgp-card" [dependencies] -pcsc = "2" nom = "6" hex-literal = "0.3" anyhow = "1" diff --git a/openpgp-card/src/apdu/mod.rs b/openpgp-card/src/apdu/mod.rs index 760ab87..bdeebff 100644 --- a/openpgp-card/src/apdu/mod.rs +++ b/openpgp-card/src/apdu/mod.rs @@ -5,16 +5,16 @@ pub mod command; pub mod commands; pub mod response; -use anyhow::{anyhow, Result}; -use pcsc::Card; +use anyhow::Result; 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}; +use crate::errors::{OcErrorStatus, OpenpgpCardError}; +use crate::CardClientBox; + +// "Maximum amount of bytes in a short APDU command or response" (from pcsc) +const MAX_BUFFER_SIZE: usize = 264; #[derive(Clone, Copy, PartialEq, Debug)] pub(crate) enum Le { @@ -121,7 +121,7 @@ fn send_command_low_level( log::debug!(" -> full APDU command: {:x?}", cmd); let buf_size = if !ext_support || ext == Le::Short { - pcsc::MAX_BUFFER_SIZE + MAX_BUFFER_SIZE } else { max_rsp_bytes }; @@ -198,68 +198,3 @@ fn send_command_low_level( Ok(resp) } } - -pub struct PcscClient { - card: Card, - card_caps: Option, -} - -impl PcscClient { - fn new(card: Card) -> Self { - Self { - card, - card_caps: None, - } - } - - 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 - /// data"). - pub fn open(card: Card) -> Result { - let card_client = PcscClient::new(card); - let ccb = Box::new(card_client) as CardClientBox; - - let mut ca = CardApp::new(ccb); - let resp = ca.select()?; - - if resp.is_ok() { - CardBase::open_card(ca.take_card()) - } else { - Err(anyhow!("Couldn't open OpenPGP application").into()) - } - } -} - -impl CardClient for PcscClient { - fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result> { - let mut resp_buffer = vec![0; buf_size]; - - let resp = self.card.transmit(cmd, &mut resp_buffer).map_err(|e| { - OpenpgpCardError::Smartcard(SmartcardError::Error(format!( - "Transmit failed: {:?}", - e - ))) - })?; - - log::debug!(" <- APDU response: {:x?}", resp); - - Ok(resp.to_vec()) - } - - fn init_caps(&mut self, caps: CardCaps) { - self.card_caps = Some(caps); - } - - fn get_caps(&self) -> Option<&CardCaps> { - self.card_caps.as_ref() - } -} diff --git a/openpgp-card/src/card.rs b/openpgp-card/src/card.rs deleted file mode 100644 index f0877fe..0000000 --- a/openpgp-card/src/card.rs +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Heiko Schaefer -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use anyhow::Result; -use pcsc::{Card, Context, Error, Protocols, Scope, ShareMode}; - -use crate::errors; - -pub fn get_cards() -> Result, errors::SmartcardError> { - let ctx = match Context::establish(Scope::User) { - Ok(ctx) => ctx, - Err(err) => { - return Err(errors::SmartcardError::ContextError(err.to_string())) - } - }; - - // List available readers. - let mut readers_buf = [0; 2048]; - let readers = match ctx.list_readers(&mut readers_buf) { - Ok(readers) => readers, - Err(err) => { - return Err(errors::SmartcardError::ReaderError(err.to_string())); - } - }; - - let mut found_reader = false; - - let mut cards = vec![]; - - // Find a reader with a SmartCard. - for reader in readers { - // We've seen at least one smartcard reader - found_reader = true; - - // Try connecting to card in this reader - let card = match ctx.connect(reader, ShareMode::Shared, Protocols::ANY) - { - Ok(card) => card, - Err(Error::NoSmartcard) => { - continue; // try next reader - } - Err(err) => { - return Err(errors::SmartcardError::SmartCardConnectionError( - err.to_string(), - )); - } - }; - - cards.push(card); - } - - if !found_reader { - Err(errors::SmartcardError::NoReaderFoundError) - } else { - Ok(cards) - } -} diff --git a/openpgp-card/src/card_app.rs b/openpgp-card/src/card_app.rs index b80bbf7..802c833 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -17,19 +17,18 @@ use anyhow::{anyhow, Result}; use crate::apdu::{commands, response::Response}; use crate::errors::OpenpgpCardError; -use crate::parse::{ - algo_info::AlgoInfo, application_id::ApplicationId, - cardholder::CardHolder, extended_cap::ExtendedCap, - extended_length_info::ExtendedLengthInfo, fingerprint, - historical::Historical, key_generation_times, pw_status::PWStatus, KeySet, -}; +use crate::parse::{fingerprint, key_generation_times}; use crate::tlv::{tag::Tag, Tlv, TlvEntry}; use crate::{ - apdu, keys, Algo, AlgoSimple, CardCaps, CardClientBox, CardUploadableKey, - DecryptMe, EccType, Hash, KeyGeneration, KeyType, PublicKeyMaterial, - RsaAttrs, Sex, + apdu, keys, Algo, AlgoInfo, AlgoSimple, ApplicationId, CardCaps, + CardClientBox, CardHolder, CardUploadableKey, DecryptMe, EccType, + ExtendedCap, ExtendedLengthInfo, Fingerprint, Hash, Historical, + KeyGeneration, KeySet, KeyType, PWStatus, PublicKeyMaterial, RsaAttrs, + Sex, }; +pub struct ARD(Tlv); + pub struct CardApp { card_client: CardClientBox, } @@ -39,7 +38,7 @@ impl CardApp { Self { card_client } } - pub(crate) fn take_card(self) -> CardClientBox { + pub fn take_card(self) -> CardClientBox { self.card_client } @@ -47,7 +46,7 @@ impl CardApp { /// /// Also initializes the underlying CardClient with the caps - some /// implementations may need this information. - pub fn init_caps(&mut self, ard: &Tlv) -> Result<()> { + pub fn init_caps(&mut self, ard: &ARD) -> Result<()> { // Determine chaining/extended length support from card // metadata and cache this information in CardApp (as a // CardCaps) @@ -98,23 +97,24 @@ impl CardApp { /// Load "application related data". /// - /// This is done once, after opening the OpenPGP card applet - /// (the data is stored in the OpenPGPCard object). - pub fn get_app_data(&mut self) -> Result { + /// This data should probably cached in a higher layer, some parts of + /// it are needed regularly, and it will not usually change, during + /// normal use of a card. + pub fn get_app_data(&mut self) -> Result { let ad = commands::get_application_data(); let resp = apdu::send_command(&mut self.card_client, ad, true)?; let entry = TlvEntry::from(resp.data()?, true)?; log::debug!(" App data TlvEntry: {:x?}", entry); - Ok(Tlv(Tag::from([0x6E]), entry)) + Ok(ARD(Tlv(Tag::from([0x6E]), entry))) } // --- pieces of application related data --- - pub fn get_aid(ard: &Tlv) -> Result { + pub fn get_aid(ard: &ARD) -> Result { // get from cached "application related data" - let aid = ard.find(&Tag::from([0x4F])); + let aid = ard.0.find(&Tag::from([0x4F])); if let Some(aid) = aid { Ok(ApplicationId::try_from(&aid.serialize()[..])?) @@ -123,9 +123,9 @@ impl CardApp { } } - pub fn get_historical(ard: &Tlv) -> Result { + pub fn get_historical(ard: &ARD) -> Result { // get from cached "application related data" - let hist = ard.find(&Tag::from([0x5F, 0x52])); + let hist = ard.0.find(&Tag::from([0x5F, 0x52])); if let Some(hist) = hist { log::debug!("Historical bytes: {:x?}", hist); @@ -136,10 +136,10 @@ impl CardApp { } pub fn get_extended_length_information( - ard: &Tlv, + ard: &ARD, ) -> Result> { // get from cached "application related data" - let eli = ard.find(&Tag::from([0x7F, 0x66])); + let eli = ard.0.find(&Tag::from([0x7F, 0x66])); log::debug!("Extended length information: {:x?}", eli); @@ -161,10 +161,10 @@ impl CardApp { } pub fn get_extended_capabilities( - ard: &Tlv, + ard: &ARD, ) -> Result { // get from cached "application related data" - let ecap = ard.find(&Tag::from([0xc0])); + let ecap = ard.0.find(&Tag::from([0xc0])); if let Some(ecap) = ecap { Ok(ExtendedCap::try_from(&ecap.serialize()[..])?) @@ -174,11 +174,11 @@ impl CardApp { } pub fn get_algorithm_attributes( - ard: &Tlv, + ard: &ARD, key_type: KeyType, ) -> Result { // get from cached "application related data" - let aa = ard.find(&Tag::from([key_type.get_algorithm_tag()])); + let aa = ard.0.find(&Tag::from([key_type.get_algorithm_tag()])); if let Some(aa) = aa { Algo::try_from(&aa.serialize()[..]) @@ -191,9 +191,9 @@ impl CardApp { } /// PW status Bytes - pub fn get_pw_status_bytes(ard: &Tlv) -> Result { + pub fn get_pw_status_bytes(ard: &ARD) -> Result { // get from cached "application related data" - let psb = ard.find(&Tag::from([0xc4])); + let psb = ard.0.find(&Tag::from([0xc4])); if let Some(psb) = psb { let pws = PWStatus::try_from(&psb.serialize())?; @@ -207,10 +207,10 @@ impl CardApp { } pub fn get_fingerprints( - ard: &Tlv, - ) -> Result, OpenpgpCardError> { + ard: &ARD, + ) -> Result, OpenpgpCardError> { // Get from cached "application related data" - let fp = ard.find(&Tag::from([0xc5])); + let fp = ard.0.find(&Tag::from([0xc5])); if let Some(fp) = fp { let fp = fingerprint::from(&fp.serialize())?; @@ -230,9 +230,9 @@ impl CardApp { } pub fn get_key_generation_times( - ard: &Tlv, + ard: &ARD, ) -> Result, OpenpgpCardError> { - let kg = ard.find(&Tag::from([0xCD])); + let kg = ard.0.find(&Tag::from([0xCD])); if let Some(kg) = kg { let kg = key_generation_times::from(&kg.serialize())?; @@ -290,12 +290,14 @@ impl CardApp { } // --- security support template (7a) --- - pub fn get_security_support_template(&mut self) -> Result { + // FIXME: parse data into a proper data structure + pub fn get_security_support_template(&mut self) -> Result> { let sst = commands::get_security_support_template(); let resp = apdu::send_command(&mut self.card_client, sst, true)?; resp.check_ok()?; - Tlv::try_from(resp.data()?) + let tlv = Tlv::try_from(resp.data()?)?; + Ok(tlv.serialize()) } // DO "Algorithm Information" (0xFA) @@ -430,7 +432,7 @@ impl CardApp { /// Run decryption operation on the smartcard /// (7.2.11 PSO: DECIPHER) - pub(crate) fn pso_decipher( + pub fn pso_decipher( &mut self, data: Vec, ) -> Result, OpenpgpCardError> { @@ -483,7 +485,7 @@ impl CardApp { /// Run signing operation on the smartcard /// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) - pub(crate) fn compute_digital_signature( + pub fn compute_digital_signature( &mut self, data: Vec, ) -> Result, OpenpgpCardError> { diff --git a/openpgp-card/src/errors.rs b/openpgp-card/src/errors.rs index cd1773b..4a84f3d 100644 --- a/openpgp-card/src/errors.rs +++ b/openpgp-card/src/errors.rs @@ -151,6 +151,9 @@ pub enum SmartcardError { #[error("The requested card '{0}' was not found.")] CardNotFound(String), + #[error("Couldn't select the OpenPGP card application")] + SelectOpenPGPCardFailed, + #[error("Failed to connect to the card: {0}")] SmartCardConnectionError(String), diff --git a/openpgp-card/src/keys.rs b/openpgp-card/src/keys.rs index aa2d71f..157a46f 100644 --- a/openpgp-card/src/keys.rs +++ b/openpgp-card/src/keys.rs @@ -10,11 +10,10 @@ use crate::apdu::command::Command; use crate::apdu::commands; use crate::card_app::CardApp; use crate::errors::OpenpgpCardError; -use crate::parse::algo_info::AlgoInfo; use crate::tlv::{tag::Tag, Tlv, TlvEntry}; use crate::{apdu, Curve, EccPub, PublicKeyMaterial, RSAPub}; use crate::{ - tlv, Algo, CardUploadableKey, EccAttrs, EccKey, KeyType, + tlv, Algo, AlgoInfo, CardUploadableKey, EccAttrs, EccKey, KeyType, PrivateKeyMaterial, RSAKey, RsaAttrs, }; diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index 56ad247..fa040d8 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -1,23 +1,11 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -use anyhow::{anyhow, Result}; +use anyhow::Result; +use std::collections::HashSet; use std::fmt; -use std::ops::{Deref, DerefMut}; - -use apdu::{response::Response, PcscClient}; -use card_app::CardApp; -use errors::{OpenpgpCardError, SmartcardError}; -use parse::{ - algo_info::AlgoInfo, application_id::ApplicationId, - cardholder::CardHolder, extended_cap::ExtendedCap, extended_cap::Features, - extended_length_info::ExtendedLengthInfo, fingerprint, - historical::Historical, pw_status::PWStatus, KeySet, -}; -use tlv::Tlv; pub mod apdu; -mod card; pub mod card_app; pub mod errors; mod keys; @@ -169,6 +157,9 @@ impl AlgoSimple { } } +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct AlgoInfo(Vec<(KeyType, Algo)>); + #[derive(Debug, Clone, Eq, PartialEq)] pub enum Algo { Rsa(RsaAttrs), @@ -430,6 +421,93 @@ pub enum DecryptMe<'a> { ECDH(&'a [u8]), } +// ---------- + +#[derive(Debug, Eq, PartialEq)] +pub struct ApplicationId { + pub application: u8, + + // GnuPG says: + // if (app->appversion >= 0x0200) + // app->app_local->extcap.is_v2 = 1; + // + // if (app->appversion >= 0x0300) + // app->app_local->extcap.is_v3 = 1; + pub version: u16, + + pub manufacturer: u16, + + pub serial: u32, +} + +#[derive(Debug)] +pub struct CardCapabilities { + command_chaining: bool, + extended_lc_le: bool, + extended_length_information: bool, +} + +#[derive(Debug)] +pub struct CardSeviceData { + select_by_full_df_name: bool, + select_by_partial_df_name: bool, + dos_available_in_ef_dir: bool, + dos_available_in_ef_atr_info: bool, + access_services: [bool; 3], + mf: bool, +} + +#[derive(Debug)] +pub struct Historical { + // category indicator byte + cib: u8, + + // Card service data (31) + csd: Option, + + // Card Capabilities (73) + cc: Option, + + // status indicator byte (o-card 3.4.1, pg 44) + sib: u8, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct ExtendedCap { + pub features: HashSet, + sm: u8, + max_len_challenge: u16, + max_len_cardholder_cert: u16, + pub max_len_special_do: u16, + pin_2_format: bool, + mse_command: bool, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum Features { + SecureMessaging, + GetChallenge, + KeyImport, + PwStatusChange, + PrivateUseDOs, + AlgoAttrsChangeable, + Aes, + KdfDo, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct ExtendedLengthInfo { + pub max_command_bytes: u16, + pub max_response_bytes: u16, +} + +#[derive(Debug)] +pub struct CardHolder { + pub name: Option, + pub lang: Option>, + pub sex: Option, +} + #[derive(Debug, PartialEq)] pub enum Sex { NotKnown, @@ -460,6 +538,29 @@ impl From for Sex { } } +#[derive(Debug)] +pub struct PWStatus { + pub(crate) pw1_cds_multi: bool, + pub(crate) pw1_derived: bool, + pub(crate) pw1_len: u8, + pub(crate) rc_len: u8, + pub(crate) pw3_derived: bool, + pub(crate) pw3_len: u8, + pub(crate) err_count_pw1: u8, + pub(crate) err_count_rst: u8, + pub(crate) err_count_pw3: u8, +} + +#[derive(Clone, Eq, PartialEq)] +pub struct Fingerprint([u8; 20]); + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct KeySet { + signature: Option, + decryption: Option, + authentication: Option, +} + /// Enum to identify one of the Key-slots on an OpenPGP card #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum KeyType { @@ -520,439 +621,6 @@ impl KeyType { } } -/// Representation of an opened OpenPGP card in its basic, freshly opened, -/// state (i.e. no passwords have been verified, default privileges apply). -pub struct CardBase { - card_app: CardApp, - - // Cache of "application related data". - // - // FIXME: Should be invalidated when changing data on the card! - // (e.g. uploading keys, etc) - ard: Tlv, -} - -impl CardBase { - pub fn new(card_app: CardApp, ard: Tlv) -> Self { - Self { card_app, ard } - } - - /// Get a reference to the internal CardApp object (for use in tests) - pub fn get_card_app(&mut self) -> &mut CardApp { - &mut self.card_app - } - - /// 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(PcscClient::open) - .map(|oc| oc.ok()) - .flatten() - .collect(); - - Ok(ocs) - } - - /// Find an OpenPGP card by "ident", open and return it. - /// - /// The ident is constructed as a concatenation of manufacturer - /// id, a colon, and the card serial. Example: "1234:5678ABCD". - pub fn open_by_ident_pcsc(ident: &str) -> Result { - let cards = card::get_cards().map_err(|e| { - OpenpgpCardError::Smartcard(SmartcardError::Error(format!( - "{:?}", - e - ))) - })?; - - for card in cards { - let res = PcscClient::open(card); - if let Ok(opened_card) = res { - let res = opened_card.get_aid(); - if let Ok(aid) = res { - if aid.ident() == ident { - return Ok(opened_card); - } - } - } - } - - Err(OpenpgpCardError::Smartcard(SmartcardError::CardNotFound( - ident.to_string(), - ))) - } - - /// Open connection to some card and select the openpgp applet - pub fn open_yolo_pcsc() -> Result { - let mut cards = card::get_cards().map_err(|e| { - OpenpgpCardError::Smartcard(SmartcardError::Error(format!( - "{:?}", - e - ))) - })?; - - // randomly use the first card in the list - let card = cards.swap_remove(0); - - PcscClient::open(card) - } - - /// 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()?; - - card_app.init_caps(&ard)?; - - Ok(Self { card_app, ard }) - } - - // --- application data --- - - /// Load "application related data". - /// - /// This is done once, after opening the OpenPGP card applet - /// (the data is stored in the OpenPGPCard object). - fn get_app_data(&mut self) -> Result { - self.card_app.get_app_data() - } - - pub fn get_aid(&self) -> Result { - CardApp::get_aid(&self.ard) - } - - pub fn get_historical(&self) -> Result { - CardApp::get_historical(&self.ard) - } - - pub fn get_extended_length_information( - &self, - ) -> Result> { - CardApp::get_extended_length_information(&self.ard) - } - - pub fn get_general_feature_management() -> Option { - unimplemented!() - } - - pub fn get_discretionary_data_objects() { - unimplemented!() - } - - pub fn get_extended_capabilities( - &self, - ) -> Result { - CardApp::get_extended_capabilities(&self.ard) - } - - pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result { - CardApp::get_algorithm_attributes(&self.ard, key_type) - } - - /// PW status Bytes - pub fn get_pw_status_bytes(&self) -> Result { - CardApp::get_pw_status_bytes(&self.ard) - } - - pub fn get_fingerprints( - &self, - ) -> Result, OpenpgpCardError> { - CardApp::get_fingerprints(&self.ard) - } - - pub fn get_ca_fingerprints(&self) { - unimplemented!() - } - - pub fn get_key_generation_times() { - unimplemented!() - } - - pub fn get_key_information() { - unimplemented!() - } - - pub fn get_uif_pso_cds() { - unimplemented!() - } - - pub fn get_uif_pso_dec() { - unimplemented!() - } - - pub fn get_uif_pso_aut() { - unimplemented!() - } - pub fn get_uif_attestation() { - unimplemented!() - } - - // --- optional private DOs (0101 - 0104) --- - - // --- login data (5e) --- - - // --- URL (5f50) --- - - pub fn get_url(&mut self) -> Result { - self.card_app.get_url() - } - - // --- cardholder related data (65) --- - pub fn get_cardholder_related_data(&mut self) -> Result { - self.card_app.get_cardholder_related_data() - } - - // --- security support template (7a) --- - pub fn get_security_support_template(&mut self) -> Result { - self.card_app.get_security_support_template() - } - - // DO "Algorithm Information" (0xFA) - pub fn list_supported_algo(&mut self) -> Result> { - // The DO "Algorithm Information" (Tag FA) shall be present if - // Algorithm attributes can be changed - let ec = self.get_extended_capabilities()?; - if !ec.features.contains(&Features::AlgoAttrsChangeable) { - // Algorithm attributes can not be changed, - // list_supported_algo is not supported - return Ok(None); - } - - self.card_app.list_supported_algo() - } - - // ---------- - - /// Delete all state on this OpenPGP card - pub fn factory_reset(&mut self) -> Result<()> { - self.card_app.factory_reset() - } - - pub fn verify_pw1_for_signing( - mut self, - pin: &str, - ) -> Result { - assert!(pin.len() >= 6); // FIXME: Err - - let res = self.card_app.verify_pw1_for_signing(pin); - - if let Ok(resp) = res { - if resp.is_ok() { - return Ok(CardSign { oc: self }); - } - } - - Err(self) - } - - pub fn check_pw1(&mut self) -> Result { - self.card_app.check_pw1() - } - - pub fn verify_pw1(mut self, pin: &str) -> Result { - assert!(pin.len() >= 6); // FIXME: Err - - let res = self.card_app.verify_pw1(pin); - - if let Ok(resp) = res { - if resp.is_ok() { - return Ok(CardUser { oc: self }); - } - } - - Err(self) - } - - pub fn check_pw3(&mut self) -> Result { - self.card_app.check_pw3() - } - - pub fn verify_pw3(mut self, pin: &str) -> Result { - assert!(pin.len() >= 8); // FIXME: Err - - let res = self.card_app.verify_pw3(pin); - - if let Ok(resp) = res { - if resp.is_ok() { - return Ok(CardAdmin { oc: self }); - } - } - - Err(self) - } -} - -/// An OpenPGP card after successful verification of PW1 in mode 82 -/// (verification for operations other than signing) -pub struct CardUser { - oc: CardBase, -} - -/// Allow access to fn of OpenPGPCard, through OpenPGPCardUser. -impl Deref for CardUser { - type Target = CardBase; - - fn deref(&self) -> &Self::Target { - &self.oc - } -} - -/// Allow access to fn of CardBase, through CardUser. -impl DerefMut for CardUser { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.oc - } -} - -impl CardUser { - /// Decrypt the ciphertext in `dm`, on the card. - pub fn decrypt( - &mut self, - dm: DecryptMe, - ) -> Result, OpenpgpCardError> { - self.card_app.decrypt(dm) - } - - /// Run decryption operation on the smartcard - /// (7.2.11 PSO: DECIPHER) - pub(crate) fn pso_decipher( - &mut self, - data: Vec, - ) -> Result, OpenpgpCardError> { - self.card_app.pso_decipher(data) - } -} - -/// An OpenPGP card after successful verification of PW1 in mode 81 -/// (verification for signing) -pub struct CardSign { - oc: CardBase, -} - -/// Allow access to fn of CardBase, through CardSign. -impl Deref for CardSign { - type Target = CardBase; - - fn deref(&self) -> &Self::Target { - &self.oc - } -} - -/// Allow access to fn of CardBase, through CardSign. -impl DerefMut for CardSign { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.oc - } -} - -// FIXME: depending on the setting in "PW1 Status byte", only one -// signature can be made after verification for signing -impl CardSign { - /// Sign the message in `hash`, on the card. - pub fn signature_for_hash( - &mut self, - hash: Hash, - ) -> Result, OpenpgpCardError> { - self.card_app.signature_for_hash(hash) - } - - /// Run signing operation on the smartcard - /// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) - pub(crate) fn compute_digital_signature( - &mut self, - data: Vec, - ) -> Result, OpenpgpCardError> { - self.card_app.compute_digital_signature(data) - } -} - -/// An OpenPGP card after successful verification of PW3 ("Admin privileges") -pub struct CardAdmin { - oc: CardBase, -} - -/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin. -impl Deref for CardAdmin { - type Target = CardBase; - - fn deref(&self) -> &Self::Target { - &self.oc - } -} - -/// Allow access to fn of OpenPGPCard, through OpenPGPCardAdmin. -impl DerefMut for CardAdmin { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.oc - } -} - -impl CardAdmin { - pub fn set_name( - &mut self, - name: &str, - ) -> Result { - if name.len() >= 40 { - return Err(anyhow!("name too long").into()); - } - - // All chars must be in ASCII7 - if name.chars().any(|c| !c.is_ascii()) { - return Err(anyhow!("Invalid char in name").into()); - }; - - self.card_app.set_name(name) - } - - pub fn set_lang( - &mut self, - lang: &str, - ) -> Result { - if lang.len() > 8 { - return Err(anyhow!("lang too long").into()); - } - - self.card_app.set_lang(lang) - } - - pub fn set_sex(&mut self, sex: Sex) -> Result { - self.card_app.set_sex(sex) - } - - pub fn set_url( - &mut self, - url: &str, - ) -> Result { - if url.chars().any(|c| !c.is_ascii()) { - return Err(anyhow!("Invalid char in url").into()); - } - - // Check for max len - let ec = self.get_extended_capabilities()?; - - if url.len() < ec.max_len_special_do as usize { - self.card_app.set_url(url) - } else { - Err(anyhow!("URL too long").into()) - } - } - - pub fn upload_key( - &mut self, - key: Box, - key_type: KeyType, - ) -> Result<(), OpenpgpCardError> { - let algo_list = self.list_supported_algo()?; - - keys::upload_key(&mut self.card_app, key, key_type, algo_list) - } -} - #[cfg(test)] mod test { use super::tlv::tag::Tag; diff --git a/openpgp-card/src/parse/algo_info.rs b/openpgp-card/src/parse/algo_info.rs index 6ad9218..8885019 100644 --- a/openpgp-card/src/parse/algo_info.rs +++ b/openpgp-card/src/parse/algo_info.rs @@ -10,10 +10,7 @@ use nom::{branch, bytes::complete as bytes, combinator, multi, sequence}; use std::fmt; use crate::parse::algo_attrs; -use crate::{Algo, KeyType}; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct AlgoInfo(Vec<(KeyType, Algo)>); +use crate::{Algo, AlgoInfo, KeyType}; impl AlgoInfo { pub fn get_by_keytype(&self, kt: KeyType) -> Vec<&Algo> { diff --git a/openpgp-card/src/parse/application_id.rs b/openpgp-card/src/parse/application_id.rs index 359fc9b..011d541 100644 --- a/openpgp-card/src/parse/application_id.rs +++ b/openpgp-card/src/parse/application_id.rs @@ -5,24 +5,7 @@ use anyhow::Result; use nom::{bytes::complete as bytes, number::complete as number}; use std::convert::TryFrom; -use crate::parse; - -#[derive(Debug, Eq, PartialEq)] -pub struct ApplicationId { - pub application: u8, - - // GnuPG says: - // if (app->appversion >= 0x0200) - // app->app_local->extcap.is_v2 = 1; - // - // if (app->appversion >= 0x0300) - // app->app_local->extcap.is_v3 = 1; - pub version: u16, - - pub manufacturer: u16, - - pub serial: u32, -} +use crate::{parse, ApplicationId}; fn parse(input: &[u8]) -> nom::IResult<&[u8], ApplicationId> { let (input, _) = bytes::tag([0xd2, 0x76, 0x0, 0x1, 0x24])(input)?; diff --git a/openpgp-card/src/parse/cardholder.rs b/openpgp-card/src/parse/cardholder.rs index c25f243..a7af0b8 100644 --- a/openpgp-card/src/parse/cardholder.rs +++ b/openpgp-card/src/parse/cardholder.rs @@ -7,14 +7,7 @@ use anyhow::Result; use crate::tlv::tag::Tag; use crate::tlv::{Tlv, TlvEntry}; -use crate::Sex; - -#[derive(Debug)] -pub struct CardHolder { - pub name: Option, - pub lang: Option>, - pub sex: Option, -} +use crate::{CardHolder, Sex}; impl TryFrom<&[u8]> for CardHolder { type Error = anyhow::Error; diff --git a/openpgp-card/src/parse/extended_cap.rs b/openpgp-card/src/parse/extended_cap.rs index 4d710dc..03597c6 100644 --- a/openpgp-card/src/parse/extended_cap.rs +++ b/openpgp-card/src/parse/extended_cap.rs @@ -8,28 +8,7 @@ use nom::{combinator, number::complete as number, sequence}; use std::collections::HashSet; use std::convert::TryFrom; -#[derive(Debug, Eq, PartialEq)] -pub struct ExtendedCap { - pub features: HashSet, - sm: u8, - max_len_challenge: u16, - max_len_cardholder_cert: u16, - pub max_len_special_do: u16, - pin_2_format: bool, - mse_command: bool, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub enum Features { - SecureMessaging, - GetChallenge, - KeyImport, - PwStatusChange, - PrivateUseDOs, - AlgoAttrsChangeable, - Aes, - KdfDo, -} +use crate::{ExtendedCap, Features}; fn features(input: &[u8]) -> nom::IResult<&[u8], HashSet> { combinator::map(number::u8, |b| { diff --git a/openpgp-card/src/parse/extended_length_info.rs b/openpgp-card/src/parse/extended_length_info.rs index f72aea5..21b778e 100644 --- a/openpgp-card/src/parse/extended_length_info.rs +++ b/openpgp-card/src/parse/extended_length_info.rs @@ -1,15 +1,10 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::parse; use anyhow::Result; use nom::{bytes::complete::tag, number::complete as number, sequence}; -#[derive(Debug, Eq, PartialEq)] -pub struct ExtendedLengthInfo { - pub max_command_bytes: u16, - pub max_response_bytes: u16, -} +use crate::{parse, ExtendedLengthInfo}; fn parse(input: &[u8]) -> nom::IResult<&[u8], (u16, u16)> { let (input, (_, cmd, _, resp)) = diff --git a/openpgp-card/src/parse/fingerprint.rs b/openpgp-card/src/parse/fingerprint.rs index a3c23b4..8093bd9 100644 --- a/openpgp-card/src/parse/fingerprint.rs +++ b/openpgp-card/src/parse/fingerprint.rs @@ -7,9 +7,7 @@ use std::fmt; use crate::errors::OpenpgpCardError; use crate::parse::KeySet; - -#[derive(Clone, Eq, PartialEq)] -pub struct Fingerprint([u8; 20]); +use crate::Fingerprint; impl From<[u8; 20]> for Fingerprint { fn from(data: [u8; 20]) -> Self { diff --git a/openpgp-card/src/parse/historical.rs b/openpgp-card/src/parse/historical.rs index dec7a84..2cf2862 100644 --- a/openpgp-card/src/parse/historical.rs +++ b/openpgp-card/src/parse/historical.rs @@ -2,15 +2,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::errors::OpenpgpCardError; +use crate::{CardCapabilities, CardSeviceData, Historical}; use anyhow::{anyhow, Result}; -#[derive(Debug)] -pub struct CardCapabilities { - command_chaining: bool, - extended_lc_le: bool, - extended_length_information: bool, -} - impl CardCapabilities { pub fn get_command_chaining(&self) -> bool { self.command_chaining @@ -39,16 +33,6 @@ impl CardCapabilities { } } -#[derive(Debug)] -pub struct CardSeviceData { - select_by_full_df_name: bool, - select_by_partial_df_name: bool, - dos_available_in_ef_dir: bool, - dos_available_in_ef_atr_info: bool, - access_services: [bool; 3], - mf: bool, -} - impl CardSeviceData { pub fn from(data: u8) -> Self { let select_by_full_df_name = data & 0x80 != 0; @@ -70,21 +54,6 @@ impl CardSeviceData { } } -#[derive(Debug)] -pub struct Historical { - // category indicator byte - cib: u8, - - // Card service data (31) - csd: Option, - - // Card Capabilities (73) - cc: Option, - - // status indicator byte (o-card 3.4.1, pg 44) - sib: u8, -} - impl Historical { pub fn get_card_capabilities(&self) -> Option<&CardCapabilities> { self.cc.as_ref() diff --git a/openpgp-card/src/parse/mod.rs b/openpgp-card/src/parse/mod.rs index 1b01b13..2cb6e21 100644 --- a/openpgp-card/src/parse/mod.rs +++ b/openpgp-card/src/parse/mod.rs @@ -15,15 +15,9 @@ pub mod historical; pub mod key_generation_times; pub mod pw_status; +use crate::KeySet; use anyhow::{anyhow, Error}; -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct KeySet { - signature: Option, - decryption: Option, - authentication: Option, -} - impl From<(Option, Option, Option)> for KeySet { fn from(tuple: (Option, Option, Option)) -> Self { Self { diff --git a/openpgp-card/src/parse/pw_status.rs b/openpgp-card/src/parse/pw_status.rs index 8260fc5..a5478ef 100644 --- a/openpgp-card/src/parse/pw_status.rs +++ b/openpgp-card/src/parse/pw_status.rs @@ -2,21 +2,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::errors::OpenpgpCardError; +use crate::PWStatus; use anyhow::anyhow; -#[derive(Debug)] -pub struct PWStatus { - pub(crate) pw1_cds_multi: bool, - pub(crate) pw1_derived: bool, - pub(crate) pw1_len: u8, - pub(crate) rc_len: u8, - pub(crate) pw3_derived: bool, - pub(crate) pw3_len: u8, - pub(crate) err_count_pw1: u8, - pub(crate) err_count_rst: u8, - pub(crate) err_count_pw3: u8, -} - impl PWStatus { pub fn try_from(input: &[u8]) -> Result { if input.len() == 7 { diff --git a/pcsc/Cargo.toml b/pcsc/Cargo.toml new file mode 100644 index 0000000..9df5a24 --- /dev/null +++ b/pcsc/Cargo.toml @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2021 Heiko Schaefer +# SPDX-License-Identifier: MIT OR Apache-2.0 + +[package] +name = "openpgp-card-pcsc" +description = "PCSC OpenPGP card backend, for use with the openpgp-card crate" +authors = ["Heiko Schaefer "] +license = "MIT OR Apache-2.0" +version = "0.0.1" +edition = "2018" +repository = "https://gitlab.com/hkos/openpgp-card" +documentation = "https://docs.rs/crate/openpgp-card-pcsc" + +[dependencies] +openpgp-card = { path = "../openpgp-card" } +pcsc = "2" +anyhow = "1" +log = "0.4" diff --git a/pcsc/src/lib.rs b/pcsc/src/lib.rs new file mode 100644 index 0000000..080a47b --- /dev/null +++ b/pcsc/src/lib.rs @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use anyhow::{anyhow, Result}; +use pcsc::{Card, Context, Error, Protocols, Scope, ShareMode}; + +use openpgp_card::card_app::CardApp; +use openpgp_card::errors::{OpenpgpCardError, SmartcardError}; +use openpgp_card::{CardCaps, CardClient, CardClientBox}; + +pub struct PcscClient { + card: Card, + card_caps: Option, +} + +impl PcscClient { + fn new(card: Card) -> Self { + Self { + card, + card_caps: None, + } + } + + /// Opened PCSC Cards without selecting the OpenPGP card application + fn pcsc_cards() -> Result, SmartcardError> { + let ctx = match Context::establish(Scope::User) { + Ok(ctx) => ctx, + Err(err) => { + return Err(SmartcardError::ContextError(err.to_string())) + } + }; + + // List available readers. + let mut readers_buf = [0; 2048]; + let readers = match ctx.list_readers(&mut readers_buf) { + Ok(readers) => readers, + Err(err) => { + return Err(SmartcardError::ReaderError(err.to_string())); + } + }; + + let mut found_reader = false; + + let mut cards = vec![]; + + // Find a reader with a SmartCard. + for reader in readers { + // We've seen at least one smartcard reader + found_reader = true; + + // Try connecting to card in this reader + let card = + match ctx.connect(reader, ShareMode::Shared, Protocols::ANY) { + Ok(card) => card, + Err(Error::NoSmartcard) => { + continue; // try next reader + } + Err(err) => { + return Err(SmartcardError::SmartCardConnectionError( + err.to_string(), + )); + } + }; + + cards.push(card); + } + + if !found_reader { + Err(SmartcardError::NoReaderFoundError) + } else { + Ok(cards) + } + } + + /// All PCSC cards, wrapped as PcscClient + fn unopened_cards() -> Result> { + Ok(Self::pcsc_cards() + .map_err(|err| anyhow!(err))? + .into_iter() + .map(PcscClient::new) + .collect()) + } + + /// Return all cards on which the OpenPGP application could be selected. + pub fn list_cards() -> Result> { + let cards = Self::unopened_cards()? + .into_iter() + .map(Self::select) + .map(|res| res.ok()) + .flatten() + .map(|ca| ca.take_card()) + .collect(); + + Ok(cards) + } + + /// Try to select the OpenPGP application on a card + fn select(card_client: PcscClient) -> Result { + let ccb = Box::new(card_client) as CardClientBox; + + let mut ca = CardApp::new(ccb); + let resp = ca.select()?; + + if resp.is_ok() { + Ok(ca) + } else { + Err(OpenpgpCardError::Smartcard( + SmartcardError::SelectOpenPGPCardFailed, + )) + } + } + + /// Returns the first OpenPGP card, with the OpenPGP application selected. + /// + /// If multiple cards are connected, this will effectively be a random + /// pick. You should consider using `open_by_ident` instead. + pub fn open_yolo() -> Result { + for card in Self::unopened_cards()? { + if let Ok(ca) = Self::select(card) { + return Ok(ca.take_card()); + } + } + + Err(OpenpgpCardError::Smartcard(SmartcardError::CardNotFound( + "No OpenPGP card found".to_string(), + ))) + } + + /// Get application related data from the card and check if 'ident' + /// matches + fn match_by_ident( + mut ca: CardApp, + ident: &str, + ) -> Result, OpenpgpCardError> { + let ard = ca.get_app_data()?; + let aid = CardApp::get_aid(&ard)?; + + if aid.ident() == ident { + Ok(Some(ca.take_card())) + } else { + Ok(None) + } + } + + /// Returns the OpenPGP card that matches `ident`, if it is available. + /// The OpenPGP application of the `CardClientBox` has been selected. + pub fn open_by_ident( + ident: &str, + ) -> Result { + for card in Self::unopened_cards()? { + if let Ok(ca) = Self::select(card) { + if let Some(matched_card) = + PcscClient::match_by_ident(ca, ident)? + { + return Ok(matched_card); + } + } + } + + Err(OpenpgpCardError::Smartcard(SmartcardError::CardNotFound( + ident.to_string(), + ))) + } +} + +impl CardClient for PcscClient { + fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result> { + let mut resp_buffer = vec![0; buf_size]; + + let resp = self.card.transmit(cmd, &mut resp_buffer).map_err(|e| { + OpenpgpCardError::Smartcard(SmartcardError::Error(format!( + "Transmit failed: {:?}", + e + ))) + })?; + + log::debug!(" <- APDU response: {:x?}", resp); + + Ok(resp.to_vec()) + } + + fn init_caps(&mut self, caps: CardCaps) { + self.card_caps = Some(caps); + } + + fn get_caps(&self) -> Option<&CardCaps> { + self.card_caps.as_ref() + } +} diff --git a/scdc/Cargo.toml b/scdc/Cargo.toml index 6534534..2c2b71c 100644 --- a/scdc/Cargo.toml +++ b/scdc/Cargo.toml @@ -3,14 +3,17 @@ [package] name = "openpgp-card-scdc" -description = "Experimental SCDaemon Client, intended for use with the openpgp-card crate" +description = "Experimental SCDaemon Client, for use with the openpgp-card crate" +authors = ["Heiko Schaefer "] +license = "MIT OR Apache-2.0" version = "0.0.1" edition = "2018" +repository = "https://gitlab.com/hkos/openpgp-card" +documentation = "https://docs.rs/crate/openpgp-card-scdc" [dependencies] openpgp-card = { path = "../openpgp-card" } sequoia-ipc = { git = "https://gitlab.com/sequoia-pgp/sequoia.git" } -libc = "0.2" hex = "0.4" anyhow = "1" futures = "0.3"