summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--openpgp/Cargo.toml2
-rw-r--r--openpgp/src/hash.rs276
-rw-r--r--openpgp/src/lib.rs51
-rw-r--r--openpgp/src/parse/key.rs39
-rw-r--r--openpgp/tests/data/keys/hash-algos/SHA224-private.gpgbin0 -> 2510 bytes
-rw-r--r--openpgp/tests/data/keys/hash-algos/SHA224.gpgbin0 -> 1208 bytes
-rw-r--r--openpgp/tests/data/keys/hash-algos/SHA256-private.gpgbin0 -> 2510 bytes
-rw-r--r--openpgp/tests/data/keys/hash-algos/SHA256.gpgbin0 -> 1208 bytes
-rw-r--r--openpgp/tests/data/keys/hash-algos/SHA384-private.gpgbin0 -> 2510 bytes
-rw-r--r--openpgp/tests/data/keys/hash-algos/SHA384.gpgbin0 -> 1208 bytes
-rw-r--r--openpgp/tests/data/keys/hash-algos/SHA512-private.gpgbin0 -> 2510 bytes
-rw-r--r--openpgp/tests/data/keys/hash-algos/SHA512.gpgbin0 -> 1208 bytes
12 files changed, 334 insertions, 34 deletions
diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml
index 6a5ad7a7..9e41958f 100644
--- a/openpgp/Cargo.toml
+++ b/openpgp/Cargo.toml
@@ -8,6 +8,6 @@ buffered-reader = { path = "../buffered-reader" }
base64 = "0.8.0"
bzip2 = "0.3.2"
flate2 = "0.2"
+nettle = { git = "https://gitlab.com/sequoia-pgp/nettle-rs.git" }
num = "0.1.40"
num-derive = "0.1.41"
-sha1 = "*"
diff --git a/openpgp/src/hash.rs b/openpgp/src/hash.rs
new file mode 100644
index 00000000..3cf27d35
--- /dev/null
+++ b/openpgp/src/hash.rs
@@ -0,0 +1,276 @@
+//! Functionality to hash packets, and generate hashes.
+
+use HashAlgo;
+
+use UserID;
+use UserAttribute;
+use Key;
+use Signature;
+
+use nettle::Hash;
+use nettle::hash::insecure_do_not_use::Sha1;
+use nettle::hash::{Sha224, Sha256, Sha384, Sha512};
+
+impl UserID {
+ // Update the Hash with a hash of the key.
+ pub fn hash<H: Hash>(&self, hash: &mut H) {
+ let mut header = [0; 5];
+
+ header[0] = 0xB4;
+ let len = self.value.len() as u32;
+ header[1] = (len >> 24) as u8;
+ header[2] = (len >> 16) as u8;
+ header[3] = (len >> 8) as u8;
+ header[4] = (len) as u8;
+
+ hash.update(&header[..]);
+ hash.update(&self.value[..]);
+ }
+}
+
+impl UserAttribute {
+ // Update the Hash with a hash of the key.
+ pub fn hash<H: Hash>(&self, hash: &mut H) {
+ let mut header = [0; 5];
+
+ header[0] = 0xD1;
+ let len = self.value.len() as u32;
+ header[1] = (len >> 24) as u8;
+ header[2] = (len >> 16) as u8;
+ header[3] = (len >> 8) as u8;
+ header[4] = (len) as u8;
+
+ hash.update(&header[..]);
+ hash.update(&self.value[..]);
+ }
+}
+
+impl Key {
+ // Update the Hash with a hash of the key.
+ pub fn hash<H: Hash>(&self, hash: &mut H) {
+ // We hash 8 bytes plus the MPIs. But, the len doesn't
+ // include the tag (1 byte) or the length (2 bytes).
+ let len = (9 - 3) + self.mpis.len();
+
+ let mut header : Vec<u8> = Vec::with_capacity(9);
+
+ // Tag. Note: we use this whether
+ header.push(0x99);
+
+ // Length (big endian).
+ header.push(((len >> 8) & 0xFF) as u8);
+ header.push((len & 0xFF) as u8);
+
+ // Version.
+ header.push(4);
+
+ // Creation time.
+ header.push(((self.creation_time >> 24) & 0xFF) as u8);
+ header.push(((self.creation_time >> 16) & 0xFF) as u8);
+ header.push(((self.creation_time >> 8) & 0xFF) as u8);
+ header.push((self.creation_time & 0xFF) as u8);
+
+ // Algorithm.
+ header.push(self.pk_algo);
+
+ hash.update(&header[..]);
+
+ // MPIs.
+ hash.update(&self.mpis[..]);
+ }
+}
+
+impl Signature {
+ // Adds the `Signature` to the provided hash context.
+ pub fn hash<H: Hash>(&self, hash: &mut H) {
+ let mut header = [0u8; 6];
+
+ // Version.
+ header[0] = 4;
+ header[1] = self.sigtype;
+ header[2] = self.pk_algo;
+ header[3] = self.hash_algo;
+
+ // The length of the hashed area, as a 16-bit endian number.
+ let len = self.hashed_area.len();
+ header[4] = (len >> 8) as u8;
+ header[5] = len as u8;
+
+ hash.update(&header[..]);
+
+ hash.update(&self.hashed_area[..]);
+
+ let mut trailer = [0u8; 6];
+
+ trailer[0] = 0x4;
+ trailer[1] = 0xff;
+ // The signature packet's length, not excluding the previous
+ // two bytes and the length.
+ let len = header.len() + self.hashed_area.len();
+ trailer[2] = (len >> 24) as u8;
+ trailer[3] = (len >> 16) as u8;
+ trailer[4] = (len >> 8) as u8;
+ trailer[5] = len as u8;
+
+ hash.update(&trailer[..]);
+ }
+}
+
+/// Hashing-related functionality.
+impl Signature {
+ // Initializes a hash context and returns the hash's size in
+ // bytes.
+ fn hash_init(&self) -> Box<Hash> {
+ match HashAlgo::from_numeric(self.hash_algo).unwrap() {
+ HashAlgo::SHA1 => Box::new(Sha1::default()),
+ HashAlgo::SHA224 => Box::new(Sha224::default()),
+ HashAlgo::SHA256 => Box::new(Sha256::default()),
+ HashAlgo::SHA384 => Box::new(Sha384::default()),
+ HashAlgo::SHA512 => Box::new(Sha512::default()),
+ algo => {
+ eprintln!("algo {:?} not implemented", algo);
+ unimplemented!();
+ },
+ }
+ }
+
+ // Return the message digest of the primary key binding over the
+ // specified primary key, subkey, and signature.
+ pub fn primary_key_binding_hash(&self, key: &Key) -> Vec<u8> {
+ let mut h = self.hash_init();
+
+ key.hash(&mut h);
+ self.hash(&mut h);
+
+ let mut digest = vec![0u8; h.digest_size()];
+ h.digest(&mut digest);
+ return digest;
+ }
+
+ // Return the message digest of the subkey binding over the
+ // specified primary key, subkey, and signature.
+ pub fn subkey_binding_hash(&self, key: &Key, subkey: &Key)
+ -> Vec<u8> {
+ let mut h = self.hash_init();
+
+ key.hash(&mut h);
+ subkey.hash(&mut h);
+ self.hash(&mut h);
+
+ let mut digest = vec![0u8; h.digest_size()];
+ h.digest(&mut digest);
+ return digest;
+ }
+
+ // Return the message digest of the user ID binding over the
+ // specified primary key, user ID, and signature.
+ pub fn userid_binding_hash(&self, key: &Key, userid: &UserID)
+ -> Vec<u8> {
+ let mut h = self.hash_init();
+
+ key.hash(&mut h);
+ userid.hash(&mut h);
+ self.hash(&mut h);
+
+ let mut digest = vec![0u8; h.digest_size()];
+ h.digest(&mut digest);
+ return digest;
+ }
+
+ // Return the message digest of the user attribute binding over
+ // the specified primary key, user attribute, and signature.
+ pub fn user_attribute_binding_hash(&self, key: &Key, ua: &UserAttribute)
+ -> Vec<u8> {
+ let mut h = self.hash_init();
+
+ key.hash(&mut h);
+ ua.hash(&mut h);
+ self.hash(&mut h);
+
+ let mut digest = vec![0u8; h.digest_size()];
+ h.digest(&mut digest);
+ return digest;
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use tpk::TPK;
+
+ macro_rules! bytes {
+ ( $x:expr ) => { include_bytes!(concat!("../tests/data/keys/", $x)) };
+ }
+
+ macro_rules! assert_match {
+ ( $error: pat = $expr:expr ) => {
+ let x = $expr;
+ if let $error = x {
+ /* Pass. */
+ } else {
+ panic!("Expected {}, got {:?}.", stringify!($error), x);
+ }
+ };
+ }
+
+ #[test]
+ fn hash_verification() {
+ fn check(tpk: TPK) -> (usize, usize, usize) {
+ let mut userid_sigs = 0;
+ for (i, binding) in tpk.userids().enumerate() {
+ for selfsig in binding.selfsigs() {
+ let h = selfsig.userid_binding_hash(
+ tpk.primary(),
+ binding.userid());
+ if h[..2] != selfsig.hash_prefix[..] {
+ eprintln!("{:?}: {:?} / {:?}",
+ i, binding.userid(), selfsig);
+ eprintln!(" Hash: {:?}", h);
+ }
+ assert_eq!(h[..2], selfsig.hash_prefix[..2]);
+ userid_sigs += 1;
+ }
+ }
+ let mut ua_sigs = 0;
+ for (i, binding) in tpk.user_attributes().enumerate() {
+ for selfsig in binding.selfsigs() {
+ let h = selfsig.user_attribute_binding_hash(
+ tpk.primary(),
+ binding.user_attribute());
+ if h[..2] != selfsig.hash_prefix[..] {
+ eprintln!("{:?}: {:?} / {:?}",
+ i, binding.user_attribute(), selfsig);
+ eprintln!(" Hash: {:?}", h);
+ }
+ assert_eq!(h[..2], selfsig.hash_prefix[..2]);
+ ua_sigs += 1;
+ }
+ }
+ let mut subkey_sigs = 0;
+ for (i, binding) in tpk.subkeys().enumerate() {
+ for selfsig in binding.selfsigs() {
+ let h = selfsig.subkey_binding_hash(
+ tpk.primary(),
+ binding.subkey());
+ if h[..2] != selfsig.hash_prefix[..] {
+ eprintln!("{:?}: {:?}", i, binding);
+ eprintln!(" Hash: {:?}", h);
+ }
+ assert_eq!(h[0], selfsig.hash_prefix[0]);
+ assert_eq!(h[1], selfsig.hash_prefix[1]);
+ subkey_sigs += 1;
+ }
+ }
+
+ (userid_sigs, ua_sigs, subkey_sigs)
+ }
+
+ check(TPK::from_bytes(bytes!("hash-algos/SHA224.gpg")).unwrap());
+ check(TPK::from_bytes(bytes!("hash-algos/SHA256.gpg")).unwrap());
+ check(TPK::from_bytes(bytes!("hash-algos/SHA384.gpg")).unwrap());
+ check(TPK::from_bytes(bytes!("hash-algos/SHA512.gpg")).unwrap());
+ check(TPK::from_bytes(bytes!("bannon-all-uids-subkeys.gpg")).unwrap());
+ let (_userid_sigs, ua_sigs, _subkey_sigs)
+ = check(TPK::from_bytes(bytes!("dkg.gpg")).unwrap());
+ assert!(ua_sigs > 0);
+ }
+}
diff --git a/openpgp/src/lib.rs b/openpgp/src/lib.rs
index 31218bcf..182a2761 100644
--- a/openpgp/src/lib.rs
+++ b/openpgp/src/lib.rs
@@ -44,7 +44,7 @@ extern crate num;
#[macro_use]
extern crate num_derive;
-extern crate sha1;
+extern crate nettle;
extern crate flate2;
extern crate bzip2;
@@ -72,6 +72,7 @@ mod packet;
mod container;
mod message;
mod iter;
+mod hash;
/// The OpenPGP packet tags as defined in [Section 4.3 of RFC 4880].
///
@@ -193,6 +194,54 @@ impl Tag {
num::ToPrimitive::to_u8(&self).unwrap()
}
}
+
+/// The OpenPGP hash algorithms as defined in [Section 9.4 of RFC 4880].
+///
+/// [Section 9.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-9.4
+///
+/// The values correspond to the serialized format.
+///
+/// Use [`HashAlgo::from_numeric`] to translate a numeric value to a symbolic
+/// one.
+///
+/// [`HashAlgo::from_numeric`]: enum.HashAlgo.html#method.from_numeric
+#[derive(Debug)]
+#[derive(FromPrimitive)]
+#[derive(ToPrimitive)]
+// We need PartialEq so that assert_eq! works.
+#[derive(PartialEq)]
+#[derive(Clone, Copy)]
+pub enum HashAlgo {
+ MD5 = 1,
+ SHA1 = 2,
+ RIPEMD = 3,
+ SHA256 = 8,
+ SHA384 = 9,
+ SHA512 = 10,
+ SHA224 = 11,
+}
+
+impl HashAlgo {
+ /// Converts a numeric value to an `Option<HashAlgo>`.
+ ///
+ /// Returns None, if the value is out of range.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use openpgp::HashAlgo;
+ ///
+ /// assert_eq!(HashAlgo::from_numeric(2), Some(HashAlgo::SHA1));
+ /// ```
+ pub fn from_numeric(value: u8) -> Option<Self> {
+ num::FromPrimitive::from_u8(value)
+ }
+
+ /// Converts a `HashAlgo` to its corresponding numeric value.
+ pub fn to_numeric(self) -> u8 {
+ num::ToPrimitive::to_u8(&self).unwrap()
+ }
+}
/// OpenPGP defines two packet formats: the old and the new format.
/// They both include the packet's so-called tag.
diff --git a/openpgp/src/parse/key.rs b/openpgp/src/parse/key.rs
index aaeb0f12..ade8d244 100644
--- a/openpgp/src/parse/key.rs
+++ b/openpgp/src/parse/key.rs
@@ -1,5 +1,6 @@
use super::*;
-use sha1;
+use nettle::Hash;
+use nettle::hash::insecure_do_not_use::Sha1;
#[cfg(test)]
use std::path::PathBuf;
@@ -14,39 +15,13 @@ impl Key {
// Computes and returns the key's fingerprint as per Section 12.2
// of RFC 4880.
pub fn fingerprint(&self) -> Fingerprint {
- let mut m = sha1::Sha1::new();
+ let mut h = Sha1::default();
- // We hash 8 bytes plus the MPIs. But, the len doesn't
- // include the tag (1 byte) or the length (2 bytes).
- let len = (9 - 3) + self.mpis.len();
+ self.hash(&mut h);
- let mut header : Vec<u8> = Vec::with_capacity(9);
-
- // Tag.
- header.push(0x99);
-
- // Length (big endian).
- header.push(((len >> 8) & 0xFF) as u8);
- header.push((len & 0xFF) as u8);
-
- // Version.
- header.push(4);
-
- // Creation time.
- header.push(((self.creation_time >> 24) & 0xFF) as u8);
- header.push(((self.creation_time >> 16) & 0xFF) as u8);
- header.push(((self.creation_time >> 8) & 0xFF) as u8);
- header.push((self.creation_time & 0xFF) as u8);
-
- // Algorithm.
- header.push(self.pk_algo);
-
- m.update(&header[..]);
-
- // MPIs.
- m.update(&self.mpis[..]);
-
- Fingerprint::from_bytes(&m.digest().bytes()[..])
+ let mut digest = vec![0u8; h.digest_size()];
+ h.digest(&mut digest);
+ Fingerprint::from_bytes(digest.as_slice())
}
// Computes and returns the key's key ID as per Section 12.2 of
diff --git a/openpgp/tests/data/keys/hash-algos/SHA224-private.gpg b/openpgp/tests/data/keys/hash-algos/SHA224-private.gpg
new file mode 100644
index 00000000..b8700c7e
--- /dev/null
+++ b/openpgp/tests/data/keys/hash-algos/SHA224-private.gpg
Binary files differ
diff --git a/openpgp/tests/data/keys/hash-algos/SHA224.gpg b/openpgp/tests/data/keys/hash-algos/SHA224.gpg
new file mode 100644
index 00000000..b84660f1
--- /dev/null
+++ b/openpgp/tests/data/keys/hash-algos/SHA224.gpg
Binary files differ
diff --git a/openpgp/tests/data/keys/hash-algos/SHA256-private.gpg b/openpgp/tests/data/keys/hash-algos/SHA256-private.gpg
new file mode 100644
index 00000000..e102d9fc
--- /dev/null
+++ b/openpgp/tests/data/keys/hash-algos/SHA256-private.gpg
Binary files differ
diff --git a/openpgp/tests/data/keys/hash-algos/SHA256.gpg b/openpgp/tests/data/keys/hash-algos/SHA256.gpg
new file mode 100644
index 00000000..4d9e753f
--- /dev/null
+++ b/openpgp/tests/data/keys/hash-algos/SHA256.gpg
Binary files differ
diff --git a/openpgp/tests/data/keys/hash-algos/SHA384-private.gpg b/openpgp/tests/data/keys/hash-algos/SHA384-private.gpg
new file mode 100644
index 00000000..5240433f
--- /dev/null
+++ b/openpgp/tests/data/keys/hash-algos/SHA384-private.gpg
Binary files differ
diff --git a/openpgp/tests/data/keys/hash-algos/SHA384.gpg b/openpgp/tests/data/keys/hash-algos/SHA384.gpg
new file mode 100644
index 00000000..dac49f00
--- /dev/null
+++ b/openpgp/tests/data/keys/hash-algos/SHA384.gpg
Binary files differ
diff --git a/openpgp/tests/data/keys/hash-algos/SHA512-private.gpg b/openpgp/tests/data/keys/hash-algos/SHA512-private.gpg
new file mode 100644
index 00000000..0db18020
--- /dev/null
+++ b/openpgp/tests/data/keys/hash-algos/SHA512-private.gpg
Binary files differ
diff --git a/openpgp/tests/data/keys/hash-algos/SHA512.gpg b/openpgp/tests/data/keys/hash-algos/SHA512.gpg
new file mode 100644
index 00000000..e321aff2
--- /dev/null
+++ b/openpgp/tests/data/keys/hash-algos/SHA512.gpg
Binary files differ