From efe8548a19172e69918396d0fdbc369df9d0eb17 Mon Sep 17 00:00:00 2001 From: Jose Quintana <1700322+joseluisq@users.noreply.github.com> Date: Mon, 9 Oct 2023 03:38:39 +0200 Subject: [PATCH] feat: ecc private keys support for `tls` feature (#1048) It adds support for `ECC` private keys when enabling the `tls` feature. This is particularly useful when generating private keys for example using clients like `Lego ACME` which defaults to `EC256` keys changelog: - ecc private keys support - refactor `TlsConfigError` to reflect intended changes - tokio-rustls 0.24 and subsequent `client_auth` updates --- examples/tls.rs | 4 ++ examples/tls/cert.ecc.pem | 12 ++++++ examples/tls/key.ecc | 5 +++ src/tls.rs | 83 +++++++++++++++++++++++++-------------- 4 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 examples/tls/cert.ecc.pem create mode 100644 examples/tls/key.ecc diff --git a/examples/tls.rs b/examples/tls.rs index 7d28e03a3..3103de9c1 100644 --- a/examples/tls.rs +++ b/examples/tls.rs @@ -13,8 +13,12 @@ async fn main() { warp::serve(routes) .tls() + // RSA .cert_path("examples/tls/cert.pem") .key_path("examples/tls/key.rsa") + // ECC + // .cert_path("examples/tls/cert.ecc.pem") + // .key_path("examples/tls/key.ecc") .run(([127, 0, 0, 1], 3030)) .await; } diff --git a/examples/tls/cert.ecc.pem b/examples/tls/cert.ecc.pem new file mode 100644 index 000000000..f661a6382 --- /dev/null +++ b/examples/tls/cert.ecc.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBtDCCAVoCCQDFz95/8CeJaDAKBggqhkjOPQQDAjBiMQswCQYDVQQGEwJERTEQ +MA4GA1UECAwHR2VybWFueTEQMA4GA1UEBwwHTGVpcHppZzESMBAGA1UEAwwJbG9j +YWwuZGV2MRswGQYJKoZIhvcNAQkBFgxoaUBsb2NhbC5kZXYwHhcNMjMwNTI4MTk0 +NzA4WhcNMjYwNTI3MTk0NzA4WjBiMQswCQYDVQQGEwJERTEQMA4GA1UECAwHR2Vy +bWFueTEQMA4GA1UEBwwHTGVpcHppZzESMBAGA1UEAwwJbG9jYWwuZGV2MRswGQYJ +KoZIhvcNAQkBFgxoaUBsb2NhbC5kZXYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AATZR4F60X+iHjeD6kySZfXljNckDb22QYQ76Ts4GFYWkdDstU6yehxyER+MZWsm +UnTE/Gy3mnpSmMzoSBfoKRmHMAoGCCqGSM49BAMCA0gAMEUCIQChOTwbAYlx6zg0 +yc3Oc+zrNY8Yd8oRUD+cG/wdz+gN/wIgP199zXAPXiYUFFd1CnIYmWJSglaOUbYj +ZP/ixZR9HQs= +-----END CERTIFICATE----- diff --git a/examples/tls/key.ecc b/examples/tls/key.ecc new file mode 100644 index 000000000..9287db76a --- /dev/null +++ b/examples/tls/key.ecc @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIPwp3LAnLEyWe2lLz66Y3QCCJ/BEMJheTM0shZnnSw6toAoGCCqGSM49 +AwEHoUQDQgAE2UeBetF/oh43g+pMkmX15YzXJA29tkGEO+k7OBhWFpHQ7LVOsnoc +chEfjGVrJlJ0xPxst5p6UpjM6EgX6CkZhw== +-----END EC PRIVATE KEY----- diff --git a/src/tls.rs b/src/tls.rs index 96b6ed74e..da324a1c9 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -25,10 +25,12 @@ pub(crate) enum TlsConfigError { Io(io::Error), /// An Error parsing the Certificate CertParseError, - /// An Error parsing a Pkcs8 key - Pkcs8ParseError, - /// An Error parsing a Rsa key - RsaParseError, + /// Identity PEM is invalid + InvalidIdentityPem, + /// Identity PEM is missing a private key such as RSA, ECC or PKCS8 + MissingPrivateKey, + /// Unknown private key format + UnknownPrivateKeyFormat, /// An error from an empty key EmptyKey, /// An error from an invalid key @@ -40,8 +42,12 @@ impl fmt::Display for TlsConfigError { match self { TlsConfigError::Io(err) => err.fmt(f), TlsConfigError::CertParseError => write!(f, "certificate parse error"), - TlsConfigError::Pkcs8ParseError => write!(f, "pkcs8 parse error"), - TlsConfigError::RsaParseError => write!(f, "rsa parse error"), + TlsConfigError::UnknownPrivateKeyFormat => write!(f, "unknown private key format"), + TlsConfigError::MissingPrivateKey => write!( + f, + "Identity PEM is missing a private key such as RSA, ECC or PKCS8" + ), + TlsConfigError::InvalidIdentityPem => write!(f, "identity PEM is invalid"), TlsConfigError::EmptyKey => write!(f, "key contains no private key"), TlsConfigError::InvalidKey(err) => write!(f, "key contains an invalid key, {}", err), } @@ -175,32 +181,30 @@ impl TlsConfigBuilder { .map(Certificate) .collect(); - let key = { - // convert it to Vec to allow reading it again if key is RSA - let mut key_vec = Vec::new(); - self.key - .read_to_end(&mut key_vec) - .map_err(TlsConfigError::Io)?; + let mut key_vec = Vec::new(); + self.key + .read_to_end(&mut key_vec) + .map_err(TlsConfigError::Io)?; - if key_vec.is_empty() { - return Err(TlsConfigError::EmptyKey); - } - - let mut pkcs8 = rustls_pemfile::pkcs8_private_keys(&mut key_vec.as_slice()) - .map_err(|_e| TlsConfigError::Pkcs8ParseError)?; - - if !pkcs8.is_empty() { - PrivateKey(pkcs8.remove(0)) - } else { - let mut rsa = rustls_pemfile::rsa_private_keys(&mut key_vec.as_slice()) - .map_err(|_e| TlsConfigError::RsaParseError)?; + if key_vec.is_empty() { + return Err(TlsConfigError::EmptyKey); + } - if !rsa.is_empty() { - PrivateKey(rsa.remove(0)) - } else { - return Err(TlsConfigError::EmptyKey); - } + let mut key_opt = None; + let mut key_cur = std::io::Cursor::new(key_vec); + for item in rustls_pemfile::read_all(&mut key_cur) + .map_err(|_e| TlsConfigError::InvalidIdentityPem)? + { + match item { + rustls_pemfile::Item::RSAKey(k) => key_opt = Some(PrivateKey(k)), + rustls_pemfile::Item::PKCS8Key(k) => key_opt = Some(PrivateKey(k)), + rustls_pemfile::Item::ECKey(k) => key_opt = Some(PrivateKey(k)), + _ => return Err(TlsConfigError::UnknownPrivateKeyFormat), } + } + let key = match key_opt { + Some(v) => v, + _ => return Err(TlsConfigError::MissingPrivateKey), }; fn read_trust_anchor( @@ -409,4 +413,25 @@ mod tests { .build() .unwrap(); } + + #[test] + fn file_ecc_cert_key() { + TlsConfigBuilder::new() + .key_path("examples/tls/key.ecc") + .cert_path("examples/tls/cert.ecc.pem") + .build() + .unwrap(); + } + + #[test] + fn bytes_ecc_cert_key() { + let key = include_str!("../examples/tls/key.ecc"); + let cert = include_str!("../examples/tls/cert.ecc.pem"); + + TlsConfigBuilder::new() + .key(key.as_bytes()) + .cert(cert.as_bytes()) + .build() + .unwrap(); + } }