Skip to content

Commit

Permalink
feat(ext/net): add TlsConn.handshake() (denoland#12467)
Browse files Browse the repository at this point in the history
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: denoland#11759.
  • Loading branch information
piscisaureus authored Oct 26, 2021
1 parent c27ef0a commit cf9c4f0
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 30 deletions.
4 changes: 2 additions & 2 deletions cli/dts/lib.deno.unstable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ declare namespace Deno {
*
* Requires `allow-net` permission.
*/
export function connectTls(options: ConnectTlsOptions): Promise<Conn>;
export function connectTls(options: ConnectTlsOptions): Promise<TlsConn>;

export interface StartTlsOptions {
/** A literal IP address or host name that can be resolved to an IP address.
Expand Down Expand Up @@ -1130,7 +1130,7 @@ declare namespace Deno {
export function startTls(
conn: Conn,
options?: StartTlsOptions,
): Promise<Conn>;
): Promise<TlsConn>;

export interface ListenTlsOptions {
/** **UNSTABLE**: new API, yet to be vetted.
Expand Down
32 changes: 12 additions & 20 deletions cli/tests/unit/tls_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
// 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() {
Expand All @@ -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.
Expand All @@ -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();
Expand Down Expand Up @@ -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",
);
Expand All @@ -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",
);
Expand All @@ -1247,7 +1239,7 @@ unitTest(
});
// Handshake fails because hostname doesn't match the certificate.
await assertRejects(
() => tlsHandshake(tlsConn),
() => tlsConn.handshake(),
Deno.errors.InvalidData,
"CertNotValidForName",
);
Expand Down
23 changes: 17 additions & 6 deletions ext/net/02_tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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);
}
}

Expand All @@ -67,7 +77,7 @@
transport,
alpnProtocols,
});
return new TLSListener(res.rid, res.localAddr);
return new TlsListener(res.rid, res.localAddr);
}

async function startTls(
Expand All @@ -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);
18 changes: 16 additions & 2 deletions ext/net/lib.deno_net.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ declare namespace Deno {
[Symbol.asyncIterator](): AsyncIterableIterator<Conn>;
}

/** Specialized listener that accepts TLS connections. */
export interface TlsListener extends Listener, AsyncIterable<TlsConn> {
/** Waits for a TLS client to connect and accepts the connection. */
accept(): Promise<TlsConn>;
[Symbol.asyncIterator](): AsyncIterableIterator<TlsConn>;
}

export interface Conn extends Reader, Writer, Closer {
/** The local address of the connection. */
readonly localAddr: Addr;
Expand All @@ -45,6 +52,13 @@ declare namespace Deno {
closeWrite(): Promise<void>;
}

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>;
}

export interface ListenOptions {
/** The port to listen on. */
port: number;
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -150,7 +164,7 @@ declare namespace Deno {
*
* Requires `allow-net` permission.
*/
export function connectTls(options: ConnectTlsOptions): Promise<Conn>;
export function connectTls(options: ConnectTlsOptions): Promise<TlsConn>;

/** Shutdown socket send operations.
*
Expand Down

0 comments on commit cf9c4f0

Please sign in to comment.