diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2019-06-08 14:47:27 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2019-06-26 09:20:33 +0200 |
commit | 433437b20b8cb33263a293f34589e29eeacabfca (patch) | |
tree | 9b2d2ec214f8595f6e8c7e4a741ea359ba89f586 /ipc/examples | |
parent | adcf04a7a2489ff4d5fe983fe7caa76c22fded2b (diff) |
ipc: GnuPG RPC support.
- This allows us to communicate with gpg-agent, and use it for
cryptographic operations.
Diffstat (limited to 'ipc/examples')
-rw-r--r-- | ipc/examples/gpg-agent-client.rs | 38 | ||||
-rw-r--r-- | ipc/examples/gpg-agent-decrypt.rs | 161 | ||||
-rw-r--r-- | ipc/examples/gpg-agent-sign.rs | 76 |
3 files changed, 275 insertions, 0 deletions
diff --git a/ipc/examples/gpg-agent-client.rs b/ipc/examples/gpg-agent-client.rs new file mode 100644 index 00000000..c586cfca --- /dev/null +++ b/ipc/examples/gpg-agent-client.rs @@ -0,0 +1,38 @@ +/// Connects to and sends commands to gpg-agent. + +extern crate futures; +use futures::future::Future; +use futures::stream::Stream; +extern crate clap; +extern crate sequoia_ipc as ipc; +use ipc::gnupg::{Context, Agent}; + +fn main() { + let matches = clap::App::new("gpg-agent-client") + .version(env!("CARGO_PKG_VERSION")) + .about("Connects to and sends commands to gpg-agent.") + .arg(clap::Arg::with_name("homedir").value_name("PATH") + .long("homedir") + .help("Use this GnuPG home directory, default: $GNUPGHOME")) + .arg(clap::Arg::with_name("commands").value_name("COMMAND") + .required(true) + .multiple(true) + .help("Commands to send to the server")) + .get_matches(); + + let ctx = if let Some(homedir) = matches.value_of("homedir") { + Context::with_homedir(homedir).unwrap() + } else { + Context::new().unwrap() + }; + let mut agent = Agent::connect(&ctx).wait().unwrap(); + + for command in matches.values_of("commands").unwrap() { + eprintln!("> {}", command); + agent.send(command).unwrap(); + agent.by_ref().for_each(|response| { + eprintln!("< {:?}", response); + Ok(()) + }).wait().unwrap(); + } +} diff --git a/ipc/examples/gpg-agent-decrypt.rs b/ipc/examples/gpg-agent-decrypt.rs new file mode 100644 index 00000000..2e423f12 --- /dev/null +++ b/ipc/examples/gpg-agent-decrypt.rs @@ -0,0 +1,161 @@ +/// Decrypts data using the openpgp crate and secrets in gpg-agent. + +use std::collections::HashMap; +use std::io; + +extern crate clap; +extern crate sequoia_openpgp as openpgp; +extern crate sequoia_ipc as ipc; + +use openpgp::crypto::SessionKey; +use openpgp::constants::SymmetricAlgorithm; +use openpgp::parse::{ + Parse, + stream::{ + DecryptionHelper, + Decryptor, + VerificationHelper, + VerificationResult, + MessageStructure, + MessageLayer, + }, +}; +use ipc::gnupg::{Context, KeyPair}; + +fn main() { + let matches = clap::App::new("gpg-agent-decrypt") + .version(env!("CARGO_PKG_VERSION")) + .about("Connects to gpg-agent and decrypts a message.") + .arg(clap::Arg::with_name("homedir").value_name("PATH") + .long("homedir") + .help("Use this GnuPG home directory, default: $GNUPGHOME")) + .arg(clap::Arg::with_name("tpk").value_name("TPK") + .required(true) + .multiple(true) + .help("Public part of the secret keys managed by gpg-agent")) + .get_matches(); + + let ctx = if let Some(homedir) = matches.value_of("homedir") { + Context::with_homedir(homedir).unwrap() + } else { + Context::new().unwrap() + }; + + // Read the TPKs from the given files. + let tpks = + matches.values_of("tpk").expect("required").map(|f| { + openpgp::TPK::from_file(f) + .expect("Failed to read key") + }).collect(); + + // Now, create a decryptor with a helper using the given TPKs. + let mut decryptor = + Decryptor::from_reader(io::stdin(), Helper::new(&ctx, tpks), None) + .unwrap(); + + // Finally, stream the decrypted data to stdout. + io::copy(&mut decryptor, &mut io::stdout()) + .expect("Decryption failed"); +} + +/// This helper provides secrets for the decryption, fetches public +/// keys for the signature verification and implements the +/// verification policy. +struct Helper<'a> { + ctx: &'a Context, + keys: HashMap<openpgp::KeyID, openpgp::packet::Key>, +} + +impl<'a> Helper<'a> { + /// Creates a Helper for the given TPKs with appropriate secrets. + fn new(ctx: &'a Context, tpks: Vec<openpgp::TPK>) -> Self { + // Map (sub)KeyIDs to secrets. + let mut keys = HashMap::new(); + for tpk in tpks { + for (sig, _, key) in tpk.keys_all() { + if sig.map(|s| (s.key_flags().can_encrypt_at_rest() + || s.key_flags().can_encrypt_for_transport())) + .unwrap_or(false) + { + keys.insert(key.keyid(), key.clone()); + } + } + } + + Helper { ctx, keys, } + } +} + +impl<'a> DecryptionHelper for Helper<'a> { + fn decrypt<D>(&mut self, + pkesks: &[openpgp::packet::PKESK], + _skesks: &[openpgp::packet::SKESK], + mut decrypt: D) + -> openpgp::Result<Option<openpgp::Fingerprint>> + where D: FnMut(SymmetricAlgorithm, &SessionKey) -> openpgp::Result<()> + { + // 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) + .and_then(|(algo, session_key)| decrypt(algo, &session_key)) + { + break; + } + } + } + // XXX: In production code, return the Fingerprint of the + // recipient's TPK here + Ok(None) + } +} + +impl<'a> VerificationHelper for Helper<'a> { + fn get_public_keys(&mut self, _ids: &[openpgp::KeyID]) + -> failure::Fallible<Vec<openpgp::TPK>> { + Ok(Vec::new()) // Feed the TPKs to the verifier here. + } + fn check(&mut self, structure: &MessageStructure) + -> failure::Fallible<()> { + use self::VerificationResult::*; + for layer in structure.iter() { + match layer { + MessageLayer::Compression { algo } => + eprintln!("Compressed using {}", algo), + MessageLayer::Encryption { sym_algo, aead_algo } => + if let Some(aead_algo) = aead_algo { + eprintln!("Encrypted and protected using {}/{}", + sym_algo, aead_algo); + } else { + eprintln!("Encrypted using {}", sym_algo); + }, + MessageLayer::SignatureGroup { ref results } => + for result in results { + match result { + GoodChecksum(ref sig, ..) => { + let issuer = sig.issuer() + .expect("good checksum has an issuer"); + eprintln!("Good signature from {}", issuer); + }, + MissingKey(ref sig) => { + let issuer = sig.issuer() + .expect("missing key checksum has an \ + issuer"); + eprintln!("No key to check signature from {}", + issuer); + }, + BadChecksum(ref sig) => + if let Some(issuer) = sig.issuer() { + eprintln!("Bad signature from {}", issuer); + } else { + eprintln!("Bad signature without issuer \ + information"); + }, + } + } + } + } + Ok(()) // Implement your verification policy here. + } +} diff --git a/ipc/examples/gpg-agent-sign.rs b/ipc/examples/gpg-agent-sign.rs new file mode 100644 index 00000000..8525abbb --- /dev/null +++ b/ipc/examples/gpg-agent-sign.rs @@ -0,0 +1,76 @@ +/// Signs data using the openpgp crate and secrets in gpg-agent. + +use std::io; + +extern crate clap; +extern crate sequoia_openpgp as openpgp; +extern crate sequoia_ipc as ipc; + +use openpgp::armor; +use openpgp::constants::DataFormat; +use openpgp::parse::Parse; +use openpgp::serialize::stream::{Message, LiteralWriter, Signer}; +use ipc::gnupg::{Context, KeyPair}; + +fn main() { + let matches = clap::App::new("gpg-agent-sign") + .version(env!("CARGO_PKG_VERSION")) + .about("Connects to gpg-agent and creates a dummy signature.") + .arg(clap::Arg::with_name("homedir").value_name("PATH") + .long("homedir") + .help("Use this GnuPG home directory, default: $GNUPGHOME")) + .arg(clap::Arg::with_name("tpk").value_name("TPK") + .required(true) + .multiple(true) + .help("Public part of the secret keys managed by gpg-agent")) + .get_matches(); + + let ctx = if let Some(homedir) = matches.value_of("homedir") { + Context::with_homedir(homedir).unwrap() + } else { + Context::new().unwrap() + }; + + // Read the TPKs from the given files. + let tpks = + matches.values_of("tpk").expect("required").map(|f| { + openpgp::TPK::from_file(f) + .expect("Failed to read key") + }).collect::<Vec<_>>(); + + // Construct a KeyPair for every signing-capable (sub)key. + let mut keypairs = tpks.iter().flat_map(|tpk| tpk.keys_valid().signing_capable().filter_map(|(_, _, key)| { + KeyPair::new(&ctx, key).ok() + })).collect::<Vec<KeyPair>>(); + + // Well, this is awkward... + let signers = keypairs.iter_mut() + .map(|s| -> &mut dyn openpgp::crypto::Signer { s }) + .collect(); + + // 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, &[]) + .expect("Failed to create an armored writer."); + + // Stream an OpenPGP message. + let message = Message::new(sink); + + // Now, create a signer that emits a signature. + let signer = Signer::new(message, signers, None) + .expect("Failed to create signer"); + + // Then, create a literal writer to wrap the data in a literal + // message packet. + let mut literal = LiteralWriter::new(signer, DataFormat::Binary, None, None) + .expect("Failed to create literal writer"); + + // Copy all the data. + io::copy(&mut io::stdin(), &mut literal) + .expect("Failed to sign data"); + + // Finally, teardown the stack to ensure all the data is written. + literal.finalize() + .expect("Failed to write data"); +} |