1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-23 15:49:44 -05:00

feat(ext/net): ALPN support in Deno.connectTls() (#12786)

This commit is contained in:
Yury Selivanov 2021-11-26 10:59:53 -08:00 committed by GitHub
parent d763633781
commit 1d3f734e18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 185 additions and 16 deletions

View file

@ -935,6 +935,29 @@ declare namespace Deno {
certChain?: string;
/** PEM formatted (RSA or PKCS8) private key of client certificate. */
privateKey?: string;
/** **UNSTABLE**: new API, yet to be vetted.
*
* Application-Layer Protocol Negotiation (ALPN) protocols supported by
* the client. If not specified, no ALPN extension will be included in the
* TLS handshake.
*/
alpnProtocols?: string[];
}
export interface TlsHandshakeInfo {
/** **UNSTABLE**: new API, yet to be vetted.
*
* Contains the ALPN protocol selected during negotiation with the server.
* If no ALPN protocol selected, returns `null`.
*/
alpnProtocol: string | null;
}
export interface TlsConn extends Conn {
/** Runs the client or server handshake protocol to completion if that has
* not happened yet. Calling this method is optional; the TLS handshake
* will be completed automatically as soon as data is sent or received. */
handshake(): Promise<TlsHandshakeInfo>;
}
/** **UNSTABLE** New API, yet to be vetted.
@ -964,6 +987,16 @@ declare namespace Deno {
alpnProtocols?: string[];
}
export interface StartTlsOptions {
/** **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**: New API should be tested first.
*
* Acquire an advisory file-system lock for the provided file. `exclusive`

View file

@ -244,6 +244,49 @@ async function tlsPair(): Promise<[Deno.Conn, Deno.Conn]> {
return endpoints;
}
async function tlsAlpn(
useStartTls: boolean,
): Promise<[Deno.TlsConn, Deno.TlsConn]> {
const port = getPort();
const listener = Deno.listenTls({
hostname: "localhost",
port,
certFile: "cli/tests/testdata/tls/localhost.crt",
keyFile: "cli/tests/testdata/tls/localhost.key",
alpnProtocols: ["deno", "rocks"],
});
const acceptPromise = listener.accept();
const caCerts = [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")];
const clientAlpnProtocols = ["rocks", "rises"];
let endpoints: [Deno.TlsConn, Deno.TlsConn];
if (!useStartTls) {
const connectPromise = Deno.connectTls({
hostname: "localhost",
port,
caCerts,
alpnProtocols: clientAlpnProtocols,
});
endpoints = await Promise.all([acceptPromise, connectPromise]);
} else {
const client = await Deno.connect({
hostname: "localhost",
port,
});
const connectPromise = Deno.startTls(client, {
hostname: "localhost",
caCerts,
alpnProtocols: clientAlpnProtocols,
});
endpoints = await Promise.all([acceptPromise, connectPromise]);
}
listener.close();
return endpoints;
}
async function sendThenCloseWriteThenReceive(
conn: Deno.Conn,
chunkCount: number,
@ -305,6 +348,38 @@ async function receiveThenSend(
conn.close();
}
Deno.test(
{ permissions: { read: true, net: true } },
async function tlsServerAlpnListenConnect() {
const [serverConn, clientConn] = await tlsAlpn(false);
const [serverHS, clientHS] = await Promise.all([
serverConn.handshake(),
clientConn.handshake(),
]);
assertStrictEquals(serverHS.alpnProtocol, "rocks");
assertStrictEquals(clientHS.alpnProtocol, "rocks");
serverConn.close();
clientConn.close();
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function tlsServerAlpnListenStartTls() {
const [serverConn, clientConn] = await tlsAlpn(true);
const [serverHS, clientHS] = await Promise.all([
serverConn.handshake(),
clientConn.handshake(),
]);
assertStrictEquals(serverHS.alpnProtocol, "rocks");
assertStrictEquals(clientHS.alpnProtocol, "rocks");
serverConn.close();
clientConn.close();
},
);
Deno.test(
{ permissions: { read: true, net: true } },
async function tlsServerStreamHalfCloseSendOneByte() {

View file

@ -41,6 +41,7 @@
caCerts = [],
certChain = undefined,
privateKey = undefined,
alpnProtocols = undefined,
}) {
const res = await opConnectTls({
port,
@ -50,6 +51,7 @@
caCerts,
certChain,
privateKey,
alpnProtocols,
});
return new TlsConn(res.rid, res.remoteAddr, res.localAddr);
}
@ -67,7 +69,7 @@
keyFile,
hostname = "0.0.0.0",
transport = "tcp",
alpnProtocols,
alpnProtocols = undefined,
}) {
const res = opListenTls({
port,
@ -82,13 +84,19 @@
async function startTls(
conn,
{ hostname = "127.0.0.1", certFile = undefined, caCerts = [] } = {},
{
hostname = "127.0.0.1",
certFile = undefined,
caCerts = [],
alpnProtocols = undefined,
} = {},
) {
const res = await opStartTls({
rid: conn.rid,
hostname,
certFile,
caCerts,
alpnProtocols,
});
return new TlsConn(res.rid, res.remoteAddr, res.localAddr);
}

View file

@ -52,11 +52,14 @@ declare namespace Deno {
closeWrite(): Promise<void>;
}
// deno-lint-ignore no-empty-interface
export interface TlsHandshakeInfo {}
export interface TlsConn extends Conn {
/** Runs the client or server handshake protocol to completion if that has
* not happened yet. Calling this method is optional; the TLS handshake
* will be completed automatically as soon as data is sent or received. */
handshake(): Promise<void>;
handshake(): Promise<TlsHandshakeInfo>;
}
export interface ListenOptions {

View file

@ -12,6 +12,7 @@ use deno_core::error::AnyError;
use deno_core::op_async;
use deno_core::op_sync;
use deno_core::AsyncRefCell;
use deno_core::ByteString;
use deno_core::CancelHandle;
use deno_core::CancelTryFuture;
use deno_core::OpPair;
@ -84,6 +85,12 @@ pub struct OpPacket {
pub remote_addr: OpAddr,
}
#[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TlsHandshakeInfo {
pub alpn_protocol: Option<ByteString>,
}
#[derive(Serialize)]
pub struct IpAddr {
pub hostname: String,

View file

@ -4,6 +4,7 @@ use crate::io::TcpStreamResource;
use crate::ops::IpAddr;
use crate::ops::OpAddr;
use crate::ops::OpConn;
use crate::ops::TlsHandshakeInfo;
use crate::resolve_addr::resolve_addr;
use crate::resolve_addr::resolve_addr_sync;
use crate::DefaultTlsOptions;
@ -29,6 +30,7 @@ use deno_core::op_sync;
use deno_core::parking_lot::Mutex;
use deno_core::AsyncRefCell;
use deno_core::AsyncResult;
use deno_core::ByteString;
use deno_core::CancelHandle;
use deno_core::CancelTryFuture;
use deno_core::OpPair;
@ -54,7 +56,6 @@ use io::Read;
use io::Write;
use serde::Deserialize;
use std::borrow::Cow;
use std::cell::Cell;
use std::cell::RefCell;
use std::convert::From;
use std::fs::File;
@ -190,6 +191,14 @@ impl TlsStream {
fn poll_handshake(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.inner_mut().poll_handshake(cx)
}
fn get_alpn_protocol(&mut self) -> Option<ByteString> {
self
.inner_mut()
.tls
.get_alpn_protocol()
.map(|s| ByteString(s.to_owned()))
}
}
impl AsyncRead for TlsStream {
@ -549,6 +558,10 @@ impl WriteHalf {
})
.await
}
fn get_alpn_protocol(&mut self) -> Option<ByteString> {
self.shared.get_alpn_protocol()
}
}
impl AsyncWrite for WriteHalf {
@ -658,6 +671,11 @@ impl Shared {
fn drop_shared_waker(self_ptr: *const ()) {
let _ = unsafe { Weak::from_raw(self_ptr as *const Self) };
}
fn get_alpn_protocol(self: &Arc<Self>) -> Option<ByteString> {
let mut tls_stream = self.tls_stream.lock();
tls_stream.get_alpn_protocol()
}
}
struct ImplementReadTrait<'a, T>(&'a mut T);
@ -698,7 +716,8 @@ pub fn init<P: NetPermissions + 'static>() -> Vec<OpPair> {
pub struct TlsStreamResource {
rd: AsyncRefCell<ReadHalf>,
wr: AsyncRefCell<WriteHalf>,
handshake_done: Cell<bool>,
// `None` when a TLS handshake hasn't been done.
handshake_info: RefCell<Option<TlsHandshakeInfo>>,
cancel_handle: CancelHandle, // Only read and handshake ops get canceled.
}
@ -707,7 +726,7 @@ impl TlsStreamResource {
Self {
rd: rd.into(),
wr: wr.into(),
handshake_done: Cell::new(false),
handshake_info: RefCell::new(None),
cancel_handle: Default::default(),
}
}
@ -744,14 +763,21 @@ impl TlsStreamResource {
Ok(())
}
pub async fn handshake(self: &Rc<Self>) -> Result<(), AnyError> {
if !self.handshake_done.get() {
let mut wr = RcRef::map(self, |r| &r.wr).borrow_mut().await;
let cancel_handle = RcRef::map(self, |r| &r.cancel_handle);
wr.handshake().try_or_cancel(cancel_handle).await?;
self.handshake_done.set(true);
pub async fn handshake(
self: &Rc<Self>,
) -> Result<TlsHandshakeInfo, AnyError> {
if let Some(tls_info) = &*self.handshake_info.borrow() {
return Ok(tls_info.clone());
}
Ok(())
let mut wr = RcRef::map(self, |r| &r.wr).borrow_mut().await;
let cancel_handle = RcRef::map(self, |r| &r.cancel_handle);
wr.handshake().try_or_cancel(cancel_handle).await?;
let alpn_protocol = wr.get_alpn_protocol();
let tls_info = TlsHandshakeInfo { alpn_protocol };
self.handshake_info.replace(Some(tls_info.clone()));
Ok(tls_info)
}
}
@ -787,6 +813,7 @@ pub struct ConnectTlsArgs {
ca_certs: Vec<String>,
cert_chain: Option<String>,
private_key: Option<String>,
alpn_protocols: Option<Vec<String>>,
}
#[derive(Deserialize)]
@ -795,6 +822,7 @@ pub struct StartTlsArgs {
rid: ResourceId,
ca_certs: Vec<String>,
hostname: String,
alpn_protocols: Option<Vec<String>>,
}
pub async fn op_tls_start<NP>(
@ -851,11 +879,20 @@ where
let local_addr = tcp_stream.local_addr()?;
let remote_addr = tcp_stream.peer_addr()?;
let tls_config = Arc::new(create_client_config(
let mut tls_config = create_client_config(
root_cert_store,
ca_certs,
unsafely_ignore_certificate_errors,
)?);
)?;
if let Some(alpn_protocols) = args.alpn_protocols {
super::check_unstable2(&state, "Deno.startTls#alpnProtocols");
tls_config.alpn_protocols =
alpn_protocols.into_iter().map(|s| s.into_bytes()).collect();
}
let tls_config = Arc::new(tls_config);
let tls_stream =
TlsStream::new_client_side(tcp_stream, &tls_config, hostname_dns);
@ -948,6 +985,12 @@ where
unsafely_ignore_certificate_errors,
)?;
if let Some(alpn_protocols) = args.alpn_protocols {
super::check_unstable2(&state, "Deno.connectTls#alpnProtocols");
tls_config.alpn_protocols =
alpn_protocols.into_iter().map(|s| s.into_bytes()).collect();
}
if args.cert_chain.is_some() || args.private_key.is_some() {
let cert_chain = args
.cert_chain
@ -1144,7 +1187,7 @@ pub async fn op_tls_handshake(
state: Rc<RefCell<OpState>>,
rid: ResourceId,
_: (),
) -> Result<(), AnyError> {
) -> Result<TlsHandshakeInfo, AnyError> {
let resource = state
.borrow()
.resource_table