1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 00:29:09 -05:00

feat: Deno.listenTLS (#3152)

This commit is contained in:
Bartek Iwańczuk 2019-10-21 20:38:28 +02:00 committed by Ry Dahl
parent 1f52c66ced
commit 6c5a981fd2
16 changed files with 646 additions and 22 deletions

View file

@ -75,7 +75,7 @@ export {
export { truncateSync, truncate } from "./truncate.ts"; export { truncateSync, truncate } from "./truncate.ts";
export { FileInfo } from "./file_info.ts"; export { FileInfo } from "./file_info.ts";
export { connect, dial, listen, Listener, Conn } from "./net.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 { metrics, Metrics } from "./metrics.ts";
export { resources } from "./resources.ts"; export { resources } from "./resources.ts";
export { export {

View file

@ -26,9 +26,11 @@ export let OP_METRICS: number;
export let OP_REPL_START: number; export let OP_REPL_START: number;
export let OP_REPL_READLINE: number; export let OP_REPL_READLINE: number;
export let OP_ACCEPT: number; export let OP_ACCEPT: number;
export let OP_ACCEPT_TLS: number;
export let OP_DIAL: number; export let OP_DIAL: number;
export let OP_SHUTDOWN: number; export let OP_SHUTDOWN: number;
export let OP_LISTEN: number; export let OP_LISTEN: number;
export let OP_LISTEN_TLS: number;
export let OP_RESOURCES: number; export let OP_RESOURCES: number;
export let OP_GET_RANDOM_VALUES: number; export let OP_GET_RANDOM_VALUES: number;
export let OP_GLOBAL_TIMER_STOP: 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_START:
case OP_REPL_READLINE: case OP_REPL_READLINE:
case OP_ACCEPT: case OP_ACCEPT:
case OP_ACCEPT_TLS:
case OP_DIAL: case OP_DIAL:
case OP_GLOBAL_TIMER: case OP_GLOBAL_TIMER:
case OP_HOST_GET_WORKER_CLOSED: case OP_HOST_GET_WORKER_CLOSED:

View file

@ -990,6 +990,29 @@ declare namespace Deno {
*/ */
export function listen(options: ListenOptions): Listener; 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 { export interface DialOptions {
port: number; port: number;
hostname?: string; hostname?: string;
@ -1018,6 +1041,7 @@ declare namespace Deno {
export interface DialTLSOptions { export interface DialTLSOptions {
port: number; port: number;
hostname?: string; hostname?: string;
certFile?: string;
} }
/** /**

View file

@ -78,7 +78,7 @@ export class ConnImpl implements Conn {
} }
} }
class ListenerImpl implements Listener { export class ListenerImpl implements Listener {
constructor( constructor(
readonly rid: number, readonly rid: number,
private transport: Transport, private transport: Transport,

View file

@ -1,13 +1,14 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. // 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 * 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... // TODO(ry) There are many configuration options to add...
// https://docs.rs/rustls/0.16.0/rustls/struct.ClientConfig.html // https://docs.rs/rustls/0.16.0/rustls/struct.ClientConfig.html
interface DialTLSOptions { interface DialTLSOptions {
port: number; port: number;
hostname?: string; hostname?: string;
certFile?: string;
} }
const dialTLSDefaults = { hostname: "127.0.0.1", transport: "tcp" }; 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); const res = await sendAsync(dispatch.OP_DIAL_TLS, options);
return new ConnImpl(res.rid, res.remoteAddr!, res.localAddr!); 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);
}

View file

@ -3,8 +3,9 @@ import { test, testPerm, assert, assertEquals } from "./test_util.ts";
import { BufWriter, BufReader } from "../../std/io/bufio.ts"; import { BufWriter, BufReader } from "../../std/io/bufio.ts";
import { TextProtoReader } from "../../std/textproto/mod.ts"; import { TextProtoReader } from "../../std/textproto/mod.ts";
import { runIfMain } from "../../std/testing/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> { test(async function dialTLSNoPerm(): Promise<void> {
let err; let err;
@ -17,15 +18,168 @@ test(async function dialTLSNoPerm(): Promise<void> {
assertEquals(err.name, "PermissionDenied"); assertEquals(err.name, "PermissionDenied");
}); });
testPerm({ net: true }, async function dialTLSBasic(): Promise<void> { test(async function dialTLSCertFileNoReadPerm(): Promise<void> {
const conn = await Deno.dialTLS({ hostname: "github.com", port: 443 }); 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); assert(conn.rid > 0);
const w = new BufWriter(conn); const w = new BufWriter(conn);
const r = new BufReader(conn); const r = new BufReader(conn);
let body = "GET / HTTP/1.1\r\n"; const body = `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`;
body += "Host: github.com\r\n"; const writeResult = await w.write(encoder.encode(body));
body += "\r\n";
const writeResult = await w.write(new TextEncoder().encode(body));
assertEquals(body.length, writeResult); assertEquals(body.length, writeResult);
await w.flush(); await w.flush();
const tpr = new TextProtoReader(r); 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 contentLength = parseInt(headers.get("content-length"));
const bodyBuf = new Uint8Array(contentLength); const bodyBuf = new Uint8Array(contentLength);
await r.readFull(bodyBuf); await r.readFull(bodyBuf);
assertEquals(decoder.decode(bodyBuf), "Hello World\n");
conn.close(); conn.close();
}); });

View file

@ -1,28 +1,52 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use super::dispatch_json::{Deserialize, JsonOp, Value}; use super::dispatch_json::{Deserialize, JsonOp, Value};
use crate::deno_error::DenoError;
use crate::deno_error::ErrorKind;
use crate::ops::json_op; use crate::ops::json_op;
use crate::resolve_addr::resolve_addr; use crate::resolve_addr::resolve_addr;
use crate::resources; use crate::resources;
use crate::state::ThreadSafeState; use crate::state::ThreadSafeState;
use crate::tokio_util;
use deno::*; use deno::*;
use futures::Future; use futures::Future;
use std; use std;
use std::convert::From; use std::convert::From;
use std::fs::File;
use std::io::BufReader;
use std::sync::Arc; use std::sync::Arc;
use tokio; use tokio;
use tokio::net::TcpListener;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio_rustls::{rustls::ClientConfig, TlsConnector}; 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;
use webpki::DNSNameRef; use webpki::DNSNameRef;
use webpki_roots; 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)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct DialTLSArgs { struct DialTLSArgs {
hostname: String, hostname: String,
port: u16, port: u16,
} cert_file: Option<String>,
pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
i.register_op("dial_tls", s.core_op(json_op(s.stateful_op(op_dial_tls))));
} }
pub fn op_dial_tls( 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 // TODO(ry) Using format! is suboptimal here. Better would be if
// state.check_net and resolve_addr() took hostname and port directly. // state.check_net and resolve_addr() took hostname and port directly.
let address = format!("{}:{}", args.hostname, args.port); let address = format!("{}:{}", args.hostname, args.port);
let cert_file = args.cert_file;
state.check_net(&address)?; state.check_net(&address)?;
if let Some(path) = cert_file.clone() {
state.check_read(&path)?;
}
let mut domain = args.hostname; let mut domain = args.hostname;
if domain.is_empty() { if domain.is_empty() {
@ -53,6 +81,12 @@ pub fn op_dial_tls(
.root_store .root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); .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)); let tls_connector = TlsConnector::from(Arc::new(config));
Ok((tls_connector, tcp_stream, local_addr, remote_addr)) Ok((tls_connector, tcp_stream, local_addr, remote_addr))
}) })
@ -78,3 +112,147 @@ pub fn op_dial_tls(
Ok(JsonOp::Async(Box::new(op))) 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)))
}

View file

@ -36,7 +36,9 @@ use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio_process; 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. pub type ResourceId = u32; // Sometimes referred to RID.
@ -47,6 +49,7 @@ type ResourceTable = BTreeMap<ResourceId, Repr>;
#[cfg(not(windows))] #[cfg(not(windows))]
use std::os::unix::io::FromRawFd; use std::os::unix::io::FromRawFd;
use futures::future::Either;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::io::FromRawHandle; use std::os::windows::io::FromRawHandle;
@ -89,8 +92,14 @@ enum Repr {
// Currently TcpListener itself does not take care of this issue. // Currently TcpListener itself does not take care of this issue.
// See: https://github.com/tokio-rs/tokio/issues/846 // See: https://github.com/tokio-rs/tokio/issues/846
TcpListener(tokio::net::TcpListener, Option<futures::task::Task>), TcpListener(tokio::net::TcpListener, Option<futures::task::Task>),
TlsListener(
tokio::net::TcpListener,
TlsAcceptor,
Option<futures::task::Task>,
),
TcpStream(tokio::net::TcpStream), TcpStream(tokio::net::TcpStream),
TlsStream(Box<TlsStream<TcpStream>>), ServerTlsStream(Box<ServerTlsStream<TcpStream>>),
ClientTlsStream(Box<ClientTlsStream<TcpStream>>),
HttpBody(HttpBody), HttpBody(HttpBody),
Repl(Arc<Mutex<Repl>>), Repl(Arc<Mutex<Repl>>),
// Enum size is bounded by the largest variant. // Enum size is bounded by the largest variant.
@ -135,8 +144,10 @@ fn inspect_repr(repr: &Repr) -> String {
Repr::Stderr(_) => "stderr", Repr::Stderr(_) => "stderr",
Repr::FsFile(_) => "fsFile", Repr::FsFile(_) => "fsFile",
Repr::TcpListener(_, _) => "tcpListener", Repr::TcpListener(_, _) => "tcpListener",
Repr::TlsListener(_, _, _) => "tlsListener",
Repr::TcpStream(_) => "tcpStream", Repr::TcpStream(_) => "tcpStream",
Repr::TlsStream(_) => "tlsStream", Repr::ClientTlsStream(_) => "clientTlsStream",
Repr::ServerTlsStream(_) => "serverTlsStream",
Repr::HttpBody(_) => "httpBody", Repr::HttpBody(_) => "httpBody",
Repr::Repl(_) => "repl", Repr::Repl(_) => "repl",
Repr::Child(_) => "child", Repr::Child(_) => "child",
@ -168,6 +179,27 @@ impl Resource {
)), )),
Some(repr) => match repr { Some(repr) => match repr {
Repr::TcpListener(ref mut s, _) => s.poll_accept(), 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"), _ => panic!("Cannot accept"),
}, },
} }
@ -252,7 +284,8 @@ impl DenoAsyncRead for Resource {
Repr::FsFile(ref mut f) => f.poll_read(buf), Repr::FsFile(ref mut f) => f.poll_read(buf),
Repr::Stdin(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::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::HttpBody(ref mut f) => f.poll_read(buf),
Repr::ChildStdout(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), 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::Stdout(ref mut f) => f.poll_write(buf),
Repr::Stderr(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::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), Repr::ChildStdin(ref mut f) => f.poll_write(buf),
_ => { _ => {
return Err(bad_resource()); return Err(bad_resource());
@ -329,6 +363,17 @@ pub fn add_tcp_listener(listener: tokio::net::TcpListener) -> Resource {
Resource { rid } 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 { pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource {
let rid = new_rid(); let rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap(); let mut tg = RESOURCE_TABLE.lock().unwrap();
@ -337,10 +382,18 @@ pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource {
Resource { rid } 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 rid = new_rid();
let mut tg = RESOURCE_TABLE.lock().unwrap(); let mut tg = RESOURCE_TABLE.lock().unwrap();
let r = tg.insert(rid, Repr::TlsStream(Box::new(stream))); 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::ServerTlsStream(Box::new(stream)));
assert!(r.is_none()); assert!(r.is_none());
Resource { rid } Resource { rid }
} }

47
cli/tests/tls/README.md Normal file
View file

@ -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`

19
cli/tests/tls/RootCA.crt Normal file
View file

@ -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-----

28
cli/tests/tls/RootCA.key Normal file
View file

@ -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-----

19
cli/tests/tls/RootCA.pem Normal file
View file

@ -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-----

View file

@ -0,0 +1,6 @@
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost

View file

@ -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-----

View file

@ -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-----

View file

@ -99,6 +99,7 @@ pub fn accept(r: Resource) -> Accept {
pub struct Accept { pub struct Accept {
state: AcceptState, state: AcceptState,
} }
impl Future for Accept { impl Future for Accept {
type Item = (TcpStream, SocketAddr); type Item = (TcpStream, SocketAddr);
type Error = io::Error; type Error = io::Error;