Improve scdaemon client backend:

- Use "--exlen" SCD parameter for extended length commands.
- Deal with Assuan line length limitations.
- Add a CardCaps to ScdClient.
This commit is contained in:
Heiko Schaefer 2021-07-29 18:13:41 +02:00
parent 56f4459932
commit ff65d200ec

View file

@ -9,15 +9,38 @@ use std::sync::Mutex;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use openpgp_card::errors::OpenpgpCardError; use openpgp_card::errors::OpenpgpCardError;
use openpgp_card::{CardBase, CardClient, CardClientBox}; use openpgp_card::{CardBase, CardCaps, CardClient, CardClientBox};
lazy_static! { lazy_static! {
pub(crate) static ref RT: Mutex<Runtime> = pub(crate) static ref RT: Mutex<Runtime> =
Mutex::new(tokio::runtime::Runtime::new().unwrap()); Mutex::new(tokio::runtime::Runtime::new().unwrap());
} }
/// The Assuan protocol which is used in GnuPG limits the length of commands.
/// Currently there seems to be no way to send longer commands via Assuan.
///
/// See:
/// https://www.gnupg.org/documentation/manuals/assuan/Client-requests.html#Client-requests
///
/// FIXME: This number is probably off by a few bytes (is "SCD " added in
/// communication within GnuPG? Are \r\n added?)
const ASSUAN_LINELENGTH: usize = 1000;
/// The maximum number of bytes for a command that can be sent via Assuan to
/// scdaemon.
/// Each command byte gets sent via Assuan as a two-character hex string,
/// and a few characters are used to send "APDU --exlen=abcd" (as a
/// conservative limit, 20 characters are subtracted).
///
/// In concrete terms, this limit means that with cards that do not support
/// command chaining (like the floss-shop OpenPGP Card 3.4), some commands
/// cannot be sent to the card. In particular, uploading rsa4096 keys will
/// fail via scdaemon, with such cards.
const CMD_SIZE_MAX: usize = ASSUAN_LINELENGTH / 2 - 20;
pub struct ScdClient { pub struct ScdClient {
client: Client, client: Client,
card_caps: Option<CardCaps>,
} }
impl ScdClient { impl ScdClient {
@ -47,7 +70,10 @@ impl ScdClient {
pub fn new(socket: &str) -> Result<Self> { pub fn new(socket: &str) -> Result<Self> {
let client = RT.lock().unwrap().block_on(Client::connect(socket))?; let client = RT.lock().unwrap().block_on(Client::connect(socket))?;
Ok(Self { client }) Ok(Self {
client,
card_caps: None,
})
} }
pub fn select_card(&mut self, serial: &str) -> Result<()> { pub fn select_card(&mut self, serial: &str) -> Result<()> {
@ -57,7 +83,7 @@ impl ScdClient {
let mut rt = RT.lock().unwrap(); let mut rt = RT.lock().unwrap();
while let Some(response) = rt.block_on(self.client.next()) { while let Some(response) = rt.block_on(self.client.next()) {
log::trace!("select res: {:x?}", response); log::debug!("select res: {:x?}", response);
if response.is_err() { if response.is_err() {
return Err(anyhow!("Card not found")); return Err(anyhow!("Card not found"));
@ -66,7 +92,7 @@ impl ScdClient {
if let Ok(Response::Status { .. }) = response { if let Ok(Response::Status { .. }) = response {
// drop remaining lines // drop remaining lines
while let Some(_drop) = rt.block_on(self.client.next()) { while let Some(_drop) = rt.block_on(self.client.next()) {
log::trace!("select drop: {:x?}", _drop); log::debug!("select drop: {:x?}", _drop);
} }
return Ok(()); return Ok(());
@ -79,18 +105,38 @@ impl ScdClient {
impl CardClient for ScdClient { impl CardClient for ScdClient {
fn transmit(&mut self, cmd: &[u8], _: usize) -> Result<Vec<u8>> { fn transmit(&mut self, cmd: &[u8], _: usize) -> Result<Vec<u8>> {
log::debug!("SCDC cmd len {}", cmd.len());
let hex = hex::encode(cmd); let hex = hex::encode(cmd);
let send = format!("APDU {}\n", hex); let ex = if let Some(caps) = self.card_caps {
log::trace!("send: '{}'", send); if caps.ext_support {
format!("--exlen={} ", caps.max_rsp_bytes)
} else {
"".to_string()
}
} else {
"".to_string()
};
let send = format!("APDU {}{}\n", ex, hex);
log::debug!("send: '{}'", send);
if send.len() > ASSUAN_LINELENGTH {
return Err(anyhow!(
"APDU command is too long ({}) to send via Assuan",
send.len()
));
}
self.client.send(send)?; self.client.send(send)?;
let mut rt = RT.lock().unwrap(); let mut rt = RT.lock().unwrap();
while let Some(response) = rt.block_on(self.client.next()) { while let Some(response) = rt.block_on(self.client.next()) {
log::trace!("res: {:x?}", response); log::debug!("res: {:x?}", response);
if response.is_err() { if response.is_err() {
unimplemented!(); unimplemented!("Err: {:x?}", response);
} }
if let Ok(Response::Data { partial }) = response { if let Ok(Response::Data { partial }) = response {
@ -98,7 +144,7 @@ impl CardClient for ScdClient {
// drop remaining lines // drop remaining lines
while let Some(drop) = rt.block_on(self.client.next()) { while let Some(drop) = rt.block_on(self.client.next()) {
log::trace!("drop: {:x?}", drop); log::debug!("drop: {:x?}", drop);
} }
return Ok(res); return Ok(res);
@ -107,4 +153,18 @@ impl CardClient for ScdClient {
Err(anyhow!("no response found")) Err(anyhow!("no response found"))
} }
fn init_caps(&mut self, caps: CardCaps) {
self.card_caps = Some(caps);
}
fn get_caps(&self) -> Option<&CardCaps> {
self.card_caps.as_ref()
}
/// Return limit for APDU command size via scdaemon (based on Assuan
/// maximum line length)
fn max_cmd_len(&self) -> Option<usize> {
Some(CMD_SIZE_MAX)
}
} }