Skip to content

Commit

Permalink
feat(node/tls): basic support of tls.createServer (#1948)
Browse files Browse the repository at this point in the history
  • Loading branch information
kt3k authored Feb 23, 2022
1 parent cbe3800 commit 4f54453
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 24 deletions.
79 changes: 58 additions & 21 deletions node/_tls_wrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ import {
constants as PipeConstants,
Pipe,
} from "./internal_binding/pipe_wrap.ts";
import { EventEmitter } from "./events.ts";
import { join } from "./path.ts";
const kConnectOptions = Symbol("connect-options");
const kIsVerified = Symbol("verified");
const kPendingSession = Symbol("pendingSession");
const kRes = Symbol("res");

let debug = debuglog("tls", (fn) => {
debug = fn;
let _debug = debuglog("tls", (fn) => {
_debug = fn;
});

function onConnectEnd(this: any) {
Expand Down Expand Up @@ -209,33 +211,68 @@ function normalizeConnectArgs(listArgs: any) {

let ipServernameWarned = false;

function tlsConnectionListener(this: ServerImpl, rawSocket: net.Socket) {
debug("net.Server.on(connection): new TLSSocket");
const socket = new TLSSocket(rawSocket, {
secureContext: this._sharedCreds,
isServer: true,
server: this,
pauseOnConnect: this.pauseOnConnect,
});

socket.on("secure", () => {
this.emit("secureConnection", this);
});
}

export function Server(options: any, listener: any) {
return new ServerImpl(options, listener);
}

export class ServerImpl extends net.Server {
_sharedCreds = null;
constructor(options: any, listener: any) {
super(options, tlsConnectionListener);

export class ServerImpl extends EventEmitter {
listener?: Deno.TlsListener;
#closed = false;
constructor(public options: any, listener: any) {
super();
if (listener) {
this.on("secureConnection", listener);
}
}

listen(port: any, callback: any): this {
const { key, cert } = this.options;

// TODO(kt3k): We can avoid creating temp cert files
// when we land the below PR:
// https://github.com/denoland/deno/pull/13740
const tmpdir = Deno.makeTempDirSync();
const certFile = join(tmpdir, "cert");
const keyFile = join(tmpdir, "key");
Deno.writeTextFileSync(certFile, cert);
Deno.writeTextFileSync(keyFile, key);

// TODO(kt3k): Get this from optional 2nd argument.
const hostname = "localhost";

this.listener = Deno.listenTls({ port, hostname, certFile, keyFile });
Deno.remove(tmpdir, { recursive: true });

callback?.();
this.#listen(this.listener);
return this;
}

async #listen(listener: Deno.TlsListener) {
while (!this.#closed) {
try {
// Creates TCP handle and socket directly from Deno.TlsConn.
// This works as TLS socket. We don't use TLSSocket class for doing
// this because Deno.startTls only supports client side tcp connection.
const handle = new TCP(TCPConstants.SOCKET, await listener.accept());
const socket = new net.Socket({ handle });
this.emit("secureConnection", socket);
} catch (e) {
if (e instanceof Deno.errors.BadResource) {
this.#closed = true;
}
// swallow
}
}
}

close(cb?: (err?: Error) => void): this {
if (this.listener) {
this.listener.close();
}
cb?.();
return this;
}
}

Server.prototype = ServerImpl.prototype;
Expand Down
51 changes: 48 additions & 3 deletions node/tls_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

import { assertEquals } from "../testing/asserts.ts";
import { delay } from "../async/delay.ts";
import { deferred } from "../async/deferred.ts";
import { fromFileUrl, join } from "./path.ts";
import { serveTls } from "../http/server.ts";
import { connect } from "./tls.ts";
import * as tls from "./tls.ts";
import * as net from "./net.ts";

const tlsTestdataDir = fromFileUrl(
new URL("../http/testdata/tls", import.meta.url),
);
const keyFile = join(tlsTestdataDir, "localhost.key");
const certFile = join(tlsTestdataDir, "localhost.crt");
const key = await Deno.readTextFile(keyFile);
const cert = await Deno.readTextFile(certFile);
const rootCaCert = await Deno.readTextFile(join(tlsTestdataDir, "RootCA.pem"));

Deno.test("tls.connect makes tls connection", async () => {
Expand All @@ -24,7 +28,7 @@ Deno.test("tls.connect makes tls connection", async () => {

await delay(200);

const conn = connect({
const conn = tls.connect({
port: 8443,
host: "localhost",
secureContext: {
Expand All @@ -39,7 +43,6 @@ Connection: close
`);
conn.on("data", (chunk) => {
const text = new TextDecoder().decode(chunk);
console.log(text);
assertEquals(text, "hello");
});
conn.on("end", () => {
Expand All @@ -48,3 +51,45 @@ Connection: close

await serve;
});

Deno.test("tls.createServer creates a TLS server", async () => {
const p = deferred();
const server = tls.createServer(
{ hostname: "localhost", key, cert },
(socket: net.Socket) => {
socket.write("welcome!\n");
socket.setEncoding("utf8");
socket.pipe(socket);
},
);
server.listen(8443, async () => {
const conn = await Deno.connectTls({
hostname: "localhost",
port: 8443,
caCerts: [rootCaCert],
});

const buf = new Uint8Array(100);
await Deno.read(conn.rid, buf);
let text: string;
text = new TextDecoder().decode(buf);
assertEquals(text.replaceAll("\0", ""), "welcome!\n");
buf.fill(0);

Deno.write(conn.rid, new TextEncoder().encode("hey\n"));
await Deno.read(conn.rid, buf);
text = new TextDecoder().decode(buf);
assertEquals(text.replaceAll("\0", ""), "hey\n");
buf.fill(0);

Deno.write(conn.rid, new TextEncoder().encode("goodbye\n"));
await Deno.read(conn.rid, buf);
text = new TextDecoder().decode(buf);
assertEquals(text.replaceAll("\0", ""), "goodbye\n");

Deno.close(conn.rid);
server.close();
p.resolve();
});
await p;
});

0 comments on commit 4f54453

Please sign in to comment.