diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d97b11..84a4408 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,13 @@ Since the author is not a native English speaker, some of the texts used in this ## Work on dependencies +### rust-openssl + +Although OpenSSL supports (Ed|X)(25519|448) since version 1.1.1, the `EVP_PKEY_get1_(ED|X)(25519|448)` functions, which are required for ACMEd, will only land in version 3.0.0. Being new function, bindings have to be written in the `openssl` crate. At first sight, such bindings should be added in the `openssl::pkey::PKey` struct along with the other `EVP_PKEY_get1_*` bindings. + +- https://github.com/sfackler/rust-openssl/issues/1263 +- https://github.com/openssl/openssl/commit/7c664b1f1b5f60bf896f5fdea5c08c401c541dfe + ### botan and botan-sys Although Botan isn't a dependency, it is considered for the replacement of OpenSSL as the default cryptographic API (although OpenSSL will be kept as an alternative). But before this can be done, the Botan crate need to support a few features: @@ -22,18 +29,10 @@ Although Botan isn't a dependency, it is considered for the replacement of OpenS - Self-signed certificate generation (via `botan_sys::botan_x509_cert_gen_selfsigned`). - CSR (requires to add bindings to [create_cert_req](https://botan.randombit.net/handbook/api_ref/x509.html#creating-pkcs-10-requests)) with DER export. - ### attohttpc Add an optional Botan support as the cryptographic library. -### rust-openssl - -An improvement that would be appreciable is to add Curve 25519 support to the [openssl](https://crates.io/crates/openssl) crate. - -- https://github.com/sfackler/rust-openssl/issues/947 -- https://github.com/sfackler/rust-openssl/pull/1275 - ### Find or create a good template engine As reported in [issue #8](https://github.com/breard-r/acmed/issues/8), there is currently no perfect template engine. A good way to help improve ACMEd would be to find or create one that supports all the listed requirements. diff --git a/README.md b/README.md index 94ccf2b..ad2d747 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ The Automatic Certificate Management Environment (ACME), is an internet standard ## Planned features - STAR certificates [RFC 8739](https://tools.ietf.org/html/rfc8739) +- EdDSA support: Ed25519 and Ed448 account keys and certificates - Daemon and certificates management via the `acmectl` tool - Nonce scoping configuration - HTTP/2 support diff --git a/acme_common/Cargo.toml b/acme_common/Cargo.toml index 2c3882e..4b2f071 100644 --- a/acme_common/Cargo.toml +++ b/acme_common/Cargo.toml @@ -15,7 +15,6 @@ name = "acme_common" [features] default = [] openssl_dyn = ["openssl", "openssl-sys"] -ed25519 = [] [dependencies] attohttpc = { version = "0.15", default-features = false } diff --git a/acme_common/src/crypto/jws_signature_algorithm.rs b/acme_common/src/crypto/jws_signature_algorithm.rs index b2a9bcc..8a8d27d 100644 --- a/acme_common/src/crypto/jws_signature_algorithm.rs +++ b/acme_common/src/crypto/jws_signature_algorithm.rs @@ -7,8 +7,10 @@ pub enum JwsSignatureAlgorithm { Rs256, Es256, Es384, - #[cfg(feature = "ed25519")] + #[cfg(ed25519)] Ed25519, + #[cfg(ed448)] + Ed448, } impl FromStr for JwsSignatureAlgorithm { @@ -19,8 +21,10 @@ impl FromStr for JwsSignatureAlgorithm { "rs256" => Ok(JwsSignatureAlgorithm::Rs256), "es256" => Ok(JwsSignatureAlgorithm::Es256), "es384" => Ok(JwsSignatureAlgorithm::Es384), - #[cfg(feature = "ed25519")] + #[cfg(ed25519)] "ed25519" => Ok(JwsSignatureAlgorithm::Ed25519), + #[cfg(ed448)] + "ed448" => Ok(JwsSignatureAlgorithm::Ed448), _ => Err(format!("{}: unknown algorithm.", s).into()), } } @@ -32,8 +36,10 @@ impl fmt::Display for JwsSignatureAlgorithm { JwsSignatureAlgorithm::Rs256 => "RS256", JwsSignatureAlgorithm::Es256 => "ES256", JwsSignatureAlgorithm::Es384 => "ES384", - #[cfg(feature = "ed25519")] + #[cfg(ed25519)] JwsSignatureAlgorithm::Ed25519 => "Ed25519", + #[cfg(ed448)] + JwsSignatureAlgorithm::Ed448 => "Ed448", }; write!(f, "{}", s) } diff --git a/acme_common/src/crypto/key_type.rs b/acme_common/src/crypto/key_type.rs index 6e3a987..edaa3ec 100644 --- a/acme_common/src/crypto/key_type.rs +++ b/acme_common/src/crypto/key_type.rs @@ -9,8 +9,10 @@ pub enum KeyType { Rsa4096, EcdsaP256, EcdsaP384, - #[cfg(feature = "ed25519")] + #[cfg(ed25519)] Ed25519, + #[cfg(ed448)] + Ed448, } impl KeyType { @@ -19,8 +21,10 @@ impl KeyType { KeyType::Rsa2048 | KeyType::Rsa4096 => JwsSignatureAlgorithm::Rs256, KeyType::EcdsaP256 => JwsSignatureAlgorithm::Es256, KeyType::EcdsaP384 => JwsSignatureAlgorithm::Es384, - #[cfg(feature = "ed25519")] + #[cfg(ed25519)] KeyType::Ed25519 => JwsSignatureAlgorithm::Ed25519, + #[cfg(ed448)] + KeyType::Ed448 => JwsSignatureAlgorithm::Ed448, } } @@ -28,8 +32,10 @@ impl KeyType { let ok = match self { KeyType::Rsa2048 | KeyType::Rsa4096 => *alg == JwsSignatureAlgorithm::Rs256, KeyType::EcdsaP256 | KeyType::EcdsaP384 => *alg == self.get_default_signature_alg(), - #[cfg(feature = "ed25519")] + #[cfg(ed25519)] KeyType::Ed25519 => *alg == self.get_default_signature_alg(), + #[cfg(ed448)] + KeyType::Ed448 => *alg == self.get_default_signature_alg(), }; if ok { Ok(()) @@ -52,8 +58,10 @@ impl FromStr for KeyType { "rsa4096" => Ok(KeyType::Rsa4096), "ecdsa_p256" => Ok(KeyType::EcdsaP256), "ecdsa_p384" => Ok(KeyType::EcdsaP384), - #[cfg(feature = "ed25519")] + #[cfg(ed25519)] "ed25519" => Ok(KeyType::Ed25519), + #[cfg(ed448)] + "ed448" => Ok(KeyType::Ed448), _ => Err(format!("{}: unknown algorithm.", s).into()), } } @@ -66,8 +74,10 @@ impl fmt::Display for KeyType { KeyType::Rsa4096 => "rsa4096", KeyType::EcdsaP256 => "ecdsa-p256", KeyType::EcdsaP384 => "ecdsa-p384", - #[cfg(feature = "ed25519")] + #[cfg(ed25519)] KeyType::Ed25519 => "ed25519", + #[cfg(ed448)] + KeyType::Ed448 => "ed448", }; write!(f, "{}", s) } diff --git a/acme_common/src/crypto/openssl_certificate.rs b/acme_common/src/crypto/openssl_certificate.rs index 9cef745..424054b 100644 --- a/acme_common/src/crypto/openssl_certificate.rs +++ b/acme_common/src/crypto/openssl_certificate.rs @@ -66,9 +66,20 @@ impl X509Certificate { Ok(native_tls::Certificate::from_pem(pem_data)?) } - pub fn from_acme_ext(domain: &str, acme_ext: &str) -> Result<(KeyPair, Self), Error> { - let key_pair = gen_keypair(KeyType::EcdsaP256)?; - let inner_cert = gen_certificate(domain, &key_pair, acme_ext)?; + pub fn from_acme_ext( + domain: &str, + acme_ext: &str, + key_type: KeyType, + ) -> Result<(KeyPair, Self), Error> { + let key_pair = gen_keypair(key_type)?; + #[cfg(not(any(ed25519, ed448)))] + let digest = MessageDigest::sha256(); + #[cfg(any(ed25519, ed448))] + let digest = match key_pair.key_type { + KeyType::Ed25519 | KeyType::Ed448 => MessageDigest::null(), + _ => MessageDigest::sha256(), + }; + let inner_cert = gen_certificate(domain, &key_pair, &digest, acme_ext)?; let cert = X509Certificate { inner_cert }; Ok((key_pair, cert)) } @@ -114,7 +125,12 @@ impl X509Certificate { } } -fn gen_certificate(domain: &str, key_pair: &KeyPair, acme_ext: &str) -> Result { +fn gen_certificate( + domain: &str, + key_pair: &KeyPair, + digest: &MessageDigest, + acme_ext: &str, +) -> Result { let mut x509_name = X509NameBuilder::new()?; x509_name.append_entry_by_text("O", APP_ORG)?; let ca_name = format!("{} TLS-ALPN-01 Authority", APP_NAME); @@ -142,19 +158,22 @@ fn gen_certificate(domain: &str, key_pair: &KeyPair, acme_ext: &str) -> Result = acme_ext.split('=').collect(); - let value = v.pop().ok_or_else(|| Error::from(INVALID_EXT_MSG))?; - let acme_ext_name = v.pop().ok_or_else(|| Error::from(INVALID_EXT_MSG))?; - if !v.is_empty() { - return Err(Error::from(INVALID_EXT_MSG)); + if !acme_ext.is_empty() { + let ctx = builder.x509v3_context(None, None); + let mut v: Vec<&str> = acme_ext.split('=').collect(); + let value = v.pop().ok_or_else(|| Error::from(INVALID_EXT_MSG))?; + let acme_ext_name = v.pop().ok_or_else(|| Error::from(INVALID_EXT_MSG))?; + if !v.is_empty() { + return Err(Error::from(INVALID_EXT_MSG)); + } + let acme_ext = X509Extension::new(None, Some(&ctx), &acme_ext_name, &value) + .map_err(|_| Error::from(INVALID_EXT_MSG))?; + builder + .append_extension(acme_ext) + .map_err(|_| Error::from(INVALID_EXT_MSG))?; } - let acme_ext = X509Extension::new(None, Some(&ctx), &acme_ext_name, &value) - .map_err(|_| Error::from(INVALID_EXT_MSG))?; - builder - .append_extension(acme_ext) - .map_err(|_| Error::from(INVALID_EXT_MSG))?; - builder.sign(&key_pair.inner_key, MessageDigest::sha256())?; + + builder.sign(&key_pair.inner_key, *digest)?; let cert = builder.build(); Ok(cert) } diff --git a/acme_common/src/crypto/openssl_keys.rs b/acme_common/src/crypto/openssl_keys.rs index c69f7d0..7b5fc29 100644 --- a/acme_common/src/crypto/openssl_keys.rs +++ b/acme_common/src/crypto/openssl_keys.rs @@ -32,6 +32,10 @@ macro_rules! get_key_type { return Err("None: Unsupported EC key".into()); } }, + #[cfg(ed25519)] + Id::ED25519 => KeyType::Ed25519, + #[cfg(ed448)] + Id::ED448 => KeyType::Ed448, _ => { return Err("Unsupported key type".into()); } @@ -70,6 +74,10 @@ impl KeyPair { 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), + #[cfg(ed25519)] + JwsSignatureAlgorithm::Ed25519 => self.sign_eddsa(data), + #[cfg(ed448)] + JwsSignatureAlgorithm::Ed448 => self.sign_eddsa(data), } } @@ -94,6 +102,13 @@ impl KeyPair { Ok(signature) } + #[cfg(any(ed25519, ed448))] + fn sign_eddsa(&self, data: &[u8]) -> Result, Error> { + let mut signer = Signer::new_without_digest(&self.inner_key)?; + let signature = signer.sign_oneshot_to_vec(data)?; + Ok(signature) + } + pub fn jwk_public_key(&self) -> Result { self.get_jwk_public_key(false) } @@ -104,8 +119,12 @@ impl KeyPair { fn get_jwk_public_key(&self, thumbprint: bool) -> Result { match self.key_type { - KeyType::EcdsaP256 | KeyType::EcdsaP384 => self.get_nist_ec_jwk(thumbprint), KeyType::Rsa2048 | KeyType::Rsa4096 => self.get_rsa_jwk(thumbprint), + KeyType::EcdsaP256 | KeyType::EcdsaP384 => self.get_ecdsa_jwk(thumbprint), + #[cfg(ed25519)] + KeyType::Ed25519 => self.get_eddsa_jwk(thumbprint), + #[cfg(ed448)] + KeyType::Ed448 => self.get_eddsa_jwk(thumbprint), } } @@ -133,12 +152,12 @@ impl KeyPair { Ok(jwk) } - fn get_nist_ec_jwk(&self, thumbprint: bool) -> Result { + fn get_ecdsa_jwk(&self, thumbprint: bool) -> Result { let (crv, alg, curve) = match self.key_type { KeyType::EcdsaP256 => ("P-256", "ES256", Nid::X9_62_PRIME256V1), KeyType::EcdsaP384 => ("P-384", "ES384", Nid::SECP384R1), _ => { - return Err("Not a NIST elliptic curve.".into()); + return Err("Not an ECDSA elliptic curve.".into()); } }; let group = EcGroup::from_curve_name(curve).unwrap(); @@ -171,6 +190,37 @@ impl KeyPair { }; Ok(jwk) } + + #[cfg(any(ed25519, ed448))] + fn get_eddsa_jwk(&self, thumbprint: bool) -> Result { + let crv = match self.key_type { + #[cfg(ed25519)] + KeyType::Ed25519 => "Ed25519", + #[cfg(ed448)] + KeyType::Ed448 => "Ed448", + _ => { + return Err("Not an EdDSA elliptic curve.".into()); + } + }; + let x = ""; + let jwk = if thumbprint { + json!({ + "crv": crv, + "kty": "OKP", + "x": x, + }) + } else { + json!({ + "alg": "EdDSA", + "crv": crv, + "kty": "OKP", + "use": "sig", + "x": x, + }) + }; + //Ok(jwk) + Err("TODO: implement get_eddsa_jwk (require binding to EVP_PKEY_get1_ED25519, which requires OpenSSL 3.0)".into()) + } } fn gen_rsa_pair(nb_bits: u32) -> Result, Error> { @@ -190,12 +240,28 @@ fn gen_ec_pair(nid: Nid) -> Result, Error> { Ok(pk) } +#[cfg(ed25519)] +fn gen_ed25519_pair() -> Result, Error> { + let pk = PKey::generate_ed25519().map_err(|_| Error::from(""))?; + Ok(pk) +} + +#[cfg(ed448)] +fn gen_ed448_pair() -> Result, Error> { + let pk = PKey::generate_ed448().map_err(|_| Error::from(""))?; + Ok(pk) +} + pub fn gen_keypair(key_type: KeyType) -> Result { let priv_key = match key_type { - KeyType::EcdsaP256 => gen_ec_pair(Nid::X9_62_PRIME256V1), - KeyType::EcdsaP384 => gen_ec_pair(Nid::SECP384R1), KeyType::Rsa2048 => gen_rsa_pair(2048), KeyType::Rsa4096 => gen_rsa_pair(4096), + KeyType::EcdsaP256 => gen_ec_pair(Nid::X9_62_PRIME256V1), + KeyType::EcdsaP384 => gen_ec_pair(Nid::SECP384R1), + #[cfg(ed25519)] + KeyType::Ed25519 => gen_ed25519_pair(), + #[cfg(ed448)] + KeyType::Ed448 => gen_ed448_pair(), } .map_err(|_| Error::from(format!("Unable to generate a {} key pair.", key_type)))?; let key_pair = KeyPair { diff --git a/acme_common/src/tests.rs b/acme_common/src/tests.rs index fed6851..703bc7c 100644 --- a/acme_common/src/tests.rs +++ b/acme_common/src/tests.rs @@ -1,3 +1,4 @@ mod certificate; mod crypto_keys; mod idna; +mod jws_signature_algorithm; diff --git a/acme_common/src/tests/certificate.rs b/acme_common/src/tests/certificate.rs index d3cc1e2..328f4e2 100644 --- a/acme_common/src/tests/certificate.rs +++ b/acme_common/src/tests/certificate.rs @@ -1,4 +1,4 @@ -use crate::crypto::X509Certificate; +use crate::crypto::{KeyType, X509Certificate}; use std::collections::HashSet; use std::iter::FromIterator; @@ -82,3 +82,41 @@ fn test_san_domains_and_ip() { let crt = X509Certificate::from_pem(CERTIFICATE_P256_DOMAINS_IP_PEM.as_bytes()).unwrap(); assert_eq!(crt.subject_alt_names(), san); } + +#[test] +fn generate_rsa2048_certificate() { + let (kp, _) = X509Certificate::from_acme_ext("example.org", "", KeyType::Rsa2048).unwrap(); + assert_eq!(kp.key_type, KeyType::Rsa2048); +} + +#[test] +fn generate_rsa4096_certificate() { + let (kp, _) = X509Certificate::from_acme_ext("example.org", "", KeyType::Rsa4096).unwrap(); + assert_eq!(kp.key_type, KeyType::Rsa4096); +} + +#[test] +fn generate_ecdsa_p256_certificate() { + let (kp, _) = X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP256).unwrap(); + assert_eq!(kp.key_type, KeyType::EcdsaP256); +} + +#[test] +fn generate_ecdsa_p384_certificate() { + let (kp, _) = X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP384).unwrap(); + assert_eq!(kp.key_type, KeyType::EcdsaP384); +} + +#[cfg(ed25519)] +#[test] +fn generate_ed25519_certificate() { + let (kp, _) = X509Certificate::from_acme_ext("example.org", "", KeyType::Ed25519).unwrap(); + assert_eq!(kp.key_type, KeyType::Ed25519); +} + +#[cfg(ed448)] +#[test] +fn generate_ed448_certificate() { + let (kp, _) = X509Certificate::from_acme_ext("example.org", "", KeyType::Ed448).unwrap(); + assert_eq!(kp.key_type, KeyType::Ed448); +} diff --git a/acme_common/src/tests/crypto_keys.rs b/acme_common/src/tests/crypto_keys.rs index 942b2e0..1a71c61 100644 --- a/acme_common/src/tests/crypto_keys.rs +++ b/acme_common/src/tests/crypto_keys.rs @@ -1,13 +1,5 @@ use crate::crypto::KeyPair; -const KEY_ECDSA_P256_PEM: &str = r#"-----BEGIN PRIVATE KEY----- -MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCQc9OXwvygYqOFT4fN -NpXynr1lu+1sSplFdYoWu7hE4g== ------END PRIVATE KEY-----"#; -const KEY_ECDSA_P384_PEM: &str = r#"-----BEGIN PRIVATE KEY----- -ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDCMsN9kHPueLABk+0PKi7WO -PO2/53dpt/yV5zOPrYPEoKs4t973nbt46IUN19lLF/s= ------END PRIVATE KEY-----"#; const KEY_RSA_2048_PEM: &str = r#"-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzfwZGF8zKNAg2 9mdZ9ieE7V2clY3oeI+2V7eV5kUwOGqhhpDaDyDmju+l0dKFwF8xeDeeGmTSED10 @@ -88,6 +80,95 @@ Q2ZTyps7X64dx6yOIRv6pPd3qZGRz2VoKW2x/sLoeErPsVtUW0u+NSKgR6O5sh7v Mc5vg/2W9HWaAXdjyrXIJyypitp0Q9M1cSowzt/BaWNvb3i/En8uEXR5zZjl/CFG yr9E4nQyE5YlYlPUK6iIRBu9j1N2MhY= -----END PRIVATE KEY-----"#; +const KEY_ECDSA_P256_PEM: &str = r#"-----BEGIN PRIVATE KEY----- +MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCQc9OXwvygYqOFT4fN +NpXynr1lu+1sSplFdYoWu7hE4g== +-----END PRIVATE KEY-----"#; +const KEY_ECDSA_P384_PEM: &str = r#"-----BEGIN PRIVATE KEY----- +ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDCMsN9kHPueLABk+0PKi7WO +PO2/53dpt/yV5zOPrYPEoKs4t973nbt46IUN19lLF/s= +-----END PRIVATE KEY-----"#; +#[cfg(ed25519)] +const KEY_ECDSA_ED25519_PEM: &str = r#"-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIJhpRNsiUzoWqNkpJKCtKV5++Tttz3locu1gQKkQnrOa +-----END PRIVATE KEY-----"#; +#[cfg(ed448)] +const KEY_ECDSA_ED448_PEM: &str = r#"-----BEGIN PRIVATE KEY----- +MEcCAQAwBQYDK2VxBDsEOcFBwsH4zU7u5RgFh48MgJPzXyjN5uXxDapZv4rG6opU +uMXco2JR1CSjKWgqgu1CAKadJIYiv2EgIw== +-----END PRIVATE KEY-----"#; + +#[test] +fn test_rsa_2048_jwk() { + let k = KeyPair::from_pem(KEY_RSA_2048_PEM.as_bytes()).unwrap(); + let jwk = k.jwk_public_key().unwrap(); + assert!(jwk.is_object()); + let jwk = jwk.as_object().unwrap(); + assert_eq!(jwk.len(), 5); + assert!(jwk.contains_key("kty")); + assert!(jwk.contains_key("e")); + assert!(jwk.contains_key("n")); + assert!(jwk.contains_key("use")); + assert!(jwk.contains_key("alg")); + assert_eq!(jwk.get("kty").unwrap(), "RSA"); + assert_eq!(jwk.get("e").unwrap(), "AQAB"); + assert_eq!(jwk.get("n").unwrap(), "s38GRhfMyjQINvZnWfYnhO1dnJWN6HiPtle3leZFMDhqoYaQ2g8g5o7vpdHShcBfMXg3nhpk0hA9dHt_GbB6iRdHGaig6wd4TngwLJ-2erLR3_0WaM0DubAJmaTe4ND9JYVyZ8gK_li-fF-NZFrrn4j1W71EUL_7St8jdivqwujHWdpS7C3piosAJW8hqz31M7lXOnV61PCb15JMLiKQMhBCezk13QWk-FQBx7ZtmA1iMFvt-Drcqdhb20iWLCMCYwtNLez4ZmofWzI4sqQmQejpJ2Ve1gGeeY2hf68qQEQf8804nksp-EIv1Y4qVhO5zvxo7m8s6ybUJqvqOz5u9Q"); + assert_eq!(jwk.get("use").unwrap(), "sig"); + assert_eq!(jwk.get("alg").unwrap(), "RS256"); +} + +#[test] +fn test_rsa_2048_jwk_thumbprint() { + let k = KeyPair::from_pem(KEY_RSA_2048_PEM.as_bytes()).unwrap(); + let jwk = k.jwk_public_key_thumbprint().unwrap(); + assert!(jwk.is_object()); + let jwk = jwk.as_object().unwrap(); + assert_eq!(jwk.len(), 3); + assert!(jwk.contains_key("kty")); + assert!(jwk.contains_key("e")); + assert!(jwk.contains_key("n")); + assert!(!jwk.contains_key("use")); + assert!(!jwk.contains_key("alg")); + assert_eq!(jwk.get("kty").unwrap(), "RSA"); + assert_eq!(jwk.get("e").unwrap(), "AQAB"); + assert_eq!(jwk.get("n").unwrap(), "s38GRhfMyjQINvZnWfYnhO1dnJWN6HiPtle3leZFMDhqoYaQ2g8g5o7vpdHShcBfMXg3nhpk0hA9dHt_GbB6iRdHGaig6wd4TngwLJ-2erLR3_0WaM0DubAJmaTe4ND9JYVyZ8gK_li-fF-NZFrrn4j1W71EUL_7St8jdivqwujHWdpS7C3piosAJW8hqz31M7lXOnV61PCb15JMLiKQMhBCezk13QWk-FQBx7ZtmA1iMFvt-Drcqdhb20iWLCMCYwtNLez4ZmofWzI4sqQmQejpJ2Ve1gGeeY2hf68qQEQf8804nksp-EIv1Y4qVhO5zvxo7m8s6ybUJqvqOz5u9Q"); +} + +#[test] +fn test_rsa_4096_jwk() { + let k = KeyPair::from_pem(KEY_RSA_4096_PEM.as_bytes()).unwrap(); + let jwk = k.jwk_public_key().unwrap(); + assert!(jwk.is_object()); + let jwk = jwk.as_object().unwrap(); + assert_eq!(jwk.len(), 5); + assert!(jwk.contains_key("kty")); + assert!(jwk.contains_key("e")); + assert!(jwk.contains_key("n")); + assert!(jwk.contains_key("use")); + assert!(jwk.contains_key("alg")); + assert_eq!(jwk.get("kty").unwrap(), "RSA"); + assert_eq!(jwk.get("e").unwrap(), "AQAB"); + assert_eq!(jwk.get("n").unwrap(), "jm0Jh8Zk-aRqUxME0yLeHhBtdDFq6IvVesr_ffFwGqtlI4tIa6MqYuuOE5qlqoBrxt1fpccqGF82j47JqeoWerkNdZxYh1uKGQa9G2vB4OBOFHxYujT-NNPPlwnVPrp1_pjxK0hiOGzZRliNhO65lMbEueQCaLbQ6wstr4aVsWKiw8O4MG8Cc2ZegkWlYg0ZHOAn-uJlAt17NJfX8x576XRwX2KCssPYeyiwBukRne3ahVVqrc35EmV9JH3CxDaG9MeBLo7FUV5P1GNOIs313cbAgdMYV6Ahr2LvsaLucFvhSwmHh42--meDui-0wdFILjrz7MsITZy4xXjl8_zqMZ2dBaO38wP5o9VCfCXaVlD3IYB9A4-Y9IQ6wEVeodzGloPQyddUZ70kZ7O3E1kYu0tBKhoAL1VcKiUvOj1ABGooqf8QIAWCiAWqDqkZJ5HiR61NC9lJ3MH-aqYXzeHUuOxdlfr3oWPQKlRyYoUW93xD4dXjHQyHsIS4gWYE9ZJ9aIomNHj93UPNfR8ScxQTQBJV9Ttgl_vOUdmlasTmo0OXbTLMexzWCVISvUuoBoTcTY4DXCFeYHz3EE3ijJ9fnDkX3SsvV43x8X58py9rEyAQnRxfeahpWEoxW6TA5qBxg1GMBQu6CY2MupqsmI0XHTXzy8xCt4Pqd_a5zP6Wh_E"); + assert_eq!(jwk.get("use").unwrap(), "sig"); + assert_eq!(jwk.get("alg").unwrap(), "RS256"); +} + +#[test] +fn test_rsa_4096_jwk_thumbprint() { + let k = KeyPair::from_pem(KEY_RSA_4096_PEM.as_bytes()).unwrap(); + let jwk = k.jwk_public_key_thumbprint().unwrap(); + assert!(jwk.is_object()); + let jwk = jwk.as_object().unwrap(); + assert_eq!(jwk.len(), 3); + assert!(jwk.contains_key("kty")); + assert!(jwk.contains_key("e")); + assert!(jwk.contains_key("n")); + assert!(!jwk.contains_key("use")); + assert!(!jwk.contains_key("alg")); + assert_eq!(jwk.get("kty").unwrap(), "RSA"); + assert_eq!(jwk.get("e").unwrap(), "AQAB"); + assert_eq!(jwk.get("n").unwrap(), "jm0Jh8Zk-aRqUxME0yLeHhBtdDFq6IvVesr_ffFwGqtlI4tIa6MqYuuOE5qlqoBrxt1fpccqGF82j47JqeoWerkNdZxYh1uKGQa9G2vB4OBOFHxYujT-NNPPlwnVPrp1_pjxK0hiOGzZRliNhO65lMbEueQCaLbQ6wstr4aVsWKiw8O4MG8Cc2ZegkWlYg0ZHOAn-uJlAt17NJfX8x576XRwX2KCssPYeyiwBukRne3ahVVqrc35EmV9JH3CxDaG9MeBLo7FUV5P1GNOIs313cbAgdMYV6Ahr2LvsaLucFvhSwmHh42--meDui-0wdFILjrz7MsITZy4xXjl8_zqMZ2dBaO38wP5o9VCfCXaVlD3IYB9A4-Y9IQ6wEVeodzGloPQyddUZ70kZ7O3E1kYu0tBKhoAL1VcKiUvOj1ABGooqf8QIAWCiAWqDqkZJ5HiR61NC9lJ3MH-aqYXzeHUuOxdlfr3oWPQKlRyYoUW93xD4dXjHQyHsIS4gWYE9ZJ9aIomNHj93UPNfR8ScxQTQBJV9Ttgl_vOUdmlasTmo0OXbTLMexzWCVISvUuoBoTcTY4DXCFeYHz3EE3ijJ9fnDkX3SsvV43x8X58py9rEyAQnRxfeahpWEoxW6TA5qBxg1GMBQu6CY2MupqsmI0XHTXzy8xCt4Pqd_a5zP6Wh_E"); +} #[test] fn test_ecdsa_p256_jwk() { @@ -193,74 +274,90 @@ fn test_ecdsa_p384_jwk_thumbprint() { ); } +#[cfg(ed25519)] #[test] -fn test_rsa_2048_jwk() { - let k = KeyPair::from_pem(KEY_RSA_2048_PEM.as_bytes()).unwrap(); +fn test_ed25519_jwk() { + let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM.as_bytes()).unwrap(); let jwk = k.jwk_public_key().unwrap(); assert!(jwk.is_object()); let jwk = jwk.as_object().unwrap(); assert_eq!(jwk.len(), 5); assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("e")); - assert!(jwk.contains_key("n")); + assert!(jwk.contains_key("crv")); + assert!(jwk.contains_key("x")); assert!(jwk.contains_key("use")); assert!(jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "RSA"); - assert_eq!(jwk.get("e").unwrap(), "AQAB"); - assert_eq!(jwk.get("n").unwrap(), "s38GRhfMyjQINvZnWfYnhO1dnJWN6HiPtle3leZFMDhqoYaQ2g8g5o7vpdHShcBfMXg3nhpk0hA9dHt_GbB6iRdHGaig6wd4TngwLJ-2erLR3_0WaM0DubAJmaTe4ND9JYVyZ8gK_li-fF-NZFrrn4j1W71EUL_7St8jdivqwujHWdpS7C3piosAJW8hqz31M7lXOnV61PCb15JMLiKQMhBCezk13QWk-FQBx7ZtmA1iMFvt-Drcqdhb20iWLCMCYwtNLez4ZmofWzI4sqQmQejpJ2Ve1gGeeY2hf68qQEQf8804nksp-EIv1Y4qVhO5zvxo7m8s6ybUJqvqOz5u9Q"); + assert_eq!(jwk.get("kty").unwrap(), "OKP"); + assert_eq!(jwk.get("crv").unwrap(), "Ed25519"); + assert_eq!( + jwk.get("x").unwrap(), + "DUX9ja8pq2wfkxuIaHzmhkdcVXMav_3rk5Y5ozOcp4o" + ); assert_eq!(jwk.get("use").unwrap(), "sig"); - assert_eq!(jwk.get("alg").unwrap(), "RS256"); + assert_eq!(jwk.get("alg").unwrap(), "EdDSA"); } +#[cfg(ed25519)] #[test] -fn test_rsa_2048_jwk_thumbprint() { - let k = KeyPair::from_pem(KEY_RSA_2048_PEM.as_bytes()).unwrap(); +fn test_ed25519_jwk_thumbprint() { + let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM.as_bytes()).unwrap(); let jwk = k.jwk_public_key_thumbprint().unwrap(); assert!(jwk.is_object()); let jwk = jwk.as_object().unwrap(); assert_eq!(jwk.len(), 3); assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("e")); - assert!(jwk.contains_key("n")); + assert!(jwk.contains_key("crv")); + assert!(jwk.contains_key("x")); assert!(!jwk.contains_key("use")); assert!(!jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "RSA"); - assert_eq!(jwk.get("e").unwrap(), "AQAB"); - assert_eq!(jwk.get("n").unwrap(), "s38GRhfMyjQINvZnWfYnhO1dnJWN6HiPtle3leZFMDhqoYaQ2g8g5o7vpdHShcBfMXg3nhpk0hA9dHt_GbB6iRdHGaig6wd4TngwLJ-2erLR3_0WaM0DubAJmaTe4ND9JYVyZ8gK_li-fF-NZFrrn4j1W71EUL_7St8jdivqwujHWdpS7C3piosAJW8hqz31M7lXOnV61PCb15JMLiKQMhBCezk13QWk-FQBx7ZtmA1iMFvt-Drcqdhb20iWLCMCYwtNLez4ZmofWzI4sqQmQejpJ2Ve1gGeeY2hf68qQEQf8804nksp-EIv1Y4qVhO5zvxo7m8s6ybUJqvqOz5u9Q"); + assert_eq!(jwk.get("kty").unwrap(), "OKP"); + assert_eq!(jwk.get("crv").unwrap(), "Ed25519"); + assert_eq!( + jwk.get("x").unwrap(), + "DUX9ja8pq2wfkxuIaHzmhkdcVXMav_3rk5Y5ozOcp4o" + ); } +#[cfg(ed448)] #[test] -fn test_rsa_4096_jwk() { - let k = KeyPair::from_pem(KEY_RSA_4096_PEM.as_bytes()).unwrap(); +fn test_ed448_jwk() { + let k = KeyPair::from_pem(KEY_ECDSA_ED448_PEM.as_bytes()).unwrap(); let jwk = k.jwk_public_key().unwrap(); assert!(jwk.is_object()); let jwk = jwk.as_object().unwrap(); assert_eq!(jwk.len(), 5); assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("e")); - assert!(jwk.contains_key("n")); + assert!(jwk.contains_key("crv")); + assert!(jwk.contains_key("x")); assert!(jwk.contains_key("use")); assert!(jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "RSA"); - assert_eq!(jwk.get("e").unwrap(), "AQAB"); - assert_eq!(jwk.get("n").unwrap(), "jm0Jh8Zk-aRqUxME0yLeHhBtdDFq6IvVesr_ffFwGqtlI4tIa6MqYuuOE5qlqoBrxt1fpccqGF82j47JqeoWerkNdZxYh1uKGQa9G2vB4OBOFHxYujT-NNPPlwnVPrp1_pjxK0hiOGzZRliNhO65lMbEueQCaLbQ6wstr4aVsWKiw8O4MG8Cc2ZegkWlYg0ZHOAn-uJlAt17NJfX8x576XRwX2KCssPYeyiwBukRne3ahVVqrc35EmV9JH3CxDaG9MeBLo7FUV5P1GNOIs313cbAgdMYV6Ahr2LvsaLucFvhSwmHh42--meDui-0wdFILjrz7MsITZy4xXjl8_zqMZ2dBaO38wP5o9VCfCXaVlD3IYB9A4-Y9IQ6wEVeodzGloPQyddUZ70kZ7O3E1kYu0tBKhoAL1VcKiUvOj1ABGooqf8QIAWCiAWqDqkZJ5HiR61NC9lJ3MH-aqYXzeHUuOxdlfr3oWPQKlRyYoUW93xD4dXjHQyHsIS4gWYE9ZJ9aIomNHj93UPNfR8ScxQTQBJV9Ttgl_vOUdmlasTmo0OXbTLMexzWCVISvUuoBoTcTY4DXCFeYHz3EE3ijJ9fnDkX3SsvV43x8X58py9rEyAQnRxfeahpWEoxW6TA5qBxg1GMBQu6CY2MupqsmI0XHTXzy8xCt4Pqd_a5zP6Wh_E"); + assert_eq!(jwk.get("kty").unwrap(), "OKP"); + assert_eq!(jwk.get("crv").unwrap(), "Ed448"); + assert_eq!( + jwk.get("x").unwrap(), + "ib9GZ8b1hip3UMzkkNBdMF4JWBTZojxsNHK-jQBH94SY3boVs4Oeo291E1dGXz7RUMqIXjkSbU4EA" + ); assert_eq!(jwk.get("use").unwrap(), "sig"); - assert_eq!(jwk.get("alg").unwrap(), "RS256"); + assert_eq!(jwk.get("alg").unwrap(), "EdDSA"); } +#[cfg(ed448)] #[test] -fn test_rsa_4096_jwk_thumbprint() { - let k = KeyPair::from_pem(KEY_RSA_4096_PEM.as_bytes()).unwrap(); +fn test_ed448_jwk_thumbprint() { + let k = KeyPair::from_pem(KEY_ECDSA_ED448_PEM.as_bytes()).unwrap(); let jwk = k.jwk_public_key_thumbprint().unwrap(); assert!(jwk.is_object()); let jwk = jwk.as_object().unwrap(); assert_eq!(jwk.len(), 3); assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("e")); - assert!(jwk.contains_key("n")); + assert!(jwk.contains_key("crv")); + assert!(jwk.contains_key("x")); assert!(!jwk.contains_key("use")); assert!(!jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "RSA"); - assert_eq!(jwk.get("e").unwrap(), "AQAB"); - assert_eq!(jwk.get("n").unwrap(), "jm0Jh8Zk-aRqUxME0yLeHhBtdDFq6IvVesr_ffFwGqtlI4tIa6MqYuuOE5qlqoBrxt1fpccqGF82j47JqeoWerkNdZxYh1uKGQa9G2vB4OBOFHxYujT-NNPPlwnVPrp1_pjxK0hiOGzZRliNhO65lMbEueQCaLbQ6wstr4aVsWKiw8O4MG8Cc2ZegkWlYg0ZHOAn-uJlAt17NJfX8x576XRwX2KCssPYeyiwBukRne3ahVVqrc35EmV9JH3CxDaG9MeBLo7FUV5P1GNOIs313cbAgdMYV6Ahr2LvsaLucFvhSwmHh42--meDui-0wdFILjrz7MsITZy4xXjl8_zqMZ2dBaO38wP5o9VCfCXaVlD3IYB9A4-Y9IQ6wEVeodzGloPQyddUZ70kZ7O3E1kYu0tBKhoAL1VcKiUvOj1ABGooqf8QIAWCiAWqDqkZJ5HiR61NC9lJ3MH-aqYXzeHUuOxdlfr3oWPQKlRyYoUW93xD4dXjHQyHsIS4gWYE9ZJ9aIomNHj93UPNfR8ScxQTQBJV9Ttgl_vOUdmlasTmo0OXbTLMexzWCVISvUuoBoTcTY4DXCFeYHz3EE3ijJ9fnDkX3SsvV43x8X58py9rEyAQnRxfeahpWEoxW6TA5qBxg1GMBQu6CY2MupqsmI0XHTXzy8xCt4Pqd_a5zP6Wh_E"); + assert_eq!(jwk.get("kty").unwrap(), "OKP"); + assert_eq!(jwk.get("crv").unwrap(), "Ed448"); + assert_eq!( + jwk.get("x").unwrap(), + "ib9GZ8b1hip3UMzkkNBdMF4JWBTZojxsNHK-jQBH94SY3boVs4Oeo291E1dGXz7RUMqIXjkSbU4EA" + ); } diff --git a/acme_common/src/tests/jws_signature_algorithm.rs b/acme_common/src/tests/jws_signature_algorithm.rs new file mode 100644 index 0000000..93b4a51 --- /dev/null +++ b/acme_common/src/tests/jws_signature_algorithm.rs @@ -0,0 +1,62 @@ +use crate::crypto::{gen_keypair, JwsSignatureAlgorithm, KeyType}; + +const TEST_DATA: &'static [u8] = &[72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]; + +#[test] +fn test_rs256_sign_rsa2048() { + let k = gen_keypair(KeyType::Rsa2048).unwrap(); + let _ = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA).unwrap(); +} + +#[test] +fn test_rs256_sign_rsa4096() { + let k = gen_keypair(KeyType::Rsa4096).unwrap(); + let _ = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA).unwrap(); +} + +#[test] +fn test_rs256_sign_ecdsa() { + let k = gen_keypair(KeyType::EcdsaP256).unwrap(); + let res = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA); + assert!(res.is_err()); +} + +#[test] +fn test_es256_sign_p256() { + let k = gen_keypair(KeyType::EcdsaP256).unwrap(); + let _ = k.sign(&JwsSignatureAlgorithm::Es256, TEST_DATA).unwrap(); +} + +#[test] +fn test_es256_sign_p384() { + let k = gen_keypair(KeyType::EcdsaP384).unwrap(); + let res = k.sign(&JwsSignatureAlgorithm::Es256, TEST_DATA); + assert!(res.is_err()); +} + +#[test] +fn test_es384_sign_p384() { + let k = gen_keypair(KeyType::EcdsaP384).unwrap(); + let _ = k.sign(&JwsSignatureAlgorithm::Es384, TEST_DATA).unwrap(); +} + +#[test] +fn test_es384_sign_p256() { + let k = gen_keypair(KeyType::EcdsaP256).unwrap(); + let res = k.sign(&JwsSignatureAlgorithm::Es384, TEST_DATA); + assert!(res.is_err()); +} + +#[cfg(ed25519)] +#[test] +fn test_ed25519_sign() { + let k = gen_keypair(KeyType::Ed25519).unwrap(); + let _ = k.sign(&JwsSignatureAlgorithm::Ed25519, TEST_DATA).unwrap(); +} + +#[cfg(ed448)] +#[test] +fn test_ed448_sign() { + let k = gen_keypair(KeyType::Ed448).unwrap(); + let _ = k.sign(&JwsSignatureAlgorithm::Ed448, TEST_DATA).unwrap(); +} diff --git a/tacd/src/main.rs b/tacd/src/main.rs index dbc7bc1..54dd1db 100644 --- a/tacd/src/main.rs +++ b/tacd/src/main.rs @@ -1,7 +1,7 @@ mod openssl_server; use crate::openssl_server::start as server_start; -use acme_common::crypto::X509Certificate; +use acme_common::crypto::{KeyType, X509Certificate}; use acme_common::error::Error; use acme_common::{clean_pid_file, to_idna}; use clap::{App, Arg, ArgMatches}; @@ -13,6 +13,7 @@ const APP_NAME: &str = env!("CARGO_PKG_NAME"); const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); const DEFAULT_PID_FILE: &str = "/var/run/tacd.pid"; const DEFAULT_LISTEN_ADDR: &str = "127.0.0.1:5001"; +const DEFAULT_CRT_KEY_TYPE: KeyType = KeyType::EcdsaP256; const ALPN_ACME_PROTO_NAME: &[u8] = b"\x0aacme-tls/1"; fn read_line(path: Option<&str>) -> Result { @@ -49,7 +50,7 @@ fn init(cnf: &ArgMatches) -> Result<(), Error> { let domain = to_idna(&domain)?; let ext = get_acme_value(cnf, "acme-ext", "acme-ext-file")?; let listen_addr = cnf.value_of("listen").unwrap_or(DEFAULT_LISTEN_ADDR); - let (pk, cert) = X509Certificate::from_acme_ext(&domain, &ext)?; + let (pk, cert) = X509Certificate::from_acme_ext(&domain, &ext, DEFAULT_CRT_KEY_TYPE)?; info!("Starting {} on {} for {}", APP_NAME, listen_addr, domain); server_start(listen_addr, &cert, &pk)?; Ok(())