summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2018-06-22 15:23:27 +0200
committerJustus Winter <justus@sequoia-pgp.org>2018-06-29 15:18:11 +0200
commit1a1a6feb9f2ed2e0c1986a415a5761b6dc6025c6 (patch)
tree63c038a418dcbce2f1ced9e64b5808a7951b08a4
parentfd900de3dc87d1c3ac93ec9e2992befd8d71c90a (diff)
openpgp: Implement signing.
- This implements the low-level functionality necessary to create signatures using RSA, DSA, ECDSA, and EdDSA.
-rw-r--r--openpgp/examples/sign-detached.rs115
-rw-r--r--openpgp/src/signature.rs165
2 files changed, 274 insertions, 6 deletions
diff --git a/openpgp/examples/sign-detached.rs b/openpgp/examples/sign-detached.rs
new file mode 100644
index 00000000..4cc0b021
--- /dev/null
+++ b/openpgp/examples/sign-detached.rs
@@ -0,0 +1,115 @@
+/// This program demonstrates how to make a detached signature.
+
+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;
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+ if args.len() < 2 {
+ panic!("A simple encryption 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();
+
+ // 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
+ }
+ });
+
+ // 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);
+
+ // 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);
+
+ // 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");
+ }
+ }
+ }
+}
diff --git a/openpgp/src/signature.rs b/openpgp/src/signature.rs
index 1f08620f..569ed85b 100644
--- a/openpgp/src/signature.rs
+++ b/openpgp/src/signature.rs
@@ -3,7 +3,7 @@ use std::fmt;
use constants::Curve;
use Error;
use Result;
-use mpis::MPIs;
+use mpis::{MPI, MPIs};
use HashAlgorithm;
use PublicKeyAlgorithm;
use SignatureType;
@@ -15,7 +15,7 @@ use packet;
use subpacket::SubpacketArea;
use serialize::Serialize;
-use nettle::{dsa, ecdsa, ed25519, rsa};
+use nettle::{dsa, ecdsa, ed25519, Hash, rsa, Yarrow};
use nettle::rsa::verify_digest_pkcs1;
#[cfg(test)]
@@ -154,7 +154,118 @@ impl Signature {
// XXX: Add subpacket handling.
- // XXX: Add signature generation.
+ /// Signs `hash` using `signer`.
+ pub fn sign_hash(&mut self, signer: &Key, signer_sec: &MPIs,
+ hash_algo: HashAlgorithm, mut hash: Box<Hash>)
+ -> Result<()> {
+ use PublicKeyAlgorithm::*;
+ use mpis::MPIs::*;
+
+ let mut rng = Yarrow::default();
+
+ // Fill out some fields, then hash the packet.
+ self.pk_algo = signer.pk_algo;
+ self.hash_algo = hash_algo;
+ self.hash(&mut hash);
+
+ // Compute the digest.
+ let mut digest = vec![0u8; hash.digest_size()];
+ hash.digest(&mut digest);
+ self.hash_prefix[0] = digest[0];
+ self.hash_prefix[1] = digest[1];
+
+ self.mpis = match (signer.pk_algo, &signer.mpis, signer_sec) {
+ (RSAEncryptSign,
+ &RSAPublicKey { ref e, ref n },
+ &RSASecretKey { ref p, ref q, ref d, .. }) => {
+ let public = rsa::PublicKey::new(&n.value, &e.value)?;
+ let secret = rsa::PrivateKey::new(&d.value, &p.value,
+ &q.value, Option::None)?;
+
+ // The signature has the length of the modulus.
+ let mut sig = vec![0u8; n.value.len()];
+
+ // As described in [Section 5.2.2 and 5.2.3 of RFC 4880],
+ // to verify the signature, we need to encode the
+ // signature data in a PKCS1-v1.5 packet.
+ //
+ // [Section 5.2.2 and 5.2.3 of RFC 4880]:
+ // https://tools.ietf.org/html/rfc4880#section-5.2.2
+ rsa::sign_digest_pkcs1(&public, &secret, &digest, hash_algo.oid()?,
+ &mut rng, &mut sig)?;
+
+ MPIs::RSASignature {
+ s: MPI::new(&sig),
+ }
+ },
+
+ (DSA,
+ &DSAPublicKey { ref p, ref q, ref g, .. },
+ &DSASecretKey { ref x }) => {
+ let params = dsa::Params::new(&p.value, &q.value, &g.value);
+ let secret = dsa::PrivateKey::new(&x.value);
+
+ let sig = dsa::sign(&params, &secret, &digest, &mut rng)?;
+
+ MPIs::DSASignature {
+ r: MPI::new(&sig.r()),
+ s: MPI::new(&sig.s()),
+ }
+ },
+
+ (EdDSA,
+ &EdDSAPublicKey { ref curve, ref q },
+ &EdDSASecretKey { ref scalar }) => match curve {
+ Curve::Ed25519 => {
+ let public = q.decode_point(&Curve::Ed25519)?.0;
+
+ let mut sig = vec![0; ed25519::ED25519_SIGNATURE_SIZE];
+ ed25519::sign(public, &scalar.value, &digest, &mut sig)?;
+
+ MPIs::EdDSASignature {
+ r: MPI::new(&sig[..32]),
+ s: MPI::new(&sig[32..]),
+ }
+ },
+ _ => return Err(
+ Error::UnsupportedEllipticCurve(curve.clone()).into()),
+ },
+
+ (ECDSA,
+ &ECDSAPublicKey { ref curve, .. },
+ &ECDSASecretKey { ref scalar }) => {
+ let secret = match curve {
+ Curve::NistP256 =>
+ ecdsa::PrivateKey::new::<ecdsa::Secp256r1>(
+ &scalar.value)?,
+ Curve::NistP384 =>
+ ecdsa::PrivateKey::new::<ecdsa::Secp384r1>(
+ &scalar.value)?,
+ Curve::NistP521 =>
+ ecdsa::PrivateKey::new::<ecdsa::Secp521r1>(
+ &scalar.value)?,
+ _ =>
+ return Err(
+ Error::UnsupportedEllipticCurve(curve.clone())
+ .into()),
+ };
+
+ let sig = ecdsa::sign(&secret, &digest, &mut rng);
+
+ MPIs::ECDSASignature {
+ r: MPI::new(&sig.r()),
+ s: MPI::new(&sig.s()),
+ }
+ },
+
+ _ => return Err(Error::InvalidArgument(format!(
+ "unsupported combination of algorithm {:?}, key {:?}, \
+ and secret key {:?}",
+ self.pk_algo, signer, signer_sec)).into()),
+ };
+
+ Ok(())
+ }
/// Verifies the signature against `hash`.
pub fn verify_hash(&self, key: &Key, hash_algo: HashAlgorithm, hash: &[u8])
@@ -398,12 +509,12 @@ impl Signature {
#[cfg(test)]
mod test {
+ use super::*;
+ use TPK;
+
#[cfg(feature = "compression-deflate")]
#[test]
fn signature_verification_test() {
- use super::*;
-
- use TPK;
use parse::PacketParser;
struct Test<'a> {
@@ -504,4 +615,46 @@ mod test {
assert_eq!(good, test.good, "Signature verification failed.");
}
}
+
+ #[test]
+ fn sign_verify() {
+ use key::SecretKey;
+
+ let hash_algo = HashAlgorithm::SHA512;
+ let mut hash = vec![0; hash_algo.context().unwrap().digest_size()];
+ Yarrow::default().random(&mut hash);
+
+ for key in &[
+ "keys/testy-private.pgp",
+ "keys/dennis-simon-anton-private.pgp",
+ "keys/erika-corinna-daniela-simone-antonia-nistp256-private.pgp",
+ "keys/erika-corinna-daniela-simone-antonia-nistp384-private.pgp",
+ "keys/erika-corinna-daniela-simone-antonia-nistp521-private.pgp",
+ "keys/emmelie-dorothea-dina-samantha-awina-ed25519-private.pgp",
+ ] {
+ let tpk = TPK::from_file(path_to(key)).unwrap();
+ let pair = tpk.primary();
+
+ if let Some(SecretKey::Unencrypted{ mpis: ref sec }) = pair.secret {
+ let mut sig = Signature::new(SignatureType::Binary);
+ let mut hash = hash_algo.context().unwrap();
+
+ // Make signature.
+ sig.sign_hash(&pair, sec, hash_algo, hash).unwrap();
+
+ // Good signature.
+ let mut hash = hash_algo.context().unwrap();
+ sig.hash(&mut hash);
+ let mut digest = vec![0u8; hash.digest_size()];
+ hash.digest(&mut digest);
+ assert!(sig.verify_hash(&pair, hash_algo, &digest).unwrap());
+
+ // Bad signature.
+ digest[0] ^= 0xff;
+ assert!(! sig.verify_hash(&pair, hash_algo, &digest).unwrap());
+ } else {
+ panic!("secret key is encrypted/missing");
+ }
+ }
+ }
}