From f640688a3b40ab2c0c2c8c8ca7e9d56c64b1e663 Mon Sep 17 00:00:00 2001 From: Rodolphe Breard Date: Wed, 26 Aug 2020 15:43:09 +0200 Subject: [PATCH] Refactor the hash function interface --- acme_common/src/crypto.rs | 38 ++++- acme_common/src/crypto/openssl_hash.rs | 16 +- acme_common/src/crypto/openssl_keys.rs | 14 +- acme_common/src/tests.rs | 1 + acme_common/src/tests/hash.rs | 140 ++++++++++++++++++ acmed/src/acme_proto/structs/authorization.rs | 8 +- 6 files changed, 198 insertions(+), 19 deletions(-) create mode 100644 acme_common/src/tests/hash.rs diff --git a/acme_common/src/crypto.rs b/acme_common/src/crypto.rs index 9d43e9b..55990b5 100644 --- a/acme_common/src/crypto.rs +++ b/acme_common/src/crypto.rs @@ -1,3 +1,7 @@ +use crate::error::Error; +use std::fmt; +use std::str::FromStr; + mod jws_signature_algorithm; mod key_type; mod openssl_certificate; @@ -8,8 +12,40 @@ pub const DEFAULT_ALGO: &str = "rsa2048"; pub const TLS_LIB_NAME: &str = env!("ACMED_TLS_LIB_NAME"); pub const TLS_LIB_VERSION: &str = env!("ACMED_TLS_LIB_VERSION"); +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum BaseHashFunction { + Sha256, + Sha384, + Sha512, +} + +impl FromStr for BaseHashFunction { + type Err = Error; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase().replace("-", "").replace("_", ""); + match s.as_str() { + "sha256" => Ok(BaseHashFunction::Sha256), + "sha384" => Ok(BaseHashFunction::Sha384), + "sha512" => Ok(BaseHashFunction::Sha512), + _ => Err(format!("{}: unknown hash function.", s).into()), + } + } +} + +impl fmt::Display for BaseHashFunction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + BaseHashFunction::Sha256 => "sha256", + BaseHashFunction::Sha384 => "sha384", + BaseHashFunction::Sha512 => "sha512", + }; + write!(f, "{}", s) + } +} + pub use jws_signature_algorithm::JwsSignatureAlgorithm; pub use key_type::KeyType; pub use openssl_certificate::{Csr, X509Certificate}; -pub use openssl_hash::{sha256, sha384}; +pub use openssl_hash::HashFunction; pub use openssl_keys::{gen_keypair, KeyPair}; diff --git a/acme_common/src/crypto/openssl_hash.rs b/acme_common/src/crypto/openssl_hash.rs index 56ac65c..aad8e87 100644 --- a/acme_common/src/crypto/openssl_hash.rs +++ b/acme_common/src/crypto/openssl_hash.rs @@ -1,7 +1,13 @@ -pub fn sha256(data: &[u8]) -> Vec { - openssl::sha::sha256(data).to_vec() -} +use openssl::sha::{sha256, sha384, sha512}; + +pub type HashFunction = super::BaseHashFunction; -pub fn sha384(data: &[u8]) -> Vec { - openssl::sha::sha384(data).to_vec() +impl HashFunction { + pub fn hash(&self, data: &[u8]) -> Vec { + match self { + HashFunction::Sha256 => sha256(data).to_vec(), + HashFunction::Sha384 => sha384(data).to_vec(), + HashFunction::Sha512 => sha512(data).to_vec(), + } + } } diff --git a/acme_common/src/crypto/openssl_keys.rs b/acme_common/src/crypto/openssl_keys.rs index 7b5fc29..e261a21 100644 --- a/acme_common/src/crypto/openssl_keys.rs +++ b/acme_common/src/crypto/openssl_keys.rs @@ -1,5 +1,5 @@ use crate::b64_encode; -use crate::crypto::{JwsSignatureAlgorithm, KeyType}; +use crate::crypto::{HashFunction, JwsSignatureAlgorithm, KeyType}; use crate::error::Error; use openssl::bn::{BigNum, BigNumContext}; use openssl::ec::{Asn1Flag, EcGroup, EcKey}; @@ -72,8 +72,8 @@ impl KeyPair { let _ = self.key_type.check_alg_compatibility(alg)?; match alg { JwsSignatureAlgorithm::Rs256 => self.sign_rsa(&MessageDigest::sha256(), data), - JwsSignatureAlgorithm::Es256 => self.sign_ecdsa(&crate::crypto::sha256, data), - JwsSignatureAlgorithm::Es384 => self.sign_ecdsa(&crate::crypto::sha384, data), + JwsSignatureAlgorithm::Es256 => self.sign_ecdsa(&HashFunction::Sha256, data), + JwsSignatureAlgorithm::Es384 => self.sign_ecdsa(&HashFunction::Sha384, data), #[cfg(ed25519)] JwsSignatureAlgorithm::Ed25519 => self.sign_eddsa(data), #[cfg(ed448)] @@ -88,12 +88,8 @@ impl KeyPair { Ok(signature) } - fn sign_ecdsa( - &self, - hash_func: &dyn Fn(&[u8]) -> Vec, - data: &[u8], - ) -> Result, Error> { - let fingerprint = hash_func(data); + fn sign_ecdsa(&self, hash_func: &HashFunction, data: &[u8]) -> Result, Error> { + let fingerprint = hash_func.hash(data); let signature = EcdsaSig::sign(&fingerprint, self.inner_key.ec_key()?.as_ref())?; let r = signature.r().to_vec(); let mut s = signature.s().to_vec(); diff --git a/acme_common/src/tests.rs b/acme_common/src/tests.rs index 703bc7c..afa7203 100644 --- a/acme_common/src/tests.rs +++ b/acme_common/src/tests.rs @@ -1,4 +1,5 @@ mod certificate; mod crypto_keys; +mod hash; mod idna; mod jws_signature_algorithm; diff --git a/acme_common/src/tests/hash.rs b/acme_common/src/tests/hash.rs new file mode 100644 index 0000000..b41dc98 --- /dev/null +++ b/acme_common/src/tests/hash.rs @@ -0,0 +1,140 @@ +use crate::crypto::HashFunction; + +#[test] +fn test_hash_from_str() { + let test_vectors = vec![ + ("sha256", HashFunction::Sha256), + ("Sha256", HashFunction::Sha256), + ("sha-256", HashFunction::Sha256), + ("SHA_256", HashFunction::Sha256), + ("sha384", HashFunction::Sha384), + ("Sha-512", HashFunction::Sha512), + ]; + for (s, ref_h) in test_vectors { + let h: HashFunction = s.parse().unwrap(); + assert_eq!(h, ref_h); + } +} + +#[test] +fn test_hash_from_invalid_str() { + let test_vectors = vec!["sha42", "sha", "", "plop"]; + for s in test_vectors { + let h = s.parse::(); + assert!(h.is_err()); + } +} + +#[test] +fn test_hash_sha256() { + let test_vectors = vec![ + ( + "Hello World!".as_bytes(), + vec![ + 127, 131, 177, 101, 127, 241, 252, 83, 185, 45, 193, 129, 72, 161, 214, 93, 252, + 45, 75, 31, 163, 214, 119, 40, 74, 221, 210, 0, 18, 109, 144, 105, + ], + ), + ( + &[], + vec![ + 227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36, 39, + 174, 65, 228, 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, 85, + ], + ), + ( + &[ + 194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8, + 45, 114, + ], + vec![ + 65, 72, 199, 76, 128, 174, 196, 223, 91, 235, 87, 119, 200, 212, 133, 13, 219, 223, + 60, 4, 73, 70, 65, 41, 226, 83, 221, 107, 112, 29, 205, 28, + ], + ), + ]; + for (data, expected) in test_vectors { + let h = HashFunction::Sha256; + let res = h.hash(data); + assert_eq!(res, expected); + } +} + +#[test] +fn test_hash_sha384() { + let test_vectors = vec![ + ( + "Hello World!".as_bytes(), + vec![ + 191, 215, 108, 14, 187, 208, 6, 254, 229, 131, 65, 5, 71, 193, 136, 123, 2, 146, + 190, 118, 213, 130, 217, 108, 36, 45, 42, 121, 39, 35, 227, 253, 111, 208, 97, 249, + 213, 207, 209, 59, 143, 150, 19, 88, 230, 173, 186, 74, + ], + ), + ( + &[], + vec![ + 56, 176, 96, 167, 81, 172, 150, 56, 76, 217, 50, 126, 177, 177, 227, 106, 33, 253, + 183, 17, 20, 190, 7, 67, 76, 12, 199, 191, 99, 246, 225, 218, 39, 78, 222, 191, + 231, 111, 101, 251, 213, 26, 210, 241, 72, 152, 185, 91, + ], + ), + ( + &[ + 194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8, + 45, 114, + ], + vec![ + 170, 126, 84, 2, 141, 91, 106, 70, 80, 53, 98, 101, 184, 3, 34, 146, 130, 238, 146, + 221, 113, 197, 154, 91, 4, 208, 229, 15, 8, 179, 51, 29, 224, 200, 187, 127, 9, + 243, 29, 171, 189, 124, 60, 39, 3, 74, 171, 156, + ], + ), + ]; + for (data, expected) in test_vectors { + let h = HashFunction::Sha384; + let res = h.hash(data); + assert_eq!(res, expected); + } +} + +#[test] +fn test_hash_sha512() { + let test_vectors = vec![ + ( + "Hello World!".as_bytes(), + vec![ + 134, 24, 68, 214, 112, 78, 133, 115, 254, 195, 77, 150, 126, 32, 188, 254, 243, + 212, 36, 207, 72, 190, 4, 230, 220, 8, 242, 189, 88, 199, 41, 116, 51, 113, 1, 94, + 173, 137, 28, 195, 207, 28, 157, 52, 180, 146, 100, 181, 16, 117, 27, 31, 249, 229, + 55, 147, 123, 196, 107, 93, 111, 244, 236, 200, + ], + ), + ( + &[], + vec![ + 207, 131, 225, 53, 126, 239, 184, 189, 241, 84, 40, 80, 214, 109, 128, 7, 214, 32, + 228, 5, 11, 87, 21, 220, 131, 244, 169, 33, 211, 108, 233, 206, 71, 208, 209, 60, + 93, 133, 242, 176, 255, 131, 24, 210, 135, 126, 236, 47, 99, 185, 49, 189, 71, 65, + 122, 129, 165, 56, 50, 122, 249, 39, 218, 62, + ], + ), + ( + &[ + 194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8, + 45, 114, + ], + vec![ + 58, 93, 210, 174, 119, 179, 246, 25, 14, 148, 182, 109, 28, 14, 16, 80, 45, 231, + 104, 169, 130, 43, 39, 221, 12, 112, 85, 159, 123, 6, 227, 35, 61, 24, 158, 190, + 162, 11, 247, 204, 98, 41, 242, 5, 52, 116, 149, 220, 124, 82, 159, 181, 74, 210, + 85, 190, 59, 130, 209, 8, 181, 247, 192, 65, + ], + ), + ]; + for (data, expected) in test_vectors { + let h = HashFunction::Sha512; + let res = h.hash(data); + assert_eq!(res, expected); + } +} diff --git a/acmed/src/acme_proto/structs/authorization.rs b/acmed/src/acme_proto/structs/authorization.rs index 1b9a268..a6aab98 100644 --- a/acmed/src/acme_proto/structs/authorization.rs +++ b/acmed/src/acme_proto/structs/authorization.rs @@ -1,6 +1,6 @@ use crate::acme_proto::structs::{ApiError, HttpApiError, Identifier}; use acme_common::b64_encode; -use acme_common::crypto::{sha256, KeyPair}; +use acme_common::crypto::{HashFunction, KeyPair}; use acme_common::error::Error; use serde::Deserialize; use std::fmt; @@ -97,14 +97,14 @@ impl Challenge { Challenge::Http01(tc) => tc.key_authorization(key_pair), Challenge::Dns01(tc) => { let ka = tc.key_authorization(key_pair)?; - let a = sha256(ka.as_bytes()); + let a = HashFunction::Sha256.hash(ka.as_bytes()); let a = b64_encode(&a); Ok(a) } Challenge::TlsAlpn01(tc) => { let acme_ext_name = format!("{}.{}", ACME_OID, ID_PE_ACME_ID); let ka = tc.key_authorization(key_pair)?; - let proof = sha256(ka.as_bytes()); + let proof = HashFunction::Sha256.hash(ka.as_bytes()); let proof_str = proof .iter() .map(|e| format!("{:02x}", e)) @@ -156,7 +156,7 @@ pub struct TokenChallenge { impl TokenChallenge { fn key_authorization(&self, key_pair: &KeyPair) -> Result { let thumbprint = key_pair.jwk_public_key_thumbprint()?; - let thumbprint = sha256(thumbprint.to_string().as_bytes()); + let thumbprint = HashFunction::Sha256.hash(thumbprint.to_string().as_bytes()); let thumbprint = b64_encode(&thumbprint); let auth = format!("{}.{}", self.token, thumbprint); Ok(auth)