summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/js/deno.ts2
-rw-r--r--cli/js/dispatch.ts3
-rw-r--r--cli/js/lib.deno_runtime.d.ts24
-rw-r--r--cli/js/net.ts2
-rw-r--r--cli/js/tls.ts46
-rw-r--r--cli/js/tls_test.ts171
-rw-r--r--cli/ops/tls.rs184
-rw-r--r--cli/resources.rs67
-rw-r--r--cli/tests/tls/README.md47
-rw-r--r--cli/tests/tls/RootCA.crt19
-rw-r--r--cli/tests/tls/RootCA.key28
-rw-r--r--cli/tests/tls/RootCA.pem19
-rw-r--r--cli/tests/tls/domains.txt6
-rw-r--r--cli/tests/tls/localhost.crt21
-rw-r--r--cli/tests/tls/localhost.key28
-rw-r--r--cli/tokio_util.rs1
16 files changed, 646 insertions, 22 deletions
diff --git a/cli/js/deno.ts b/cli/js/deno.ts
index 511e4f0ec..4b0e3ff96 100644
--- a/cli/js/deno.ts
+++ b/cli/js/deno.ts
@@ -75,7 +75,7 @@ export {
export { truncateSync, truncate } from "./truncate.ts";
export { FileInfo } from "./file_info.ts";
export { connect, dial, listen, Listener, Conn } from "./net.ts";
-export { dialTLS } from "./tls.ts";
+export { dialTLS, listenTLS } from "./tls.ts";
export { metrics, Metrics } from "./metrics.ts";
export { resources } from "./resources.ts";
export {
diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts
index bff4d0f5b..38405a866 100644
--- a/cli/js/dispatch.ts
+++ b/cli/js/dispatch.ts
@@ -26,9 +26,11 @@ export let OP_METRICS: number;
export let OP_REPL_START: number;
export let OP_REPL_READLINE: number;
export let OP_ACCEPT: number;
+export let OP_ACCEPT_TLS: number;
export let OP_DIAL: number;
export let OP_SHUTDOWN: number;
export let OP_LISTEN: number;
+export let OP_LISTEN_TLS: number;
export let OP_RESOURCES: number;
export let OP_GET_RANDOM_VALUES: number;
export let OP_GLOBAL_TIMER_STOP: number;
@@ -81,6 +83,7 @@ export function asyncMsgFromRust(opId: number, ui8: Uint8Array): void {
case OP_REPL_START:
case OP_REPL_READLINE:
case OP_ACCEPT:
+ case OP_ACCEPT_TLS:
case OP_DIAL:
case OP_GLOBAL_TIMER:
case OP_HOST_GET_WORKER_CLOSED:
diff --git a/cli/js/lib.deno_runtime.d.ts b/cli/js/lib.deno_runtime.d.ts
index 94b6b61cd..3036ea2d1 100644
--- a/cli/js/lib.deno_runtime.d.ts
+++ b/cli/js/lib.deno_runtime.d.ts
@@ -990,6 +990,29 @@ declare namespace Deno {
*/
export function listen(options: ListenOptions): Listener;
+ export interface ListenTLSOptions {
+ port: number;
+ hostname?: string;
+ transport?: Transport;
+ certFile: string;
+ keyFile: string;
+ }
+
+ /** Listen announces on the local transport address over TLS (transport layer security).
+ *
+ * @param options
+ * @param options.port The port to connect to. (Required.)
+ * @param options.hostname A literal IP address or host name that can be
+ * resolved to an IP address. If not specified, defaults to 0.0.0.0
+ * @param options.certFile Server certificate file
+ * @param options.keyFile Server public key file
+ *
+ * Examples:
+ *
+ * Deno.listenTLS({ port: 443, certFile: "./my_server.crt", keyFile: "./my_server.key" })
+ */
+ export function listenTLS(options: ListenTLSOptions): Listener;
+
export interface DialOptions {
port: number;
hostname?: string;
@@ -1018,6 +1041,7 @@ declare namespace Deno {
export interface DialTLSOptions {
port: number;
hostname?: string;
+ certFile?: string;
}
/**
diff --git a/cli/js/net.ts b/cli/js/net.ts
index a7ad2b73c..f463da60b 100644
--- a/cli/js/net.ts
+++ b/cli/js/net.ts
@@ -78,7 +78,7 @@ export class ConnImpl implements Conn {
}
}
-class ListenerImpl implements Listener {
+export class ListenerImpl implements Listener {
constructor(
readonly rid: number,
private transport: Transport,
diff --git a/cli/js/tls.ts b/cli/js/tls.ts
index ec24b458b..3e38c7854 100644
--- a/cli/js/tls.ts
+++ b/cli/js/tls.ts
@@ -1,13 +1,14 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
-import { sendAsync } from "./dispatch_json.ts";
+import { sendAsync, sendSync } from "./dispatch_json.ts";
import * as dispatch from "./dispatch.ts";
-import { Conn, ConnImpl } from "./net.ts";
+import { Listener, Transport, Conn, ConnImpl, ListenerImpl } from "./net.ts";
// TODO(ry) There are many configuration options to add...
// https://docs.rs/rustls/0.16.0/rustls/struct.ClientConfig.html
interface DialTLSOptions {
port: number;
hostname?: string;
+ certFile?: string;
}
const dialTLSDefaults = { hostname: "127.0.0.1", transport: "tcp" };
@@ -19,3 +20,44 @@ export async function dialTLS(options: DialTLSOptions): Promise<Conn> {
const res = await sendAsync(dispatch.OP_DIAL_TLS, options);
return new ConnImpl(res.rid, res.remoteAddr!, res.localAddr!);
}
+
+class TLSListenerImpl extends ListenerImpl {
+ async accept(): Promise<Conn> {
+ const res = await sendAsync(dispatch.OP_ACCEPT_TLS, { rid: this.rid });
+ return new ConnImpl(res.rid, res.remoteAddr, res.localAddr);
+ }
+}
+
+export interface ListenTLSOptions {
+ port: number;
+ hostname?: string;
+ transport?: Transport;
+ certFile: string;
+ keyFile: string;
+}
+
+/** Listen announces on the local transport address over TLS (transport layer security).
+ *
+ * @param options
+ * @param options.port The port to connect to. (Required.)
+ * @param options.hostname A literal IP address or host name that can be
+ * resolved to an IP address. If not specified, defaults to 0.0.0.0
+ * @param options.certFile Server certificate file
+ * @param options.keyFile Server public key file
+ *
+ * Examples:
+ *
+ * Deno.listenTLS({ port: 443, certFile: "./my_server.crt", keyFile: "./my_server.key" })
+ */
+export function listenTLS(options: ListenTLSOptions): Listener {
+ const hostname = options.hostname || "0.0.0.0";
+ const transport = options.transport || "tcp";
+ const res = sendSync(dispatch.OP_LISTEN_TLS, {
+ hostname,
+ port: options.port,
+ transport,
+ certFile: options.certFile,
+ keyFile: options.keyFile
+ });
+ return new TLSListenerImpl(res.rid, transport, res.localAddr);
+}
diff --git a/cli/js/tls_test.ts b/cli/js/tls_test.ts
index 79e6bcad8..58cafd23e 100644
--- a/cli/js/tls_test.ts
+++ b/cli/js/tls_test.ts
@@ -3,8 +3,9 @@ import { test, testPerm, assert, assertEquals } from "./test_util.ts";
import { BufWriter, BufReader } from "../../std/io/bufio.ts";
import { TextProtoReader } from "../../std/textproto/mod.ts";
import { runIfMain } from "../../std/testing/mod.ts";
-// TODO(ry) The tests in this file use github.com:443, but it would be better to
-// not rely on an internet connection and rather use a localhost TLS server.
+
+const encoder = new TextEncoder();
+const decoder = new TextDecoder();
test(async function dialTLSNoPerm(): Promise<void> {
let err;
@@ -17,15 +18,168 @@ test(async function dialTLSNoPerm(): Promise<void> {
assertEquals(err.name, "PermissionDenied");
});
-testPerm({ net: true }, async function dialTLSBasic(): Promise<void> {
- const conn = await Deno.dialTLS({ hostname: "github.com", port: 443 });
+test(async function dialTLSCertFileNoReadPerm(): Promise<void> {
+ let err;
+ try {
+ await Deno.dialTLS({
+ hostname: "github.com",
+ port: 443,
+ certFile: "cli/tests/tls/RootCA.crt"
+ });
+ } catch (e) {
+ err = e;
+ }
+ assertEquals(err.kind, Deno.ErrorKind.PermissionDenied);
+ assertEquals(err.name, "PermissionDenied");
+});
+
+testPerm(
+ { read: true, net: true },
+ async function listenTLSNonExistentCertKeyFiles(): Promise<void> {
+ let err;
+ const options = {
+ hostname: "localhost",
+ port: 4500,
+ certFile: "cli/tests/tls/localhost.crt",
+ keyFile: "cli/tests/tls/localhost.key"
+ };
+
+ try {
+ Deno.listenTLS({
+ ...options,
+ certFile: "./non/existent/file"
+ });
+ } catch (e) {
+ err = e;
+ }
+ assertEquals(err.kind, Deno.ErrorKind.NotFound);
+ assertEquals(err.name, "NotFound");
+
+ try {
+ Deno.listenTLS({
+ ...options,
+ keyFile: "./non/existent/file"
+ });
+ } catch (e) {
+ err = e;
+ }
+ assertEquals(err.kind, Deno.ErrorKind.NotFound);
+ assertEquals(err.name, "NotFound");
+ }
+);
+
+testPerm({ net: true }, async function listenTLSNoReadPerm(): Promise<void> {
+ let err;
+ try {
+ Deno.listenTLS({
+ hostname: "localhost",
+ port: 4500,
+ certFile: "cli/tests/tls/localhost.crt",
+ keyFile: "cli/tests/tls/localhost.key"
+ });
+ } catch (e) {
+ err = e;
+ }
+ assertEquals(err.kind, Deno.ErrorKind.PermissionDenied);
+ assertEquals(err.name, "PermissionDenied");
+});
+
+testPerm(
+ { read: true, write: true, net: true },
+ async function listenTLSEmptyKeyFile(): Promise<void> {
+ let err;
+ const options = {
+ hostname: "localhost",
+ port: 4500,
+ certFile: "cli/tests/tls/localhost.crt",
+ keyFile: "cli/tests/tls/localhost.key"
+ };
+
+ const testDir = Deno.makeTempDirSync();
+ const keyFilename = testDir + "/key.pem";
+ Deno.writeFileSync(keyFilename, new Uint8Array([]), {
+ perm: 0o666
+ });
+
+ try {
+ Deno.listenTLS({
+ ...options,
+ keyFile: keyFilename
+ });
+ } catch (e) {
+ err = e;
+ }
+ assertEquals(err.kind, Deno.ErrorKind.Other);
+ assertEquals(err.name, "Other");
+ }
+);
+
+testPerm(
+ { read: true, write: true, net: true },
+ async function listenTLSEmptyCertFile(): Promise<void> {
+ let err;
+ const options = {
+ hostname: "localhost",
+ port: 4500,
+ certFile: "cli/tests/tls/localhost.crt",
+ keyFile: "cli/tests/tls/localhost.key"
+ };
+
+ const testDir = Deno.makeTempDirSync();
+ const certFilename = testDir + "/cert.crt";
+ Deno.writeFileSync(certFilename, new Uint8Array([]), {
+ perm: 0o666
+ });
+
+ try {
+ Deno.listenTLS({
+ ...options,
+ certFile: certFilename
+ });
+ } catch (e) {
+ err = e;
+ }
+ assertEquals(err.kind, Deno.ErrorKind.Other);
+ assertEquals(err.name, "Other");
+ }
+);
+
+testPerm({ read: true, net: true }, async function dialAndListenTLS(): Promise<
+ void
+> {
+ const hostname = "localhost";
+ const port = 4500;
+
+ const listener = Deno.listenTLS({
+ hostname,
+ port,
+ certFile: "cli/tests/tls/localhost.crt",
+ keyFile: "cli/tests/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): Promise<void> => {
+ assert(conn.remoteAddr != null);
+ assert(conn.localAddr != null);
+ await conn.write(response);
+ conn.close();
+ }
+ );
+
+ const conn = await Deno.dialTLS({
+ hostname,
+ port,
+ certFile: "cli/tests/tls/RootCA.pem"
+ });
assert(conn.rid > 0);
const w = new BufWriter(conn);
const r = new BufReader(conn);
- let body = "GET / HTTP/1.1\r\n";
- body += "Host: github.com\r\n";
- body += "\r\n";
- const writeResult = await w.write(new TextEncoder().encode(body));
+ 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);
@@ -41,6 +195,7 @@ testPerm({ net: true }, async function dialTLSBasic(): Promise<void> {
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();
});
diff --git a/cli/ops/tls.rs b/cli/ops/tls.rs
index 6528884af..a0f4197ba 100644
--- a/cli/ops/tls.rs
+++ b/cli/ops/tls.rs
@@ -1,28 +1,52 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use super::dispatch_json::{Deserialize, JsonOp, Value};
+use crate::deno_error::DenoError;
+use crate::deno_error::ErrorKind;
use crate::ops::json_op;
use crate::resolve_addr::resolve_addr;
use crate::resources;
use crate::state::ThreadSafeState;
+use crate::tokio_util;
use deno::*;
use futures::Future;
use std;
use std::convert::From;
+use std::fs::File;
+use std::io::BufReader;
use std::sync::Arc;
use tokio;
+use tokio::net::TcpListener;
use tokio::net::TcpStream;
use tokio_rustls::{rustls::ClientConfig, TlsConnector};
+use tokio_rustls::{
+ rustls::{
+ internal::pemfile::{certs, pkcs8_private_keys, rsa_private_keys},
+ Certificate, NoClientAuth, PrivateKey, ServerConfig,
+ },
+ TlsAcceptor,
+};
use webpki;
use webpki::DNSNameRef;
use webpki_roots;
+pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
+ i.register_op("dial_tls", s.core_op(json_op(s.stateful_op(op_dial_tls))));
+ i.register_op(
+ "listen_tls",
+ s.core_op(json_op(s.stateful_op(op_listen_tls))),
+ );
+ i.register_op(
+ "accept_tls",
+ s.core_op(json_op(s.stateful_op(op_accept_tls))),
+ );
+}
+
#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
struct DialTLSArgs {
hostname: String,
port: u16,
-}
-pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
- i.register_op("dial_tls", s.core_op(json_op(s.stateful_op(op_dial_tls))));
+ cert_file: Option<String>,
}
pub fn op_dial_tls(
@@ -35,8 +59,12 @@ pub fn op_dial_tls(
// TODO(ry) Using format! is suboptimal here. Better would be if
// state.check_net and resolve_addr() took hostname and port directly.
let address = format!("{}:{}", args.hostname, args.port);
+ let cert_file = args.cert_file;
state.check_net(&address)?;
+ if let Some(path) = cert_file.clone() {
+ state.check_read(&path)?;
+ }
let mut domain = args.hostname;
if domain.is_empty() {
@@ -53,6 +81,12 @@ pub fn op_dial_tls(
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
+ if let Some(path) = cert_file {
+ let key_file = File::open(path)?;
+ let reader = &mut BufReader::new(key_file);
+ config.root_store.add_pem_file(reader).unwrap();
+ }
+
let tls_connector = TlsConnector::from(Arc::new(config));
Ok((tls_connector, tcp_stream, local_addr, remote_addr))
})
@@ -78,3 +112,147 @@ pub fn op_dial_tls(
Ok(JsonOp::Async(Box::new(op)))
}
+
+fn load_certs(path: &str) -> Result<Vec<Certificate>, ErrBox> {
+ let cert_file = File::open(path)?;
+ let reader = &mut BufReader::new(cert_file);
+
+ let certs = certs(reader).map_err(|_| {
+ DenoError::new(ErrorKind::Other, "Unable to decode certificate".to_string())
+ })?;
+
+ if certs.is_empty() {
+ let e = DenoError::new(
+ ErrorKind::Other,
+ "No certificates found in cert file".to_string(),
+ );
+ return Err(ErrBox::from(e));
+ }
+
+ Ok(certs)
+}
+
+fn key_decode_err() -> DenoError {
+ DenoError::new(ErrorKind::Other, "Unable to decode key".to_string())
+}
+
+fn key_not_found_err() -> DenoError {
+ DenoError::new(ErrorKind::Other, "No keys found in key file".to_string())
+}
+
+/// Starts with -----BEGIN RSA PRIVATE KEY-----
+fn load_rsa_keys(path: &str) -> Result<Vec<PrivateKey>, ErrBox> {
+ let key_file = File::open(path)?;
+ let reader = &mut BufReader::new(key_file);
+ let keys = rsa_private_keys(reader).map_err(|_| key_decode_err())?;
+ Ok(keys)
+}
+
+/// Starts with -----BEGIN PRIVATE KEY-----
+fn load_pkcs8_keys(path: &str) -> Result<Vec<PrivateKey>, ErrBox> {
+ let key_file = File::open(path)?;
+ let reader = &mut BufReader::new(key_file);
+ let keys = pkcs8_private_keys(reader).map_err(|_| key_decode_err())?;
+ Ok(keys)
+}
+
+fn load_keys(path: &str) -> Result<Vec<PrivateKey>, ErrBox> {
+ let path = path.to_string();
+ let mut keys = load_rsa_keys(&path)?;
+
+ if keys.is_empty() {
+ keys = load_pkcs8_keys(&path)?;
+ }
+
+ if keys.is_empty() {
+ return Err(ErrBox::from(key_not_found_err()));
+ }
+
+ Ok(keys)
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct ListenTlsArgs {
+ transport: String,
+ hostname: String,
+ port: u16,
+ cert_file: String,
+ key_file: String,
+}
+
+fn op_listen_tls(
+ state: &ThreadSafeState,
+ args: Value,
+ _zero_copy: Option<PinnedBuf>,
+) -> Result<JsonOp, ErrBox> {
+ let args: ListenTlsArgs = serde_json::from_value(args)?;
+ assert_eq!(args.transport, "tcp");
+
+ // TODO(ry) Using format! is suboptimal here. Better would be if
+ // state.check_net and resolve_addr() took hostname and port directly.
+ let address = format!("{}:{}", args.hostname, args.port);
+ let cert_file = args.cert_file;
+ let key_file = args.key_file;
+
+ state.check_net(&address)?;
+ state.check_read(&cert_file)?;
+ state.check_read(&key_file)?;
+
+ let mut config = ServerConfig::new(NoClientAuth::new());
+ config
+ .set_single_cert(load_certs(&cert_file)?, load_keys(&key_file)?.remove(0))
+ .expect("invalid key or certificate");
+ let acceptor = TlsAcceptor::from(Arc::new(config));
+ let addr = resolve_addr(&address).wait()?;
+ let listener = TcpListener::bind(&addr)?;
+ let local_addr = listener.local_addr()?;
+ let resource = resources::add_tls_listener(listener, acceptor);
+
+ Ok(JsonOp::Sync(json!({
+ "rid": resource.rid,
+ "localAddr": local_addr.to_string()
+ })))
+}
+
+#[derive(Deserialize)]
+struct AcceptTlsArgs {
+ rid: i32,
+}
+
+fn op_accept_tls(
+ _state: &ThreadSafeState,
+ args: Value,
+ _zero_copy: Option<PinnedBuf>,
+) -> Result<JsonOp, ErrBox> {
+ let args: AcceptTlsArgs = serde_json::from_value(args)?;
+ let server_rid = args.rid as u32;
+
+ let server_resource = resources::lookup(server_rid)?;
+ let op = tokio_util::accept(server_resource)
+ .and_then(move |(tcp_stream, _socket_addr)| {
+ let local_addr = tcp_stream.local_addr()?;
+ let remote_addr = tcp_stream.peer_addr()?;
+ Ok((tcp_stream, local_addr, remote_addr))
+ })
+ .and_then(move |(tcp_stream, local_addr, remote_addr)| {
+ let mut server_resource = resources::lookup(server_rid).unwrap();
+ server_resource
+ .poll_accept_tls(tcp_stream)
+ .and_then(move |tls_stream| {
+ let tls_stream_resource =
+ resources::add_server_tls_stream(tls_stream);
+ Ok((tls_stream_resource, local_addr, remote_addr))
+ })
+ })
+ .map_err(ErrBox::from)
+ .and_then(move |(tls_stream_resource, local_addr, remote_addr)| {
+ futures::future::ok(json!({
+ "rid": tls_stream_resource.rid,
+ "localAddr": local_addr.to_string(),
+ "remoteAddr": remote_addr.to_string(),
+ }))
+ });
+
+ Ok(JsonOp::Async(Box::new(op)))
+}
diff --git a/cli/resources.rs b/cli/resources.rs
index fc4aa7eb5..1c791191d 100644
--- a/cli/resources.rs
+++ b/cli/resources.rs
@@ -36,7 +36,9 @@ use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpStream;
use tokio::sync::mpsc;
use tokio_process;
-use tokio_rustls::client::TlsStream;
+use tokio_rustls::client::TlsStream as ClientTlsStream;
+use tokio_rustls::server::TlsStream as ServerTlsStream;
+use tokio_rustls::TlsAcceptor;
pub type ResourceId = u32; // Sometimes referred to RID.
@@ -47,6 +49,7 @@ type ResourceTable = BTreeMap<ResourceId, Repr>;
#[cfg(not(windows))]
use std::os::unix::io::FromRawFd;
+use futures::future::Either;
#[cfg(windows)]
use std::os::windows::io::FromRawHandle;
@@ -89,8 +92,14 @@ enum Repr {
// Currently TcpListener itself does not take care of this issue.
// See: https://github.com/tokio-rs/tokio/issues/846
TcpListener(tokio::net::TcpListener, Option<futures::task::Task>),
+ TlsListener(
+ tokio::net::TcpListener,
+ TlsAcceptor,
+ Option<futures::task::Task>,
+ ),
TcpStream(tokio::net::TcpStream),
- TlsStream(Box<TlsStream<TcpStream>>),
+ ServerTlsStream(Box<ServerTlsStream<TcpStream>>),
+ ClientTlsStream(Box<ClientTlsStream<TcpStream>>),
HttpBody(HttpBody),
Repl(Arc<Mutex<Repl>>),
// Enum size is bounded by the largest variant.
@@ -135,8 +144,10 @@ fn inspect_repr(repr: &Repr) -> String {
Repr::Stderr(_) => "stderr",
Repr::FsFile(_) => "fsFile",
Repr::TcpListener(_, _) => "tcpListener",
+ Repr::TlsListener(_, _, _) => "tlsListener",
Repr::TcpStream(_) => "tcpStream",
- Repr::TlsStream(_) => "tlsStream",
+ Repr::ClientTlsStream(_) => "clientTlsStream",
+ Repr::ServerTlsStream(_) => "serverTlsStream",
Repr::HttpBody(_) => "httpBody",
Repr::Repl(_) => "repl",
Repr::Child(_) => "child",
@@ -168,6 +179,27 @@ impl Resource {
)),
Some(repr) => match repr {
Repr::TcpListener(ref mut s, _) => s.poll_accept(),
+ Repr::TlsListener(ref mut s, _, _) => s.poll_accept(),
+ _ => panic!("Cannot accept"),
+ },
+ }
+ }
+
+ pub fn poll_accept_tls(
+ &mut self,
+ tcp_stream: TcpStream,
+ ) -> impl Future<Item = ServerTlsStream<TcpStream>, Error = Error> {
+ let mut table = RESOURCE_TABLE.lock().unwrap();
+ let maybe_repr = table.get_mut(&self.rid);
+ match maybe_repr {
+ None => Either::A(futures::future::err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "Listener has been closed",
+ ))),
+ Some(repr) => match repr {
+ Repr::TlsListener(_, ref mut acceptor, _) => {
+ Either::B(acceptor.accept(tcp_stream))
+ }
_ => panic!("Cannot accept"),
},
}
@@ -252,7 +284,8 @@ impl DenoAsyncRead for Resource {
Repr::FsFile(ref mut f) => f.poll_read(buf),
Repr::Stdin(ref mut f) => f.poll_read(buf),
Repr::TcpStream(ref mut f) => f.poll_read(buf),
- Repr::TlsStream(ref mut f) => f.poll_read(buf),
+ Repr::ClientTlsStream(ref mut f) => f.poll_read(buf),
+ Repr::ServerTlsStream(ref mut f) => f.poll_read(buf),
Repr::HttpBody(ref mut f) => f.poll_read(buf),
Repr::ChildStdout(ref mut f) => f.poll_read(buf),
Repr::ChildStderr(ref mut f) => f.poll_read(buf),
@@ -293,7 +326,8 @@ impl DenoAsyncWrite for Resource {
Repr::Stdout(ref mut f) => f.poll_write(buf),
Repr::Stderr(ref mut f) => f.poll_write(buf),
Repr::TcpStream(ref mut f) => f.poll_write(buf),
- Repr::TlsStream(ref mut f) => f.poll_write(buf),
+ Repr::ClientTlsStream(ref mut f) => f.poll_write(buf),
+ Repr::ServerTlsStream(ref mut f) => f.poll_write(buf),
Repr::ChildStdin(ref mut f) => f.poll_write(buf),
_ => {
return Err(bad_resource());
@@ -329,6 +363,17 @@ pub fn add_tcp_listener(listener: tokio::net::TcpListener) -> Resource {
Resource { rid }
}
+pub fn add_tls_listener(
+ listener: tokio::net::TcpListener,
+ acceptor: TlsAcceptor,
+) -> Resource {
+ let rid = new_rid();
+ let mut tg = RESOURCE_TABLE.lock().unwrap();
+ let r = tg.insert(rid, Repr::TlsListener(listener, acceptor, None));
+ assert!(r.is_none());
+ Resource { rid }
+}
+
pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource {
let rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap();
@@ -337,10 +382,18 @@ pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource {
Resource { rid }
}
-pub fn add_tls_stream(stream: TlsStream<TcpStream>) -> Resource {
+pub fn add_tls_stream(stream: ClientTlsStream<TcpStream>) -> Resource {
+ let rid = new_rid();
+ let mut tg = RESOURCE_TABLE.lock().unwrap();
+ let r = tg.insert(rid, Repr::ClientTlsStream(Box::new(stream)));
+ assert!(r.is_none());
+ Resource { rid }
+}
+
+pub fn add_server_tls_stream(stream: ServerTlsStream<TcpStream>) -> Resource {
let rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap();
- let r = tg.insert(rid, Repr::TlsStream(Box::new(stream)));
+ let r = tg.insert(rid, Repr::ServerTlsStream(Box::new(stream)));
assert!(r.is_none());
Resource { rid }
}
diff --git a/cli/tests/tls/README.md b/cli/tests/tls/README.md
new file mode 100644
index 000000000..14399ae82
--- /dev/null
+++ b/cli/tests/tls/README.md
@@ -0,0 +1,47 @@
+The certificates in this dir expire on Sept, 27th, 2118
+
+Certificates generated using original instructions from this gist:
+https://gist.github.com/cecilemuller/9492b848eb8fe46d462abeb26656c4f8
+
+## Certificate authority (CA)
+
+Generate RootCA.pem, RootCA.key & RootCA.crt:
+
+```shell
+openssl req -x509 -nodes -new -sha256 -days 36135 -newkey rsa:2048 -keyout RootCA.key -out RootCA.pem -subj "/C=US/CN=Example-Root-CA"
+openssl x509 -outform pem -in RootCA.pem -out RootCA.crt
+```
+
+Note that Example-Root-CA is an example, you can customize the name.
+
+## Domain name certificate
+
+First, create a file domains.txt that lists all your local domains (here we only
+list localhost):
+
+```shell
+authorityKeyIdentifier=keyid,issuer
+basicConstraints=CA:FALSE
+keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
+subjectAltName = @alt_names
+[alt_names]
+DNS.1 = localhost
+```
+
+Generate localhost.key, localhost.csr, and localhost.crt:
+
+```shell
+openssl req -new -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.csr -subj "/C=US/ST=YourState/L=YourCity/O=Example-Certificates/CN=localhost.local"
+openssl x509 -req -sha256 -days 36135 -in localhost.csr -CA RootCA.pem -CAkey RootCA.key -CAcreateserial -extfile domains.txt -out localhost.crt
+```
+
+Note that the country / state / city / name in the first command can be
+customized.
+
+For testing purposes we need following files:
+
+- `RootCA.crt`
+- `RootCA.key`
+- `RootCA.pem`
+- `locahost.crt`
+- `locahost.key`
diff --git a/cli/tests/tls/RootCA.crt b/cli/tests/tls/RootCA.crt
new file mode 100644
index 000000000..c2f84ceeb
--- /dev/null
+++ b/cli/tests/tls/RootCA.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDIzCCAgugAwIBAgIJAMKPPW4tsOymMA0GCSqGSIb3DQEBCwUAMCcxCzAJBgNV
+BAYTAlVTMRgwFgYDVQQDDA9FeGFtcGxlLVJvb3QtQ0EwIBcNMTkxMDIxMTYyODIy
+WhgPMjExODA5MjcxNjI4MjJaMCcxCzAJBgNVBAYTAlVTMRgwFgYDVQQDDA9FeGFt
+cGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMH/IO
+2qtHfyBKwANNPB4K0q5JVSg8XxZdRpTTlz0CwU0oRO3uHrI52raCCfVeiQutyZop
+eFZTDWeXGudGAFA2B5m3orWt0s+touPi8MzjsG2TQ+WSI66QgbXTNDitDDBtTVcV
+5G3Ic+3SppQAYiHSekLISnYWgXLl+k5CnEfTowg6cjqjVr0KjL03cTN3H7b+6+0S
+ws4rYbW1j4ExR7K6BFNH6572yq5qR20E6GqlY+EcOZpw4CbCk9lS8/CWuXze/vMs
+OfDcc6K+B625d27wyEGZHedBomT2vAD7sBjvO8hn/DP1Qb46a8uCHR6NSfnJ7bXO
+G1igaIbgY1zXirNdAgMBAAGjUDBOMB0GA1UdDgQWBBTzut+pwwDfqmMYcI9KNWRD
+hxcIpTAfBgNVHSMEGDAWgBTzut+pwwDfqmMYcI9KNWRDhxcIpTAMBgNVHRMEBTAD
+AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB9AqSbZ+hEglAgSHxAMCqRFdhVu7MvaQM0
+P090mhGlOCt3yB7kdGfsIrUW6nQcTz7PPQFRaJMrFHPvFvPootkBUpTYR4hTkdce
+H6RCRu2Jxl4Y9bY/uezd9YhGCYfUtfjA6/TH9FcuZfttmOOlxOt01XfNvVMIR6RM
+z/AYhd+DeOXjr35F/VHeVpnk+55L0PYJsm1CdEbOs5Hy1ecR7ACuDkXnbM4fpz9I
+kyIWJwk2zJReKcJMgi1aIinDM9ao/dca1G99PHOw8dnr4oyoTiv8ao6PWiSRHHMi
+MNf4EgWfK+tZMnuqfpfO9740KzfcVoMNo4QJD4yn5YxroUOO/Azi
+-----END CERTIFICATE-----
diff --git a/cli/tests/tls/RootCA.key b/cli/tests/tls/RootCA.key
new file mode 100644
index 000000000..98ce53b0b
--- /dev/null
+++ b/cli/tests/tls/RootCA.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDMH/IO2qtHfyBK
+wANNPB4K0q5JVSg8XxZdRpTTlz0CwU0oRO3uHrI52raCCfVeiQutyZopeFZTDWeX
+GudGAFA2B5m3orWt0s+touPi8MzjsG2TQ+WSI66QgbXTNDitDDBtTVcV5G3Ic+3S
+ppQAYiHSekLISnYWgXLl+k5CnEfTowg6cjqjVr0KjL03cTN3H7b+6+0Sws4rYbW1
+j4ExR7K6BFNH6572yq5qR20E6GqlY+EcOZpw4CbCk9lS8/CWuXze/vMsOfDcc6K+
+B625d27wyEGZHedBomT2vAD7sBjvO8hn/DP1Qb46a8uCHR6NSfnJ7bXOG1igaIbg
+Y1zXirNdAgMBAAECggEASvdsicILZ42ryWgtjj8G9Yick7gft9RgPU9/txnzQUDG
+2oQ+Mda6M/88ShPoNpj0XhYNdS+J3KSup9MsnwvcaYtvC/9I5BbpSObq9NzlErYn
++A7WkE5kfRP2OCQUsJEqc+oUkqi7HQRekp+0+VMRAuD+B9s49VkDXq0H8vS8eF/e
+J9nj6c/RTK+Er5ccG5jSLrSy3kiIjAN1a6OIU/YPjPx7qv8ZZ6TLeRtvc8PV++cH
+wB1qapZg5cuKge9UEcg+WINCkD2n9iK1jKC1ULYsiuwUR6LX9YHLUwr6S5/Dwwqc
+Vb9nmftqJtCz+McrqRCdfeqSNGi0tjVEX7i+DtfZrQKBgQD7firgBE7nb53VDirG
+W8Leo6EhCS/hCZFo0QhSBUCeOpmSaMsCzUIlqqPIBIQXir0AtBno/qXYiIJ4HgUB
+lScrK+7KUirEO8o4x6xC2hbPk/A7fTgf0G5Mvj2TRidiLGGIupuRHeyjigiGa0mG
+yWLoil6MJX44usnE49qDVy77/wKBgQDPyHThAugFSsVedxy29NQx7Zp8s/htpGHZ
+wYksbunz+NlO/xzRvSu2OAps/WD6F+3KhCB5fV2tESVs7u2oQPLcjmIpurDtATWE
+DJAAvcBl1L+cpQGN4D8zUrrZO8rw01sUZSv+kAnfsC01exzZe64+VDl3a1cYZkDT
+A9RmbF/AowKBgDTYVxQJc7cH6idZub1CjNkRkwsJDimARDC9M71gYyqcb6anJHlr
+PgoCKDYgVM1Jlttt/L/Lunecf6XT0QN7HubgbWXQDDJ9yclSk6zcfMyTbnhhoIh2
+2KaBlxi6Ng5X+wqrA4NjwVS/7XipVKLg8EqiwKk8O6CaB0m7AzB0AmhrAoGAcGsi
+YYNzCTn1IzEKxiocjI7jYMj2hkvD7U766qFvzuI6oLUCYLAa8FHNwj4ss+Mycrmd
+4F1ly3dVamSzDK9nNtGKZs1tYC2hSLqLRvtjFzVOHnBgMOS9DQWbtmDVYgrYYmaC
+sQ45aV8mdqMPbtOt6GclWGkpDDh2pjSSPIAyJkUCgYAHw7dKqYO/YQPKmswVZm5t
+TelfdJJG6GCXnFryyqo4pmEMy/i5kzF1t9Cnchhx/WeU+wGxrWd3RMP/sqP7MW9q
+6Ie9Jj2vk4lUBoeFKk+kLeBUr+TkLSdcVEI0DSOdX681AUmxkVzVjGKYeiNa+V6u
+XmgzS8JEYoMbNEAKXYX2qg==
+-----END PRIVATE KEY-----
diff --git a/cli/tests/tls/RootCA.pem b/cli/tests/tls/RootCA.pem
new file mode 100644
index 000000000..c2f84ceeb
--- /dev/null
+++ b/cli/tests/tls/RootCA.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDIzCCAgugAwIBAgIJAMKPPW4tsOymMA0GCSqGSIb3DQEBCwUAMCcxCzAJBgNV
+BAYTAlVTMRgwFgYDVQQDDA9FeGFtcGxlLVJvb3QtQ0EwIBcNMTkxMDIxMTYyODIy
+WhgPMjExODA5MjcxNjI4MjJaMCcxCzAJBgNVBAYTAlVTMRgwFgYDVQQDDA9FeGFt
+cGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMH/IO
+2qtHfyBKwANNPB4K0q5JVSg8XxZdRpTTlz0CwU0oRO3uHrI52raCCfVeiQutyZop
+eFZTDWeXGudGAFA2B5m3orWt0s+touPi8MzjsG2TQ+WSI66QgbXTNDitDDBtTVcV
+5G3Ic+3SppQAYiHSekLISnYWgXLl+k5CnEfTowg6cjqjVr0KjL03cTN3H7b+6+0S
+ws4rYbW1j4ExR7K6BFNH6572yq5qR20E6GqlY+EcOZpw4CbCk9lS8/CWuXze/vMs
+OfDcc6K+B625d27wyEGZHedBomT2vAD7sBjvO8hn/DP1Qb46a8uCHR6NSfnJ7bXO
+G1igaIbgY1zXirNdAgMBAAGjUDBOMB0GA1UdDgQWBBTzut+pwwDfqmMYcI9KNWRD
+hxcIpTAfBgNVHSMEGDAWgBTzut+pwwDfqmMYcI9KNWRDhxcIpTAMBgNVHRMEBTAD
+AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB9AqSbZ+hEglAgSHxAMCqRFdhVu7MvaQM0
+P090mhGlOCt3yB7kdGfsIrUW6nQcTz7PPQFRaJMrFHPvFvPootkBUpTYR4hTkdce
+H6RCRu2Jxl4Y9bY/uezd9YhGCYfUtfjA6/TH9FcuZfttmOOlxOt01XfNvVMIR6RM
+z/AYhd+DeOXjr35F/VHeVpnk+55L0PYJsm1CdEbOs5Hy1ecR7ACuDkXnbM4fpz9I
+kyIWJwk2zJReKcJMgi1aIinDM9ao/dca1G99PHOw8dnr4oyoTiv8ao6PWiSRHHMi
+MNf4EgWfK+tZMnuqfpfO9740KzfcVoMNo4QJD4yn5YxroUOO/Azi
+-----END CERTIFICATE-----
diff --git a/cli/tests/tls/domains.txt b/cli/tests/tls/domains.txt
new file mode 100644
index 000000000..0bba95d33
--- /dev/null
+++ b/cli/tests/tls/domains.txt
@@ -0,0 +1,6 @@
+authorityKeyIdentifier=keyid,issuer
+basicConstraints=CA:FALSE
+keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
+subjectAltName = @alt_names
+[alt_names]
+DNS.1 = localhost
diff --git a/cli/tests/tls/localhost.crt b/cli/tests/tls/localhost.crt
new file mode 100644
index 000000000..a71ae9050
--- /dev/null
+++ b/cli/tests/tls/localhost.crt
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDajCCAlKgAwIBAgIJAOPyQVdy/UpPMA0GCSqGSIb3DQEBCwUAMCcxCzAJBgNV
+BAYTAlVTMRgwFgYDVQQDDA9FeGFtcGxlLVJvb3QtQ0EwIBcNMTkxMDIxMTYyODU4
+WhgPMjExODA5MjcxNjI4NThaMG0xCzAJBgNVBAYTAlVTMRIwEAYDVQQIDAlZb3Vy
+U3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MR0wGwYDVQQKDBRFeGFtcGxlLUNlcnRp
+ZmljYXRlczEYMBYGA1UEAwwPbG9jYWxob3N0LmxvY2FsMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAz9svjVdf5jihUBtofd84XKdb8dEHQRJfDNKaJ4Ar
+baqMHAdnqi/fWtlqEEMn8gweZ7+4hshECY5mnx4Hhy7IAbePDsTTbSm01dChhlxF
+uvd9QuvzvrqSjSq+v4Jlau+pQIhUzzV12dF5bFvrIrGWxCZp+W7lLDZI6Pd6Su+y
+ZIeiwrUaPMzdUePNf2hZI/IvWCUMCIyoqrrKHdHoPuvQCW17IyxsnFQJNbmN+Rtp
+BQilhtwvBbggCBWhHxEdiqBaZHDw6Zl+bU7ejx1mu9A95wpQ9SCL2cRkAlz2LDOy
+wznrTAwGcvqvFKxlV+3HsaD7rba4kCA1Ihp5mm/dS2k94QIDAQABo1EwTzAfBgNV
+HSMEGDAWgBTzut+pwwDfqmMYcI9KNWRDhxcIpTAJBgNVHRMEAjAAMAsGA1UdDwQE
+AwIE8DAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAKVu
+vVpu5nPGAGn1SX4FQUcbn9Z5wgBkjnZxfJHJQX4sYIRlcirZviPHCZGPWex4VHC+
+lFMm+70YEN2uoe5jGrdgcugzx2Amc7/mLrsvvpMsaS0PlxNMcqhdM1WHbGjjdNln
+XICVITSKnB1fSGH6uo9CMCWw5kgPS9o4QWrLLkxnds3hoz7gVEUyi/6V65mcfFNA
+lof9iKcK9JsSHdBs35vpv7UKLX+96RM7Nm2Mu0yue5JiS79/zuMA/Kryxot4jv5z
+ecdWFl0eIyQBZmBzMw2zPUqkxEnXLiKjV8jutEg/4qovTOB6YiA41qbARXdzNA2V
+FYuchcTcWmnmVVRFyyU=
+-----END CERTIFICATE-----
diff --git a/cli/tests/tls/localhost.key b/cli/tests/tls/localhost.key
new file mode 100644
index 000000000..42774c977
--- /dev/null
+++ b/cli/tests/tls/localhost.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDP2y+NV1/mOKFQ
+G2h93zhcp1vx0QdBEl8M0pongCttqowcB2eqL99a2WoQQyfyDB5nv7iGyEQJjmaf
+HgeHLsgBt48OxNNtKbTV0KGGXEW6931C6/O+upKNKr6/gmVq76lAiFTPNXXZ0Xls
+W+sisZbEJmn5buUsNkjo93pK77Jkh6LCtRo8zN1R481/aFkj8i9YJQwIjKiqusod
+0eg+69AJbXsjLGycVAk1uY35G2kFCKWG3C8FuCAIFaEfER2KoFpkcPDpmX5tTt6P
+HWa70D3nClD1IIvZxGQCXPYsM7LDOetMDAZy+q8UrGVX7cexoPuttriQIDUiGnma
+b91LaT3hAgMBAAECggEBAJABfn+BQorBP1m9s3ZJmcXvmW7+7/SwYrQCkRS+4te2
+6h1dMAAj7K4HpUkhDeLPbJ1aoeCXjTPFuemRp4uL6Lvvzahgy059L7FXOyFYemMf
+pmQgDx5cKr6tF7yc/eDJrExuZ7urgTvouiRNxqmhuh+psZBDuXkZHwhwtQSH7uNg
+KBDKu0qWO73vFLcLckdGEU3+H9oIWs5xcvvOkWzyvHbRGFJSihgcRpPPHodF5xB9
+T/gZIoJHMmCbUMlWaSasUyNXTuvCnkvBDol8vXrMJCVzKZj9GpPDcIFdc08GSn4I
+pTdSNwzUcHbdERzdVU28Xt+t6W5rvp/4FWrssi4IzkUCgYEA//ZcEcBguRD4OFrx
+6wbSjzCcUW1NWhzA8uTOORZi4SvndcH1cU4S2wznuHNubU1XlrGwJX6PUGebmY/l
+53B5PJvStbVtZCVIxllR+ZVzRuL8wLodRHzlYH8GOzHwoa4ivSupkzl72ij1u/tI
+NMLGfYEKVdNd8zXIESUY88NszvsCgYEAz+MDp3xOhFaCe+CPv80A592cJcfzc8Al
++rahEOu+VdN2QBZf86PIf2Bfv/t0QvnRvs1z648TuH6h83YSggOAbmfHyd789jkq
+UWlktIaXbVn+VaHmPTcBWTg3ZTlvG+fiFCbZXiYhm+UUf1MDqZHdiifAoyVIjV/Z
+YhCNJo3q39MCgYEAknrpK5t9fstwUcfyA/9OhnVaL9suVjB4V0iLn+3ovlXCywgp
+ryLv9X3IKi2c9144jtu3I23vFCOGz3WjKzSZnQ7LogNmy9XudNxu5jcZ1mpWHPEl
+iKk1F2j6Juwoek5OQRX4oHFYKHwiTOa75r3Em9Q6Fu20KVgQ24bwZafj3/sCgYAy
+k0AoVw2jFIjaKl/Ogclen4OFjYek+XJD9Hpq62964d866Dafx5DXrFKfGkXGpZBp
+owI4pK5fjC9KU8dc6g0szwLEEgPowy+QbtuZL8VXTTWbD7A75E3nrs2LStXFLDzM
+OkdXqF801h6Oe1vAvUPwgItVJZTpEBCK0wwD/TLPEQKBgQDRkhlTtAoHW7W6STd0
+A/OWc0dxhzMurpxg0bLgCqUjw1ESGrSCGhffFn0IWa8sv19VWsZuBhTgjNatZsYB
+AhDs/6OosT/3nJoh2/t0hYDj1FBI0lPXWYD4pesuZ5yIMrmSaAOtIzp4BGY7ui8N
+wOqcq/jdiHj/MKEdqOXy3YAJrA==
+-----END PRIVATE KEY-----
diff --git a/cli/tokio_util.rs b/cli/tokio_util.rs
index 4ee73eef9..1341c657a 100644
--- a/cli/tokio_util.rs
+++ b/cli/tokio_util.rs
@@ -99,6 +99,7 @@ pub fn accept(r: Resource) -> Accept {
pub struct Accept {
state: AcceptState,
}
+
impl Future for Accept {
type Item = (TcpStream, SocketAddr);
type Error = io::Error;