summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2018-07-02 11:38:28 +0200
committerJustus Winter <justus@sequoia-pgp.org>2018-07-02 13:09:26 +0200
commit4f6f0e48ad0ccc5556945a0c7bde58441f98e161 (patch)
tree949628d27114fb03c7a233188675c8c9e47163f8
parent9cf384a58077cfaa6ab9af48cc023b010e2d2f8d (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.rs99
-rw-r--r--openpgp/examples/sign.rs55
-rw-r--r--openpgp/src/serialize/stream.rs68
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()),