diff --git a/scdc/src/lib.rs b/scdc/src/lib.rs index cdeb2e9..b209c94 100644 --- a/scdc/src/lib.rs +++ b/scdc/src/lib.rs @@ -3,11 +3,13 @@ //! This crate provides `ScdClient`, which is an implementation of the //! CardClient trait that uses GnuPG's scdaemon to access OpenPGP cards. +//! To access scdaemon, GnuPG Agent is used. use anyhow::{anyhow, Result}; use futures::StreamExt; 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 tokio::runtime::Runtime; @@ -48,36 +50,76 @@ const ASSUAN_LINELENGTH: usize = 1000; const CMD_SIZE_MAX: usize = ASSUAN_LINELENGTH / 2 - 25; pub struct ScdClient { - client: Client, + agent: Agent, card_caps: Option, } impl ScdClient { /// Initialize an ScdClient object that is connected to an scdaemon - /// instance via `socket` - fn new(socket: &str) -> Result { - let client = RT.lock().unwrap().block_on(Client::connect(socket))?; - Ok(Self { - client, + /// instance via a GnuPG `agent` instance. + /// + /// If `agent` is None, a Context with the default GnuPG home directory + /// is used. + fn new(agent: Option) -> Result { + 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, - }) + }; + + // 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 /// backend. If multiple cards are available, scdaemon implicitly /// selects one. - pub fn open(socket: &str) -> Result { - let card = ScdClient::new(socket)?; + pub fn open( + agent: Option, + ) -> Result { + let card = ScdClient::new(agent)?; Ok(Box::new(card) as CardClientBox) } /// Create a CardClientBox object that uses an scdaemon instance as its /// backend. Requests the specific card `serial`. pub fn open_by_serial( - socket: &str, + agent: Option, serial: &str, ) -> Result { - let mut card = ScdClient::new(socket)?; + let mut card = ScdClient::new(agent)?; card.select_card(serial)?; Ok(Box::new(card) as CardClientBox) @@ -86,12 +128,12 @@ impl ScdClient { /// Ask scdameon to switch to using a specific OpenPGP card, based on /// its `serial`. fn select_card(&mut self, serial: &str) -> Result<()> { - let send = format!("SERIALNO --demand={}\n", serial); - self.client.send(send)?; + let send = format!("SCD SERIALNO --demand={}", serial); + self.agent.send(send)?; 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); if response.is_err() { @@ -100,7 +142,7 @@ impl ScdClient { if let Ok(Response::Status { .. }) = response { // 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); } @@ -126,7 +168,7 @@ impl CardClient for ScdClient { "".to_string() }; - let send = format!("APDU {}{}\n", ext, hex); + let send = format!("SCD APDU {}{}\n", ext, hex); log::debug!("SCDC command: '{}'", send); 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(); - 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); if response.is_err() { return Err(anyhow!( @@ -153,7 +195,7 @@ impl CardClient for ScdClient { let res = partial; // 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); }