diff options
Diffstat (limited to 'ipc')
-rw-r--r-- | ipc/Cargo.toml | 14 | ||||
-rw-r--r-- | ipc/examples/gpg-agent-decrypt.rs | 16 | ||||
-rw-r--r-- | ipc/examples/gpg-agent-sign.rs | 2 | ||||
-rw-r--r-- | ipc/src/assuan/mod.rs | 17 | ||||
-rw-r--r-- | ipc/src/gnupg.rs | 26 | ||||
-rw-r--r-- | ipc/src/lib.rs | 120 | ||||
-rw-r--r-- | ipc/tests/gpg-agent.rs | 47 |
7 files changed, 170 insertions, 72 deletions
diff --git a/ipc/Cargo.toml b/ipc/Cargo.toml index 745e4d9d..d74bfd6e 100644 --- a/ipc/Cargo.toml +++ b/ipc/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "sequoia-ipc" description = "Interprocess communication infrastructure for Sequoia" -version = "0.15.0" +version = "0.17.0" authors = [ "Justus Winter <justus@sequoia-pgp.org>", "Kai Michaelis <kai@sequoia-pgp.org>", "Neal H. Walfield <neal@sequoia-pgp.org>", ] -documentation = "https://docs.sequoia-pgp.org/0.15.0/sequoia_ipc" +documentation = "https://docs.sequoia-pgp.org/0.17.0/sequoia_ipc" build = "build.rs" homepage = "https://sequoia-pgp.org/" repository = "https://gitlab.com/sequoia-pgp/sequoia" @@ -20,8 +20,8 @@ gitlab = { repository = "sequoia-pgp/sequoia" } maintenance = { status = "actively-developed" } [dependencies] -sequoia-openpgp = { path = "../openpgp", version = "0.15" } -sequoia-core = { path = "../core", version = "0.15" } +sequoia-openpgp = { path = "../openpgp", version = "0.17" } +sequoia-core = { path = "../core", version = "0.17" } anyhow = "1" capnp-rpc = "0.10" @@ -36,6 +36,12 @@ thiserror = "1" tokio = "0.1" tokio-core = "0.1" tokio-io = "0.1.4" +parity-tokio-ipc = "0.4" +socket2 = "= 0.3.11" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3", default-features = false, features = ["winsock2"] } +ctor = "0.1" [build-dependencies] lalrpop = "0.17" diff --git a/ipc/examples/gpg-agent-decrypt.rs b/ipc/examples/gpg-agent-decrypt.rs index bd524e80..76be71db 100644 --- a/ipc/examples/gpg-agent-decrypt.rs +++ b/ipc/examples/gpg-agent-decrypt.rs @@ -15,7 +15,7 @@ use crate::openpgp::parse::{ Parse, stream::{ DecryptionHelper, - Decryptor, + DecryptorBuilder, VerificationHelper, GoodChecksum, VerificationError, @@ -56,9 +56,8 @@ fn main() { }).collect(); // Now, create a decryptor with a helper using the given Certs. - let mut decryptor = - Decryptor::from_reader(p, io::stdin(), Helper::new(&ctx, p, certs), None) - .unwrap(); + let mut decryptor = DecryptorBuilder::from_reader(io::stdin()).unwrap() + .with_policy(p, None, Helper::new(&ctx, p, certs)).unwrap(); // Finally, stream the decrypted data to stdout. io::copy(&mut decryptor, &mut io::stdout()) @@ -101,14 +100,15 @@ impl<'a> DecryptionHelper for Helper<'a> { sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> openpgp::Result<Option<openpgp::Fingerprint>> - where D: FnMut(SymmetricAlgorithm, &SessionKey) -> openpgp::Result<()> + where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { // Try each PKESK until we succeed. for pkesk in pkesks { if let Some(key) = self.keys.get(pkesk.recipient()) { let mut pair = KeyPair::new(self.ctx, key)?; - if let Ok(_) = pkesk.decrypt(&mut pair, sym_algo) - .and_then(|(algo, session_key)| decrypt(algo, &session_key)) + if pkesk.decrypt(&mut pair, sym_algo) + .map(|(algo, session_key)| decrypt(algo, &session_key)) + .unwrap_or(false) { break; } @@ -121,7 +121,7 @@ impl<'a> DecryptionHelper for Helper<'a> { } impl<'a> VerificationHelper for Helper<'a> { - fn get_public_keys(&mut self, _ids: &[openpgp::KeyHandle]) + fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<openpgp::Cert>> { Ok(Vec::new()) // Feed the Certs to the verifier here. } diff --git a/ipc/examples/gpg-agent-sign.rs b/ipc/examples/gpg-agent-sign.rs index 5bad17cd..2991089a 100644 --- a/ipc/examples/gpg-agent-sign.rs +++ b/ipc/examples/gpg-agent-sign.rs @@ -51,7 +51,7 @@ fn main() { // Compose a writer stack corresponding to the output format and // packet structure we want. First, we want the output to be // ASCII armored. - let sink = armor::Writer::new(io::stdout(), armor::Kind::Message, &[]) + let sink = armor::Writer::new(io::stdout(), armor::Kind::Message) .expect("Failed to create an armored writer."); // Stream an OpenPGP message. diff --git a/ipc/src/assuan/mod.rs b/ipc/src/assuan/mod.rs index 4234b00e..4b9842c5 100644 --- a/ipc/src/assuan/mod.rs +++ b/ipc/src/assuan/mod.rs @@ -10,7 +10,7 @@ use std::path::Path; use lalrpop_util::ParseError; use futures::{future, Async, Future, Stream}; -use tokio::net::UnixStream; +use parity_tokio_ipc::IpcConnection; use tokio_io::io; use tokio_io::AsyncRead; @@ -26,7 +26,7 @@ const MAX_LINE_LENGTH: usize = 1000; // Load the generated code. lalrpop_util::lalrpop_mod!( - #[allow(missing_docs)] grammar, "/assuan/grammar.rs"); + #[allow(missing_docs, unused_parens)] grammar, "/assuan/grammar.rs"); /// A connection to an Assuan server. /// @@ -54,15 +54,15 @@ lalrpop_util::lalrpop_mod!( /// [`Connection::data()`]: #method.data /// [`Connection::cancel()`]: #method.cancel pub struct Client { - r: BufReader<io::ReadHalf<UnixStream>>, // xxx: abstract over + r: BufReader<io::ReadHalf<IpcConnection>>, // xxx: abstract over buffer: Vec<u8>, done: bool, w: WriteState, } enum WriteState { - Ready(io::WriteHalf<UnixStream>), - Sending(future::FromErr<io::WriteAll<io::WriteHalf<tokio::net::UnixStream>, Vec<u8>>, anyhow::Error>), + Ready(io::WriteHalf<IpcConnection>), + Sending(future::FromErr<io::WriteAll<io::WriteHalf<IpcConnection>, Vec<u8>>, anyhow::Error>), Transitioning, Dead, } @@ -73,7 +73,10 @@ impl Client { -> impl Future<Item = Client, Error = anyhow::Error> where P: AsRef<Path> { - UnixStream::connect(path).from_err() + // XXX: Implement Windows support using TCP + nonce approach used upstream + // https://gnupg.org/documentation/manuals/assuan.pdf#Socket%20wrappers + future::result(IpcConnection::connect(path, &Default::default())) + .map_err(Into::into) .and_then(ConnectionFuture::new) } @@ -185,7 +188,7 @@ impl Client { struct ConnectionFuture(Option<Client>); impl ConnectionFuture { - fn new(c: UnixStream) -> Self { + fn new(c: IpcConnection) -> Self { let (r, w) = c.split(); let buffer = Vec::with_capacity(MAX_LINE_LENGTH); Self(Some(Client { diff --git a/ipc/src/gnupg.rs b/ipc/src/gnupg.rs index 4ab52d65..4b565553 100644 --- a/ipc/src/gnupg.rs +++ b/ipc/src/gnupg.rs @@ -3,6 +3,7 @@ #![warn(missing_docs)] use std::collections::BTreeMap; +use std::convert::TryFrom; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::process::Command; @@ -285,7 +286,7 @@ impl Agent { pub fn sign<'a, R>(&'a mut self, key: &'a Key<key::PublicParts, R>, algo: HashAlgorithm, digest: &'a [u8]) - -> impl Future<Item = crypto::mpis::Signature, + -> impl Future<Item = crypto::mpi::Signature, Error = anyhow::Error> + 'a where R: key::KeyRole { @@ -296,7 +297,7 @@ impl Agent { /// by the agent. pub fn decrypt<'a, R>(&'a mut self, key: &'a Key<key::PublicParts, R>, - ciphertext: &'a crypto::mpis::Ciphertext) + ciphertext: &'a crypto::mpi::Ciphertext) -> impl Future<Item = crypto::SessionKey, Error = anyhow::Error> + 'a where R: key::KeyRole @@ -307,14 +308,15 @@ impl Agent { /// Computes options that we want to communicate. fn options() -> Vec<String> { use std::env::var; - use std::ffi::CStr; let mut r = Vec::new(); if let Ok(tty) = var("GPG_TTY") { r.push(format!("OPTION ttyname={}", tty)); } else { + #[cfg(unix)] unsafe { + use std::ffi::CStr; let tty = libc::ttyname(0); if ! tty.is_null() { if let Ok(tty) = CStr::from_ptr(tty).to_str() { @@ -404,7 +406,7 @@ fn protocol_error<T>(response: &assuan::Response) -> Result<T> { impl<'a, 'b, 'c, R> Future for SigningRequest<'a, 'b, 'c, R> where R: key::KeyRole { - type Item = crypto::mpis::Signature; + type Item = crypto::mpi::Signature; type Error = anyhow::Error; fn poll(&mut self) -> std::result::Result<Async<Self::Item>, Self::Error> { @@ -518,7 +520,7 @@ struct DecryptionRequest<'a, 'b, 'c, R> { c: &'a mut assuan::Client, key: &'b Key<key::PublicParts, R>, - ciphertext: &'c crypto::mpis::Ciphertext, + ciphertext: &'c crypto::mpi::Ciphertext, options: Vec<String>, state: DecryptionRequestState, } @@ -528,7 +530,7 @@ impl<'a, 'b, 'c, R> DecryptionRequest<'a, 'b, 'c, R> { fn new(c: &'a mut assuan::Client, key: &'b Key<key::PublicParts, R>, - ciphertext: &'c crypto::mpis::Ciphertext) + ciphertext: &'c crypto::mpi::Ciphertext) -> Self { Self { c, @@ -629,7 +631,7 @@ impl<'a, 'b, 'c, R> Future for DecryptionRequest<'a, 'b, 'c, R> }, Async::Ready(None) => { let mut buf = Vec::new(); - Sexp::from_ciphertext(&self.ciphertext)? + Sexp::try_from(self.ciphertext)? .serialize(&mut buf)?; self.c.data(&buf)?; self.state = Inquire(Vec::new(), true); @@ -699,7 +701,7 @@ impl<'a> KeyPair<'a> { where R: key::KeyRole { Ok(KeyPair { - public: key.mark_role_unspecified_ref(), + public: key.role_as_unspecified(), agent_socket: ctx.socket("agent")?.into(), }) } @@ -711,10 +713,10 @@ impl<'a> crypto::Signer for KeyPair<'a> { } fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) - -> openpgp::Result<openpgp::crypto::mpis::Signature> + -> openpgp::Result<openpgp::crypto::mpi::Signature> { use crate::openpgp::types::PublicKeyAlgorithm::*; - use crate::openpgp::crypto::mpis::PublicKey; + use crate::openpgp::crypto::mpi::PublicKey; #[allow(deprecated)] match (self.public.pk_algo(), self.public.mpis()) @@ -741,11 +743,11 @@ impl<'a> crypto::Decryptor for KeyPair<'a> { self.public } - fn decrypt(&mut self, ciphertext: &crypto::mpis::Ciphertext, + fn decrypt(&mut self, ciphertext: &crypto::mpi::Ciphertext, _plaintext_len: Option<usize>) -> openpgp::Result<crypto::SessionKey> { - use crate::openpgp::crypto::mpis::{PublicKey, Ciphertext}; + use crate::openpgp::crypto::mpi::{PublicKey, Ciphertext}; match (self.public.mpis(), ciphertext) { (PublicKey::RSA { .. }, Ciphertext::RSA { .. }) diff --git a/ipc/src/lib.rs b/ipc/src/lib.rs index 623aa66d..7e6a62ab 100644 --- a/ipc/src/lib.rs +++ b/ipc/src/lib.rs @@ -41,7 +41,7 @@ use std::io::{self, Read, Write}; use std::net::{Ipv4Addr, SocketAddr, TcpStream, TcpListener}; use std::path::PathBuf; -use anyhow::Result; +use anyhow::{anyhow, Result}; use fs2::FileExt; use futures::{Future, Stream}; @@ -52,11 +52,12 @@ use tokio_io::AsyncRead; use capnp_rpc::{RpcSystem, twoparty}; use capnp_rpc::rpc_twoparty_capnp::Side; -/* Unix-specific options. */ -use std::os::unix::io::{IntoRawFd, FromRawFd}; -use std::os::unix::fs::OpenOptionsExt; - -/* XXX: Implement Windows support. */ +#[cfg(unix)] +use std::os::unix::{io::{IntoRawFd, FromRawFd}, fs::OpenOptionsExt}; +#[cfg(windows)] +use std::os::windows::io::{AsRawSocket, IntoRawSocket, FromRawSocket}; +#[cfg(windows)] +use winapi::um::winsock2; use std::process::{Command, Stdio}; use std::thread; @@ -68,6 +69,21 @@ use sequoia_core as core; pub mod assuan; pub mod gnupg; +macro_rules! platform { + { unix => { $($unix:tt)* }, windows => { $($windows:tt)* } } => { + if cfg!(unix) { + #[cfg(unix)] { $($unix)* } + #[cfg(not(unix))] { unreachable!() } + } else if cfg!(windows) { + #[cfg(windows)] { $($windows)* } + #[cfg(not(windows))] { unreachable!() } + } else { + #[cfg(not(any(unix, windows)))] compile_error!("Unsupported platform"); + unreachable!() + } + } +} + /// Servers need to implement this trait. pub trait Handler { /// Called on every connection. @@ -139,12 +155,14 @@ impl Descriptor { }; fs::create_dir_all(self.ctx.home())?; - let mut file = fs::OpenOptions::new() + let mut file = fs::OpenOptions::new(); + file .read(true) .write(true) - .create(true) - .mode(0o600) - .open(&self.rendezvous)?; + .create(true); + #[cfg(unix)] + file.mode(0o600); + let mut file = file.open(&self.rendezvous)?; file.lock_exclusive()?; let mut c = vec![]; @@ -205,17 +223,44 @@ impl Descriptor { } fn fork(&self, listener: TcpListener) -> Result<()> { - Command::new(&self.executable) + let mut cmd = Command::new(&self.executable); + cmd .arg("--home") .arg(self.ctx.home()) .arg("--lib") .arg(self.ctx.lib()) .arg("--ephemeral") .arg(self.ctx.ephemeral().to_string()) - .stdin(unsafe { Stdio::from_raw_fd(listener.into_raw_fd()) }) .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()?; + .stderr(Stdio::null()); + + platform! { + unix => { + // Pass the listening TCP socket as child stdin. + cmd.stdin(unsafe { Stdio::from_raw_fd(listener.into_raw_fd()) }); + }, + windows => { + // Sockets for `TcpListener` are not inheritable by default, so + // let's make them so, since we'll pass them to a child process. + unsafe { + match winapi::um::handleapi::SetHandleInformation( + listener.as_raw_socket() as _, + winapi::um::winbase::HANDLE_FLAG_INHERIT, + winapi::um::winbase::HANDLE_FLAG_INHERIT, + ) { + 0 => Err(std::io::Error::last_os_error()), + _ => Ok(()) + }? + }; + // We can't pass the socket to stdin directly on Windows, since + // non-overlapped (blocking) I/O handles can be redirected there. + // We use Tokio (async I/O), so we just pass it via env var rather + // than establishing a separate channel to pass the socket through. + cmd.env("SOCKET", format!("{}", listener.into_raw_socket())); + } + } + + cmd.spawn()?; Ok(()) } @@ -253,7 +298,7 @@ impl Server { if args.len() != 7 || args[1] != "--home" || args[3] != "--lib" || args[5] != "--ephemeral" { - return Err(anyhow::anyhow!( + return Err(anyhow!( "Usage: {} --home <HOMEDIR> --lib <LIBDIR> \ --ephemeral true|false", args[0])); } @@ -266,7 +311,7 @@ impl Server { cfg.set_ephemeral(); } } else { - return Err(anyhow::anyhow!( + return Err(anyhow!( "Expected 'true' or 'false' for --ephemeral, got: {}", args[6])); } @@ -276,8 +321,11 @@ impl Server { /// Turns this process into a server. /// - /// External servers must call this early on. Expects 'stdin' to - /// be a listening TCP socket. + /// External servers must call this early on. + /// + /// On Linux expects 'stdin' to be a listening TCP socket. + /// On Windows this expects `SOCKET` env var to be set to a listening socket + /// of the Windows Sockets API `SOCKET` value. /// /// # Example /// @@ -299,7 +347,14 @@ impl Server { /// } /// ``` pub fn serve(&mut self) -> Result<()> { - self.serve_listener(unsafe { TcpListener::from_raw_fd(0) }) + let listener = platform! { + unix => { unsafe { TcpListener::from_raw_fd(0) } }, + windows => { + let socket = std::env::var("SOCKET")?.parse()?; + unsafe { TcpListener::from_raw_socket(socket) } + } + }; + self.serve_listener(listener) } fn serve_listener(&mut self, l: TcpListener) -> Result<()> { @@ -427,3 +482,30 @@ pub enum Error { #[error("Connection closed unexpectedly.")] ConnectionClosed(Vec<u8>), } + +// Global initialization and cleanup of the Windows Sockets API (WSA) module. +// NOTE: This has to be top-level in order for `ctor::{ctor, dtor}` to work. +#[cfg(windows)] +use std::sync::atomic::{AtomicBool, Ordering}; +#[cfg(windows)] +static WSA_INITED: AtomicBool = AtomicBool::new(false); + +#[cfg(windows)] +#[ctor::ctor] +fn wsa_startup() { + unsafe { + let ret = winsock2::WSAStartup( + 0x202, // version 2.2 + &mut std::mem::zeroed(), + ); + WSA_INITED.store(ret != 0, Ordering::SeqCst); + } +} + +#[cfg(windows)] +#[ctor::dtor] +fn wsa_cleanup() { + if WSA_INITED.load(Ordering::SeqCst) { + let _ = unsafe { winsock2::WSACleanup() }; + } +} diff --git a/ipc/tests/gpg-agent.rs b/ipc/tests/gpg-agent.rs index 45c532e6..c303cdd4 100644 --- a/ipc/tests/gpg-agent.rs +++ b/ipc/tests/gpg-agent.rs @@ -12,7 +12,7 @@ use crate::openpgp::types::{ SymmetricAlgorithm, }; use crate::openpgp::crypto::SessionKey; -use crate::openpgp::parse::stream::*; +use crate::openpgp::parse::{Parse, stream::*}; use crate::openpgp::serialize::{Serialize, stream::*}; use crate::openpgp::cert::prelude::*; use crate::openpgp::policy::Policy; @@ -27,7 +27,7 @@ macro_rules! make_context { Err(e) => { eprintln!("SKIP: Failed to create GnuPG context: {}\n\ SKIP: Is GnuPG installed?", e); - return; + return Ok(()); }, }; match ctx.start("gpg-agent") { @@ -35,7 +35,7 @@ macro_rules! make_context { Err(e) => { eprintln!("SKIP: Failed to create GnuPG context: {}\n\ SKIP: Is the GnuPG agent installed?", e); - return; + return Ok(()); }, } ctx @@ -43,23 +43,25 @@ macro_rules! make_context { } #[test] -fn nop() { +fn nop() -> openpgp::Result<()> { let ctx = make_context!(); let mut agent = Agent::connect(&ctx).wait().unwrap(); agent.send("NOP").unwrap(); let response = agent.wait().collect::<Vec<_>>(); assert_eq!(response.len(), 1); assert!(response[0].is_ok()); + Ok(()) } #[test] -fn help() { +fn help() -> openpgp::Result<()> { let ctx = make_context!(); let mut agent = Agent::connect(&ctx).wait().unwrap(); agent.send("HELP").unwrap(); let response = agent.wait().collect::<Vec<_>>(); assert!(response.len() > 3); assert!(response.iter().last().unwrap().is_ok()); + Ok(()) } const MESSAGE: &'static str = "дружба"; @@ -79,7 +81,7 @@ fn gpg_import(ctx: &Context, what: &[u8]) { } #[test] -fn sign() { +fn sign() -> openpgp::Result<()> { use self::CipherSuite::*; use openpgp::policy::StandardPolicy as P; @@ -131,8 +133,8 @@ fn sign() { let helper = Helper { cert: &cert }; // Now, create a verifier with a helper using the given Certs. - let mut verifier = - Verifier::from_bytes(p, &message, helper, None).unwrap(); + let mut verifier = VerifierBuilder::from_bytes(&message)? + .with_policy(p, None, helper)?; // Verify the data. let mut sink = Vec::new(); @@ -145,7 +147,7 @@ fn sign() { } impl<'a> VerificationHelper for Helper<'a> { - fn get_public_keys(&mut self, _ids: &[openpgp::KeyHandle]) + fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<openpgp::Cert>> { // Return public keys for signature verification here. Ok(vec![self.cert.clone()]) @@ -184,10 +186,11 @@ fn sign() { } } } + Ok(()) } #[test] -fn decrypt() { +fn decrypt() -> openpgp::Result<()> { use self::CipherSuite::*; use openpgp::policy::StandardPolicy as P; @@ -207,18 +210,19 @@ fn decrypt() { let mut message = Vec::new(); { - let recipient = + let recipients = cert.keys().with_policy(p, None).alive().revoked(false) .for_transport_encryption() - .map(|ka| ka.key().into()) - .nth(0).unwrap(); + .map(|ka| ka.key()) + .collect::<Vec<_>>(); // Start streaming an OpenPGP message. let message = Message::new(&mut message); // We want to encrypt a literal data packet. let encryptor = - Encryptor::for_recipient(message, recipient).build().unwrap(); + Encryptor::for_recipients(message, recipients) + .build().unwrap(); // Emit a literal data packet. let mut literal_writer = LiteralWriter::new( @@ -237,8 +241,8 @@ fn decrypt() { let helper = Helper { policy: p, ctx: &ctx, cert: &cert, }; // Now, create a decryptor with a helper using the given Certs. - let mut decryptor = Decryptor::from_bytes(p, &message, helper, None) - .unwrap(); + let mut decryptor = DecryptorBuilder::from_bytes(&message).unwrap() + .with_policy(p, None, helper).unwrap(); // Decrypt the data. let mut sink = Vec::new(); @@ -252,7 +256,7 @@ fn decrypt() { } impl<'a> VerificationHelper for Helper<'a> { - fn get_public_keys(&mut self, _ids: &[openpgp::KeyHandle]) + fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<openpgp::Cert>> { // Return public keys for signature verification here. Ok(Vec::new()) @@ -272,8 +276,7 @@ fn decrypt() { sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> openpgp::Result<Option<openpgp::Fingerprint>> - where D: FnMut(SymmetricAlgorithm, &SessionKey) -> - openpgp::Result<()> + where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { let mut keypair = KeyPair::new( self.ctx, @@ -283,11 +286,13 @@ fn decrypt() { .unwrap(); pkesks[0].decrypt(&mut keypair, sym_algo) - .and_then(|(algo, session_key)| decrypt(algo, &session_key)) - .map(|_| None) + .map(|(algo, session_key)| decrypt(algo, &session_key)); + // XXX: In production code, return the Fingerprint of the // recipient's Cert here + Ok(None) } } } + Ok(()) } |