Skip to content

Commit

Permalink
feat: ecc private keys support for tls feature (#1048)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
joseluisq committed Oct 9, 2023
1 parent 9d08146 commit efe8548
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 29 deletions.
4 changes: 4 additions & 0 deletions examples/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
12 changes: 12 additions & 0 deletions examples/tls/cert.ecc.pem
Original file line number Diff line number Diff line change
@@ -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-----
5 changes: 5 additions & 0 deletions examples/tls/key.ecc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIPwp3LAnLEyWe2lLz66Y3QCCJ/BEMJheTM0shZnnSw6toAoGCCqGSM49
AwEHoUQDQgAE2UeBetF/oh43g+pMkmX15YzXJA29tkGEO+k7OBhWFpHQ7LVOsnoc
chEfjGVrJlJ0xPxst5p6UpjM6EgX6CkZhw==
-----END EC PRIVATE KEY-----
83 changes: 54 additions & 29 deletions src/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
}
Expand Down Expand Up @@ -175,32 +181,30 @@ impl TlsConfigBuilder {
.map(Certificate)
.collect();

let key = {
// convert it to Vec<u8> 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(
Expand Down Expand Up @@ -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();
}
}

0 comments on commit efe8548

Please sign in to comment.