summaryrefslogtreecommitdiffstats
path: root/ipc/examples
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2019-06-08 14:47:27 +0200
committerJustus Winter <justus@sequoia-pgp.org>2019-06-26 09:20:33 +0200
commit433437b20b8cb33263a293f34589e29eeacabfca (patch)
tree9b2d2ec214f8595f6e8c7e4a741ea359ba89f586 /ipc/examples
parentadcf04a7a2489ff4d5fe983fe7caa76c22fded2b (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.rs38
-rw-r--r--ipc/examples/gpg-agent-decrypt.rs161
-rw-r--r--ipc/examples/gpg-agent-sign.rs76
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");
+}