From 8d55d8b6be8731d37ccf6a29127b3a91a8319d0b Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Sat, 10 Apr 2021 22:04:44 +0200 Subject: [PATCH] feat(unstable): ALPN config in listenTls (#10065) This commit adds the ability for users to configure ALPN protocols when calling `Deno.listenTls`. --- cli/dts/lib.deno.unstable.d.ts | 10 ++++ cli/tests/integration_tests.rs | 85 ++++++++++++++++++++++++++++++++++ cli/tests/listen_tls_alpn.ts | 12 +++++ runtime/js/40_tls.js | 2 + runtime/ops/tls.rs | 6 +++ 5 files changed, 115 insertions(+) create mode 100644 cli/tests/listen_tls_alpn.ts diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index ffe80eab7a..9113d38c7f 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -1001,6 +1001,16 @@ declare namespace Deno { options?: StartTlsOptions, ): Promise; + export interface ListenTlsOptions { + /** **UNSTABLE**: new API, yet to be vetted. + * + * Application-Layer Protocol Negotiation (ALPN) protocols to announce to + * the client. If not specified, no ALPN extension will be included in the + * TLS handshake. + */ + alpnProtocols?: string[]; + } + /** **UNSTABLE**: The `signo` argument may change to require the Deno.Signal * enum. * diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 2d4d8995ea..827fc871b1 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -5,11 +5,17 @@ use deno_core::serde_json; use deno_core::url; use deno_runtime::deno_fetch::reqwest; use deno_runtime::deno_websocket::tokio_tungstenite; +use rustls::Session; use std::fs; +use std::io::BufReader; +use std::io::Cursor; use std::io::{BufRead, Read, Write}; use std::process::Command; +use std::sync::Arc; use tempfile::TempDir; use test_util as util; +use tokio_rustls::rustls; +use tokio_rustls::webpki; #[test] fn js_unit_tests_lint() { @@ -5879,3 +5885,82 @@ console.log("finish"); handle.abort(); } } + +#[tokio::test] +async fn listen_tls_alpn() { + let child = util::deno_cmd() + .current_dir(util::root_path()) + .arg("run") + .arg("--unstable") + .arg("--quiet") + .arg("--allow-net") + .arg("--allow-read") + .arg("./cli/tests/listen_tls_alpn.ts") + .arg("4504") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let mut stdout = child.stdout.unwrap(); + let mut buffer = [0; 5]; + let read = stdout.read(&mut buffer).unwrap(); + assert_eq!(read, 5); + let msg = std::str::from_utf8(&buffer).unwrap(); + assert_eq!(msg, "READY"); + + let mut cfg = rustls::ClientConfig::new(); + let reader = + &mut BufReader::new(Cursor::new(include_bytes!("./tls/RootCA.crt"))); + cfg.root_store.add_pem_file(reader).unwrap(); + cfg.alpn_protocols.push("foobar".as_bytes().to_vec()); + + let tls_connector = tokio_rustls::TlsConnector::from(Arc::new(cfg)); + let hostname = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap(); + let stream = tokio::net::TcpStream::connect("localhost:4504") + .await + .unwrap(); + + let tls_stream = tls_connector.connect(hostname, stream).await.unwrap(); + let (_, session) = tls_stream.get_ref(); + + let alpn = session.get_alpn_protocol().unwrap(); + assert_eq!(std::str::from_utf8(alpn).unwrap(), "foobar"); +} + +#[tokio::test] +async fn listen_tls_alpn_fail() { + let child = util::deno_cmd() + .current_dir(util::root_path()) + .arg("run") + .arg("--unstable") + .arg("--quiet") + .arg("--allow-net") + .arg("--allow-read") + .arg("./cli/tests/listen_tls_alpn.ts") + .arg("4505") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let mut stdout = child.stdout.unwrap(); + let mut buffer = [0; 5]; + let read = stdout.read(&mut buffer).unwrap(); + assert_eq!(read, 5); + let msg = std::str::from_utf8(&buffer).unwrap(); + assert_eq!(msg, "READY"); + + let mut cfg = rustls::ClientConfig::new(); + let reader = + &mut BufReader::new(Cursor::new(include_bytes!("./tls/RootCA.crt"))); + cfg.root_store.add_pem_file(reader).unwrap(); + cfg.alpn_protocols.push("boofar".as_bytes().to_vec()); + + let tls_connector = tokio_rustls::TlsConnector::from(Arc::new(cfg)); + let hostname = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap(); + let stream = tokio::net::TcpStream::connect("localhost:4505") + .await + .unwrap(); + + let tls_stream = tls_connector.connect(hostname, stream).await.unwrap(); + let (_, session) = tls_stream.get_ref(); + + assert!(session.get_alpn_protocol().is_none()); +} diff --git a/cli/tests/listen_tls_alpn.ts b/cli/tests/listen_tls_alpn.ts new file mode 100644 index 0000000000..6aedf0e4f0 --- /dev/null +++ b/cli/tests/listen_tls_alpn.ts @@ -0,0 +1,12 @@ +const listener = Deno.listenTls({ + port: Number(Deno.args[0]), + certFile: "./cli/tests/tls/localhost.crt", + keyFile: "./cli/tests/tls/localhost.key", + alpnProtocols: ["h2", "http/1.1", "foobar"], +}); + +console.log("READY"); + +for await (const conn of listener) { + conn.close(); +} diff --git a/runtime/js/40_tls.js b/runtime/js/40_tls.js index da43afaac2..e11754b0d1 100644 --- a/runtime/js/40_tls.js +++ b/runtime/js/40_tls.js @@ -51,6 +51,7 @@ keyFile, hostname = "0.0.0.0", transport = "tcp", + alpnProtocols, }) { const res = opListenTls({ port, @@ -58,6 +59,7 @@ keyFile, hostname, transport, + alpnProtocols, }); return new TLSListener(res.rid, res.localAddr); } diff --git a/runtime/ops/tls.rs b/runtime/ops/tls.rs index d9c5f1854a..83dbbfcd1d 100644 --- a/runtime/ops/tls.rs +++ b/runtime/ops/tls.rs @@ -300,6 +300,7 @@ pub struct ListenTlsArgs { port: u16, cert_file: String, key_file: String, + alpn_protocols: Option>, } fn op_listen_tls( @@ -318,6 +319,11 @@ fn op_listen_tls( permissions.read.check(Path::new(&key_file))?; } let mut config = ServerConfig::new(NoClientAuth::new()); + if let Some(alpn_protocols) = args.alpn_protocols { + super::check_unstable(state, "Deno.listenTls#alpn_protocols"); + config.alpn_protocols = + alpn_protocols.into_iter().map(|s| s.into_bytes()).collect(); + } config .set_single_cert(load_certs(&cert_file)?, load_keys(&key_file)?.remove(0)) .expect("invalid key or certificate");