summaryrefslogtreecommitdiff
path: root/cli/tests/unit/tls_test.ts
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2024-02-10 13:22:13 -0700
committerGitHub <noreply@github.com>2024-02-10 20:22:13 +0000
commitf5e46c9bf2f50d66a953fa133161fc829cecff06 (patch)
tree8faf2f5831c1c7b11d842cd9908d141082c869a5 /cli/tests/unit/tls_test.ts
parentd2477f780630a812bfd65e3987b70c0d309385bb (diff)
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests -> tests, and updates of relative paths for files. This is the first step towards aggregate all of the integration test files under tests/, which will lead to a set of integration tests that can run without the CLI binary being built. While we could leave these tests under `cli`, it would require us to keep a more complex directory structure for the various test runners. In addition, we have a lot of complexity to ignore various test files in the `cli` project itself (cargo publish exclusion rules, autotests = false, etc). And finally, the `tests/` folder will eventually house the `test_ffi`, `test_napi` and other testing code, reducing the size of the root repo directory. For easier review, the extremely large and noisy "move" is in the first commit (with no changes -- just a move), while the remainder of the changes to actual files is in the second commit.
Diffstat (limited to 'cli/tests/unit/tls_test.ts')
-rw-r--r--cli/tests/unit/tls_test.ts1546
1 files changed, 0 insertions, 1546 deletions
diff --git a/cli/tests/unit/tls_test.ts b/cli/tests/unit/tls_test.ts
deleted file mode 100644
index bbf48cf8f..000000000
--- a/cli/tests/unit/tls_test.ts
+++ /dev/null
@@ -1,1546 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-import {
- assert,
- assertEquals,
- assertNotEquals,
- assertRejects,
- assertStrictEquals,
- assertThrows,
-} from "./test_util.ts";
-import { BufReader, BufWriter } from "@test_util/std/io/mod.ts";
-import { readAll } from "@test_util/std/streams/read_all.ts";
-import { writeAll } from "@test_util/std/streams/write_all.ts";
-import { TextProtoReader } from "../testdata/run/textproto.ts";
-
-const encoder = new TextEncoder();
-const decoder = new TextDecoder();
-const cert = await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt");
-const key = await Deno.readTextFile("cli/tests/testdata/tls/localhost.key");
-const caCerts = [await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem")];
-
-async function sleep(msec: number) {
- await new Promise((res, _rej) => setTimeout(res, msec));
-}
-
-function unreachable(): never {
- throw new Error("Unreachable code reached");
-}
-
-Deno.test({ permissions: { net: false } }, async function connectTLSNoPerm() {
- await assertRejects(async () => {
- await Deno.connectTls({ hostname: "deno.land", port: 443 });
- }, Deno.errors.PermissionDenied);
-});
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function connectTLSInvalidHost() {
- await assertRejects(async () => {
- await Deno.connectTls({ hostname: "256.0.0.0", port: 3567 });
- }, TypeError);
- },
-);
-
-Deno.test(
- { permissions: { net: true, read: false } },
- async function connectTLSCertFileNoReadPerm() {
- await assertRejects(async () => {
- await Deno.connectTls({
- hostname: "deno.land",
- port: 443,
- certFile: "cli/tests/testdata/tls/RootCA.crt",
- });
- }, Deno.errors.PermissionDenied);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- function listenTLSNonExistentCertKeyFiles() {
- const options = {
- hostname: "localhost",
- port: 3500,
- certFile: "cli/tests/testdata/tls/localhost.crt",
- keyFile: "cli/tests/testdata/tls/localhost.key",
- };
-
- assertThrows(() => {
- Deno.listenTls({
- ...options,
- certFile: "./non/existent/file",
- });
- }, Deno.errors.NotFound);
-
- assertThrows(() => {
- Deno.listenTls({
- ...options,
- keyFile: "./non/existent/file",
- });
- }, Deno.errors.NotFound);
- },
-);
-
-Deno.test(
- { permissions: { net: true, read: false } },
- function listenTLSNoReadPerm() {
- assertThrows(() => {
- Deno.listenTls({
- hostname: "localhost",
- port: 3500,
- certFile: "cli/tests/testdata/tls/localhost.crt",
- keyFile: "cli/tests/testdata/tls/localhost.key",
- });
- }, Deno.errors.PermissionDenied);
- },
-);
-
-Deno.test(
- {
- permissions: { read: true, write: true, net: true },
- },
- function listenTLSEmptyKeyFile() {
- const options = {
- hostname: "localhost",
- port: 3500,
- certFile: "cli/tests/testdata/tls/localhost.crt",
- keyFile: "cli/tests/testdata/tls/localhost.key",
- };
-
- const testDir = Deno.makeTempDirSync();
- const keyFilename = testDir + "/key.pem";
- Deno.writeFileSync(keyFilename, new Uint8Array([]), {
- mode: 0o666,
- });
-
- assertThrows(() => {
- Deno.listenTls({
- ...options,
- keyFile: keyFilename,
- });
- }, Error);
- },
-);
-
-Deno.test(
- { permissions: { read: true, write: true, net: true } },
- function listenTLSEmptyCertFile() {
- const options = {
- hostname: "localhost",
- port: 3500,
- certFile: "cli/tests/testdata/tls/localhost.crt",
- keyFile: "cli/tests/testdata/tls/localhost.key",
- };
-
- const testDir = Deno.makeTempDirSync();
- const certFilename = testDir + "/cert.crt";
- Deno.writeFileSync(certFilename, new Uint8Array([]), {
- mode: 0o666,
- });
-
- assertThrows(() => {
- Deno.listenTls({
- ...options,
- certFile: certFilename,
- });
- }, Error);
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function startTlsWithoutExclusiveAccessToTcpConn() {
- const hostname = "localhost";
- const port = getPort();
-
- const tcpListener = Deno.listen({ hostname, port });
- const [serverConn, clientConn] = await Promise.all([
- tcpListener.accept(),
- Deno.connect({ hostname, port }),
- ]);
-
- const buf = new Uint8Array(128);
- const readPromise = clientConn.read(buf);
- // `clientConn` is being used by a pending promise (`readPromise`) so
- // `Deno.startTls` cannot consume the connection.
- await assertRejects(
- () => Deno.startTls(clientConn, { hostname }),
- Deno.errors.BadResource,
- );
-
- serverConn.close();
- tcpListener.close();
- await readPromise;
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function dialAndListenTLS() {
- const { promise, resolve } = Promise.withResolvers<void>();
- const hostname = "localhost";
- const port = 3500;
-
- const listener = Deno.listenTls({
- hostname,
- port,
- cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"),
- key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"),
- });
-
- const response = encoder.encode(
- "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n",
- );
-
- listener.accept().then(
- async (conn) => {
- assert(conn.remoteAddr != null);
- assert(conn.localAddr != null);
- await conn.write(response);
- // TODO(bartlomieju): this might be a bug
- setTimeout(() => {
- conn.close();
- resolve();
- }, 0);
- },
- );
-
- const conn = await Deno.connectTls({ hostname, port, caCerts });
- assert(conn.rid > 0);
- const w = new BufWriter(conn);
- const r = new BufReader(conn);
- const body = `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`;
- const writeResult = await w.write(encoder.encode(body));
- assertEquals(body.length, writeResult);
- await w.flush();
- const tpr = new TextProtoReader(r);
- const statusLine = await tpr.readLine();
- assert(statusLine !== null, `line must be read: ${String(statusLine)}`);
- const m = statusLine.match(/^(.+?) (.+?) (.+?)$/);
- assert(m !== null, "must be matched");
- const [_, proto, status, ok] = m;
- assertEquals(proto, "HTTP/1.1");
- assertEquals(status, "200");
- assertEquals(ok, "OK");
- const headers = await tpr.readMimeHeader();
- assert(headers !== null);
- const contentLength = parseInt(headers.get("content-length")!);
- const bodyBuf = new Uint8Array(contentLength);
- await r.readFull(bodyBuf);
- assertEquals(decoder.decode(bodyBuf), "Hello World\n");
- conn.close();
- listener.close();
- await promise;
- },
-);
-Deno.test(
- { permissions: { read: false, net: true } },
- async function listenTlsWithCertAndKey() {
- const { promise, resolve } = Promise.withResolvers<void>();
- const hostname = "localhost";
- const port = 3500;
-
- const listener = Deno.listenTls({ hostname, port, cert, key });
-
- const response = encoder.encode(
- "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n",
- );
-
- listener.accept().then(
- async (conn) => {
- assert(conn.remoteAddr != null);
- assert(conn.localAddr != null);
- await conn.write(response);
- setTimeout(() => {
- conn.close();
- resolve();
- }, 0);
- },
- );
-
- const conn = await Deno.connectTls({ hostname, port, caCerts });
- assert(conn.rid > 0);
- const w = new BufWriter(conn);
- const r = new BufReader(conn);
- const body = `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`;
- const writeResult = await w.write(encoder.encode(body));
- assertEquals(body.length, writeResult);
- await w.flush();
- const tpr = new TextProtoReader(r);
- const statusLine = await tpr.readLine();
- assert(statusLine !== null, `line must be read: ${String(statusLine)}`);
- const m = statusLine.match(/^(.+?) (.+?) (.+?)$/);
- assert(m !== null, "must be matched");
- const [_, proto, status, ok] = m;
- assertEquals(proto, "HTTP/1.1");
- assertEquals(status, "200");
- assertEquals(ok, "OK");
- const headers = await tpr.readMimeHeader();
- assert(headers !== null);
- const contentLength = parseInt(headers.get("content-length")!);
- const bodyBuf = new Uint8Array(contentLength);
- await r.readFull(bodyBuf);
- assertEquals(decoder.decode(bodyBuf), "Hello World\n");
- conn.close();
- listener.close();
- await promise;
- },
-);
-
-let nextPort = 3501;
-function getPort() {
- return nextPort++;
-}
-
-async function tlsPair(): Promise<[Deno.Conn, Deno.Conn]> {
- const port = getPort();
- const listener = Deno.listenTls({
- hostname: "localhost",
- port,
- cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"),
- key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"),
- });
-
- const acceptPromise = listener.accept();
- const connectPromise = Deno.connectTls({
- hostname: "localhost",
- port,
- caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
- });
- const endpoints = await Promise.all([acceptPromise, connectPromise]);
-
- listener.close();
-
- return endpoints;
-}
-
-async function tlsAlpn(
- useStartTls: boolean,
-): Promise<[Deno.TlsConn, Deno.TlsConn]> {
- const port = getPort();
- const listener = Deno.listenTls({
- hostname: "localhost",
- port,
- cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"),
- key: await Deno.readTextFile("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,
- chunkSize: number,
-) {
- const byteCount = chunkCount * chunkSize;
- const buf = new Uint8Array(chunkSize); // Note: buf is size of _chunk_.
- let n: number;
-
- // Slowly send 42s.
- buf.fill(42);
- for (let remaining = byteCount; remaining > 0; remaining -= n) {
- n = await conn.write(buf.subarray(0, remaining));
- assert(n >= 1);
- await sleep(10);
- }
-
- // Send EOF.
- await conn.closeWrite();
-
- // Receive 69s.
- for (let remaining = byteCount; remaining > 0; remaining -= n) {
- buf.fill(0);
- n = await conn.read(buf) as number;
- assert(n >= 1);
- assertStrictEquals(buf[0], 69);
- assertStrictEquals(buf[n - 1], 69);
- }
-
- conn.close();
-}
-
-async function receiveThenSend(
- conn: Deno.Conn,
- chunkCount: number,
- chunkSize: number,
-) {
- const byteCount = chunkCount * chunkSize;
- const buf = new Uint8Array(byteCount); // Note: buf size equals `byteCount`.
- let n: number;
-
- // Receive 42s.
- for (let remaining = byteCount; remaining > 0; remaining -= n) {
- buf.fill(0);
- n = await conn.read(buf) as number;
- assert(n >= 1);
- assertStrictEquals(buf[0], 42);
- assertStrictEquals(buf[n - 1], 42);
- }
-
- // Slowly send 69s.
- buf.fill(69);
- for (let remaining = byteCount; remaining > 0; remaining -= n) {
- n = await conn.write(buf.subarray(0, remaining));
- assert(n >= 1);
- await sleep(10);
- }
-
- 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() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendThenCloseWriteThenReceive(serverConn, 1, 1),
- receiveThenSend(clientConn, 1, 1),
- ]);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsClientStreamHalfCloseSendOneByte() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendThenCloseWriteThenReceive(clientConn, 1, 1),
- receiveThenSend(serverConn, 1, 1),
- ]);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsServerStreamHalfCloseSendOneChunk() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendThenCloseWriteThenReceive(serverConn, 1, 1 << 20 /* 1 MB */),
- receiveThenSend(clientConn, 1, 1 << 20 /* 1 MB */),
- ]);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsClientStreamHalfCloseSendOneChunk() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendThenCloseWriteThenReceive(clientConn, 1, 1 << 20 /* 1 MB */),
- receiveThenSend(serverConn, 1, 1 << 20 /* 1 MB */),
- ]);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsServerStreamHalfCloseSendManyBytes() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendThenCloseWriteThenReceive(serverConn, 100, 1),
- receiveThenSend(clientConn, 100, 1),
- ]);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsClientStreamHalfCloseSendManyBytes() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendThenCloseWriteThenReceive(clientConn, 100, 1),
- receiveThenSend(serverConn, 100, 1),
- ]);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsServerStreamHalfCloseSendManyChunks() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendThenCloseWriteThenReceive(serverConn, 100, 1 << 16 /* 64 kB */),
- receiveThenSend(clientConn, 100, 1 << 16 /* 64 kB */),
- ]);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsClientStreamHalfCloseSendManyChunks() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendThenCloseWriteThenReceive(clientConn, 100, 1 << 16 /* 64 kB */),
- receiveThenSend(serverConn, 100, 1 << 16 /* 64 kB */),
- ]);
- },
-);
-
-const largeAmount = 1 << 20 /* 1 MB */;
-
-async function sendAlotReceiveNothing(conn: Deno.Conn) {
- // Start receive op.
- const readBuf = new Uint8Array(1024);
- const readPromise = conn.read(readBuf);
-
- const timeout = setTimeout(() => {
- throw new Error("Failed to send buffer in a reasonable amount of time");
- }, 10_000);
-
- // Send 1 MB of data.
- const writeBuf = new Uint8Array(largeAmount);
- writeBuf.fill(42);
- await writeAll(conn, writeBuf);
-
- clearTimeout(timeout);
-
- // Send EOF.
- await conn.closeWrite();
-
- // Close the connection.
- conn.close();
-
- // Read op should be canceled.
- await assertRejects(
- async () => await readPromise,
- Deno.errors.Interrupted,
- );
-}
-
-async function receiveAlotSendNothing(conn: Deno.Conn) {
- const readBuf = new Uint8Array(1024);
- let n: number | null;
- let nread = 0;
-
- const timeout = setTimeout(() => {
- throw new Error(
- `Failed to read buffer in a reasonable amount of time (got ${nread}/${largeAmount})`,
- );
- }, 10_000);
-
- // Receive 1 MB of data.
- try {
- for (; nread < largeAmount; nread += n!) {
- n = await conn.read(readBuf);
- assertStrictEquals(typeof n, "number");
- assert(n! > 0);
- assertStrictEquals(readBuf[0], 42);
- }
- } catch (e) {
- throw new Error(
- `Got an error (${e.message}) after reading ${nread}/${largeAmount} bytes`,
- { cause: e },
- );
- }
- clearTimeout(timeout);
-
- // Close the connection, without sending anything at all.
- conn.close();
-}
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsServerStreamCancelRead() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendAlotReceiveNothing(serverConn),
- receiveAlotSendNothing(clientConn),
- ]);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsClientStreamCancelRead() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendAlotReceiveNothing(clientConn),
- receiveAlotSendNothing(serverConn),
- ]);
- },
-);
-
-async function sendReceiveEmptyBuf(conn: Deno.Conn) {
- const byteBuf = new Uint8Array([1]);
- const emptyBuf = new Uint8Array(0);
- let n: number | null;
-
- n = await conn.write(emptyBuf);
- assertStrictEquals(n, 0);
-
- n = await conn.read(emptyBuf);
- assertStrictEquals(n, 0);
-
- n = await conn.write(byteBuf);
- assertStrictEquals(n, 1);
-
- n = await conn.read(byteBuf);
- assertStrictEquals(n, 1);
-
- await conn.closeWrite();
-
- n = await conn.write(emptyBuf);
- assertStrictEquals(n, 0);
-
- await assertRejects(async () => {
- await conn.write(byteBuf);
- }, Deno.errors.NotConnected);
-
- n = await conn.write(emptyBuf);
- assertStrictEquals(n, 0);
-
- n = await conn.read(byteBuf);
- assertStrictEquals(n, null);
-
- conn.close();
-}
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsStreamSendReceiveEmptyBuf() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- sendReceiveEmptyBuf(serverConn),
- sendReceiveEmptyBuf(clientConn),
- ]);
- },
-);
-
-function immediateClose(conn: Deno.Conn) {
- conn.close();
- return Promise.resolve();
-}
-
-async function closeWriteAndClose(conn: Deno.Conn) {
- await conn.closeWrite();
-
- if (await conn.read(new Uint8Array(1)) !== null) {
- throw new Error("did not expect to receive data on TLS stream");
- }
-
- conn.close();
-}
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsServerStreamImmediateClose() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- immediateClose(serverConn),
- closeWriteAndClose(clientConn),
- ]);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsClientStreamImmediateClose() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- closeWriteAndClose(serverConn),
- immediateClose(clientConn),
- ]);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsClientAndServerStreamImmediateClose() {
- const [serverConn, clientConn] = await tlsPair();
- await Promise.all([
- immediateClose(serverConn),
- immediateClose(clientConn),
- ]);
- },
-);
-
-async function tlsWithTcpFailureTestImpl(
- phase: "handshake" | "traffic",
- cipherByteCount: number,
- failureMode: "corruption" | "shutdown",
- reverse: boolean,
-) {
- const tlsPort = getPort();
- const tlsListener = Deno.listenTls({
- hostname: "localhost",
- port: tlsPort,
- cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"),
- key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"),
- });
-
- const tcpPort = getPort();
- const tcpListener = Deno.listen({ hostname: "localhost", port: tcpPort });
-
- const [tlsServerConn, tcpServerConn] = await Promise.all([
- tlsListener.accept(),
- Deno.connect({ hostname: "localhost", port: tlsPort }),
- ]);
-
- const [tcpClientConn, tlsClientConn] = await Promise.all([
- tcpListener.accept(),
- Deno.connectTls({
- hostname: "localhost",
- port: tcpPort,
- caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
- }),
- ]);
-
- tlsListener.close();
- tcpListener.close();
-
- const {
- tlsConn1,
- tlsConn2,
- tcpConn1,
- tcpConn2,
- } = reverse
- ? {
- tlsConn1: tlsClientConn,
- tlsConn2: tlsServerConn,
- tcpConn1: tcpClientConn,
- tcpConn2: tcpServerConn,
- }
- : {
- tlsConn1: tlsServerConn,
- tlsConn2: tlsClientConn,
- tcpConn1: tcpServerConn,
- tcpConn2: tcpClientConn,
- };
-
- const tcpForwardingInterruptDeferred1 = Promise.withResolvers<void>();
- const tcpForwardingPromise1 = forwardBytes(
- tcpConn2,
- tcpConn1,
- cipherByteCount,
- tcpForwardingInterruptDeferred1,
- );
-
- const tcpForwardingInterruptDeferred2 = Promise.withResolvers<void>();
- const tcpForwardingPromise2 = forwardBytes(
- tcpConn1,
- tcpConn2,
- Infinity,
- tcpForwardingInterruptDeferred2,
- );
-
- switch (phase) {
- case "handshake": {
- let expectedError;
- switch (failureMode) {
- case "corruption":
- expectedError = Deno.errors.InvalidData;
- break;
- case "shutdown":
- expectedError = Deno.errors.UnexpectedEof;
- break;
- default:
- unreachable();
- }
-
- const tlsTrafficPromise1 = Promise.all([
- assertRejects(
- () => sendBytes(tlsConn1, 0x01, 1),
- expectedError,
- ),
- assertRejects(
- () => receiveBytes(tlsConn1, 0x02, 1),
- expectedError,
- ),
- ]);
-
- const tlsTrafficPromise2 = Promise.all([
- assertRejects(
- () => sendBytes(tlsConn2, 0x02, 1),
- Deno.errors.UnexpectedEof,
- ),
- assertRejects(
- () => receiveBytes(tlsConn2, 0x01, 1),
- Deno.errors.UnexpectedEof,
- ),
- ]);
-
- await tcpForwardingPromise1;
-
- switch (failureMode) {
- case "corruption":
- await sendBytes(tcpConn1, 0xff, 1 << 14 /* 16 kB */);
- break;
- case "shutdown":
- await tcpConn1.closeWrite();
- break;
- default:
- unreachable();
- }
- await tlsTrafficPromise1;
-
- tcpForwardingInterruptDeferred2.resolve();
- await tcpForwardingPromise2;
- await tcpConn2.closeWrite();
- await tlsTrafficPromise2;
-
- break;
- }
-
- case "traffic": {
- await Promise.all([
- sendBytes(tlsConn2, 0x88, 8888),
- receiveBytes(tlsConn1, 0x88, 8888),
- sendBytes(tlsConn1, 0x99, 99999),
- receiveBytes(tlsConn2, 0x99, 99999),
- ]);
-
- tcpForwardingInterruptDeferred1.resolve();
- await tcpForwardingInterruptDeferred1.promise;
-
- switch (failureMode) {
- case "corruption":
- await sendBytes(tcpConn1, 0xff, 1 << 14 /* 16 kB */);
- await assertRejects(
- () => receiveEof(tlsConn1),
- Deno.errors.InvalidData,
- );
- tcpForwardingInterruptDeferred2.resolve();
- break;
- case "shutdown":
- await Promise.all([
- tcpConn1.closeWrite(),
- await assertRejects(
- () => receiveEof(tlsConn1),
- Deno.errors.UnexpectedEof,
- ),
- await tlsConn1.closeWrite(),
- await receiveEof(tlsConn2),
- ]);
- break;
- default:
- unreachable();
- }
-
- await tcpForwardingPromise2;
-
- break;
- }
-
- default:
- unreachable();
- }
-
- tlsServerConn.close();
- tlsClientConn.close();
- tcpServerConn.close();
- tcpClientConn.close();
-
- async function sendBytes(
- conn: Deno.Conn,
- byte: number,
- count: number,
- ) {
- let buf = new Uint8Array(1 << 12 /* 4 kB */);
- buf.fill(byte);
-
- while (count > 0) {
- buf = buf.subarray(0, Math.min(buf.length, count));
- const nwritten = await conn.write(buf);
- assertStrictEquals(nwritten, buf.length);
- count -= nwritten;
- }
- }
-
- async function receiveBytes(
- conn: Deno.Conn,
- byte: number,
- count: number,
- ) {
- let buf = new Uint8Array(1 << 12 /* 4 kB */);
- while (count > 0) {
- buf = buf.subarray(0, Math.min(buf.length, count));
- const r = await conn.read(buf);
- assertNotEquals(r, null);
- assert(buf.subarray(0, r!).every((b) => b === byte));
- count -= r!;
- }
- }
-
- async function receiveEof(conn: Deno.Conn) {
- const buf = new Uint8Array(1);
- const r = await conn.read(buf);
- assertStrictEquals(r, null);
- }
-
- async function forwardBytes(
- source: Deno.Conn,
- sink: Deno.Conn,
- count: number,
- interruptPromise: ReturnType<typeof Promise.withResolvers<void>>,
- ) {
- let buf = new Uint8Array(1 << 12 /* 4 kB */);
- while (count > 0) {
- buf = buf.subarray(0, Math.min(buf.length, count));
- const nread = await Promise.race([
- source.read(buf),
- interruptPromise.promise,
- ]);
- if (nread == null) break; // Either EOF or interrupted.
- const nwritten = await sink.write(buf.subarray(0, nread));
- assertStrictEquals(nread, nwritten);
- count -= nwritten;
- }
- }
-}
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsHandshakeWithTcpCorruptionImmediately() {
- await tlsWithTcpFailureTestImpl("handshake", 0, "corruption", false);
- await tlsWithTcpFailureTestImpl("handshake", 0, "corruption", true);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsHandshakeWithTcpShutdownImmediately() {
- await tlsWithTcpFailureTestImpl("handshake", 0, "shutdown", false);
- await tlsWithTcpFailureTestImpl("handshake", 0, "shutdown", true);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsHandshakeWithTcpCorruptionAfter70Bytes() {
- await tlsWithTcpFailureTestImpl("handshake", 76, "corruption", false);
- await tlsWithTcpFailureTestImpl("handshake", 78, "corruption", true);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsHandshakeWithTcpShutdownAfter70bytes() {
- await tlsWithTcpFailureTestImpl("handshake", 77, "shutdown", false);
- await tlsWithTcpFailureTestImpl("handshake", 79, "shutdown", true);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsHandshakeWithTcpCorruptionAfter200Bytes() {
- await tlsWithTcpFailureTestImpl("handshake", 200, "corruption", false);
- await tlsWithTcpFailureTestImpl("handshake", 202, "corruption", true);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsHandshakeWithTcpShutdownAfter200bytes() {
- await tlsWithTcpFailureTestImpl("handshake", 201, "shutdown", false);
- await tlsWithTcpFailureTestImpl("handshake", 203, "shutdown", true);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsTrafficWithTcpCorruption() {
- await tlsWithTcpFailureTestImpl("traffic", Infinity, "corruption", false);
- await tlsWithTcpFailureTestImpl("traffic", Infinity, "corruption", true);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsTrafficWithTcpShutdown() {
- await tlsWithTcpFailureTestImpl("traffic", Infinity, "shutdown", false);
- await tlsWithTcpFailureTestImpl("traffic", Infinity, "shutdown", true);
- },
-);
-
-function createHttpsListener(port: number): Deno.Listener {
- // Query format: `curl --insecure https://localhost:8443/z/12345`
- // The server returns a response consisting of 12345 times the letter 'z'.
- const listener = Deno.listenTls({
- hostname: "localhost",
- port,
- cert: Deno.readTextFileSync("./cli/tests/testdata/tls/localhost.crt"),
- key: Deno.readTextFileSync("./cli/tests/testdata/tls/localhost.key"),
- });
-
- serve(listener);
- return listener;
-
- async function serve(listener: Deno.Listener) {
- for await (const conn of listener) {
- const EOL = "\r\n";
-
- // Read GET request plus headers.
- const buf = new Uint8Array(1 << 12 /* 4 kB */);
- const decoder = new TextDecoder();
- let req = "";
- while (!req.endsWith(EOL + EOL)) {
- const n = await conn.read(buf);
- if (n === null) throw new Error("Unexpected EOF");
- req += decoder.decode(buf.subarray(0, n));
- }
-
- // Parse GET request.
- const { filler, count, version } =
- /^GET \/(?<filler>[^\/]+)\/(?<count>\d+) HTTP\/(?<version>1\.\d)\r\n/
- .exec(req)!.groups as {
- filler: string;
- count: string;
- version: string;
- };
-
- // Generate response.
- const resBody = new TextEncoder().encode(filler.repeat(+count));
- const resHead = new TextEncoder().encode(
- [
- `HTTP/${version} 200 OK`,
- `Content-Length: ${resBody.length}`,
- "Content-Type: text/plain",
- ].join(EOL) + EOL + EOL,
- );
-
- // Send response.
- await writeAll(conn, resHead);
- await writeAll(conn, resBody);
-
- // Close TCP connection.
- conn.close();
- }
- }
-}
-
-async function curl(url: string): Promise<string> {
- const { success, code, stdout, stderr } = await new Deno.Command("curl", {
- args: ["--insecure", url],
- }).output();
-
- if (!success) {
- throw new Error(
- `curl ${url} failed: ${code}:\n${new TextDecoder().decode(stderr)}`,
- );
- }
- return new TextDecoder().decode(stdout);
-}
-
-Deno.test(
- { permissions: { read: true, net: true, run: true } },
- async function curlFakeHttpsServer() {
- const port = getPort();
- const listener = createHttpsListener(port);
-
- const res1 = await curl(`https://localhost:${port}/d/1`);
- assertStrictEquals(res1, "d");
-
- const res2 = await curl(`https://localhost:${port}/e/12345`);
- assertStrictEquals(res2, "e".repeat(12345));
-
- const count3 = 1 << 17; // 128 kB.
- const res3 = await curl(`https://localhost:${port}/n/${count3}`);
- assertStrictEquals(res3, "n".repeat(count3));
-
- const count4 = 12345678;
- const res4 = await curl(`https://localhost:${port}/o/${count4}`);
- assertStrictEquals(res4, "o".repeat(count4));
-
- listener.close();
- },
-);
-
-Deno.test(
- // Ignored because gmail appears to reject us on CI sometimes
- { ignore: true, permissions: { read: true, net: true } },
- async function startTls() {
- const hostname = "smtp.gmail.com";
- const port = 587;
- const encoder = new TextEncoder();
-
- const conn = await Deno.connect({
- hostname,
- port,
- });
-
- let writer = new BufWriter(conn);
- let reader = new TextProtoReader(new BufReader(conn));
-
- let line: string | null = (await reader.readLine()) as string;
- assert(line.startsWith("220"));
-
- await writer.write(encoder.encode(`EHLO ${hostname}\r\n`));
- await writer.flush();
-
- while ((line = (await reader.readLine()) as string)) {
- assert(line.startsWith("250"));
- if (line.startsWith("250 ")) break;
- }
-
- await writer.write(encoder.encode("STARTTLS\r\n"));
- await writer.flush();
-
- line = await reader.readLine();
-
- // Received the message that the server is ready to establish TLS
- assertEquals(line, "220 2.0.0 Ready to start TLS");
-
- const tlsConn = await Deno.startTls(conn, { hostname });
- writer = new BufWriter(tlsConn);
- reader = new TextProtoReader(new BufReader(tlsConn));
-
- // After that use TLS communication again
- await writer.write(encoder.encode(`EHLO ${hostname}\r\n`));
- await writer.flush();
-
- while ((line = (await reader.readLine()) as string)) {
- assert(line.startsWith("250"));
- if (line.startsWith("250 ")) break;
- }
-
- tlsConn.close();
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function connectTLSBadClientCertPrivateKey(): Promise<void> {
- await assertRejects(async () => {
- await Deno.connectTls({
- hostname: "deno.land",
- port: 443,
- certChain: "bad data",
- privateKey: await Deno.readTextFile(
- "cli/tests/testdata/tls/localhost.key",
- ),
- });
- }, Deno.errors.InvalidData);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function connectTLSBadPrivateKey(): Promise<void> {
- await assertRejects(async () => {
- await Deno.connectTls({
- hostname: "deno.land",
- port: 443,
- certChain: await Deno.readTextFile(
- "cli/tests/testdata/tls/localhost.crt",
- ),
- privateKey: "bad data",
- });
- }, Deno.errors.InvalidData);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function connectTLSNotPrivateKey(): Promise<void> {
- await assertRejects(async () => {
- await Deno.connectTls({
- hostname: "deno.land",
- port: 443,
- certChain: await Deno.readTextFile(
- "cli/tests/testdata/tls/localhost.crt",
- ),
- privateKey: "",
- });
- }, Deno.errors.InvalidData);
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function connectWithClientCert() {
- // The test_server running on port 4552 responds with 'PASS' if client
- // authentication was successful. Try it by running test_server and
- // curl --key cli/tests/testdata/tls/localhost.key \
- // --cert cli/tests/testdata/tls/localhost.crt \
- // --cacert cli/tests/testdata/tls/RootCA.crt https://localhost:4552/
- const conn = await Deno.connectTls({
- hostname: "localhost",
- port: 4552,
- certChain: await Deno.readTextFile(
- "cli/tests/testdata/tls/localhost.crt",
- ),
- privateKey: await Deno.readTextFile(
- "cli/tests/testdata/tls/localhost.key",
- ),
- caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
- });
- const result = decoder.decode(await readAll(conn));
- assertEquals(result, "PASS");
- conn.close();
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function connectTLSCaCerts() {
- const conn = await Deno.connectTls({
- hostname: "localhost",
- port: 4557,
- caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
- });
- const result = decoder.decode(await readAll(conn));
- assertEquals(result, "PASS");
- conn.close();
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function connectTLSCertFile() {
- const conn = await Deno.connectTls({
- hostname: "localhost",
- port: 4557,
- certFile: "cli/tests/testdata/tls/RootCA.pem",
- });
- const result = decoder.decode(await readAll(conn));
- assertEquals(result, "PASS");
- conn.close();
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function startTLSCaCerts() {
- const plainConn = await Deno.connect({
- hostname: "localhost",
- port: 4557,
- });
- const conn = await Deno.startTls(plainConn, {
- hostname: "localhost",
- caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
- });
- const result = decoder.decode(await readAll(conn));
- assertEquals(result, "PASS");
- conn.close();
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsHandshakeSuccess() {
- const hostname = "localhost";
- const port = getPort();
-
- const listener = Deno.listenTls({
- hostname,
- port,
- cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"),
- key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"),
- });
- const acceptPromise = listener.accept();
- const connectPromise = Deno.connectTls({
- hostname,
- port,
- certFile: "cli/tests/testdata/tls/RootCA.crt",
- });
- const [conn1, conn2] = await Promise.all([acceptPromise, connectPromise]);
- listener.close();
-
- await Promise.all([conn1.handshake(), conn2.handshake()]);
-
- // Begin sending a 10mb blob over the TLS connection.
- const whole = new Uint8Array(10 << 20); // 10mb.
- whole.fill(42);
- const sendPromise = writeAll(conn1, whole);
- // Set up the other end to receive half of the large blob.
- const half = new Uint8Array(whole.byteLength / 2);
- const receivePromise = readFull(conn2, half);
-
- 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 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);
- await sendPromise;
-
- await conn1.handshake();
- await conn2.handshake();
-
- await conn1.closeWrite();
- await conn2.closeWrite();
-
- await conn1.handshake();
- await conn2.handshake();
-
- conn1.close();
- conn2.close();
-
- async function readFull(conn: Deno.Conn, buf: Uint8Array) {
- let offset, n;
- for (offset = 0; offset < buf.length; offset += n) {
- n = await conn.read(buf.subarray(offset, buf.length));
- assert(n != null && n > 0);
- }
- return offset;
- }
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function tlsHandshakeFailure() {
- const hostname = "localhost";
- const port = getPort();
-
- async function server() {
- const listener = Deno.listenTls({
- hostname,
- port,
- cert: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.crt"),
- key: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.key"),
- });
- for await (const conn of listener) {
- for (let i = 0; i < 10; i++) {
- // Handshake fails because the client rejects the server certificate.
- await assertRejects(
- () => conn.handshake(),
- Deno.errors.InvalidData,
- "received fatal alert",
- );
- }
- conn.close();
- break;
- }
- }
-
- async function connectTlsClient() {
- const conn = await Deno.connectTls({ hostname, port });
- // Handshake fails because the server presents a self-signed certificate.
- await assertRejects(
- () => conn.handshake(),
- Deno.errors.InvalidData,
- "invalid peer certificate: UnknownIssuer",
- );
- conn.close();
- }
-
- await Promise.all([server(), connectTlsClient()]);
-
- async function startTlsClient() {
- const tcpConn = await Deno.connect({ hostname, port });
- const tlsConn = await Deno.startTls(tcpConn, {
- hostname: "foo.land",
- caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
- });
- // Handshake fails because hostname doesn't match the certificate.
- await assertRejects(
- () => tlsConn.handshake(),
- Deno.errors.InvalidData,
- "NotValidForName",
- );
- tlsConn.close();
- }
-
- await Promise.all([server(), startTlsClient()]);
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function listenTlsWithReuseAddr() {
- const deferred1 = Promise.withResolvers<void>();
- const hostname = "localhost";
- const port = 3500;
-
- const listener1 = Deno.listenTls({ hostname, port, cert, key });
-
- listener1.accept().then((conn) => {
- conn.close();
- deferred1.resolve();
- });
-
- const conn1 = await Deno.connectTls({ hostname, port, caCerts });
- conn1.close();
- await deferred1.promise;
- listener1.close();
-
- const deferred2 = Promise.withResolvers<void>();
- const listener2 = Deno.listenTls({ hostname, port, cert, key });
-
- listener2.accept().then((conn) => {
- conn.close();
- deferred2.resolve();
- });
-
- const conn2 = await Deno.connectTls({ hostname, port, caCerts });
- conn2.close();
- await deferred2.promise;
- listener2.close();
- },
-);
-
-Deno.test({
- ignore: Deno.build.os !== "linux",
- permissions: { net: true },
-}, async function listenTlsReusePort() {
- const hostname = "localhost";
- const port = 4003;
- const listener1 = Deno.listenTls({
- hostname,
- port,
- cert,
- key,
- reusePort: true,
- });
- const listener2 = Deno.listenTls({
- hostname,
- port,
- cert,
- key,
- reusePort: true,
- });
- let p1;
- let p2;
- let listener1Recv = false;
- let listener2Recv = false;
- while (!listener1Recv || !listener2Recv) {
- if (!p1) {
- p1 = listener1.accept().then((conn) => {
- conn.close();
- listener1Recv = true;
- p1 = undefined;
- }).catch(() => {});
- }
- if (!p2) {
- p2 = listener2.accept().then((conn) => {
- conn.close();
- listener2Recv = true;
- p2 = undefined;
- }).catch(() => {});
- }
- const conn = await Deno.connectTls({ hostname, port, caCerts });
- conn.close();
- await Promise.race([p1, p2]);
- }
- listener1.close();
- listener2.close();
-});
-
-Deno.test({
- ignore: Deno.build.os === "linux",
- permissions: { net: true },
-}, function listenTlsReusePortDoesNothing() {
- const hostname = "localhost";
- const port = 4003;
- const listener1 = Deno.listenTls({
- hostname,
- port,
- cert,
- key,
- reusePort: true,
- });
- assertThrows(() => {
- Deno.listenTls({ hostname, port, cert, key, reusePort: true });
- }, Deno.errors.AddrInUse);
- listener1.close();
-});
-
-Deno.test({
- permissions: { net: true },
-}, function listenTlsDoesNotThrowOnStringPort() {
- const listener = Deno.listenTls({
- hostname: "localhost",
- // @ts-ignore String port is not allowed by typing, but it shouldn't throw
- // for backwards compatibility.
- port: "0",
- cert,
- key,
- });
- listener.close();
-});
-
-Deno.test(
- { permissions: { net: true, read: true } },
- function listenTLSInvalidCert() {
- assertThrows(() => {
- Deno.listenTls({
- hostname: "localhost",
- port: 3500,
- certFile: "cli/tests/testdata/tls/invalid.crt",
- keyFile: "cli/tests/testdata/tls/localhost.key",
- });
- }, Deno.errors.InvalidData);
- },
-);
-
-Deno.test(
- { permissions: { net: true, read: true } },
- function listenTLSInvalidKey() {
- assertThrows(() => {
- Deno.listenTls({
- hostname: "localhost",
- port: 3500,
- certFile: "cli/tests/testdata/tls/localhost.crt",
- keyFile: "cli/tests/testdata/tls/invalid.key",
- });
- }, Deno.errors.InvalidData);
- },
-);