summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWiktor Kwapisiewicz <wiktor@metacode.biz>2021-12-29 15:31:25 +0100
committerWiktor Kwapisiewicz <wiktor@metacode.biz>2021-12-31 09:56:11 +0100
commitde8fab8d1b74fa87d3c20d7a2b9e49aea929e6ea (patch)
tree06265139ecc5295846bb507ddaecd90cfa586ebd
parent0e23277b852edf1317df4aebaaabe5a404a6ad05 (diff)
openpgp: Add ability to restrict hash algorithms for signing.
-rw-r--r--openpgp/src/crypto/asymmetric.rs39
-rw-r--r--openpgp/src/crypto/hash.rs22
-rw-r--r--openpgp/src/lib.rs4
-rw-r--r--openpgp/src/serialize/stream.rs107
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!();
+ };
+ }
}