diff --git a/openpgp-card/src/apdu/mod.rs b/openpgp-card/src/apdu/mod.rs index 0a03c4e..d169540 100644 --- a/openpgp-card/src/apdu/mod.rs +++ b/openpgp-card/src/apdu/mod.rs @@ -11,7 +11,7 @@ use std::convert::TryFrom; use crate::apdu::command::Command; use crate::apdu::response::Response; use crate::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError}; -use crate::CardBase; +use crate::CardCaps; #[derive(Clone, Copy, PartialEq)] pub(crate) enum Le { @@ -27,11 +27,15 @@ pub(crate) enum Le { pub(crate) fn send_command( card: &Card, cmd: Command, - ext: Le, - oc: Option<&CardBase>, + expect_reply: bool, + card_caps: Option<&CardCaps>, ) -> Result { - let mut resp = - Response::try_from(send_command_low_level(&card, cmd, ext, oc)?)?; + let mut resp = Response::try_from(send_command_low_level( + &card, + cmd, + expect_reply, + card_caps, + )?)?; while resp.status()[0] == 0x61 { // More data is available for this command from the card @@ -42,8 +46,8 @@ pub(crate) fn send_command( let next = Response::try_from(send_command_low_level( &card, commands::get_response(), - ext, - oc, + expect_reply, + card_caps, )?)?; // FIXME: first check for 0x61xx or 0x9000? @@ -67,45 +71,45 @@ pub(crate) fn send_command( fn send_command_low_level( card: &Card, cmd: Command, - ext: Le, - oc: Option<&CardBase>, + expect_reply: bool, + card_caps: Option<&CardCaps>, ) -> Result, OpenpgpCardError> { - log::trace!(" -> full APDU command: {:x?}", cmd); - log::trace!(" serialized: {:x?}", cmd.serialize(ext)); + let (ext_support, chaining_support, max_cmd_bytes) = + if let Some(caps) = card_caps { + log::trace!("found card caps data!"); - // default settings - let mut ext_support = false; - let mut chaining_support = false; - let mut chunk_size = 255; + ( + caps.ext_support, + caps.chaining_support, + caps.max_cmd_bytes as usize, + ) + } else { + log::trace!("found NO card caps data!"); - // Get feature configuration from card metadata - if let Some(oc) = oc { - if let Ok(hist) = oc.get_historical() { - if let Some(cc) = hist.get_card_capabilities() { - chaining_support = cc.get_command_chaining(); - ext_support = cc.get_extended_lc_le(); - } - } - - if let Ok(Some(eli)) = oc.get_extended_length_information() { - chunk_size = eli.max_command_bytes as usize; - } - } + // default settings + (false, false, 255) + }; log::trace!( "ext le/lc {}, chaining {}, command chunk size {}", ext_support, chaining_support, - chunk_size + max_cmd_bytes ); - // update Le setting to 'long', if we're using a larger chunk size - let ext = match (ext, chunk_size > 0xff) { - (Le::None, _) => Le::None, + // Set Le to 'long', if we're using an extended chunk size. + // + // According to the Card spec 3.4.1, pg 47, + // 255 is the maximum value for 'Lc' in short mode: + // "A short Lc field consists of one byte not set to '00' (1 to 255 dec.)" + let ext = match (expect_reply, ext_support && max_cmd_bytes > 0xFF) { + (false, _) => Le::None, (_, true) => Le::Long, - _ => ext, + _ => Le::Short, }; + log::trace!(" -> full APDU command: {:x?}", cmd); + let buf_size = if !ext_support { pcsc::MAX_BUFFER_SIZE } else { @@ -120,7 +124,7 @@ fn send_command_low_level( log::trace!("chained command mode"); // Break up payload into chunks that fit into one command, each - let chunks: Vec<_> = cmd.data.chunks(chunk_size).collect(); + let chunks: Vec<_> = cmd.data.chunks(max_cmd_bytes).collect(); for (i, d) in chunks.iter().enumerate() { let last = i == chunks.len() - 1; diff --git a/openpgp-card/src/card_app.rs b/openpgp-card/src/card_app.rs new file mode 100644 index 0000000..e250665 --- /dev/null +++ b/openpgp-card/src/card_app.rs @@ -0,0 +1,523 @@ +// SPDX-FileCopyrightText: 2021 Heiko Schaefer +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Direct, low-level, access to OpenPGP card functionality. +//! +//! No checks are performed here (e.g. for valid data lengths). +//! Such checks should be performed on a higher layer, if needed. +//! +//! Also, no caching of data is done here. If necessary, caching should +//! be done on a higher layer. + +use std::convert::TryFrom; + +use anyhow::{anyhow, Result}; +use pcsc::*; + +use apdu::{commands, response::Response}; +use 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::{ + apdu, key_upload, parse, tlv, CardCaps, CardUploadableKey, DecryptMe, + KeyType, Sex, +}; + +pub(crate) struct CardApp { + card: Card, + card_caps: Option, +} + +impl CardApp { + pub fn new(card: Card) -> Self { + Self { + card, + card_caps: None, + } + } + + pub fn set_caps(self, card_caps: CardCaps) -> Self { + Self { + card: self.card, + card_caps: Some(card_caps), + } + } + + pub fn card(&self) -> &Card { + &self.card + } + + pub fn card_caps(&self) -> Option<&CardCaps> { + self.card_caps.as_ref() + } + + // --- application data --- + + /// 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(&self) -> Result { + let ad = commands::get_application_data(); + let resp = apdu::send_command(&self.card, ad, true, None)?; + let entry = TlvEntry::from(resp.data()?, true)?; + + log::trace!(" App data TlvEntry: {:x?}", entry); + + Ok(Tlv(Tag::from([0x6E]), entry)) + } + + // --- pieces of application related data --- + + pub fn get_aid(ard: &Tlv) -> Result { + // get from cached "application related data" + let aid = ard.find(&Tag::from([0x4F])); + + if let Some(aid) = aid { + Ok(ApplicationId::try_from(&aid.serialize()[..])?) + } else { + Err(anyhow!("Couldn't get Application ID.").into()) + } + } + + pub fn get_historical(ard: &Tlv) -> Result { + // get from cached "application related data" + let hist = ard.find(&Tag::from([0x5F, 0x52])); + + if let Some(hist) = hist { + log::debug!("Historical bytes: {:x?}", hist); + Historical::from(&hist.serialize()) + } else { + Err(anyhow!("Failed to get historical bytes.").into()) + } + } + + pub fn get_extended_length_information( + ard: &Tlv, + ) -> Result> { + // get from cached "application related data" + let eli = ard.find(&Tag::from([0x7F, 0x66])); + + log::debug!("Extended length information: {:x?}", eli); + + if let Some(eli) = eli { + // The card has returned extended length information + Ok(Some(ExtendedLengthInfo::from(&eli.serialize()[..])?)) + } else { + // The card didn't return this (optional) DO. That is ok. + Ok(None) + } + } + + pub fn get_general_feature_management() -> Option { + unimplemented!() + } + + pub fn get_discretionary_data_objects() { + unimplemented!() + } + + pub fn get_extended_capabilities( + ard: &Tlv, + ) -> Result { + // get from cached "application related data" + let ecap = ard.find(&Tag::from([0xc0])); + + if let Some(ecap) = ecap { + Ok(ExtendedCap::try_from(&ecap.serialize()[..])?) + } else { + Err(anyhow!("Failed to get extended capabilities.").into()) + } + } + + pub fn get_algorithm_attributes( + ard: &Tlv, + key_type: KeyType, + ) -> Result { + // get from cached "application related data" + let aa = ard.find(&Tag::from([key_type.get_algorithm_tag()])); + + if let Some(aa) = aa { + Algo::try_from(&aa.serialize()[..]) + } else { + Err(anyhow!( + "Failed to get algorithm attributes for {:?}.", + key_type + )) + } + } + + /// PW status Bytes + pub fn get_pw_status_bytes(ard: &Tlv) -> Result { + // get from cached "application related data" + let psb = ard.find(&Tag::from([0xc4])); + + if let Some(psb) = psb { + let pws = PWStatus::try_from(&psb.serialize())?; + + log::debug!("PW Status: {:x?}", pws); + + Ok(pws) + } else { + Err(anyhow!("Failed to get PW status Bytes.")) + } + } + + pub fn get_fingerprints( + ard: &Tlv, + ) -> Result, OpenpgpCardError> { + // Get from cached "application related data" + let fp = ard.find(&Tag::from([0xc5])); + + if let Some(fp) = fp { + let fp = fingerprint::from(&fp.serialize())?; + + log::debug!("Fp: {:x?}", fp); + + Ok(fp) + } else { + Err(anyhow!("Failed to get fingerprints.").into()) + } + } + + // --- + + pub fn get_ca_fingerprints() { + 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(&self) -> Result { + let resp = apdu::send_command( + &self.card, + commands::get_url(), + true, + self.card_caps.as_ref(), + )?; + + Ok(String::from_utf8_lossy(resp.data()?).to_string()) + } + + // --- cardholder related data (65) --- + pub fn get_cardholder_related_data(&self) -> Result { + let crd = commands::cardholder_related_data(); + let resp = apdu::send_command( + &self.card, + crd, + true, + self.card_caps.as_ref(), + )?; + resp.check_ok()?; + + CardHolder::try_from(resp.data()?) + } + + // --- security support template (7a) --- + pub fn get_security_support_template(&self) -> Result { + let sst = commands::get_security_support_template(); + let resp = apdu::send_command( + &self.card, + sst, + true, + self.card_caps.as_ref(), + )?; + resp.check_ok()?; + + Tlv::try_from(resp.data()?) + } + + // DO "Algorithm Information" (0xFA) + pub fn list_supported_algo(&self) -> Result> { + let resp = apdu::send_command( + &self.card, + commands::get_algo_list(), + true, + self.card_caps.as_ref(), + )?; + resp.check_ok()?; + + let ai = AlgoInfo::try_from(resp.data()?)?; + Ok(Some(ai)) + } + + // ---------- + + /// Delete all state on this OpenPGP card + pub fn factory_reset(&self) -> Result<()> { + // send 4 bad requests to verify pw1 + // [apdu 00 20 00 81 08 40 40 40 40 40 40 40 40] + for _ in 0..4 { + let verify = commands::verify_pw1_81([0x40; 8].to_vec()); + let resp = apdu::send_command( + &self.card, + verify, + false, + self.card_caps.as_ref(), + )?; + if !(resp.status() == [0x69, 0x82] + || resp.status() == [0x69, 0x83]) + { + return Err(anyhow!("Unexpected status for reset, at pw1.")); + } + } + + // send 4 bad requests to verify pw3 + // [apdu 00 20 00 83 08 40 40 40 40 40 40 40 40] + for _ in 0..4 { + let verify = commands::verify_pw3([0x40; 8].to_vec()); + let resp = apdu::send_command( + &self.card, + verify, + false, + self.card_caps.as_ref(), + )?; + + if !(resp.status() == [0x69, 0x82] + || resp.status() == [0x69, 0x83]) + { + return Err(anyhow!("Unexpected status for reset, at pw3.")); + } + } + + // terminate_df [apdu 00 e6 00 00] + let term = commands::terminate_df(); + let resp = apdu::send_command( + &self.card, + term, + false, + self.card_caps.as_ref(), + )?; + resp.check_ok()?; + + // activate_file [apdu 00 44 00 00] + let act = commands::activate_file(); + let resp = apdu::send_command( + &self.card, + act, + false, + self.card_caps.as_ref(), + )?; + resp.check_ok()?; + + // FIXME: does the connection need to be re-opened on some cards, + // after reset?! + + Ok(()) + } + + pub fn verify_pw1_for_signing( + &self, + pin: &str, + ) -> Result { + assert!(pin.len() >= 6); // FIXME: Err + + let verify = commands::verify_pw1_81(pin.as_bytes().to_vec()); + apdu::send_command(&self.card, verify, false, self.card_caps.as_ref()) + } + + pub fn check_pw1(&self) -> Result { + let verify = commands::verify_pw1_82(vec![]); + apdu::send_command(&self.card, verify, false, self.card_caps.as_ref()) + } + + pub fn verify_pw1(&self, pin: &str) -> Result { + assert!(pin.len() >= 6); // FIXME: Err + + let verify = commands::verify_pw1_82(pin.as_bytes().to_vec()); + apdu::send_command(&self.card, verify, false, self.card_caps.as_ref()) + } + + pub fn check_pw3(&self) -> Result { + let verify = commands::verify_pw3(vec![]); + apdu::send_command(&self.card, verify, false, self.card_caps.as_ref()) + } + + pub fn verify_pw3(&self, pin: &str) -> Result { + assert!(pin.len() >= 8); // FIXME: Err + + let verify = commands::verify_pw3(pin.as_bytes().to_vec()); + apdu::send_command(&self.card, verify, false, self.card_caps.as_ref()) + } + + // --- decrypt --- + + /// Decrypt the ciphertext in `dm`, on the card. + pub fn decrypt(&self, dm: DecryptMe) -> Result, OpenpgpCardError> { + match dm { + DecryptMe::RSA(message) => { + let mut data = vec![0x0]; + data.extend_from_slice(message); + + // Call the card to decrypt `data` + self.pso_decipher(data) + } + DecryptMe::ECDH(eph) => { + // External Public Key + let epk = Tlv(Tag(vec![0x86]), TlvEntry::S(eph.to_vec())); + + // Public Key DO + let pkdo = Tlv(Tag(vec![0x7f, 0x49]), TlvEntry::C(vec![epk])); + + // Cipher DO + let cdo = Tlv(Tag(vec![0xa6]), TlvEntry::C(vec![pkdo])); + + self.pso_decipher(cdo.serialize()) + } + } + } + + /// Run decryption operation on the smartcard + /// (7.2.11 PSO: DECIPHER) + pub(crate) fn pso_decipher( + &self, + data: Vec, + ) -> Result, OpenpgpCardError> { + // The OpenPGP card is already connected and PW1 82 has been verified + let dec_cmd = commands::decryption(data); + let resp = apdu::send_command( + &self.card, + dec_cmd, + true, + self.card_caps.as_ref(), + )?; + resp.check_ok()?; + + Ok(resp.data().map(|d| d.to_vec())?) + } + + // --- sign --- + + /// Sign the message in `hash`, on the card. + pub fn signature_for_hash( + &self, + hash: Hash, + ) -> Result, OpenpgpCardError> { + let data = match hash { + Hash::SHA256(_) | Hash::SHA384(_) | Hash::SHA512(_) => { + let tlv = Tlv( + Tag(vec![0x30]), + TlvEntry::C(vec![ + Tlv( + Tag(vec![0x30]), + TlvEntry::C(vec![ + Tlv( + Tag(vec![0x06]), + // unwrapping is ok, for SHA* + TlvEntry::S(hash.oid().unwrap().to_vec()), + ), + Tlv(Tag(vec![0x05]), TlvEntry::S(vec![])), + ]), + ), + Tlv( + Tag(vec![0x04]), + TlvEntry::S(hash.digest().to_vec()), + ), + ]), + ); + + tlv.serialize() + } + Hash::EdDSA(d) => d.to_vec(), + }; + + self.compute_digital_signature(data) + } + + /// Run signing operation on the smartcard + /// (7.2.10 PSO: COMPUTE DIGITAL SIGNATURE) + pub(crate) fn compute_digital_signature( + &self, + data: Vec, + ) -> Result, OpenpgpCardError> { + let dec_cmd = commands::signature(data); + + let resp = apdu::send_command( + &self.card, + dec_cmd, + true, + self.card_caps.as_ref(), + )?; + + Ok(resp.data().map(|d| d.to_vec())?) + } + + // --- admin --- + + pub fn set_name(&self, name: &str) -> Result { + let put_name = commands::put_name(name.as_bytes().to_vec()); + apdu::send_command( + &self.card, + put_name, + false, + self.card_caps.as_ref(), + ) + } + + pub fn set_lang(&self, lang: &str) -> Result { + let put_lang = commands::put_lang(lang.as_bytes().to_vec()); + apdu::send_command( + &self.card, + put_lang, + false, + self.card_caps.as_ref(), + ) + } + + pub fn set_sex(&self, sex: Sex) -> Result { + let put_sex = commands::put_sex(sex.as_u8()); + apdu::send_command(&self.card, put_sex, false, self.card_caps.as_ref()) + } + + pub fn set_url(&self, url: &str) -> Result { + let put_url = commands::put_url(url.as_bytes().to_vec()); + apdu::send_command(&self.card, put_url, false, self.card_caps.as_ref()) + } + + pub fn upload_key( + &self, + key: Box, + key_type: KeyType, + ) -> Result<(), OpenpgpCardError> { + let algo_list = self.list_supported_algo()?; + + key_upload::upload_key(&self, key, key_type, algo_list) + } +} diff --git a/openpgp-card/src/key_upload.rs b/openpgp-card/src/key_upload.rs index d2f5292..863e12f 100644 --- a/openpgp-card/src/key_upload.rs +++ b/openpgp-card/src/key_upload.rs @@ -3,31 +3,29 @@ use anyhow::{anyhow, Result}; -use crate::apdu; use crate::apdu::command::Command; use crate::apdu::commands; -use crate::apdu::Le; +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::{ - tlv, CardAdmin, CardUploadableKey, EccKey, EccType, KeyType, - PrivateKeyMaterial, RSAKey, + tlv, CardUploadableKey, EccKey, EccType, KeyType, PrivateKeyMaterial, + RSAKey, }; +use pcsc::Card; /// Upload an explicitly selected Key to the card as a specific KeyType. /// /// The client needs to make sure that the key is suitable for `key_type`. pub(crate) fn upload_key( - oca: &CardAdmin, + card_app: &CardApp, key: Box, key_type: KeyType, + algo_list: Option, ) -> Result<(), OpenpgpCardError> { - // FIXME: the list of algorithms is retrieved multiple times, it should - // be cached - let algo_list = oca.list_supported_algo()?; - let (algo_cmd, key_cmd) = match key.get_key()? { PrivateKeyMaterial::R(rsa_key) => { // RSA bitsize @@ -35,7 +33,7 @@ pub(crate) fn upload_key( let rsa_bits = (((rsa_key.get_n().len() * 8 + 31) / 32) * 32) as u16; - // FIXME: deal with absence of algo list (unwrap!) + // FIXME: deal with absence of algo list (don't just unwrap!) // Get suitable algorithm from card's list let algo = get_card_algo_rsa(algo_list.unwrap(), key_type, rsa_bits); @@ -71,12 +69,13 @@ pub(crate) fn upload_key( }; copy_key_to_card( - oca, + card_app.card(), key_type, key.get_ts(), key.get_fp(), algo_cmd, key_cmd, + card_app.card_caps(), )?; Ok(()) @@ -369,12 +368,13 @@ fn ecc_algo_attrs_cmd( } fn copy_key_to_card( - oca: &CardAdmin, + card: &Card, key_type: KeyType, ts: u64, fp: Vec, algo_cmd: Command, key_cmd: Command, + card_caps: Option<&CardCaps>, ) -> Result<(), OpenpgpCardError> { let fp_cmd = commands::put_data(&[key_type.get_fingerprint_put_tag()], fp); @@ -391,15 +391,13 @@ fn copy_key_to_card( // Send all the commands - let ext = Le::None; // FIXME?! - // FIXME: Only write algo attributes to the card if "extended // capabilities" show that they are changeable! - apdu::send_command(oca.card(), algo_cmd, ext, Some(oca))?.check_ok()?; + apdu::send_command(card, algo_cmd, false, card_caps)?.check_ok()?; - apdu::send_command(oca.card(), key_cmd, ext, Some(oca))?.check_ok()?; - apdu::send_command(oca.card(), fp_cmd, ext, Some(oca))?.check_ok()?; - apdu::send_command(oca.card(), time_cmd, ext, Some(oca))?.check_ok()?; + apdu::send_command(card, key_cmd, false, card_caps)?.check_ok()?; + apdu::send_command(card, fp_cmd, false, card_caps)?.check_ok()?; + apdu::send_command(card, time_cmd, false, card_caps)?.check_ok()?; Ok(()) } diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index a5b642e..d2fea40 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -1,12 +1,10 @@ // SPDX-FileCopyrightText: 2021 Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 -use std::convert::TryFrom; - use anyhow::{anyhow, Result}; use pcsc::*; -use apdu::{commands, response::Response, Le}; +use apdu::{commands, response::Response}; use parse::{ algo_attrs::Algo, algo_info::AlgoInfo, application_id::ApplicationId, cardholder::CardHolder, extended_cap::ExtendedCap, extended_cap::Features, @@ -15,18 +13,26 @@ use parse::{ }; use tlv::Tlv; +use crate::card_app::CardApp; use crate::errors::{OpenpgpCardError, SmartcardError}; -use crate::tlv::tag::Tag; -use crate::tlv::TlvEntry; use std::ops::Deref; mod apdu; mod card; +mod card_app; pub mod errors; mod key_upload; mod parse; mod tlv; +/// Information about the capabilities of the card. +/// (feature configuration from card metadata) +pub(crate) struct CardCaps { + pub(crate) ext_support: bool, + pub(crate) chaining_support: bool, + pub(crate) max_cmd_bytes: u16, +} + /// Container for a hash value. /// These hash values can be signed by the card. pub enum Hash<'a> { @@ -210,7 +216,7 @@ 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: Card, + card_app: CardApp, // Cache of "application related data". // @@ -280,14 +286,43 @@ impl CardBase { /// Open connection to a specific card and select the openpgp applet fn open_card(card: Card) -> Result { let select_openpgp = commands::select_openpgp(); - - let resp = apdu::send_command(&card, select_openpgp, Le::Short, None)?; + let resp = apdu::send_command(&card, select_openpgp, true, None)?; if resp.is_ok() { // read and cache "application related data" - let ard = Self::get_app_data(&card)?; + let card_app = CardApp::new(card); + let ard = card_app.get_app_data()?; - Ok(Self { card, 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 { + 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()) } @@ -299,54 +334,22 @@ impl CardBase { /// /// This is done once, after opening the OpenPGP card applet /// (the data is stored in the OpenPGPCard object). - fn get_app_data(card: &Card) -> Result { - let ad = commands::get_application_data(); - let resp = apdu::send_command(card, ad, Le::Short, None)?; - let entry = TlvEntry::from(resp.data()?, true)?; - - log::trace!(" App data TlvEntry: {:x?}", entry); - - Ok(Tlv(Tag::from([0x6E]), entry)) + fn get_app_data(&self) -> Result { + self.card_app.get_app_data() } pub fn get_aid(&self) -> Result { - // get from cached "application related data" - let aid = self.ard.find(&Tag::from([0x4F])); - - if let Some(aid) = aid { - Ok(ApplicationId::try_from(&aid.serialize()[..])?) - } else { - Err(anyhow!("Couldn't get Application ID.").into()) - } + CardApp::get_aid(&self.ard) } pub fn get_historical(&self) -> Result { - // get from cached "application related data" - let hist = self.ard.find(&Tag::from([0x5F, 0x52])); - - if let Some(hist) = hist { - log::debug!("Historical bytes: {:x?}", hist); - Historical::from(&hist.serialize()) - } else { - Err(anyhow!("Failed to get historical bytes.").into()) - } + CardApp::get_historical(&self.ard) } pub fn get_extended_length_information( &self, ) -> Result> { - // get from cached "application related data" - let eli = self.ard.find(&Tag::from([0x7F, 0x66])); - - log::debug!("Extended length information: {:x?}", eli); - - if let Some(eli) = eli { - // The card has returned extended length information - Ok(Some(ExtendedLengthInfo::from(&eli.serialize()[..])?)) - } else { - // The card didn't return this (optional) DO. That is ok. - Ok(None) - } + CardApp::get_extended_length_information(&self.ard) } pub fn get_general_feature_management() -> Option { @@ -360,61 +363,22 @@ impl CardBase { pub fn get_extended_capabilities( &self, ) -> Result { - // get from cached "application related data" - let ecap = self.ard.find(&Tag::from([0xc0])); - - if let Some(ecap) = ecap { - Ok(ExtendedCap::try_from(&ecap.serialize()[..])?) - } else { - Err(anyhow!("Failed to get extended capabilities.").into()) - } + CardApp::get_extended_capabilities(&self.ard) } pub fn get_algorithm_attributes(&self, key_type: KeyType) -> Result { - // get from cached "application related data" - let aa = self.ard.find(&Tag::from([key_type.get_algorithm_tag()])); - - if let Some(aa) = aa { - Algo::try_from(&aa.serialize()[..]) - } else { - Err(anyhow!( - "Failed to get algorithm attributes for {:?}.", - key_type - )) - } + CardApp::get_algorithm_attributes(&self.ard, key_type) } /// PW status Bytes pub fn get_pw_status_bytes(&self) -> Result { - // get from cached "application related data" - let psb = self.ard.find(&Tag::from([0xc4])); - - if let Some(psb) = psb { - let pws = PWStatus::try_from(&psb.serialize())?; - - log::debug!("PW Status: {:x?}", pws); - - Ok(pws) - } else { - Err(anyhow!("Failed to get PW status Bytes.").into()) - } + CardApp::get_pw_status_bytes(&self.ard) } pub fn get_fingerprints( &self, ) -> Result, OpenpgpCardError> { - // Get from cached "application related data" - let fp = self.ard.find(&Tag::from([0xc5])); - - if let Some(fp) = fp { - let fp = fingerprint::from(&fp.serialize())?; - - log::debug!("Fp: {:x?}", fp); - - Ok(fp) - } else { - Err(anyhow!("Failed to get fingerprints.").into()) - } + CardApp::get_fingerprints(&self.ard) } pub fn get_ca_fingerprints(&self) { @@ -451,51 +415,17 @@ impl CardBase { // --- URL (5f50) --- pub fn get_url(&self) -> Result { - let _eli = self.get_extended_length_information()?; - - // FIXME: figure out Le - let resp = apdu::send_command( - &self.card, - commands::get_url(), - Le::Long, - Some(self), - )?; - - log::trace!( - " final response: {:x?}, data len {}", - resp, - resp.raw_data().len() - ); - - Ok(String::from_utf8_lossy(resp.data()?).to_string()) + self.card_app.get_url() } // --- cardholder related data (65) --- pub fn get_cardholder_related_data(&self) -> Result { - let crd = commands::cardholder_related_data(); - let resp = apdu::send_command(&self.card, crd, Le::Short, Some(self))?; - resp.check_ok()?; - - CardHolder::try_from(resp.data()?) + self.card_app.get_cardholder_related_data() } // --- security support template (7a) --- pub fn get_security_support_template(&self) -> Result { - let sst = commands::get_security_support_template(); - - let ext = self.get_extended_length_information()?.is_some(); - let ext = if ext { Le::Long } else { Le::Short }; - - let resp = apdu::send_command(&self.card, sst, ext, Some(self))?; - resp.check_ok()?; - - log::trace!( - " final response: {:x?}, data len {}", - resp, - resp.data()?.len() - ); - - Tlv::try_from(resp.data()?) + self.card_app.get_security_support_template() } // DO "Algorithm Information" (0xFA) @@ -509,63 +439,14 @@ impl CardBase { return Ok(None); } - let resp = apdu::send_command( - &self.card, - commands::get_algo_list(), - Le::Short, - Some(self), - )?; - resp.check_ok()?; - - let ai = AlgoInfo::try_from(resp.data()?)?; - Ok(Some(ai)) + self.card_app.list_supported_algo() } // ---------- /// Delete all state on this OpenPGP card pub fn factory_reset(&self) -> Result<()> { - // send 4 bad requests to verify pw1 - // [apdu 00 20 00 81 08 40 40 40 40 40 40 40 40] - for _ in 0..4 { - let verify = commands::verify_pw1_81([0x40; 8].to_vec()); - let resp = - apdu::send_command(&self.card, verify, Le::None, Some(self))?; - if !(resp.status() == [0x69, 0x82] - || resp.status() == [0x69, 0x83]) - { - return Err(anyhow!("Unexpected status for reset, at pw1.")); - } - } - - // send 4 bad requests to verify pw3 - // [apdu 00 20 00 83 08 40 40 40 40 40 40 40 40] - for _ in 0..4 { - let verify = commands::verify_pw3([0x40; 8].to_vec()); - let resp = - apdu::send_command(&self.card, verify, Le::None, Some(self))?; - - if !(resp.status() == [0x69, 0x82] - || resp.status() == [0x69, 0x83]) - { - return Err(anyhow!("Unexpected status for reset, at pw3.")); - } - } - - // terminate_df [apdu 00 e6 00 00] - let term = commands::terminate_df(); - let resp = apdu::send_command(&self.card, term, Le::None, Some(self))?; - resp.check_ok()?; - - // activate_file [apdu 00 44 00 00] - let act = commands::activate_file(); - let resp = apdu::send_command(&self.card, act, Le::None, Some(self))?; - resp.check_ok()?; - - // FIXME: does the connection need to be re-opened on some cards, - // after reset?! - - Ok(()) + self.card_app.factory_reset() } pub fn verify_pw1_for_signing( @@ -574,9 +455,7 @@ impl CardBase { ) -> Result { assert!(pin.len() >= 6); // FIXME: Err - let verify = commands::verify_pw1_81(pin.as_bytes().to_vec()); - let res = - apdu::send_command(&self.card, verify, Le::None, Some(&self)); + let res = self.card_app.verify_pw1_for_signing(pin); if let Ok(resp) = res { if resp.is_ok() { @@ -588,16 +467,13 @@ impl CardBase { } pub fn check_pw1(&self) -> Result { - let verify = commands::verify_pw1_82(vec![]); - apdu::send_command(&self.card, verify, Le::None, Some(&self)) + self.card_app.check_pw1() } pub fn verify_pw1(self, pin: &str) -> Result { assert!(pin.len() >= 6); // FIXME: Err - let verify = commands::verify_pw1_82(pin.as_bytes().to_vec()); - let res = - apdu::send_command(&self.card, verify, Le::None, Some(&self)); + let res = self.card_app.verify_pw1(pin); if let Ok(resp) = res { if resp.is_ok() { @@ -609,16 +485,13 @@ impl CardBase { } pub fn check_pw3(&self) -> Result { - let verify = commands::verify_pw3(vec![]); - apdu::send_command(&self.card, verify, Le::None, Some(&self)) + self.card_app.check_pw3() } pub fn verify_pw3(self, pin: &str) -> Result { assert!(pin.len() >= 8); // FIXME: Err - let verify = commands::verify_pw3(pin.as_bytes().to_vec()); - let res = - apdu::send_command(&self.card, verify, Le::None, Some(&self)); + let res = self.card_app.verify_pw3(pin); if let Ok(resp) = res { if resp.is_ok() { @@ -648,27 +521,7 @@ impl Deref for CardUser { impl CardUser { /// Decrypt the ciphertext in `dm`, on the card. pub fn decrypt(&self, dm: DecryptMe) -> Result, OpenpgpCardError> { - match dm { - DecryptMe::RSA(message) => { - let mut data = vec![0x0]; - data.extend_from_slice(message); - - // Call the card to decrypt `data` - self.pso_decipher(data) - } - DecryptMe::ECDH(eph) => { - // External Public Key - let epk = Tlv(Tag(vec![0x86]), TlvEntry::S(eph.to_vec())); - - // Public Key DO - let pkdo = Tlv(Tag(vec![0x7f, 0x49]), TlvEntry::C(vec![epk])); - - // Cipher DO - let cdo = Tlv(Tag(vec![0xa6]), TlvEntry::C(vec![pkdo])); - - self.pso_decipher(cdo.serialize()) - } - } + self.card_app.decrypt(dm) } /// Run decryption operation on the smartcard @@ -677,13 +530,7 @@ impl CardUser { &self, data: Vec, ) -> Result, OpenpgpCardError> { - // The OpenPGP card is already connected and PW1 82 has been verified - let dec_cmd = commands::decryption(data); - let resp = - apdu::send_command(&self.card, dec_cmd, Le::Short, Some(self))?; - resp.check_ok()?; - - Ok(resp.data().map(|d| d.to_vec())?) + self.card_app.pso_decipher(data) } } @@ -710,33 +557,7 @@ impl CardSign { &self, hash: Hash, ) -> Result, OpenpgpCardError> { - match hash { - Hash::SHA256(_) | Hash::SHA384(_) | Hash::SHA512(_) => { - let tlv = Tlv( - Tag(vec![0x30]), - TlvEntry::C(vec![ - Tlv( - Tag(vec![0x30]), - TlvEntry::C(vec![ - Tlv( - Tag(vec![0x06]), - // unwrapping is ok, for SHA* - TlvEntry::S(hash.oid().unwrap().to_vec()), - ), - Tlv(Tag(vec![0x05]), TlvEntry::S(vec![])), - ]), - ), - Tlv( - Tag(vec![0x04]), - TlvEntry::S(hash.digest().to_vec()), - ), - ]), - ); - - Ok(self.compute_digital_signature(tlv.serialize())?) - } - Hash::EdDSA(d) => Ok(self.compute_digital_signature(d.to_vec())?), - } + self.card_app.signature_for_hash(hash) } /// Run signing operation on the smartcard @@ -745,12 +566,7 @@ impl CardSign { &self, data: Vec, ) -> Result, OpenpgpCardError> { - let dec_cmd = commands::signature(data); - - let resp = - apdu::send_command(&self.card, dec_cmd, Le::Short, Some(self))?; - - Ok(resp.data().map(|d| d.to_vec())?) + self.card_app.compute_digital_signature(data) } } @@ -769,10 +585,6 @@ impl Deref for CardAdmin { } impl CardAdmin { - pub(crate) fn card(&self) -> &Card { - &self.card - } - pub fn set_name(&self, name: &str) -> Result { if name.len() >= 40 { return Err(anyhow!("name too long").into()); @@ -783,8 +595,7 @@ impl CardAdmin { return Err(anyhow!("Invalid char in name").into()); }; - let put_name = commands::put_name(name.as_bytes().to_vec()); - apdu::send_command(self.card(), put_name, Le::None, Some(self)) + self.card_app.set_name(name) } pub fn set_lang(&self, lang: &str) -> Result { @@ -792,13 +603,11 @@ impl CardAdmin { return Err(anyhow!("lang too long").into()); } - let put_lang = commands::put_lang(lang.as_bytes().to_vec()); - apdu::send_command(self.card(), put_lang, Le::None, Some(self)) + self.card_app.set_lang(lang) } pub fn set_sex(&self, sex: Sex) -> Result { - let put_sex = commands::put_sex(sex.as_u8()); - apdu::send_command(self.card(), put_sex, Le::None, Some(self)) + self.card_app.set_sex(sex) } pub fn set_url(&self, url: &str) -> Result { @@ -810,8 +619,7 @@ impl CardAdmin { let ec = self.get_extended_capabilities()?; if url.len() < ec.max_len_special_do as usize { - let put_url = commands::put_url(url.as_bytes().to_vec()); - apdu::send_command(self.card(), put_url, Le::None, Some(self)) + self.card_app.set_url(url) } else { Err(anyhow!("URL too long").into()) } @@ -822,7 +630,9 @@ impl CardAdmin { key: Box, key_type: KeyType, ) -> Result<(), OpenpgpCardError> { - key_upload::upload_key(self, key, key_type) + let algo_list = self.list_supported_algo()?; + + key_upload::upload_key(&self.card_app, key, key_type, algo_list) } }