diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2018-07-02 11:38:28 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2018-07-02 13:09:26 +0200 |
commit | 4f6f0e48ad0ccc5556945a0c7bde58441f98e161 (patch) | |
tree | 949628d27114fb03c7a233188675c8c9e47163f8 | |
parent | 9cf384a58077cfaa6ab9af48cc023b010e2d2f8d (diff) |
openpgp: Implement detached signature generation in the Signer.
- Also, simplify the example accordingly.
- Add an example for a normal signature.
-rw-r--r-- | openpgp/examples/sign-detached.rs | 99 | ||||
-rw-r--r-- | openpgp/examples/sign.rs | 55 | ||||
-rw-r--r-- | openpgp/src/serialize/stream.rs | 68 |
3 files changed, 125 insertions, 97 deletions
diff --git a/openpgp/examples/sign-detached.rs b/openpgp/examples/sign-detached.rs index 4cc0b021..a4052907 100644 --- a/openpgp/examples/sign-detached.rs +++ b/openpgp/examples/sign-detached.rs @@ -2,19 +2,15 @@ use std::env; use std::io; -use std::iter; -extern crate time; extern crate openpgp; -use openpgp::{armor, Key, Signature}; -use openpgp::constants::{SignatureType, HashAlgorithm}; -use openpgp::SecretKey; -use openpgp::serialize::Serialize; +use openpgp::armor; +use openpgp::serialize::stream::{wrap, Signer}; fn main() { let args: Vec<String> = env::args().collect(); if args.len() < 2 { - panic!("A simple encryption filter.\n\n\ + panic!("A simple filter creating a detached signature.\n\n\ Usage: {} <secret-keyfile> [<secret-keyfile>...] \ <input >output\n", args[0]); } @@ -29,87 +25,22 @@ fn main() { .expect("Failed to read key") }).collect(); - // Hash the file. - let hash_algo = HashAlgorithm::SHA512; - let hashes = openpgp::hash_file(io::stdin(), &[hash_algo]) - .expect("Failed to hash file"); - - // Get the one hash we computed. - let hash = &hashes[0].1; - // Compose a writer stack corresponding to the output format and // packet structure we want. First, we want the output to be as // armored. - let mut sink = armor::Writer::new(io::stdout(), armor::Kind::Message); - - for tsk in tsks { - // We need to find all (sub)keys capable of signing. - let can_sign = |key: &Key, sig: &Signature| -> bool { - sig.key_flags().can_sign() - // Check expiry. - && ! sig.signature_expired() - && ! sig.key_expired(key) - }; - - // Gather all signing-capable subkeys. - let subkeys = tsk.subkeys().filter_map(|skb| { - let key = skb.subkey(); - // The first signature is the most recent binding - // signature. - if skb.selfsigs().next() - .map(|sig| can_sign(key, sig)) - .unwrap_or(false) { - Some(key) - } else { - None - } - }); + let sink = armor::Writer::new(io::stdout(), armor::Kind::Signature); - // Check if the primary key is signing-capable. - let primary_can_sign = - // The key capabilities are defined by the most recent - // binding signature of the primary user id (or the - // most recent user id binding if no user id is marked - // as primary). In any case, this is the first user id. - tsk.userids().next().map(|ub| { - ub.selfsigs().next() - .map(|sig| can_sign(tsk.primary(), sig)) - .unwrap_or(false) - }).unwrap_or(false); + // Now, create a signer that emits a detached signature. + let mut signer = Signer::detached( + wrap(sink), &tsks.iter().collect::<Vec<&openpgp::TPK>>()) + .expect("Failed to create signer"); - // If the primary key is signing-capable, prepend to - // subkeys via iterator magic. - let keys = - iter::once(tsk.primary()) - .filter(|_| primary_can_sign) - .chain(subkeys); + // Finally, just copy all the data. + io::copy(&mut io::stdin(), &mut signer) + .expect("Failed to sign data"); - // For every suitable key, compute and emit a signature. - for key in keys { - if let &SecretKey::Unencrypted { mpis: ref sec } = - key.secret.as_ref().expect("No secret key") - { - // Clone hash so that we can hash the signature - // packet, and compute the digest. - let mut hash = hash.clone(); - - // Make and hash a signature packet. - let mut sig = Signature::new(SignatureType::Binary); - sig.set_signature_creation_time(time::now()) - .expect("Failed to set creation time"); - sig.set_issuer_fingerprint(key.fingerprint()) - .expect("Failed to set issuer fingerprint"); - sig.set_issuer(key.keyid()) - .expect("Failed to set issuer"); - - // Make signature. - sig.sign_hash(&key, sec, hash_algo, hash) - .expect("Failed to compute signature"); - - // And emit the packet. - sig.serialize(&mut sink) - .expect("Failed to write packet"); - } - } - } + // Teardown the stack to ensure all the data is written. + let _ = signer.into_inner() + .expect("Failed to write data") + .unwrap(); } diff --git a/openpgp/examples/sign.rs b/openpgp/examples/sign.rs new file mode 100644 index 00000000..f1b8282d --- /dev/null +++ b/openpgp/examples/sign.rs @@ -0,0 +1,55 @@ +/// This program demonstrates how to sign data. + +use std::env; +use std::io; + +extern crate openpgp; +use openpgp::armor; +use openpgp::serialize::stream::{wrap, LiteralWriter, Signer}; + +fn main() { + let args: Vec<String> = env::args().collect(); + if args.len() < 2 { + panic!("A simple signing filter.\n\n\ + Usage: {} <secret-keyfile> [<secret-keyfile>...] \ + <input >output\n", args[0]); + } + + // Read the transferable secret keys from the given files. + let tsks: Vec<openpgp::TPK> = args[1..].iter().map(|f| { + openpgp::TPK::from_reader( + // Use an openpgp::Reader so that we accept both armored + // and plain PGP data. + openpgp::Reader::from_file(f) + .expect("Failed to open file")) + .expect("Failed to read key") + }).collect(); + + // Compose a writer stack corresponding to the output format and + // packet structure we want. First, we want the output to be as + // armored. + let sink = armor::Writer::new(io::stdout(), armor::Kind::Message); + + // Now, create a signer that emits a detached signature. + let signer = Signer::new( + wrap(sink), &tsks.iter().collect::<Vec<&openpgp::TPK>>()) + .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, 'b', None, 0) + .expect("Failed to create literal writer"); + + // Finally, just copy all the data. + io::copy(&mut io::stdin(), &mut literal) + .expect("Failed to sign data"); + + // Teardown the stack to ensure all the data is written. + let signer = literal.into_inner() + .expect("Failed to write data") + .unwrap(); + + let _ = signer.into_inner() + .expect("Failed to write data") + .unwrap(); +} diff --git a/openpgp/src/serialize/stream.rs b/openpgp/src/serialize/stream.rs index 70e9bf41..4f4d27ee 100644 --- a/openpgp/src/serialize/stream.rs +++ b/openpgp/src/serialize/stream.rs @@ -168,6 +168,7 @@ pub struct Signer<'a> { // digests. inner: Option<writer::Stack<'a, Cookie>>, keys: Vec<&'a Key>, + detached: bool, hash: Box<Hash>, cookie: Cookie, } @@ -196,8 +197,41 @@ impl<'a> Signer<'a> { /// # Ok(()) /// # } /// ``` - pub fn new(mut inner: writer::Stack<'a, Cookie>, signers: &[&'a TPK]) + pub fn new(inner: writer::Stack<'a, Cookie>, signers: &[&'a TPK]) -> Result<writer::Stack<'a, Cookie>> { + Self::make(inner, signers, false) + } + + /// Creates a signer for a detached signature. + /// + /// # Example + /// + /// ``` + /// use openpgp::serialize::stream::{wrap, Signer, LiteralWriter}; + /// # use openpgp::{Result, TPK}; + /// # let tsk = TPK::from_bytes(include_bytes!( + /// # "../../tests/data/keys/testy-new-private.pgp")) + /// # .unwrap(); + /// # f(tsk).unwrap(); + /// # fn f(tsk: TPK) -> Result<()> { + /// let mut o = vec![]; + /// { + /// let mut signer = Signer::detached(wrap(&mut o), &[&tsk])?; + /// signer.write_all(b"Make it so, number one!")?; + /// // In reality, just io::copy() the file to be signed. + /// let _ = signer.into_inner()?.unwrap(); + /// } + /// # Ok(()) + /// # } + /// ``` + pub fn detached(inner: writer::Stack<'a, Cookie>, signers: &[&'a TPK]) + -> Result<writer::Stack<'a, Cookie>> { + Self::make(inner, signers, true) + } + + fn make(mut inner: writer::Stack<'a, Cookie>, signers: &[&'a TPK], + detached: bool) + -> Result<writer::Stack<'a, Cookie>> { // Just always use SHA512. let hash_algo = HashAlgorithm::SHA512; let mut signing_keys = Vec::new(); @@ -255,24 +289,27 @@ impl<'a> Signer<'a> { } } - // For every key we collected, build and emit a one pass - // signature packet. - for (i, key) in signing_keys.iter().enumerate() { - let mut ops = OnePassSig::new(SignatureType::Binary) - .pk_algo(key.pk_algo) - .hash_algo(hash_algo) - .issuer(key.fingerprint().to_keyid()); - - if i == signing_keys.len() - 1 { - ops.last = 1; + if ! detached { + // For every key we collected, build and emit a one pass + // signature packet. + for (i, key) in signing_keys.iter().enumerate() { + let mut ops = OnePassSig::new(SignatureType::Binary) + .pk_algo(key.pk_algo) + .hash_algo(hash_algo) + .issuer(key.fingerprint().to_keyid()); + + if i == signing_keys.len() - 1 { + ops.last = 1; + } + ops.serialize(&mut inner)?; } - ops.serialize(&mut inner)?; } let level = inner.cookie_ref().level + 1; Ok(Box::new(Signer { inner: Some(inner), keys: signing_keys, + detached: detached, hash: hash_algo.context()?, cookie: Cookie { level: level, @@ -334,7 +371,12 @@ impl<'a> fmt::Debug for Signer<'a> { impl<'a> Write for Signer<'a> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { let written = match self.inner.as_mut() { - Some(ref mut w) => w.write(buf), + // If we are creating a normal signature, pass data + // through. + Some(ref mut w) if ! self.detached => w.write(buf), + // If we are creating a detached signature, just hash all + // bytes. + Some(_) => Ok(buf.len()), // When we are popped off the stack, we have no inner // writer. Just hash all bytes. None => Ok(buf.len()), |