diff options
author | Neal H. Walfield <neal@pep.foundation> | 2018-01-14 21:40:57 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2018-01-14 21:59:53 +0100 |
commit | 5d69d7b36830c2a43a05b4527998f860afd2aa4d (patch) | |
tree | 6804beb1f2bf5f3cfcb9aacbf966edf02c8ca4fa /openpgp | |
parent | 7a88168b1869e3b8b19c9349ecd0f0adc4f112c6 (diff) |
openpgp: Implement hashing.
- Switch to using nettle for low-level hash functionality.
- Add functionality for hasing UserID, UserAttribute, Key and
Signature packets.
- Add functionliaty to compute the hashes for primary key bindings,
subkey bindings, user id bindings and user attribute bindings.
Diffstat (limited to 'openpgp')
-rw-r--r-- | openpgp/Cargo.toml | 2 | ||||
-rw-r--r-- | openpgp/src/hash.rs | 276 | ||||
-rw-r--r-- | openpgp/src/lib.rs | 51 | ||||
-rw-r--r-- | openpgp/src/parse/key.rs | 39 | ||||
-rw-r--r-- | openpgp/tests/data/keys/hash-algos/SHA224-private.gpg | bin | 0 -> 2510 bytes | |||
-rw-r--r-- | openpgp/tests/data/keys/hash-algos/SHA224.gpg | bin | 0 -> 1208 bytes | |||
-rw-r--r-- | openpgp/tests/data/keys/hash-algos/SHA256-private.gpg | bin | 0 -> 2510 bytes | |||
-rw-r--r-- | openpgp/tests/data/keys/hash-algos/SHA256.gpg | bin | 0 -> 1208 bytes | |||
-rw-r--r-- | openpgp/tests/data/keys/hash-algos/SHA384-private.gpg | bin | 0 -> 2510 bytes | |||
-rw-r--r-- | openpgp/tests/data/keys/hash-algos/SHA384.gpg | bin | 0 -> 1208 bytes | |||
-rw-r--r-- | openpgp/tests/data/keys/hash-algos/SHA512-private.gpg | bin | 0 -> 2510 bytes | |||
-rw-r--r-- | openpgp/tests/data/keys/hash-algos/SHA512.gpg | bin | 0 -> 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 Binary files differnew file mode 100644 index 00000000..b8700c7e --- /dev/null +++ b/openpgp/tests/data/keys/hash-algos/SHA224-private.gpg diff --git a/openpgp/tests/data/keys/hash-algos/SHA224.gpg b/openpgp/tests/data/keys/hash-algos/SHA224.gpg Binary files differnew file mode 100644 index 00000000..b84660f1 --- /dev/null +++ b/openpgp/tests/data/keys/hash-algos/SHA224.gpg diff --git a/openpgp/tests/data/keys/hash-algos/SHA256-private.gpg b/openpgp/tests/data/keys/hash-algos/SHA256-private.gpg Binary files differnew file mode 100644 index 00000000..e102d9fc --- /dev/null +++ b/openpgp/tests/data/keys/hash-algos/SHA256-private.gpg diff --git a/openpgp/tests/data/keys/hash-algos/SHA256.gpg b/openpgp/tests/data/keys/hash-algos/SHA256.gpg Binary files differnew file mode 100644 index 00000000..4d9e753f --- /dev/null +++ b/openpgp/tests/data/keys/hash-algos/SHA256.gpg diff --git a/openpgp/tests/data/keys/hash-algos/SHA384-private.gpg b/openpgp/tests/data/keys/hash-algos/SHA384-private.gpg Binary files differnew file mode 100644 index 00000000..5240433f --- /dev/null +++ b/openpgp/tests/data/keys/hash-algos/SHA384-private.gpg diff --git a/openpgp/tests/data/keys/hash-algos/SHA384.gpg b/openpgp/tests/data/keys/hash-algos/SHA384.gpg Binary files differnew file mode 100644 index 00000000..dac49f00 --- /dev/null +++ b/openpgp/tests/data/keys/hash-algos/SHA384.gpg diff --git a/openpgp/tests/data/keys/hash-algos/SHA512-private.gpg b/openpgp/tests/data/keys/hash-algos/SHA512-private.gpg Binary files differnew file mode 100644 index 00000000..0db18020 --- /dev/null +++ b/openpgp/tests/data/keys/hash-algos/SHA512-private.gpg diff --git a/openpgp/tests/data/keys/hash-algos/SHA512.gpg b/openpgp/tests/data/keys/hash-algos/SHA512.gpg Binary files differnew file mode 100644 index 00000000..e321aff2 --- /dev/null +++ b/openpgp/tests/data/keys/hash-algos/SHA512.gpg |