From 636fbf9cf648f923449f8bd2f18682a2dc955743 Mon Sep 17 00:00:00 2001 From: Rodolphe Breard Date: Sat, 22 Aug 2020 19:27:59 +0200 Subject: [PATCH] Refactor the JWS signature algorithm management Being tied with the key type, the signature algorithm should therefore be at the same place than the key type, hence `acme_common::crypto`. This reorganization will allow to specify the account key type as well as the signature algorithm in the configuration. --- acme_common/src/crypto.rs | 2 + .../src/crypto/jws_signature_algorithm.rs | 60 ++++++++++ acme_common/src/crypto/key_type.rs | 30 +++++ acme_common/src/crypto/openssl_keys.rs | 17 +-- acmed/src/acme_proto/account.rs | 9 +- acmed/src/jws.rs | 24 ++-- acmed/src/jws/algorithms.rs | 104 ------------------ acmed/src/main.rs | 2 +- 8 files changed, 120 insertions(+), 128 deletions(-) create mode 100644 acme_common/src/crypto/jws_signature_algorithm.rs delete mode 100644 acmed/src/jws/algorithms.rs diff --git a/acme_common/src/crypto.rs b/acme_common/src/crypto.rs index 1919fcc..9d43e9b 100644 --- a/acme_common/src/crypto.rs +++ b/acme_common/src/crypto.rs @@ -1,3 +1,4 @@ +mod jws_signature_algorithm; mod key_type; mod openssl_certificate; mod openssl_hash; @@ -7,6 +8,7 @@ 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"); +pub use jws_signature_algorithm::JwsSignatureAlgorithm; pub use key_type::KeyType; pub use openssl_certificate::{Csr, X509Certificate}; pub use openssl_hash::{sha256, sha384}; diff --git a/acme_common/src/crypto/jws_signature_algorithm.rs b/acme_common/src/crypto/jws_signature_algorithm.rs new file mode 100644 index 0000000..8ccf57e --- /dev/null +++ b/acme_common/src/crypto/jws_signature_algorithm.rs @@ -0,0 +1,60 @@ +use crate::error::Error; +use std::fmt; +use std::str::FromStr; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum JwsSignatureAlgorithm { + Rs256, + Es256, + Es384, + Ed25519, +} + +impl FromStr for JwsSignatureAlgorithm { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "rs256" => Ok(JwsSignatureAlgorithm::Rs256), + "es256" => Ok(JwsSignatureAlgorithm::Es256), + "es384" => Ok(JwsSignatureAlgorithm::Es384), + "ed25519" => Ok(JwsSignatureAlgorithm::Ed25519), + _ => Err(format!("{}: unknown algorithm.", s).into()), + } + } +} + +impl fmt::Display for JwsSignatureAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + JwsSignatureAlgorithm::Rs256 => "RS256", + JwsSignatureAlgorithm::Es256 => "ES256", + JwsSignatureAlgorithm::Es384 => "ES384", + JwsSignatureAlgorithm::Ed25519 => "Ed25519", + }; + write!(f, "{}", s) + } +} + +#[cfg(test)] +mod tests { + use super::JwsSignatureAlgorithm; + use std::str::FromStr; + + #[test] + fn test_es256_from_str() { + let variants = ["ES256", "Es256", "es256"]; + for v in variants.iter() { + let a = JwsSignatureAlgorithm::from_str(v); + assert!(a.is_ok()); + let a = a.unwrap(); + assert_eq!(a, JwsSignatureAlgorithm::Es256); + } + } + + #[test] + fn test_es256_to_str() { + let a = JwsSignatureAlgorithm::Es256; + assert_eq!(a.to_string().as_str(), "ES256"); + } +} diff --git a/acme_common/src/crypto/key_type.rs b/acme_common/src/crypto/key_type.rs index 34a6676..3441cb8 100644 --- a/acme_common/src/crypto/key_type.rs +++ b/acme_common/src/crypto/key_type.rs @@ -1,3 +1,4 @@ +use crate::crypto::JwsSignatureAlgorithm; use crate::error::Error; use std::fmt; use std::str::FromStr; @@ -11,6 +12,35 @@ pub enum KeyType { Rsa4096, } +impl KeyType { + pub fn get_default_signature_alg(&self) -> JwsSignatureAlgorithm { + match self { + KeyType::Curve25519 => JwsSignatureAlgorithm::Ed25519, + KeyType::EcdsaP256 => JwsSignatureAlgorithm::Es256, + KeyType::EcdsaP384 => JwsSignatureAlgorithm::Es384, + KeyType::Rsa2048 | KeyType::Rsa4096 => JwsSignatureAlgorithm::Rs256, + } + } + + pub fn check_alg_compatibility(&self, alg: &JwsSignatureAlgorithm) -> Result<(), Error> { + let ok = match self { + KeyType::Curve25519 | KeyType::EcdsaP256 | KeyType::EcdsaP384 => { + *alg == self.get_default_signature_alg() + } + KeyType::Rsa2048 | KeyType::Rsa4096 => *alg == JwsSignatureAlgorithm::Rs256, + }; + if ok { + Ok(()) + } else { + let err_msg = format!( + "Incompatible signature algorithm: {} cannot be used with an {} key.", + alg, self + ); + Err(err_msg.into()) + } + } +} + impl FromStr for KeyType { type Err = Error; diff --git a/acme_common/src/crypto/openssl_keys.rs b/acme_common/src/crypto/openssl_keys.rs index 2194c67..73172c2 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::KeyType; +use crate::crypto::{JwsSignatureAlgorithm, KeyType}; use crate::error::Error; use openssl::bn::{BigNum, BigNumContext}; use openssl::ec::{Asn1Flag, EcGroup, EcKey}; @@ -64,12 +64,15 @@ impl KeyPair { self.inner_key.public_key_to_pem().map_err(Error::from) } - pub fn sign(&self, data: &[u8]) -> Result, Error> { - match self.key_type { - KeyType::Curve25519 => Err("Curve25519 signatures are not implemented yet".into()), - KeyType::EcdsaP256 => self.sign_ecdsa(&crate::crypto::sha256, data), - KeyType::EcdsaP384 => self.sign_ecdsa(&crate::crypto::sha384, data), - KeyType::Rsa2048 | KeyType::Rsa4096 => self.sign_rsa(&MessageDigest::sha256(), data), + pub fn sign(&self, alg: &JwsSignatureAlgorithm, data: &[u8]) -> Result, Error> { + 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::Ed25519 => { + Err("Curve25519 signatures are not implemented yet".into()) + } } } diff --git a/acmed/src/acme_proto/account.rs b/acmed/src/acme_proto/account.rs index 9c34045..d68d23e 100644 --- a/acmed/src/acme_proto/account.rs +++ b/acmed/src/acme_proto/account.rs @@ -2,12 +2,10 @@ use crate::acme_proto::http; use crate::acme_proto::structs::Account; use crate::certificate::Certificate; use crate::endpoint::Endpoint; -use crate::jws::algorithms::SignatureAlgorithm; use crate::jws::encode_jwk; use crate::storage; -use acme_common::crypto::KeyPair; +use acme_common::crypto::{gen_keypair, KeyPair}; use acme_common::error::Error; -use std::str::FromStr; pub struct AccountManager { pub key_pair: KeyPair, @@ -41,9 +39,8 @@ impl AccountManager { pub fn init_account(cert: &Certificate) -> Result<(), Error> { if !storage::account_files_exists(cert) { - // TODO: allow to change the signature algo - let sign_alg = SignatureAlgorithm::from_str(crate::DEFAULT_JWS_SIGN_ALGO)?; - let key_pair = sign_alg.gen_key_pair()?; + // TODO: allow to change the account key type + let key_pair = gen_keypair(crate::DEFAULT_ACCOUNT_KEY_TYPE)?; storage::set_account_keypair(cert, &key_pair)?; cert.info(&format!("Account {} created", &cert.account.name)); } else { diff --git a/acmed/src/jws.rs b/acmed/src/jws.rs index 8b4ef5b..0221126 100644 --- a/acmed/src/jws.rs +++ b/acmed/src/jws.rs @@ -1,12 +1,9 @@ -use crate::jws::algorithms::SignatureAlgorithm; use acme_common::b64_encode; -use acme_common::crypto::KeyPair; +use acme_common::crypto::{JwsSignatureAlgorithm, KeyPair}; use acme_common::error::Error; use serde::Serialize; use serde_json::value::Value; -pub mod algorithms; - #[derive(Serialize)] struct JwsData { protected: String, @@ -30,11 +27,16 @@ struct JwsProtectedHeaderKid { url: String, } -fn get_data(key_pair: &KeyPair, protected: &str, payload: &[u8]) -> Result { +fn get_data( + key_pair: &KeyPair, + sign_alg: &JwsSignatureAlgorithm, + protected: &str, + payload: &[u8], +) -> Result { let protected = b64_encode(protected); let payload = b64_encode(payload); let signing_input = format!("{}.{}", protected, payload); - let signature = key_pair.sign(signing_input.as_bytes())?; + let signature = key_pair.sign(sign_alg, signing_input.as_bytes())?; let signature = b64_encode(&signature); let data = JwsData { protected, @@ -51,7 +53,8 @@ pub fn encode_jwk( url: &str, nonce: &str, ) -> Result { - let sign_alg = SignatureAlgorithm::from_pkey(key_pair)?; + // TODO: allow to change the signature algo + let sign_alg = key_pair.key_type.get_default_signature_alg(); let protected = JwsProtectedHeaderJwk { alg: sign_alg.to_string(), jwk: key_pair.jwk_public_key()?, @@ -59,7 +62,7 @@ pub fn encode_jwk( url: url.into(), }; let protected = serde_json::to_string(&protected)?; - get_data(key_pair, &protected, payload) + get_data(key_pair, &sign_alg, &protected, payload) } pub fn encode_kid( @@ -69,7 +72,8 @@ pub fn encode_kid( url: &str, nonce: &str, ) -> Result { - let sign_alg = SignatureAlgorithm::from_pkey(key_pair)?; + // TODO: allow to change the signature algo + let sign_alg = key_pair.key_type.get_default_signature_alg(); let protected = JwsProtectedHeaderKid { alg: sign_alg.to_string(), kid: key_id.to_string(), @@ -77,7 +81,7 @@ pub fn encode_kid( url: url.into(), }; let protected = serde_json::to_string(&protected)?; - get_data(key_pair, &protected, payload) + get_data(key_pair, &sign_alg, &protected, payload) } #[cfg(test)] diff --git a/acmed/src/jws/algorithms.rs b/acmed/src/jws/algorithms.rs deleted file mode 100644 index 7e53c6f..0000000 --- a/acmed/src/jws/algorithms.rs +++ /dev/null @@ -1,104 +0,0 @@ -use acme_common::crypto::{gen_keypair, KeyPair, KeyType}; -use acme_common::error::Error; -use std::fmt; -use std::str::FromStr; - -#[derive(Debug, PartialEq, Eq)] -pub enum SignatureAlgorithm { - Rs256, - Es256, - Es384, -} - -impl fmt::Display for SignatureAlgorithm { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - SignatureAlgorithm::Rs256 => "RS256", - SignatureAlgorithm::Es256 => "ES256", - SignatureAlgorithm::Es384 => "ES384", - }; - write!(f, "{}", s) - } -} - -impl FromStr for SignatureAlgorithm { - type Err = Error; - - fn from_str(data: &str) -> Result { - match data.to_lowercase().as_str() { - "rs256" => Ok(SignatureAlgorithm::Rs256), - "es256" => Ok(SignatureAlgorithm::Es256), - "es384" => Ok(SignatureAlgorithm::Es384), - _ => Err(format!("{}: unknown signature algorithm", data).into()), - } - } -} - -impl SignatureAlgorithm { - pub fn from_pkey(key_pair: &KeyPair) -> Result { - match key_pair.key_type { - KeyType::Rsa2048 => Ok(SignatureAlgorithm::Rs256), - KeyType::Rsa4096 => Ok(SignatureAlgorithm::Rs256), - KeyType::EcdsaP256 => Ok(SignatureAlgorithm::Es256), - KeyType::EcdsaP384 => Ok(SignatureAlgorithm::Es384), - t => Err(format!("{}: unsupported key type", t).into()), - } - } - - pub fn gen_key_pair(&self) -> Result { - match self { - SignatureAlgorithm::Rs256 => gen_keypair(KeyType::Rsa2048), - SignatureAlgorithm::Es256 => gen_keypair(KeyType::EcdsaP256), - SignatureAlgorithm::Es384 => gen_keypair(KeyType::EcdsaP384), - } - } -} - -#[cfg(test)] -mod tests { - use super::SignatureAlgorithm; - use acme_common::crypto::KeyPair; - use std::str::FromStr; - - #[test] - fn test_es256_from_str() { - let variants = ["ES256", "Es256", "es256"]; - for v in variants.iter() { - let a = SignatureAlgorithm::from_str(v); - assert!(a.is_ok()); - let a = a.unwrap(); - assert_eq!(a, SignatureAlgorithm::Es256); - } - } - - #[test] - fn test_es256_to_str() { - let a = SignatureAlgorithm::Es256; - assert_eq!(a.to_string().as_str(), "ES256"); - } - - #[test] - fn test_eddsa_ed25519_from_str() { - let variants = ["ES256", "Es256", "es256"]; - for v in variants.iter() { - let a = SignatureAlgorithm::from_str(v); - assert!(a.is_ok()); - let a = a.unwrap(); - assert_eq!(a, SignatureAlgorithm::Es256); - } - } - - #[test] - fn test_from_p256() { - let pem = b"-----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg6To1BW8qTehGhPca -0eMcW8iQU4yA02dvtKkuqfny4HChRANCAAQwxx+j3wYGzD5LSFNBTLlT7J+7rWrq -4BGdR8705iwpBeOQgMpLj+9vuFutlVtmoYpJSYa9+49Hxz8aCe1AQeWt ------END PRIVATE KEY-----"; - let k = KeyPair::from_pem(pem).unwrap(); - let s = SignatureAlgorithm::from_pkey(&k); - assert!(s.is_ok()); - let s = s.unwrap(); - assert_eq!(s, SignatureAlgorithm::Es256) - } -} diff --git a/acmed/src/main.rs b/acmed/src/main.rs index 8ac8ab5..eabf974 100644 --- a/acmed/src/main.rs +++ b/acmed/src/main.rs @@ -26,7 +26,7 @@ pub const DEFAULT_CERT_FILE_MODE: u32 = 0o644; pub const DEFAULT_PK_FILE_MODE: u32 = 0o600; pub const DEFAULT_ACCOUNT_FILE_MODE: u32 = 0o600; pub const DEFAULT_KP_REUSE: bool = false; -pub const DEFAULT_JWS_SIGN_ALGO: &str = "ES256"; +pub const DEFAULT_ACCOUNT_KEY_TYPE: crypto::KeyType = crypto::KeyType::EcdsaP256; pub const DEFAULT_POOL_NB_TRIES: usize = 20; pub const DEFAULT_POOL_WAIT_SEC: u64 = 5; pub const DEFAULT_HTTP_FAIL_NB_RETRY: usize = 10;