diff options
author | Sean Michael Wykes <sean.wykes@nascent.com.br> | 2021-08-25 09:25:12 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-25 14:25:12 +0200 |
commit | dccf4cbe36d66140f9e35a6ee755c3c440d745f9 (patch) | |
tree | af3114696f1649d77474f69cd3361d58aea34275 /ext/tls/lib.rs | |
parent | 5d814a4c244d489b4ae51002a0cf1d3c2fe16058 (diff) |
feat(fetch): mTLS client certificates for fetch() (#11721)
This commit adds support for specifying client certificates when using fetch, by means of `Deno.createHttpClient`.
Diffstat (limited to 'ext/tls/lib.rs')
-rw-r--r-- | ext/tls/lib.rs | 70 |
1 files changed, 69 insertions, 1 deletions
diff --git a/ext/tls/lib.rs b/ext/tls/lib.rs index 8f56f0ffd..7632da5e6 100644 --- a/ext/tls/lib.rs +++ b/ext/tls/lib.rs @@ -7,6 +7,7 @@ pub use webpki; pub use webpki_roots; use deno_core::error::anyhow; +use deno_core::error::custom_error; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; @@ -17,9 +18,13 @@ use reqwest::header::USER_AGENT; use reqwest::redirect::Policy; use reqwest::Client; use rustls::internal::msgs::handshake::DigitallySignedStruct; +use rustls::internal::pemfile::certs; +use rustls::internal::pemfile::pkcs8_private_keys; +use rustls::internal::pemfile::rsa_private_keys; use rustls::Certificate; use rustls::ClientConfig; use rustls::HandshakeSignatureValid; +use rustls::PrivateKey; use rustls::RootCertStore; use rustls::ServerCertVerified; use rustls::ServerCertVerifier; @@ -28,6 +33,7 @@ use rustls::TLSError; use rustls::WebPKIVerifier; use serde::Deserialize; use std::collections::HashMap; +use std::io::BufRead; use std::io::BufReader; use std::io::Cursor; use std::sync::Arc; @@ -156,6 +162,54 @@ pub fn create_client_config( Ok(tls_config) } +pub fn load_certs( + reader: &mut dyn BufRead, +) -> Result<Vec<Certificate>, AnyError> { + let certs = certs(reader) + .map_err(|_| custom_error("InvalidData", "Unable to decode certificate"))?; + + if certs.is_empty() { + let e = custom_error("InvalidData", "No certificates found in cert file"); + return Err(e); + } + + Ok(certs) +} + +fn key_decode_err() -> AnyError { + custom_error("InvalidData", "Unable to decode key") +} + +fn key_not_found_err() -> AnyError { + custom_error("InvalidData", "No keys found in key file") +} + +/// Starts with -----BEGIN RSA PRIVATE KEY----- +fn load_rsa_keys(mut bytes: &[u8]) -> Result<Vec<PrivateKey>, AnyError> { + let keys = rsa_private_keys(&mut bytes).map_err(|_| key_decode_err())?; + Ok(keys) +} + +/// Starts with -----BEGIN PRIVATE KEY----- +fn load_pkcs8_keys(mut bytes: &[u8]) -> Result<Vec<PrivateKey>, AnyError> { + let keys = pkcs8_private_keys(&mut bytes).map_err(|_| key_decode_err())?; + Ok(keys) +} + +pub fn load_private_keys(bytes: &[u8]) -> Result<Vec<PrivateKey>, AnyError> { + let mut keys = load_rsa_keys(bytes)?; + + if keys.is_empty() { + keys = load_pkcs8_keys(bytes)?; + } + + if keys.is_empty() { + return Err(key_not_found_err()); + } + + Ok(keys) +} + /// Create new instance of async reqwest::Client. This client supports /// proxies and doesn't follow redirects. pub fn create_http_client( @@ -164,12 +218,26 @@ pub fn create_http_client( ca_data: Option<Vec<u8>>, proxy: Option<Proxy>, unsafely_ignore_certificate_errors: Option<Vec<String>>, + client_cert_chain_and_key: Option<(String, String)>, ) -> Result<Client, AnyError> { - let tls_config = create_client_config( + let mut tls_config = create_client_config( root_cert_store, ca_data, unsafely_ignore_certificate_errors, )?; + + if let Some((cert_chain, private_key)) = client_cert_chain_and_key { + // The `remove` is safe because load_private_keys checks that there is at least one key. + let private_key = load_private_keys(private_key.as_bytes())?.remove(0); + + tls_config + .set_single_client_cert( + load_certs(&mut cert_chain.as_bytes())?, + private_key, + ) + .expect("invalid client key or certificate"); + } + let mut headers = HeaderMap::new(); headers.insert(USER_AGENT, user_agent.parse().unwrap()); let mut builder = Client::builder() |