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:
parent
56f4459932
commit
ff65d200ec
1 changed files with 69 additions and 9 deletions
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue