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.
This commit is contained in:
Heiko Schaefer 2021-07-29 18:08:04 +02:00
parent 9d93570d9f
commit dbf2e9e3fb
4 changed files with 103 additions and 167 deletions

View file

@ -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<Response, OpenpgpCardError> {
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<Vec<u8>, 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<CardCaps>,
}
impl PcscClient {
fn new(card: Card) -> Self {
Self { card }
Self {
card,
card_caps: None,
}
}
pub fn list_cards() -> Result<Vec<PcscClient>> {
@ -204,7 +218,7 @@ impl PcscClient {
/// data").
pub fn open(card: Card) -> Result<CardBase, OpenpgpCardError> {
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()
}
}

View file

@ -31,23 +31,22 @@ use crate::{
pub struct CardApp {
card_client: CardClientBox,
card_caps: Option<CardCaps>,
}
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<Self> {
/// 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<Response, OpenpgpCardError> {
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<Tlv> {
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<CardHolder> {
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<Tlv> {
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<Response, OpenpgpCardError> {
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<Response, OpenpgpCardError> {
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<Vec<u8>, 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<Vec<u8>, 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<Response, OpenpgpCardError> {
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<Response, OpenpgpCardError> {
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<Response, OpenpgpCardError> {
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<Response, OpenpgpCardError> {
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(

View file

@ -67,8 +67,6 @@ pub(crate) fn upload_key(
}
};
let caps: Option<CardCaps> = 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<u8>,
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(())
}

View file

@ -27,17 +27,27 @@ mod tlv;
pub trait CardClient {
fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>>;
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<usize> {
None
}
}
pub type CardClientBox = Box<dyn CardClient + Send + Sync>;
/// 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 })
}