From 750edf459c4304695befed32d9315d4af5c8a3f5 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Wed, 7 Dec 2022 15:32:43 +0100 Subject: ipc: Rework Agent::sign using async fn. - Previously, the code used an explicit state machine because it predated the async fn support in rustc. - This also fixes a bug where server and client lose sync if the server returns an error. --- ipc/src/gnupg.rs | 219 +++++++++---------------------------------------------- 1 file changed, 33 insertions(+), 186 deletions(-) diff --git a/ipc/src/gnupg.rs b/ipc/src/gnupg.rs index 300890bf..c6aa1468 100644 --- a/ipc/src/gnupg.rs +++ b/ipc/src/gnupg.rs @@ -8,7 +8,7 @@ use std::ffi::OsStr; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; -use futures::{Future, Stream, StreamExt}; +use futures::{Stream, StreamExt}; use std::task::{Poll, self}; use std::pin::Pin; @@ -347,7 +347,38 @@ impl Agent { algo: HashAlgorithm, digest: &'a [u8]) -> Result { - SigningRequest::new(&mut self.c, key, algo, digest).await + for option in Self::options() { + self.send_simple(option).await?; + } + + let grip = Keygrip::of(key.public.mpis())?; + self.send_simple(format!("SIGKEY {}", grip)).await?; + self.send_simple( + format!("SETKEYDESC {}", + assuan::escape(&key.password_prompt))).await?; + + let algo = u8::from(algo); + let digest = hex::encode(&digest); + self.send_simple(format!("SETHASH {} {}", algo, digest)).await?; + self.send("PKSIGN")?; + + let mut data = Vec::new(); + while let Some(r) = self.next().await { + match r? { + assuan::Response::Ok { .. } + | assuan::Response::Comment { .. } + | assuan::Response::Status { .. } => + (), // Ignore. + assuan::Response::Error { ref message, .. } => + return assuan::operation_failed(self, message).await, + assuan::Response::Data { ref partial } => + data.extend_from_slice(partial), + r => + return assuan::protocol_error(&r), + } + } + + Sexp::from_bytes(&data)?.to_signature() } /// Decrypts `ciphertext` using `key` with the secret bits managed @@ -462,190 +493,6 @@ impl Agent { } } -struct SigningRequest<'a, 'b, 'c> -{ - c: &'a mut assuan::Client, - key: &'b KeyPair, - algo: HashAlgorithm, - digest: &'c [u8], - options: Vec, - state: SigningRequestState, -} - -impl<'a, 'b, 'c> SigningRequest<'a, 'b, 'c> -{ - fn new(c: &'a mut assuan::Client, - key: &'b KeyPair, - algo: HashAlgorithm, - digest: &'c [u8]) - -> Self { - Self { - c, key, algo, digest, - options: Agent::options(), - state: SigningRequestState::Start, - } - } -} - -#[derive(Debug)] -enum SigningRequestState { - Start, - Options, - SigKey, - SetKeyDesc, - SetHash, - PkSign(Vec), -} - -/// Returns a convenient Err value for use in the state machines -/// below. -fn operation_failed(message: &Option) -> Result { - Err(Error::OperationFailed( - message.as_ref().map(|e| e.to_string()) - .unwrap_or_else(|| "Unknown reason".into())) - .into()) -} - -/// Returns a convenient Err value for use in the state machines -/// below. -fn protocol_error(response: &assuan::Response) -> Result { - Err(Error::ProtocolError( - format!("Got unexpected response {:?}", response)) - .into()) -} - -impl<'a, 'b, 'c> Future for SigningRequest<'a, 'b, 'c> -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { - use self::SigningRequestState::*; - - // The compiler is not smart enough to figure out disjoint borrows - // through Pin via DerefMut (which wholly borrows `self`), so unwrap it - let Self { c, state, key, options, algo, digest } = Pin::into_inner(self); - let mut client = Pin::new(c); - - loop { - match state { - Start => { - if options.is_empty() { - let grip = Keygrip::of(key.public.mpis())?; - client.send(format!("SIGKEY {}", grip))?; - *state = SigKey; - } else { - let opts = options.pop().unwrap(); - client.send(opts)?; - *state = Options; - } - }, - - Options => match client.as_mut().poll_next(cx)? { - Poll::Ready(Some(r)) => match r { - assuan::Response::Ok { .. } - | assuan::Response::Comment { .. } - | assuan::Response::Status { .. } => - (), // Ignore. - assuan::Response::Error { ref message, .. } => - return Poll::Ready(operation_failed(message)), - _ => - return Poll::Ready(protocol_error(&r)), - }, - Poll::Ready(None) => { - if let Some(option) = options.pop() { - client.send(option)?; - } else { - let grip = Keygrip::of(key.public.mpis())?; - client.send(format!("SIGKEY {}", grip))?; - *state = SigKey; - } - }, - Poll::Pending => return Poll::Pending, - }, - - SigKey => match client.as_mut().poll_next(cx)? { - Poll::Ready(Some(r)) => match r { - assuan::Response::Ok { .. } - | assuan::Response::Comment { .. } - | assuan::Response::Status { .. } => - (), // Ignore. - assuan::Response::Error { ref message, .. } => - return Poll::Ready(operation_failed(message)), - _ => - return Poll::Ready(protocol_error(&r)), - }, - Poll::Ready(None) => { - client.send( - format!("SETKEYDESC {}", - assuan::escape(&key.password_prompt)))?; - *state = SetKeyDesc; - }, - Poll::Pending => return Poll::Pending, - }, - - SetKeyDesc => match client.as_mut().poll_next(cx)? { - Poll::Ready(Some(r)) => match r { - assuan::Response::Ok { .. } - | assuan::Response::Comment { .. } - | assuan::Response::Status { .. } => - (), // Ignore. - assuan::Response::Error { ref message, .. } => - return Poll::Ready(operation_failed(message)), - _ => - return Poll::Ready(protocol_error(&r)), - }, - Poll::Ready(None) => { - let algo = u8::from(*algo); - let digest = hex::encode(&digest); - client.send(format!("SETHASH {} {}", algo, digest))?; - *state = SetHash; - }, - Poll::Pending => return Poll::Pending, - }, - - SetHash => match client.as_mut().poll_next(cx)? { - Poll::Ready(Some(r)) => match r { - assuan::Response::Ok { .. } - | assuan::Response::Comment { .. } - | assuan::Response::Status { .. } => - (), // Ignore. - assuan::Response::Error { ref message, .. } => - return Poll::Ready(operation_failed(message)), - _ => - return Poll::Ready(protocol_error(&r)), - }, - Poll::Ready(None) => { - client.send("PKSIGN")?; - *state = PkSign(Vec::new()); - }, - Poll::Pending => return Poll::Pending, - }, - - - PkSign(ref mut data) => match client.as_mut().poll_next(cx)? { - Poll::Ready(Some(r)) => match r { - assuan::Response::Ok { .. } - | assuan::Response::Comment { .. } - | assuan::Response::Status { .. } => - (), // Ignore. - assuan::Response::Error { ref message, .. } => - return Poll::Ready(operation_failed(message)), - assuan::Response::Data { ref partial } => - data.extend_from_slice(partial), - _ => - return Poll::Ready(protocol_error(&r)), - }, - Poll::Ready(None) => { - return Poll::Ready( - Sexp::from_bytes(&data)?.to_signature()); - }, - Poll::Pending => return Poll::Pending, - }, - } - } - } -} - /// A cryptographic key pair. /// /// A `KeyPair` is a combination of public and secret key. This -- cgit v1.2.3