summaryrefslogtreecommitdiff
path: root/extensions/net
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2021-08-09 15:55:00 +0200
committerGitHub <noreply@github.com>2021-08-09 15:55:00 +0200
commit3ab50b355141f744a0acec1a5cc3b3b95247d4b1 (patch)
tree5ab6c3a216f5ce5cc5ee8fbc12e99dfac09496d7 /extensions/net
parentf402904e6e227ee60a88d991cb9818d5340f5c1d (diff)
feat: support client certificates for connectTls (#11598)
Co-authored-by: Daniel Lamando <dan@danopia.net> Co-authored-by: Erik Price <github@erikprice.net>
Diffstat (limited to 'extensions/net')
-rw-r--r--extensions/net/02_tls.js4
-rw-r--r--extensions/net/lib.deno_net.d.ts5
-rw-r--r--extensions/net/lib.deno_net.unstable.d.ts26
-rw-r--r--extensions/net/ops_tls.rs78
4 files changed, 93 insertions, 20 deletions
diff --git a/extensions/net/02_tls.js b/extensions/net/02_tls.js
index 4fafe9079..343ec2e4f 100644
--- a/extensions/net/02_tls.js
+++ b/extensions/net/02_tls.js
@@ -28,12 +28,16 @@
hostname = "127.0.0.1",
transport = "tcp",
certFile = undefined,
+ certChain = undefined,
+ privateKey = undefined,
}) {
const res = await opConnectTls({
port,
hostname,
transport,
certFile,
+ certChain,
+ privateKey,
});
return new Conn(res.rid, res.remoteAddr, res.localAddr);
}
diff --git a/extensions/net/lib.deno_net.d.ts b/extensions/net/lib.deno_net.d.ts
index 25397f960..d35e01e31 100644
--- a/extensions/net/lib.deno_net.d.ts
+++ b/extensions/net/lib.deno_net.d.ts
@@ -68,9 +68,10 @@ declare namespace Deno {
): Listener;
export interface ListenTlsOptions extends ListenOptions {
- /** Server certificate file. */
+ /** Path to a file containing a PEM formatted CA certificate. Requires
+ * `--allow-read`. */
certFile: string;
- /** Server public key file. */
+ /** Server public key file. Requires `--allow-read`.*/
keyFile: string;
transport?: "tcp";
diff --git a/extensions/net/lib.deno_net.unstable.d.ts b/extensions/net/lib.deno_net.unstable.d.ts
index adeeb1466..145f232c0 100644
--- a/extensions/net/lib.deno_net.unstable.d.ts
+++ b/extensions/net/lib.deno_net.unstable.d.ts
@@ -191,6 +191,32 @@ declare namespace Deno {
options: ConnectOptions | UnixConnectOptions,
): Promise<Conn>;
+ export interface ConnectTlsClientCertOptions {
+ /** PEM formatted client certificate chain. */
+ certChain: string;
+ /** PEM formatted (RSA or PKCS8) private key of client certificate. */
+ privateKey: string;
+ }
+
+ /** **UNSTABLE** New API, yet to be vetted.
+ *
+ * Create a TLS connection with an attached client certificate.
+ *
+ * ```ts
+ * const conn = await Deno.connectTls({
+ * hostname: "deno.land",
+ * port: 443,
+ * certChain: "---- BEGIN CERTIFICATE ----\n ...",
+ * privateKey: "---- BEGIN PRIVATE KEY ----\n ...",
+ * });
+ * ```
+ *
+ * Requires `allow-net` permission.
+ */
+ export function connectTls(
+ options: ConnectTlsOptions & ConnectTlsClientCertOptions,
+ ): Promise<Conn>;
+
export interface StartTlsOptions {
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `127.0.0.1`. */
diff --git a/extensions/net/ops_tls.rs b/extensions/net/ops_tls.rs
index 124da2f03..7c4563390 100644
--- a/extensions/net/ops_tls.rs
+++ b/extensions/net/ops_tls.rs
@@ -14,6 +14,7 @@ use deno_core::error::bad_resource_id;
use deno_core::error::custom_error;
use deno_core::error::generic_error;
use deno_core::error::invalid_hostname;
+use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::futures::future::poll_fn;
use deno_core::futures::ready;
@@ -57,6 +58,7 @@ use std::cell::RefCell;
use std::convert::From;
use std::fs::File;
use std::io;
+use std::io::BufRead;
use std::io::BufReader;
use std::io::ErrorKind;
use std::ops::Deref;
@@ -649,6 +651,8 @@ pub struct ConnectTlsArgs {
hostname: String,
port: u16,
cert_file: Option<String>,
+ cert_chain: Option<String>,
+ private_key: Option<String>,
}
#[derive(Deserialize)]
@@ -717,6 +721,7 @@ where
let remote_addr = tcp_stream.peer_addr()?;
let tls_config = Arc::new(create_client_config(root_cert_store, ca_data)?);
+
let tls_stream =
TlsStream::new_client_side(tcp_stream, &tls_config, hostname_dns);
@@ -755,6 +760,14 @@ where
};
let port = args.port;
let cert_file = args.cert_file.as_deref();
+
+ if args.cert_chain.is_some() {
+ super::check_unstable2(&state, "ConnectTlsOptions.certChain");
+ }
+ if args.private_key.is_some() {
+ super::check_unstable2(&state, "ConnectTlsOptions.privateKey");
+ }
+
{
let mut s = state.borrow_mut();
let permissions = s.borrow_mut::<NP>();
@@ -788,7 +801,28 @@ where
let tcp_stream = TcpStream::connect(connect_addr).await?;
let local_addr = tcp_stream.local_addr()?;
let remote_addr = tcp_stream.peer_addr()?;
- let tls_config = Arc::new(create_client_config(root_cert_store, ca_data)?);
+
+ let mut tls_config = create_client_config(root_cert_store, ca_data)?;
+
+ if args.cert_chain.is_some() || args.private_key.is_some() {
+ let cert_chain = args
+ .cert_chain
+ .ok_or_else(|| type_error("No certificate chain provided"))?;
+ let private_key = args
+ .private_key
+ .ok_or_else(|| type_error("No private key provided"))?;
+
+ // 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,
+ )?;
+ }
+
+ let tls_config = Arc::new(tls_config);
+
let tls_stream =
TlsStream::new_client_side(tcp_stream, &tls_config, hostname_dns);
@@ -812,10 +846,7 @@ where
})
}
-fn load_certs(path: &str) -> Result<Vec<Certificate>, AnyError> {
- let cert_file = File::open(path)?;
- let reader = &mut BufReader::new(cert_file);
-
+fn load_certs(reader: &mut dyn BufRead) -> Result<Vec<Certificate>, AnyError> {
let certs = certs(reader)
.map_err(|_| custom_error("InvalidData", "Unable to decode certificate"))?;
@@ -827,6 +858,12 @@ fn load_certs(path: &str) -> Result<Vec<Certificate>, AnyError> {
Ok(certs)
}
+fn load_certs_from_file(path: &str) -> Result<Vec<Certificate>, AnyError> {
+ let cert_file = File::open(path)?;
+ let reader = &mut BufReader::new(cert_file);
+ load_certs(reader)
+}
+
fn key_decode_err() -> AnyError {
custom_error("InvalidData", "Unable to decode key")
}
@@ -836,27 +873,22 @@ fn key_not_found_err() -> AnyError {
}
/// Starts with -----BEGIN RSA PRIVATE KEY-----
-fn load_rsa_keys(path: &str) -> Result<Vec<PrivateKey>, AnyError> {
- let key_file = File::open(path)?;
- let reader = &mut BufReader::new(key_file);
- let keys = rsa_private_keys(reader).map_err(|_| key_decode_err())?;
+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(path: &str) -> Result<Vec<PrivateKey>, AnyError> {
- let key_file = File::open(path)?;
- let reader = &mut BufReader::new(key_file);
- let keys = pkcs8_private_keys(reader).map_err(|_| key_decode_err())?;
+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)
}
-fn load_keys(path: &str) -> Result<Vec<PrivateKey>, AnyError> {
- let path = path.to_string();
- let mut keys = load_rsa_keys(&path)?;
+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(&path)?;
+ keys = load_pkcs8_keys(bytes)?;
}
if keys.is_empty() {
@@ -866,6 +898,13 @@ fn load_keys(path: &str) -> Result<Vec<PrivateKey>, AnyError> {
Ok(keys)
}
+fn load_private_keys_from_file(
+ path: &str,
+) -> Result<Vec<PrivateKey>, AnyError> {
+ let key_bytes = std::fs::read(path)?;
+ load_private_keys(&key_bytes)
+}
+
pub struct TlsListenerResource {
tcp_listener: AsyncRefCell<TcpListener>,
tls_config: Arc<ServerConfig>,
@@ -921,7 +960,10 @@ where
alpn_protocols.into_iter().map(|s| s.into_bytes()).collect();
}
tls_config
- .set_single_cert(load_certs(cert_file)?, load_keys(key_file)?.remove(0))
+ .set_single_cert(
+ load_certs_from_file(cert_file)?,
+ load_private_keys_from_file(key_file)?.remove(0),
+ )
.expect("invalid key or certificate");
let bind_addr = resolve_addr_sync(hostname, port)?