From cf9c4f0031a46b58989a984af0528a2005547e2d Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Tue, 26 Oct 2021 22:27:47 +0200 Subject: [PATCH] feat(ext/net): add TlsConn.handshake() (#12467) A `handshake()` method was added that returns when the TLS handshake is complete. The `TlsListener` and `TlsConn` interfaces were added to accomodate this new method. Closes: #11759. --- cli/dts/lib.deno.unstable.d.ts | 4 ++-- cli/tests/unit/tls_test.ts | 32 ++++++++++++-------------------- ext/net/02_tls.js | 23 +++++++++++++++++------ ext/net/lib.deno_net.d.ts | 18 ++++++++++++++++-- 4 files changed, 47 insertions(+), 30 deletions(-) diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index a84883574c..5a570d0054 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -1091,7 +1091,7 @@ declare namespace Deno { * * Requires `allow-net` permission. */ - export function connectTls(options: ConnectTlsOptions): Promise; + export function connectTls(options: ConnectTlsOptions): Promise; export interface StartTlsOptions { /** A literal IP address or host name that can be resolved to an IP address. @@ -1130,7 +1130,7 @@ declare namespace Deno { export function startTls( conn: Conn, options?: StartTlsOptions, - ): Promise; + ): Promise; export interface ListenTlsOptions { /** **UNSTABLE**: new API, yet to be vetted. diff --git a/cli/tests/unit/tls_test.ts b/cli/tests/unit/tls_test.ts index 17f2312c0e..c66f958d0a 100644 --- a/cli/tests/unit/tls_test.ts +++ b/cli/tests/unit/tls_test.ts @@ -1122,14 +1122,6 @@ unitTest( }, ); -// TODO(piscisaureus): use `TlsConn.handhake()` instead, once this is added to -// the public API in Deno 1.16. -function tlsHandshake(conn: Deno.Conn): Promise { - // deno-lint-ignore no-explicit-any - const opAsync = (Deno as any).core.opAsync; - return opAsync("op_tls_handshake", conn.rid); -} - unitTest( { permissions: { read: true, net: true } }, async function tlsHandshakeSuccess() { @@ -1151,7 +1143,7 @@ unitTest( const [conn1, conn2] = await Promise.all([acceptPromise, connectPromise]); listener.close(); - await Promise.all([tlsHandshake(conn1), tlsHandshake(conn2)]); + await Promise.all([conn1.handshake(), conn2.handshake()]); // Begin sending a 10mb blob over the TLS connection. const whole = new Uint8Array(10 << 20); // 10mb. @@ -1162,28 +1154,28 @@ unitTest( const half = new Uint8Array(whole.byteLength / 2); const receivePromise = readFull(conn2, half); - await tlsHandshake(conn1); - await tlsHandshake(conn2); + await conn1.handshake(); + await conn2.handshake(); // Finish receiving the first 5mb. assertEquals(await receivePromise, half.length); // See that we can call `handshake()` in the middle of large reads and writes. - await tlsHandshake(conn1); - await tlsHandshake(conn2); + await conn1.handshake(); + await conn2.handshake(); // Receive second half of large blob. Wait for the send promise and check it. assertEquals(await readFull(conn2, half), half.length); assertEquals(await sendPromise, whole.length); - await tlsHandshake(conn1); - await tlsHandshake(conn2); + await conn1.handshake(); + await conn2.handshake(); await conn1.closeWrite(); await conn2.closeWrite(); - await tlsHandshake(conn1); - await tlsHandshake(conn2); + await conn1.handshake(); + await conn2.handshake(); conn1.close(); conn2.close(); @@ -1216,7 +1208,7 @@ unitTest( for (let i = 0; i < 10; i++) { // Handshake fails because the client rejects the server certificate. await assertRejects( - () => tlsHandshake(conn), + () => conn.handshake(), Deno.errors.InvalidData, "BadCertificate", ); @@ -1230,7 +1222,7 @@ unitTest( const conn = await Deno.connectTls({ hostname, port }); // Handshake fails because the server presents a self-signed certificate. await assertRejects( - () => tlsHandshake(conn), + () => conn.handshake(), Deno.errors.InvalidData, "UnknownIssuer", ); @@ -1247,7 +1239,7 @@ unitTest( }); // Handshake fails because hostname doesn't match the certificate. await assertRejects( - () => tlsHandshake(tlsConn), + () => tlsConn.handshake(), Deno.errors.InvalidData, "CertNotValidForName", ); diff --git a/ext/net/02_tls.js b/ext/net/02_tls.js index 9f8fb314ca..df7923f4cd 100644 --- a/ext/net/02_tls.js +++ b/ext/net/02_tls.js @@ -23,6 +23,16 @@ return core.opAsync("op_start_tls", args); } + function opTlsHandshake(rid) { + return core.opAsync("op_tls_handshake", rid); + } + + class TlsConn extends Conn { + handshake() { + return opTlsHandshake(this.rid); + } + } + async function connectTls({ port, hostname = "127.0.0.1", @@ -41,13 +51,13 @@ certChain, privateKey, }); - return new Conn(res.rid, res.remoteAddr, res.localAddr); + return new TlsConn(res.rid, res.remoteAddr, res.localAddr); } - class TLSListener extends Listener { + class TlsListener extends Listener { async accept() { const res = await opAcceptTLS(this.rid); - return new Conn(res.rid, res.remoteAddr, res.localAddr); + return new TlsConn(res.rid, res.remoteAddr, res.localAddr); } } @@ -67,7 +77,7 @@ transport, alpnProtocols, }); - return new TLSListener(res.rid, res.localAddr); + return new TlsListener(res.rid, res.localAddr); } async function startTls( @@ -80,13 +90,14 @@ certFile, caCerts, }); - return new Conn(res.rid, res.remoteAddr, res.localAddr); + return new TlsConn(res.rid, res.remoteAddr, res.localAddr); } window.__bootstrap.tls = { startTls, listenTls, connectTls, - TLSListener, + TlsConn, + TlsListener, }; })(this); diff --git a/ext/net/lib.deno_net.d.ts b/ext/net/lib.deno_net.d.ts index 45f1194fbe..1b67fcf227 100644 --- a/ext/net/lib.deno_net.d.ts +++ b/ext/net/lib.deno_net.d.ts @@ -33,6 +33,13 @@ declare namespace Deno { [Symbol.asyncIterator](): AsyncIterableIterator; } + /** Specialized listener that accepts TLS connections. */ + export interface TlsListener extends Listener, AsyncIterable { + /** Waits for a TLS client to connect and accepts the connection. */ + accept(): Promise; + [Symbol.asyncIterator](): AsyncIterableIterator; + } + export interface Conn extends Reader, Writer, Closer { /** The local address of the connection. */ readonly localAddr: Addr; @@ -45,6 +52,13 @@ declare namespace Deno { closeWrite(): Promise; } + 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; + } + export interface ListenOptions { /** The port to listen on. */ port: number; @@ -90,7 +104,7 @@ declare namespace Deno { * ``` * * Requires `allow-net` permission. */ - export function listenTls(options: ListenTlsOptions): Listener; + export function listenTls(options: ListenTlsOptions): TlsListener; export interface ConnectOptions { /** The port to connect to. */ @@ -150,7 +164,7 @@ declare namespace Deno { * * Requires `allow-net` permission. */ - export function connectTls(options: ConnectTlsOptions): Promise; + export function connectTls(options: ConnectTlsOptions): Promise; /** Shutdown socket send operations. *