Skip to content

Commit 8d55d8b

Browse files
authored
feat(unstable): ALPN config in listenTls (#10065)
This commit adds the ability for users to configure ALPN protocols when calling `Deno.listenTls`.
1 parent 1c6602b commit 8d55d8b

File tree

5 files changed

+115
-0
lines changed

5 files changed

+115
-0
lines changed

cli/dts/lib.deno.unstable.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,16 @@ declare namespace Deno {
10011001
options?: StartTlsOptions,
10021002
): Promise<Conn>;
10031003

1004+
export interface ListenTlsOptions {
1005+
/** **UNSTABLE**: new API, yet to be vetted.
1006+
*
1007+
* Application-Layer Protocol Negotiation (ALPN) protocols to announce to
1008+
* the client. If not specified, no ALPN extension will be included in the
1009+
* TLS handshake.
1010+
*/
1011+
alpnProtocols?: string[];
1012+
}
1013+
10041014
/** **UNSTABLE**: The `signo` argument may change to require the Deno.Signal
10051015
* enum.
10061016
*

cli/tests/integration_tests.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ use deno_core::serde_json;
55
use deno_core::url;
66
use deno_runtime::deno_fetch::reqwest;
77
use deno_runtime::deno_websocket::tokio_tungstenite;
8+
use rustls::Session;
89
use std::fs;
10+
use std::io::BufReader;
11+
use std::io::Cursor;
912
use std::io::{BufRead, Read, Write};
1013
use std::process::Command;
14+
use std::sync::Arc;
1115
use tempfile::TempDir;
1216
use test_util as util;
17+
use tokio_rustls::rustls;
18+
use tokio_rustls::webpki;
1319

1420
#[test]
1521
fn js_unit_tests_lint() {
@@ -5879,3 +5885,82 @@ console.log("finish");
58795885
handle.abort();
58805886
}
58815887
}
5888+
5889+
#[tokio::test]
5890+
async fn listen_tls_alpn() {
5891+
let child = util::deno_cmd()
5892+
.current_dir(util::root_path())
5893+
.arg("run")
5894+
.arg("--unstable")
5895+
.arg("--quiet")
5896+
.arg("--allow-net")
5897+
.arg("--allow-read")
5898+
.arg("./cli/tests/listen_tls_alpn.ts")
5899+
.arg("4504")
5900+
.stdout(std::process::Stdio::piped())
5901+
.spawn()
5902+
.unwrap();
5903+
let mut stdout = child.stdout.unwrap();
5904+
let mut buffer = [0; 5];
5905+
let read = stdout.read(&mut buffer).unwrap();
5906+
assert_eq!(read, 5);
5907+
let msg = std::str::from_utf8(&buffer).unwrap();
5908+
assert_eq!(msg, "READY");
5909+
5910+
let mut cfg = rustls::ClientConfig::new();
5911+
let reader =
5912+
&mut BufReader::new(Cursor::new(include_bytes!("./tls/RootCA.crt")));
5913+
cfg.root_store.add_pem_file(reader).unwrap();
5914+
cfg.alpn_protocols.push("foobar".as_bytes().to_vec());
5915+
5916+
let tls_connector = tokio_rustls::TlsConnector::from(Arc::new(cfg));
5917+
let hostname = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
5918+
let stream = tokio::net::TcpStream::connect("localhost:4504")
5919+
.await
5920+
.unwrap();
5921+
5922+
let tls_stream = tls_connector.connect(hostname, stream).await.unwrap();
5923+
let (_, session) = tls_stream.get_ref();
5924+
5925+
let alpn = session.get_alpn_protocol().unwrap();
5926+
assert_eq!(std::str::from_utf8(alpn).unwrap(), "foobar");
5927+
}
5928+
5929+
#[tokio::test]
5930+
async fn listen_tls_alpn_fail() {
5931+
let child = util::deno_cmd()
5932+
.current_dir(util::root_path())
5933+
.arg("run")
5934+
.arg("--unstable")
5935+
.arg("--quiet")
5936+
.arg("--allow-net")
5937+
.arg("--allow-read")
5938+
.arg("./cli/tests/listen_tls_alpn.ts")
5939+
.arg("4505")
5940+
.stdout(std::process::Stdio::piped())
5941+
.spawn()
5942+
.unwrap();
5943+
let mut stdout = child.stdout.unwrap();
5944+
let mut buffer = [0; 5];
5945+
let read = stdout.read(&mut buffer).unwrap();
5946+
assert_eq!(read, 5);
5947+
let msg = std::str::from_utf8(&buffer).unwrap();
5948+
assert_eq!(msg, "READY");
5949+
5950+
let mut cfg = rustls::ClientConfig::new();
5951+
let reader =
5952+
&mut BufReader::new(Cursor::new(include_bytes!("./tls/RootCA.crt")));
5953+
cfg.root_store.add_pem_file(reader).unwrap();
5954+
cfg.alpn_protocols.push("boofar".as_bytes().to_vec());
5955+
5956+
let tls_connector = tokio_rustls::TlsConnector::from(Arc::new(cfg));
5957+
let hostname = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
5958+
let stream = tokio::net::TcpStream::connect("localhost:4505")
5959+
.await
5960+
.unwrap();
5961+
5962+
let tls_stream = tls_connector.connect(hostname, stream).await.unwrap();
5963+
let (_, session) = tls_stream.get_ref();
5964+
5965+
assert!(session.get_alpn_protocol().is_none());
5966+
}

cli/tests/listen_tls_alpn.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const listener = Deno.listenTls({
2+
port: Number(Deno.args[0]),
3+
certFile: "./cli/tests/tls/localhost.crt",
4+
keyFile: "./cli/tests/tls/localhost.key",
5+
alpnProtocols: ["h2", "http/1.1", "foobar"],
6+
});
7+
8+
console.log("READY");
9+
10+
for await (const conn of listener) {
11+
conn.close();
12+
}

runtime/js/40_tls.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@
5151
keyFile,
5252
hostname = "0.0.0.0",
5353
transport = "tcp",
54+
alpnProtocols,
5455
}) {
5556
const res = opListenTls({
5657
port,
5758
certFile,
5859
keyFile,
5960
hostname,
6061
transport,
62+
alpnProtocols,
6163
});
6264
return new TLSListener(res.rid, res.localAddr);
6365
}

runtime/ops/tls.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ pub struct ListenTlsArgs {
300300
port: u16,
301301
cert_file: String,
302302
key_file: String,
303+
alpn_protocols: Option<Vec<String>>,
303304
}
304305

305306
fn op_listen_tls(
@@ -318,6 +319,11 @@ fn op_listen_tls(
318319
permissions.read.check(Path::new(&key_file))?;
319320
}
320321
let mut config = ServerConfig::new(NoClientAuth::new());
322+
if let Some(alpn_protocols) = args.alpn_protocols {
323+
super::check_unstable(state, "Deno.listenTls#alpn_protocols");
324+
config.alpn_protocols =
325+
alpn_protocols.into_iter().map(|s| s.into_bytes()).collect();
326+
}
321327
config
322328
.set_single_cert(load_certs(&cert_file)?, load_keys(&key_file)?.remove(0))
323329
.expect("invalid key or certificate");

0 commit comments

Comments
 (0)