diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2018-06-22 15:23:27 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2018-06-29 15:18:11 +0200 |
commit | 1a1a6feb9f2ed2e0c1986a415a5761b6dc6025c6 (patch) | |
tree | 63c038a418dcbce2f1ced9e64b5808a7951b08a4 | |
parent | fd900de3dc87d1c3ac93ec9e2992befd8d71c90a (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.rs | 115 | ||||
-rw-r--r-- | openpgp/src/signature.rs | 165 |
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(¶ms, &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"); + } + } + } } |