summaryrefslogtreecommitdiffstats
path: root/openpgp/src/packet/userid.rs
diff options
context:
space:
mode:
Diffstat (limited to 'openpgp/src/packet/userid.rs')
-rw-r--r--openpgp/src/packet/userid.rs105
1 files changed, 105 insertions, 0 deletions
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);
+ }
}