From dbf2e9e3fbdd66c14e85900d0afec2d4ca2f43fe Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Thu, 29 Jul 2021 18:08:04 +0200 Subject: [PATCH] A CardClient instance can now contain a CardCaps (which specifies how commands should be sent to the card). Add max_rsp_bytes field to CardCaps. --- openpgp-card/src/apdu/mod.rs | 74 +++++++++------ openpgp-card/src/card_app.rs | 162 +++++++-------------------------- openpgp-card/src/key_upload.rs | 12 +-- openpgp-card/src/lib.rs | 22 ++++- 4 files changed, 103 insertions(+), 167 deletions(-) diff --git a/openpgp-card/src/apdu/mod.rs b/openpgp-card/src/apdu/mod.rs index 4de8ac6..d82723b 100644 --- a/openpgp-card/src/apdu/mod.rs +++ b/openpgp-card/src/apdu/mod.rs @@ -16,7 +16,7 @@ use crate::card_app::CardApp; use crate::errors::{OcErrorStatus, OpenpgpCardError, SmartcardError}; use crate::{CardBase, CardCaps, CardClient, CardClientBox}; -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Debug)] pub(crate) enum Le { None, Short, @@ -31,37 +31,34 @@ pub(crate) fn send_command( card_client: &mut CardClientBox, cmd: Command, expect_reply: bool, - card_caps: Option<&CardCaps>, ) -> Result { let mut resp = Response::try_from(send_command_low_level( card_client, cmd, expect_reply, - card_caps, )?)?; while resp.status()[0] == 0x61 { // More data is available for this command from the card - log::trace!(" response was truncated, getting more data"); + log::debug!(" response was truncated, getting more data"); // Get additional data let next = Response::try_from(send_command_low_level( card_client, commands::get_response(), expect_reply, - card_caps, )?)?; // FIXME: first check for 0x61xx or 0x9000? - log::trace!(" appending {} bytes to response", next.raw_data().len()); + log::debug!(" appending {} bytes to response", next.raw_data().len()); // Append new data to resp.data and overwrite status. resp.raw_mut_data().extend_from_slice(next.raw_data()); resp.set_status(next.status()); } - log::trace!(" final response len: {}", resp.raw_data().len()); + log::debug!(" final response len: {}", resp.raw_data().len()); Ok(resp) } @@ -75,29 +72,39 @@ fn send_command_low_level( card_client: &mut CardClientBox, cmd: Command, expect_reply: bool, - card_caps: Option<&CardCaps>, ) -> Result, OpenpgpCardError> { - let (ext_support, chaining_support, max_cmd_bytes) = - if let Some(caps) = card_caps { - log::trace!("found card caps data!"); + let (ext_support, chaining_support, mut max_cmd_bytes, max_rsp_bytes) = + if let Some(caps) = card_client.get_caps() { + log::debug!("found card caps data!"); ( caps.ext_support, caps.chaining_support, caps.max_cmd_bytes as usize, + caps.max_rsp_bytes as usize, ) } else { - log::trace!("found NO card caps data!"); + log::debug!("found NO card caps data!"); // default settings - (false, false, 255) + (false, false, 255, 255) }; - log::trace!( - "ext le/lc {}, chaining {}, command chunk size {}", + // If the CardClient implementation has an inherent limit for the cmd + // size, take that limit into account. + // (E.g. when using scdaemon as a CardClient backend, there is a + // limitation to 1000 bytes length for Assuan commands, which + // translates to maximum command length of a bit under 500 bytes) + if let Some(max_cardclient_cmd_bytes) = card_client.max_cmd_len() { + max_cmd_bytes = usize::min(max_cmd_bytes, max_cardclient_cmd_bytes); + } + + log::debug!( + "ext le/lc {}, chaining {}, max cmd {}, max rsp {}", ext_support, chaining_support, - max_cmd_bytes + max_cmd_bytes, + max_rsp_bytes ); // Set Le to 'long', if we're using an extended chunk size. @@ -111,18 +118,20 @@ fn send_command_low_level( _ => Le::Short, }; - log::trace!(" -> full APDU command: {:x?}", cmd); + log::debug!(" -> full APDU command: {:x?}", cmd); - let buf_size = if !ext_support { + let buf_size = if !ext_support || ext == Le::Short { pcsc::MAX_BUFFER_SIZE } else { - pcsc::MAX_BUFFER_SIZE_EXTENDED + max_rsp_bytes }; + log::trace!("buf_size {}", buf_size); + if chaining_support && !cmd.data.is_empty() { // Send command in chained mode - log::trace!("chained command mode"); + log::debug!("chained command mode"); // Break up payload into chunks that fit into one command, each let chunks: Vec<_> = cmd.data.chunks(max_cmd_bytes).collect(); @@ -138,11 +147,12 @@ fn send_command_low_level( let serialized = partial .serialize(ext) .map_err(OpenpgpCardError::InternalError)?; - log::trace!(" -> chunked APDU command: {:x?}", &serialized); + + log::debug!(" -> chunked APDU command: {:x?}", &serialized); let resp = card_client.transmit(&serialized, buf_size)?; - log::trace!(" <- APDU chunk response: {:x?}", &resp); + log::debug!(" <- APDU chunk response: {:x?}", &resp); if resp.len() < 2 { return Err(OcErrorStatus::ResponseLength(resp.len()).into()); @@ -175,7 +185,7 @@ fn send_command_low_level( let resp = card_client.transmit(&serialized, buf_size)?; - log::trace!(" <- APDU response: {:x?}", resp); + log::debug!(" <- APDU response: {:x?}", resp); Ok(resp) } @@ -183,11 +193,15 @@ fn send_command_low_level( pub struct PcscClient { card: Card, + card_caps: Option, } impl PcscClient { fn new(card: Card) -> Self { - Self { card } + Self { + card, + card_caps: None, + } } pub fn list_cards() -> Result> { @@ -204,7 +218,7 @@ impl PcscClient { /// data"). pub fn open(card: Card) -> Result { let card_client = PcscClient::new(card); - let mut ccb = Box::new(card_client) as CardClientBox; + let ccb = Box::new(card_client) as CardClientBox; let mut ca = CardApp::new(ccb); let resp = ca.select()?; @@ -228,8 +242,16 @@ impl CardClient for PcscClient { ))) })?; - log::trace!(" <- APDU response: {:x?}", resp); + 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_app.rs b/openpgp-card/src/card_app.rs index b34112a..88d44a2 100644 --- a/openpgp-card/src/card_app.rs +++ b/openpgp-card/src/card_app.rs @@ -31,23 +31,22 @@ use crate::{ pub struct CardApp { card_client: CardClientBox, - card_caps: Option, } impl CardApp { pub fn new(card_client: CardClientBox) -> Self { - Self { - card_client, - card_caps: None, - } + Self { card_client } } pub(crate) fn take_card(self) -> CardClientBox { self.card_client } - /// Read capabilities from the card, and set them in the CardApp - pub fn init_caps(self, ard: &Tlv) -> Result { + /// Read capabilities from the card, and set them in the CardApp. + /// + /// Also initializes the underlying CardClient with the caps - some + /// implementations may need this information. + pub fn init_caps(&mut self, ard: &Tlv) -> Result<()> { // Determine chaining/extended length support from card // metadata and cache this information in CardApp (as a // CardCaps) @@ -62,44 +61,36 @@ impl CardApp { } } - let max_cmd_bytes = if let Ok(Some(eli)) = + let (max_cmd_bytes, max_rsp_bytes) = if let Ok(Some(eli)) = CardApp::get_extended_length_information(&ard) { - eli.max_command_bytes + (eli.max_command_bytes, eli.max_response_bytes) } else { - 255 + (255, 255) }; let caps = CardCaps { ext_support, chaining_support, max_cmd_bytes, + max_rsp_bytes, }; - Ok(self.set_caps(caps)) - } + self.card_client.init_caps(caps); - pub fn set_caps(self, card_caps: CardCaps) -> Self { - Self { - card_client: self.card_client, - card_caps: Some(card_caps), - } + Ok(()) } pub fn card(&mut self) -> &mut CardClientBox { &mut self.card_client } - pub fn card_caps(&self) -> Option<&CardCaps> { - self.card_caps.as_ref() - } - // --- select --- /// "Select" the OpenPGP card application pub fn select(&mut self) -> Result { let select_openpgp = commands::select_openpgp(); - apdu::send_command(&mut self.card_client, select_openpgp, false, None) + apdu::send_command(&mut self.card_client, select_openpgp, false) } // --- application data --- @@ -110,10 +101,10 @@ impl CardApp { /// (the data is stored in the OpenPGPCard object). 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, None)?; + let resp = apdu::send_command(&mut self.card_client, ad, true)?; let entry = TlvEntry::from(resp.data()?, true)?; - log::trace!(" App data TlvEntry: {:x?}", entry); + log::debug!(" App data TlvEntry: {:x?}", entry); Ok(Tlv(Tag::from([0x6E]), entry)) } @@ -283,7 +274,6 @@ impl CardApp { &mut self.card_client, commands::get_url(), true, - self.card_caps.as_ref(), )?; Ok(String::from_utf8_lossy(resp.data()?).to_string()) @@ -292,12 +282,7 @@ impl CardApp { // --- cardholder related data (65) --- pub fn get_cardholder_related_data(&mut self) -> Result { let crd = commands::cardholder_related_data(); - let resp = apdu::send_command( - &mut self.card_client, - crd, - true, - self.card_caps.as_ref(), - )?; + let resp = apdu::send_command(&mut self.card_client, crd, true)?; resp.check_ok()?; CardHolder::try_from(resp.data()?) @@ -306,12 +291,7 @@ impl CardApp { // --- security support template (7a) --- 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, - self.card_caps.as_ref(), - )?; + let resp = apdu::send_command(&mut self.card_client, sst, true)?; resp.check_ok()?; Tlv::try_from(resp.data()?) @@ -323,7 +303,6 @@ impl CardApp { &mut self.card_client, commands::get_algo_list(), true, - self.card_caps.as_ref(), )?; resp.check_ok()?; @@ -339,12 +318,8 @@ impl CardApp { // [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( - &mut self.card_client, - verify, - false, - self.card_caps.as_ref(), - )?; + let resp = + apdu::send_command(&mut self.card_client, verify, false)?; if !(resp.status() == [0x69, 0x82] || resp.status() == [0x69, 0x83]) { @@ -356,12 +331,8 @@ impl CardApp { // [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( - &mut self.card_client, - verify, - false, - self.card_caps.as_ref(), - )?; + let resp = + apdu::send_command(&mut self.card_client, verify, false)?; if !(resp.status() == [0x69, 0x82] || resp.status() == [0x69, 0x83]) @@ -372,22 +343,12 @@ impl CardApp { // terminate_df [apdu 00 e6 00 00] let term = commands::terminate_df(); - let resp = apdu::send_command( - &mut self.card_client, - term, - false, - self.card_caps.as_ref(), - )?; + let resp = apdu::send_command(&mut self.card_client, term, false)?; resp.check_ok()?; // activate_file [apdu 00 44 00 00] let act = commands::activate_file(); - let resp = apdu::send_command( - &mut self.card_client, - act, - false, - self.card_caps.as_ref(), - )?; + let resp = apdu::send_command(&mut self.card_client, act, false)?; resp.check_ok()?; // FIXME: does the connection need to be re-opened on some cards, @@ -403,22 +364,12 @@ impl CardApp { assert!(pin.len() >= 6); // FIXME: Err let verify = commands::verify_pw1_81(pin.as_bytes().to_vec()); - apdu::send_command( - &mut self.card_client, - verify, - false, - self.card_caps.as_ref(), - ) + apdu::send_command(&mut self.card_client, verify, false) } pub fn check_pw1(&mut self) -> Result { let verify = commands::verify_pw1_82(vec![]); - apdu::send_command( - &mut self.card_client, - verify, - false, - self.card_caps.as_ref(), - ) + apdu::send_command(&mut self.card_client, verify, false) } pub fn verify_pw1( @@ -428,22 +379,12 @@ impl CardApp { assert!(pin.len() >= 6); // FIXME: Err let verify = commands::verify_pw1_82(pin.as_bytes().to_vec()); - apdu::send_command( - &mut self.card_client, - verify, - false, - self.card_caps.as_ref(), - ) + apdu::send_command(&mut self.card_client, verify, false) } pub fn check_pw3(&mut self) -> Result { let verify = commands::verify_pw3(vec![]); - apdu::send_command( - &mut self.card_client, - verify, - false, - self.card_caps.as_ref(), - ) + apdu::send_command(&mut self.card_client, verify, false) } pub fn verify_pw3( @@ -453,12 +394,7 @@ impl CardApp { assert!(pin.len() >= 8); // FIXME: Err let verify = commands::verify_pw3(pin.as_bytes().to_vec()); - apdu::send_command( - &mut self.card_client, - verify, - false, - self.card_caps.as_ref(), - ) + apdu::send_command(&mut self.card_client, verify, false) } // --- decrypt --- @@ -499,12 +435,7 @@ impl CardApp { ) -> 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( - &mut self.card_client, - dec_cmd, - true, - self.card_caps.as_ref(), - )?; + let resp = apdu::send_command(&mut self.card_client, dec_cmd, true)?; resp.check_ok()?; Ok(resp.data().map(|d| d.to_vec())?) @@ -557,12 +488,7 @@ impl CardApp { ) -> Result, OpenpgpCardError> { let dec_cmd = commands::signature(data); - let resp = apdu::send_command( - &mut self.card_client, - dec_cmd, - true, - self.card_caps.as_ref(), - )?; + let resp = apdu::send_command(&mut self.card_client, dec_cmd, true)?; Ok(resp.data().map(|d| d.to_vec())?) } @@ -574,12 +500,7 @@ impl CardApp { name: &str, ) -> Result { let put_name = commands::put_name(name.as_bytes().to_vec()); - apdu::send_command( - &mut self.card_client, - put_name, - false, - self.card_caps.as_ref(), - ) + apdu::send_command(&mut self.card_client, put_name, false) } pub fn set_lang( @@ -587,22 +508,12 @@ impl CardApp { lang: &str, ) -> Result { let put_lang = commands::put_lang(lang.as_bytes().to_vec()); - apdu::send_command( - self.card_client.borrow_mut(), - put_lang, - false, - self.card_caps.as_ref(), - ) + apdu::send_command(self.card_client.borrow_mut(), put_lang, false) } pub fn set_sex(&mut self, sex: Sex) -> Result { let put_sex = commands::put_sex(sex.as_u8()); - apdu::send_command( - self.card_client.borrow_mut(), - put_sex, - false, - self.card_caps.as_ref(), - ) + apdu::send_command(self.card_client.borrow_mut(), put_sex, false) } pub fn set_url( @@ -610,12 +521,7 @@ impl CardApp { url: &str, ) -> Result { let put_url = commands::put_url(url.as_bytes().to_vec()); - apdu::send_command( - &mut self.card_client, - put_url, - false, - self.card_caps.as_ref(), - ) + apdu::send_command(&mut self.card_client, put_url, false) } pub fn upload_key( diff --git a/openpgp-card/src/key_upload.rs b/openpgp-card/src/key_upload.rs index 8c5f8ac..ea9909a 100644 --- a/openpgp-card/src/key_upload.rs +++ b/openpgp-card/src/key_upload.rs @@ -67,8 +67,6 @@ pub(crate) fn upload_key( } }; - let caps: Option = card_app.card_caps().copied(); - copy_key_to_card( card_app.card(), key_type, @@ -76,7 +74,6 @@ pub(crate) fn upload_key( key.get_fp(), algo_cmd, key_cmd, - caps.as_ref(), )?; Ok(()) @@ -375,7 +372,6 @@ fn copy_key_to_card( 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); @@ -395,11 +391,11 @@ fn copy_key_to_card( // FIXME: Only write algo attributes to the card if "extended // capabilities" show that they are changeable! - apdu::send_command(card_client, algo_cmd, false, card_caps)?.check_ok()?; + apdu::send_command(card_client, algo_cmd, false)?.check_ok()?; - apdu::send_command(card_client, key_cmd, false, card_caps)?.check_ok()?; - apdu::send_command(card_client, fp_cmd, false, card_caps)?.check_ok()?; - apdu::send_command(card_client, time_cmd, false, card_caps)?.check_ok()?; + apdu::send_command(card_client, key_cmd, false)?.check_ok()?; + apdu::send_command(card_client, fp_cmd, false)?.check_ok()?; + apdu::send_command(card_client, time_cmd, false)?.check_ok()?; Ok(()) } diff --git a/openpgp-card/src/lib.rs b/openpgp-card/src/lib.rs index e804a35..b81b7a5 100644 --- a/openpgp-card/src/lib.rs +++ b/openpgp-card/src/lib.rs @@ -27,17 +27,27 @@ mod tlv; pub trait CardClient { fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result>; + fn init_caps(&mut self, caps: CardCaps); + fn get_caps(&self) -> Option<&CardCaps>; + + /// If a CardClient implementation introduces an inherent limit for + /// maximum number of bytes per command, this fn can indicate that + /// limit by returning `Some(max_cmd_len)`. + fn max_cmd_len(&self) -> Option { + None + } } pub type CardClientBox = Box; /// Information about the capabilities of the card. /// (feature configuration from card metadata) -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct CardCaps { - pub(crate) ext_support: bool, - pub(crate) chaining_support: bool, - pub(crate) max_cmd_bytes: u16, + pub ext_support: bool, + pub chaining_support: bool, + pub max_cmd_bytes: u16, + pub max_rsp_bytes: u16, } impl CardCaps { @@ -45,11 +55,13 @@ impl CardCaps { ext_support: bool, chaining_support: bool, max_cmd_bytes: u16, + max_rsp_bytes: u16, ) -> CardCaps { Self { ext_support, chaining_support, max_cmd_bytes, + max_rsp_bytes, } } } @@ -324,7 +336,7 @@ impl CardBase { let ard = card_app.get_app_data()?; - card_app = card_app.init_caps(&ard)?; + card_app.init_caps(&ard)?; Ok(Self { card_app, ard }) }