Communicate with scdaemon via gpg-agent (this removes the need for a "socket" parameter)

This commit is contained in:
Heiko Schaefer 2021-08-03 17:46:13 +02:00
parent caffc8a20c
commit f46d94f989

View file

@ -3,11 +3,13 @@
//! This crate provides `ScdClient`, which is an implementation of the //! This crate provides `ScdClient`, which is an implementation of the
//! CardClient trait that uses GnuPG's scdaemon to access OpenPGP cards. //! CardClient trait that uses GnuPG's scdaemon to access OpenPGP cards.
//! To access scdaemon, GnuPG Agent is used.
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use futures::StreamExt; use futures::StreamExt;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use sequoia_ipc::assuan::{Client, Response}; use sequoia_ipc::assuan::Response;
use sequoia_ipc::gnupg::{Agent, Context};
use std::sync::Mutex; use std::sync::Mutex;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
@ -48,36 +50,76 @@ const ASSUAN_LINELENGTH: usize = 1000;
const CMD_SIZE_MAX: usize = ASSUAN_LINELENGTH / 2 - 25; const CMD_SIZE_MAX: usize = ASSUAN_LINELENGTH / 2 - 25;
pub struct ScdClient { pub struct ScdClient {
client: Client, agent: Agent,
card_caps: Option<CardCaps>, card_caps: Option<CardCaps>,
} }
impl ScdClient { impl ScdClient {
/// Initialize an ScdClient object that is connected to an scdaemon /// Initialize an ScdClient object that is connected to an scdaemon
/// instance via `socket` /// instance via a GnuPG `agent` instance.
fn new(socket: &str) -> Result<Self> { ///
let client = RT.lock().unwrap().block_on(Client::connect(socket))?; /// If `agent` is None, a Context with the default GnuPG home directory
Ok(Self { /// is used.
client, fn new(agent: Option<Agent>) -> Result<Self> {
let agent = if let Some(agent) = agent {
agent
} else {
// Create and use a new Agent based on a default Context
let ctx = Context::new()?;
RT.lock().unwrap().block_on(Agent::connect(&ctx))?
};
let mut scdc = Self {
agent,
card_caps: None, card_caps: None,
}) };
// call "SCD SERIALNO", which causes scdaemon to be started by gpg
// agent (if it's not running yet)
scdc.init()?;
Ok(scdc)
}
fn init(&mut self) -> Result<()> {
let mut rt = RT.lock().unwrap();
let send = format!("SCD SERIALNO");
self.agent.send(send)?;
while let Some(response) = rt.block_on(self.agent.next()) {
log::debug!("init res: {:x?}", response);
if let Ok(Response::Status { .. }) = response {
// drop remaining lines
while let Some(_drop) = rt.block_on(self.agent.next()) {
log::trace!("init drop: {:x?}", _drop);
}
return Ok(());
}
}
Err(anyhow!("SCDC init() failed"))
} }
/// Create a CardClientBox object that uses an scdaemon instance as its /// Create a CardClientBox object that uses an scdaemon instance as its
/// backend. If multiple cards are available, scdaemon implicitly /// backend. If multiple cards are available, scdaemon implicitly
/// selects one. /// selects one.
pub fn open(socket: &str) -> Result<CardClientBox, OpenpgpCardError> { pub fn open(
let card = ScdClient::new(socket)?; agent: Option<Agent>,
) -> Result<CardClientBox, OpenpgpCardError> {
let card = ScdClient::new(agent)?;
Ok(Box::new(card) as CardClientBox) Ok(Box::new(card) as CardClientBox)
} }
/// Create a CardClientBox object that uses an scdaemon instance as its /// Create a CardClientBox object that uses an scdaemon instance as its
/// backend. Requests the specific card `serial`. /// backend. Requests the specific card `serial`.
pub fn open_by_serial( pub fn open_by_serial(
socket: &str, agent: Option<Agent>,
serial: &str, serial: &str,
) -> Result<CardClientBox, OpenpgpCardError> { ) -> Result<CardClientBox, OpenpgpCardError> {
let mut card = ScdClient::new(socket)?; let mut card = ScdClient::new(agent)?;
card.select_card(serial)?; card.select_card(serial)?;
Ok(Box::new(card) as CardClientBox) Ok(Box::new(card) as CardClientBox)
@ -86,12 +128,12 @@ impl ScdClient {
/// Ask scdameon to switch to using a specific OpenPGP card, based on /// Ask scdameon to switch to using a specific OpenPGP card, based on
/// its `serial`. /// its `serial`.
fn select_card(&mut self, serial: &str) -> Result<()> { fn select_card(&mut self, serial: &str) -> Result<()> {
let send = format!("SERIALNO --demand={}\n", serial); let send = format!("SCD SERIALNO --demand={}", serial);
self.client.send(send)?; self.agent.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.agent.next()) {
log::debug!("select res: {:x?}", response); log::debug!("select res: {:x?}", response);
if response.is_err() { if response.is_err() {
@ -100,7 +142,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.agent.next()) {
log::debug!("select drop: {:x?}", _drop); log::debug!("select drop: {:x?}", _drop);
} }
@ -126,7 +168,7 @@ impl CardClient for ScdClient {
"".to_string() "".to_string()
}; };
let send = format!("APDU {}{}\n", ext, hex); let send = format!("SCD APDU {}{}\n", ext, hex);
log::debug!("SCDC command: '{}'", send); log::debug!("SCDC command: '{}'", send);
if send.len() > ASSUAN_LINELENGTH { if send.len() > ASSUAN_LINELENGTH {
@ -136,11 +178,11 @@ impl CardClient for ScdClient {
)); ));
} }
self.client.send(send)?; self.agent.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.agent.next()) {
log::debug!("res: {:x?}", response); log::debug!("res: {:x?}", response);
if response.is_err() { if response.is_err() {
return Err(anyhow!( return Err(anyhow!(
@ -153,7 +195,7 @@ impl CardClient for ScdClient {
let res = partial; let res = partial;
// drop remaining lines // drop remaining lines
while let Some(drop) = rt.block_on(self.client.next()) { while let Some(drop) = rt.block_on(self.agent.next()) {
log::trace!("drop: {:x?}", drop); log::trace!("drop: {:x?}", drop);
} }