feat: startTLS (#4773)

This commit is contained in:
EnokMan 2020-04-18 10:21:20 -05:00 committed by GitHub
parent 10469ec279
commit 47617e60d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 222 additions and 12 deletions

View file

@ -106,7 +106,7 @@ export { resources, close } from "./ops/resources.ts";
export { signal, signals, Signal, SignalStream } from "./signals.ts";
export { FileInfo, statSync, lstatSync, stat, lstat } from "./ops/fs/stat.ts";
export { symlinkSync, symlink } from "./ops/fs/symlink.ts";
export { connectTLS, listenTLS } from "./tls.ts";
export { connectTLS, listenTLS, startTLS } from "./tls.ts";
export { truncateSync, truncate } from "./ops/fs/truncate.ts";
export { isatty, setRaw } from "./ops/tty.ts";
export { umask } from "./ops/fs/umask.ts";

View file

@ -2038,6 +2038,33 @@ declare namespace Deno {
*/
export function connectTLS(options: ConnectTLSOptions): Promise<Conn>;
export interface StartTLSOptions {
/** A literal IP address or host name that can be resolved to an IP address.
* If not specified, defaults to `127.0.0.1`. */
hostname?: string;
/** Server certificate file. */
certFile?: string;
}
/** **UNSTABLE**: new API, yet to be vetted.
*
* Start TLS handshake from an existing connection using
* an optional cert file, hostname (default is "127.0.0.1"). The
* cert file is optional and if not included Mozilla's root certificates will
* be used (see also https://github.com/ctz/webpki-roots for specifics)
* Using this function requires that the other end of the connection is
* prepared for TLS handshake.
*
* const conn = await Deno.connect({ port: 80, hostname: "127.0.0.1" });
* const tlsConn = await Deno.startTLS(conn, { certFile: "./certs/my_custom_root_CA.pem", hostname: "127.0.0.1", port: 80 });
*
* Requires `allow-net` permission.
*/
export function startTLS(
conn: Conn,
options?: StartTLSOptions
): Promise<Conn>;
/** **UNSTABLE**: not sure if broken or not */
export interface Metrics {
opsDispatched: number;

View file

@ -8,7 +8,7 @@ export interface ConnectTLSRequest {
certFile?: string;
}
interface ConnectTLSResponse {
interface EstablishTLSResponse {
rid: number;
localAddr: {
hostname: string;
@ -24,7 +24,7 @@ interface ConnectTLSResponse {
export function connectTLS(
args: ConnectTLSRequest
): Promise<ConnectTLSResponse> {
): Promise<EstablishTLSResponse> {
return sendAsync("op_connect_tls", args);
}
@ -66,3 +66,13 @@ interface ListenTLSResponse {
export function listenTLS(args: ListenTLSRequest): ListenTLSResponse {
return sendSync("op_listen_tls", args);
}
export interface StartTLSRequest {
rid: number;
hostname: string;
certFile?: string;
}
export function startTLS(args: StartTLSRequest): Promise<EstablishTLSResponse> {
return sendAsync("op_start_tls", args);
}

View file

@ -209,3 +209,54 @@ unitTest(
await resolvable;
}
);
unitTest(
{ perms: { read: true, net: true } },
async function startTLS(): Promise<void> {
const hostname = "smtp.gmail.com";
const port = 587;
const encoder = new TextEncoder();
let conn = await Deno.connect({
hostname,
port,
});
let writer = new BufWriter(conn);
let reader = new TextProtoReader(new BufReader(conn));
let line: string | Deno.EOF = (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");
conn = await Deno.startTLS(conn, { hostname });
writer = new BufWriter(conn);
reader = new TextProtoReader(new BufReader(conn));
// 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;
}
conn.close();
}
);

View file

@ -57,3 +57,20 @@ export function listenTLS({
});
return new TLSListenerImpl(res.rid, res.localAddr);
}
interface StartTLSOptions {
hostname?: string;
certFile?: string;
}
export async function startTLS(
conn: Conn,
{ hostname = "127.0.0.1", certFile = undefined }: StartTLSOptions = {}
): Promise<Conn> {
const res = await tlsOps.startTLS({
rid: conn.rid,
hostname,
certFile,
});
return new ConnImpl(res.rid, res.remoteAddr!, res.localAddr!);
}