diff options
author | Neal H. Walfield <neal@pep.foundation> | 2020-12-11 14:41:17 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2020-12-11 14:46:30 +0100 |
commit | 35119b755db270ab43a8e1ec13577bc0f9846546 (patch) | |
tree | 2499fe86c242b8aa7e05df02f56640e11e8e920b /openpgp/src/packet | |
parent | 582a079f1cccc07bd74432ceb55da09e698da2d0 (diff) |
openpgp: Pass the hash algo's security reqs to Policy::signature.
- If the signer controls the data that is being signed, then the
hash algorithm only needs second pre-image resistance.
- This observation can be used to extend the life of hash algorithms
that have been weakened, as is the case for SHA-1.
- Introduces a new `enum HashAlgoSecurity`, which is now passed to
`Policy::signature`.
- See #595.
Diffstat (limited to 'openpgp/src/packet')
-rw-r--r-- | openpgp/src/packet/key.rs | 30 | ||||
-rw-r--r-- | openpgp/src/packet/unknown.rs | 30 | ||||
-rw-r--r-- | openpgp/src/packet/user_attribute.rs | 30 | ||||
-rw-r--r-- | openpgp/src/packet/userid.rs | 105 |
4 files changed, 195 insertions, 0 deletions
diff --git a/openpgp/src/packet/key.rs b/openpgp/src/packet/key.rs index 4003e507..87745997 100644 --- a/openpgp/src/packet/key.rs +++ b/openpgp/src/packet/key.rs @@ -110,6 +110,7 @@ use crate::crypto::Password; use crate::KeyID; use crate::Fingerprint; use crate::KeyHandle; +use crate::policy::HashAlgoSecurity; mod conversions; @@ -832,6 +833,35 @@ impl<P, R> Key4<P, R> where P: key::KeyParts, R: key::KeyRole, { + /// The security requirements of the hash algorithm for + /// self-signatures. + /// + /// A cryptographic hash algorithm usually has [three security + /// properties]: pre-image resistance, second pre-image + /// resistance, and collision resistance. If an attacker can + /// influence the signed data, then the hash algorithm needs to + /// have both second pre-image resistance, and collision + /// resistance. If not, second pre-image resistance is + /// sufficient. + /// + /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties + /// + /// In general, an attacker may be able to influence third-party + /// signatures. But direct key signatures, and binding signatures + /// are only over data fully determined by signer. And, an + /// attacker's control over self signatures over User IDs is + /// limited due to their structure. + /// + /// These observations can be used to extend the life of a hash + /// algorithm after its collision resistance has been partially + /// compromised, but not completely broken. For more details, + /// please refer to the documentation for [HashAlgoSecurity]. + /// + /// [HashAlgoSecurity]: ../policy/enum.HashAlgoSecurity.html + pub fn hash_algo_security(&self) -> HashAlgoSecurity { + HashAlgoSecurity::SecondPreImageResistance + } + /// Compares the public bits of two keys. /// /// This returns `Ordering::Equal` if the public MPIs, creation diff --git a/openpgp/src/packet/unknown.rs b/openpgp/src/packet/unknown.rs index 086f1b32..86758601 100644 --- a/openpgp/src/packet/unknown.rs +++ b/openpgp/src/packet/unknown.rs @@ -4,6 +4,7 @@ use std::cmp::Ordering; use crate::packet::Tag; use crate::packet; use crate::Packet; +use crate::policy::HashAlgoSecurity; /// Holds an unknown packet. /// @@ -73,6 +74,35 @@ impl Unknown { } } + /// The security requirements of the hash algorithm for + /// self-signatures. + /// + /// A cryptographic hash algorithm usually has [three security + /// properties]: pre-image resistance, second pre-image + /// resistance, and collision resistance. If an attacker can + /// influence the signed data, then the hash algorithm needs to + /// have both second pre-image resistance, and collision + /// resistance. If not, second pre-image resistance is + /// sufficient. + /// + /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties + /// + /// In general, an attacker may be able to influence third-party + /// signatures. But direct key signatures, and binding signatures + /// are only over data fully determined by signer. And, an + /// attacker's control over self signatures over User IDs is + /// limited due to their structure. + /// + /// These observations can be used to extend the life of a hash + /// algorithm after its collision resistance has been partially + /// compromised, but not completely broken. For more details, + /// please refer to the documentation for [HashAlgoSecurity]. + /// + /// [HashAlgoSecurity]: ../policy/enum.HashAlgoSecurity.html + pub fn hash_algo_security(&self) -> HashAlgoSecurity { + HashAlgoSecurity::CollisionResistance + } + /// Gets the unknown packet's tag. pub fn tag(&self) -> Tag { self.tag diff --git a/openpgp/src/packet/user_attribute.rs b/openpgp/src/packet/user_attribute.rs index 851e3126..57d93e82 100644 --- a/openpgp/src/packet/user_attribute.rs +++ b/openpgp/src/packet/user_attribute.rs @@ -20,6 +20,7 @@ use crate::packet::{ header::BodyLength, }; use crate::Packet; +use crate::policy::HashAlgoSecurity; use crate::serialize::Marshal; use crate::serialize::MarshalInto; @@ -74,6 +75,35 @@ impl UserAttribute { }) } + /// The security requirements of the hash algorithm for + /// self-signatures. + /// + /// A cryptographic hash algorithm usually has [three security + /// properties]: pre-image resistance, second pre-image + /// resistance, and collision resistance. If an attacker can + /// influence the signed data, then the hash algorithm needs to + /// have both second pre-image resistance, and collision + /// resistance. If not, second pre-image resistance is + /// sufficient. + /// + /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties + /// + /// In general, an attacker may be able to influence third-party + /// signatures. But direct key signatures, and binding signatures + /// are only over data fully determined by signer. And, an + /// attacker's control over self signatures over User IDs is + /// limited due to their structure. + /// + /// These observations can be used to extend the life of a hash + /// algorithm after its collision resistance has been partially + /// compromised, but not completely broken. For more details, + /// please refer to the documentation for [HashAlgoSecurity]. + /// + /// [HashAlgoSecurity]: ../policy/enum.HashAlgoSecurity.html + pub fn hash_algo_security(&self) -> HashAlgoSecurity { + HashAlgoSecurity::CollisionResistance + } + /// Gets the user attribute packet's raw, unparsed value. /// /// Most likely you will want to use [`subpackets()`] to iterate diff --git a/openpgp/src/packet/userid.rs b/openpgp/src/packet/userid.rs index 44e9b510..49d189bf 100644 --- a/openpgp/src/packet/userid.rs +++ b/openpgp/src/packet/userid.rs @@ -15,6 +15,7 @@ use crate::Result; use crate::packet; use crate::Packet; use crate::Error; +use crate::policy::HashAlgoSecurity; /// A conventionally parsed UserID. #[derive(Clone, Debug)] @@ -474,6 +475,8 @@ pub struct UserID { /// Use `UserID::default()` to get a UserID with a default settings. value: Vec<u8>, + hash_algo_security: HashAlgoSecurity, + parsed: Mutex<RefCell<Option<ConventionallyParsedUserID>>>, } assert_send_and_sync!(UserID); @@ -482,6 +485,7 @@ impl From<Vec<u8>> for UserID { fn from(u: Vec<u8>) -> Self { UserID { common: Default::default(), + hash_algo_security: UserID::determine_hash_algo_security(&u), value: u, parsed: Mutex::new(RefCell::new(None)), } @@ -571,6 +575,7 @@ impl Clone for UserID { fn clone(&self) -> Self { UserID { common: self.common.clone(), + hash_algo_security: self.hash_algo_security, value: self.value.clone(), parsed: Mutex::new(RefCell::new(None)), } @@ -684,6 +689,78 @@ impl UserID { Ok(UserID::from(value)) } + /// The security requirements of the hash algorithm for + /// self-signatures. + /// + /// A cryptographic hash algorithm usually has [three security + /// properties]: pre-image resistance, second pre-image + /// resistance, and collision resistance. If an attacker can + /// influence the signed data, then the hash algorithm needs to + /// have both second pre-image resistance, and collision + /// resistance. If not, second pre-image resistance is + /// sufficient. + /// + /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties + /// + /// In general, an attacker may be able to influence third-party + /// signatures. But direct key signatures, and binding signatures + /// are only over data fully determined by signer. And, an + /// attacker's control over self signatures over User IDs is + /// limited due to their structure. + /// + /// In the case of self signatures over User IDs, an attacker may + /// be able to control the content of the User ID packet. + /// However, unlike an image, there is no easy way to hide large + /// amounts of arbitrary data (e.g., the 512 bytes needed by the + /// [SHA-1 is a Shambles] attack) from the user. Further, normal + /// User IDs are short and encoded using UTF-8. + /// + /// [SHA-1 is a Shambles]: https://sha-mbles.github.io/ + /// + /// These observations can be used to extend the life of a hash + /// algorithm after its collision resistance has been partially + /// compromised, but not completely broken. Specifically for the + /// case of User IDs, we relax the requirement for strong + /// collision resistance for self signatures over User IDs if: + /// + /// - The User ID is at most 96 bytes long, + /// - It contains valid UTF-8, and + /// - It doesn't contain a UTF-8 control character (this includes + /// the NUL byte). + /// + /// + /// For more details, please refer to the documentation for + /// [HashAlgoSecurity]. + /// + /// [HashAlgoSecurity]: ../policy/enum.HashAlgoSecurity.html + pub fn hash_algo_security(&self) -> HashAlgoSecurity { + self.hash_algo_security + } + + // See documentation for hash_algo_security. + fn determine_hash_algo_security(u: &[u8]) -> HashAlgoSecurity { + // SHA-1 has 64 byte (512-bit) blocks. A block and a half (96 + // bytes) is more than enough for all but malicious users. + if u.len() > 96 { + return HashAlgoSecurity::CollisionResistance; + } + + // Check that the User ID is valid UTF-8. + match str::from_utf8(u) { + Ok(s) => { + // And doesn't contain control characters. + if s.chars().any(char::is_control) { + return HashAlgoSecurity::CollisionResistance; + } + } + Err(_err) => { + return HashAlgoSecurity::CollisionResistance; + } + } + + HashAlgoSecurity::SecondPreImageResistance + } + /// Constructs a User ID. /// /// This does a basic check and any necessary escaping to form a @@ -1280,4 +1357,32 @@ mod tests { .unwrap().value(), b"Foo Q. Bar <foo@bar.com>"); } + + #[test] + fn hash_algo_security() { + // Acceptable. + assert_eq!(UserID::from("Alice Lovelace <alice@lovelace.org>") + .hash_algo_security(), + HashAlgoSecurity::SecondPreImageResistance); + + // Embedded NUL. + assert_eq!(UserID::from(&b"Alice Lovelace <alice@lovelace.org>\0"[..]) + .hash_algo_security(), + HashAlgoSecurity::CollisionResistance); + assert_eq!( + UserID::from( + &b"Alice Lovelace <alice@lovelace.org>\0Hidden!"[..]) + .hash_algo_security(), + HashAlgoSecurity::CollisionResistance); + + // Long strings. + assert_eq!( + UserID::from(String::from_utf8(vec!['a' as u8; 90]).unwrap()) + .hash_algo_security(), + HashAlgoSecurity::SecondPreImageResistance); + assert_eq!( + UserID::from(String::from_utf8(vec!['a' as u8; 100]).unwrap()) + .hash_algo_security(), + HashAlgoSecurity::CollisionResistance); + } } |