diff options
author | Wiktor Kwapisiewicz <wiktor@metacode.biz> | 2021-12-29 15:31:25 +0100 |
---|---|---|
committer | Wiktor Kwapisiewicz <wiktor@metacode.biz> | 2021-12-31 09:56:11 +0100 |
commit | de8fab8d1b74fa87d3c20d7a2b9e49aea929e6ea (patch) | |
tree | 06265139ecc5295846bb507ddaecd90cfa586ebd /openpgp/src | |
parent | 0e23277b852edf1317df4aebaaabe5a404a6ad05 (diff) |
openpgp: Add ability to restrict hash algorithms for signing.
Diffstat (limited to 'openpgp/src')
-rw-r--r-- | openpgp/src/crypto/asymmetric.rs | 39 | ||||
-rw-r--r-- | openpgp/src/crypto/hash.rs | 22 | ||||
-rw-r--r-- | openpgp/src/lib.rs | 4 | ||||
-rw-r--r-- | openpgp/src/serialize/stream.rs | 107 |
4 files changed, 172 insertions, 0 deletions
diff --git a/openpgp/src/crypto/asymmetric.rs b/openpgp/src/crypto/asymmetric.rs index 469bac49..4d29eb4b 100644 --- a/openpgp/src/crypto/asymmetric.rs +++ b/openpgp/src/crypto/asymmetric.rs @@ -39,6 +39,19 @@ pub trait Signer { /// Returns a reference to the public key. fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole>; + /// Returns a list of hashes that this signer accepts. + /// + /// Some cryptographic libraries or hardware modules support signing digests + /// produced with only a limited set of hashing algorithms. This function + /// indicates to callers which algorithm digests are supported by this signer. + /// + /// The default implementation of this function allows all hash algorithms to + /// be used. Provide an explicit implementation only when a smaller subset + /// of hashing algorithms is valid for this `Signer` implementation. + fn acceptable_hashes(&self) -> &[HashAlgorithm] { + &crate::crypto::hash::DEFAULT_HASHES_SORTED + } + /// Creates a signature over the `digest` produced by `hash_algo`. fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature>; @@ -49,6 +62,19 @@ impl Signer for Box<dyn Signer> { self.as_ref().public() } + /// Returns a list of hashes that this signer accepts. + /// + /// Some cryptographic libraries or hardware modules support signing digests + /// produced with only a limited set of hashing algorithms. This function + /// indicates to callers which algorithm digests are supported by this signer. + /// + /// The default implementation of this function allows all hash algorithms to + /// be used. Provide an explicit implementation only when a smaller subset + /// of hashing algorithms is valid for this `Signer` implementation. + fn acceptable_hashes(&self) -> &[HashAlgorithm] { + self.as_ref().acceptable_hashes() + } + fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { self.as_mut().sign(hash_algo, digest) @@ -60,6 +86,19 @@ impl Signer for Box<dyn Signer + Send + Sync> { self.as_ref().public() } + /// Returns a list of hashes that this signer accepts. + /// + /// Some cryptographic libraries or hardware modules support signing digests + /// produced with only a limited set of hashing algorithms. This function + /// indicates to callers which algorithm digests are supported by this signer. + /// + /// The default implementation of this function allows all hash algorithms to + /// be used. Provide an explicit implementation only when a smaller subset + /// of hashing algorithms is valid for this `Signer` implementation. + fn acceptable_hashes(&self) -> &[HashAlgorithm] { + self.as_ref().acceptable_hashes() + } + fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { self.as_mut().sign(hash_algo, digest) diff --git a/openpgp/src/crypto/hash.rs b/openpgp/src/crypto/hash.rs index b78f1a20..65c13ef9 100644 --- a/openpgp/src/crypto/hash.rs +++ b/openpgp/src/crypto/hash.rs @@ -51,6 +51,28 @@ use std::io::{self, Write}; // hashed to files /tmp/hash-N, where N is a number. const DUMP_HASHED_VALUES: Option<&str> = None; +lazy_static::lazy_static! { + /// List of hashes that the signer may produce. + /// This list is ordered by the preference so that the most preferred + /// hash algorithm is first. + pub(crate) static ref DEFAULT_HASHES: Vec<HashAlgorithm> = vec![ + HashAlgorithm::default(), + HashAlgorithm::SHA512, + HashAlgorithm::SHA384, + HashAlgorithm::SHA256, + HashAlgorithm::SHA224, + HashAlgorithm::SHA1, + HashAlgorithm::RipeMD, + HashAlgorithm::MD5, + ]; + + pub(crate) static ref DEFAULT_HASHES_SORTED: Vec<HashAlgorithm> = { + let mut hashes = DEFAULT_HASHES.clone(); + hashes.sort(); + hashes + }; +} + /// Hasher capable of calculating a digest for the input byte stream. /// /// This provides an abstract interface to the hash functions used in diff --git a/openpgp/src/lib.rs b/openpgp/src/lib.rs index 4cc96884..11878174 100644 --- a/openpgp/src/lib.rs +++ b/openpgp/src/lib.rs @@ -318,6 +318,10 @@ pub enum Error { #[error("Invalid key: {0:?}")] InvalidKey(String), + /// No hash algorithm found that would be accepted by all signers. + #[error("No acceptable hash")] + NoAcceptableHash, + /// The operation is not allowed, because it violates the policy. /// /// The optional time is the time at which the operation was diff --git a/openpgp/src/serialize/stream.rs b/openpgp/src/serialize/stream.rs index dfc49d0d..09abac17 100644 --- a/openpgp/src/serialize/stream.rs +++ b/openpgp/src/serialize/stream.rs @@ -1247,6 +1247,29 @@ impl<'a> Signer<'a> { assert!(!self.signers.is_empty(), "The constructor adds a signer."); assert!(self.inner.is_some(), "The constructor adds an inner writer."); + let mut acceptable_hashes = crate::crypto::hash::DEFAULT_HASHES.to_vec(); + + let is_sorted = |data: &[HashAlgorithm]| { + data.windows(2).all(|w| w[0] <= w[1]) + }; + + for signer in &self.signers { + let mut signer_hashes = signer.acceptable_hashes(); + let mut signer_hashes_; + if ! is_sorted(signer_hashes) { + signer_hashes_ = signer_hashes.to_vec(); + signer_hashes_.sort(); + signer_hashes = &signer_hashes_; + } + acceptable_hashes.retain(|hash| signer_hashes.binary_search(hash).is_ok()); + } + + if let Some(hash) = acceptable_hashes.first() { + self.hash = hash.context().unwrap(); + } else { + return Err(Error::NoAcceptableHash.into()); + } + match self.mode { SignatureMode::Inline => { // For every key we collected, build and emit a one pass @@ -3662,4 +3685,88 @@ mod test { Ok(()) } + + struct BadSigner; + + impl crypto::Signer for BadSigner { + fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { + panic!("public not impl") + } + + /// Returns a list of hashes that this signer accepts. + fn acceptable_hashes(&self) -> &[HashAlgorithm] { + &[] + } + + fn sign(&mut self, _hash_algo: HashAlgorithm, _digest: &[u8]) + -> Result<crypto::mpi::Signature> { + panic!("sign not impl") + } + } + + struct GoodSigner(Vec<HashAlgorithm>, Key<key::PublicParts, key::UnspecifiedRole>); + + impl crypto::Signer for GoodSigner { + fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { + &self.1 + } + + /// Returns a list of hashes that this signer accepts. + fn acceptable_hashes(&self) -> &[HashAlgorithm] { + &self.0 + } + + fn sign(&mut self, _hash_algo: HashAlgorithm, _digest: &[u8]) + -> Result<crypto::mpi::Signature> { + unimplemented!() + } + } + + impl Default for GoodSigner { + fn default() -> Self { + let p = &P::new(); + + let (cert, _) = CertBuilder::new().generate().unwrap(); + + let ka = cert.keys().with_policy(p, None).next().unwrap(); + + Self(vec![HashAlgorithm::default()], ka.key().clone()) + } + } + + #[test] + fn overlapping_hashes() { + let mut signature = vec![]; + let message = Message::new(&mut signature); + + Signer::new(message, GoodSigner::default()).build().unwrap(); + } + + #[test] + fn no_overlapping_hashes() { + let mut signature = vec![]; + let message = Message::new(&mut signature); + let signer = Signer::new(message, BadSigner); + + if let Err(e) = signer.build() { + assert_eq!(e.downcast_ref::<Error>(), Some(&Error::NoAcceptableHash)); + } else { + unreachable!(); + }; + } + + #[test] + fn no_overlapping_hashes_for_new_signer() { + let mut signature = vec![]; + let message = Message::new(&mut signature); + + let mut signer = Signer::new(message, GoodSigner::default()); + signer = signer.add_signer(BadSigner); + + if let Err(e) = signer.build() { + assert_eq!(e.downcast_ref::<Error>(), Some(&Error::NoAcceptableHash)); + } else { + unreachable!(); + }; + } } |