diff --git a/cli/js/deno.ts b/cli/js/deno.ts index 511e4f0ecc..4b0e3ff96e 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 bff4d0f5b3..38405a866b 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 94b6b61cdd..3036ea2d11 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 a7ad2b73cc..f463da60bd 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 ec24b458be..3e38c7854f 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 { 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 { + 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 79e6bcad85..58cafd23e7 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 { let err; @@ -17,15 +18,168 @@ test(async function dialTLSNoPerm(): Promise { assertEquals(err.name, "PermissionDenied"); }); -testPerm({ net: true }, async function dialTLSBasic(): Promise { - const conn = await Deno.dialTLS({ hostname: "github.com", port: 443 }); +test(async function dialTLSCertFileNoReadPerm(): Promise { + 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 { + 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 { + 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 { + 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 { + 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 => { + 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 { 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 6528884af7..a0f4197baa 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, } 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, 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, 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, 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, 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, +) -> Result { + 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, +) -> Result { + 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 fc4aa7eb5a..1c791191d4 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; #[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), + TlsListener( + tokio::net::TcpListener, + TlsAcceptor, + Option, + ), TcpStream(tokio::net::TcpStream), - TlsStream(Box>), + ServerTlsStream(Box>), + ClientTlsStream(Box>), HttpBody(HttpBody), Repl(Arc>), // 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, 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) -> Resource { +pub fn add_tls_stream(stream: ClientTlsStream) -> 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::ClientTlsStream(Box::new(stream))); + assert!(r.is_none()); + Resource { rid } +} + +pub fn add_server_tls_stream(stream: ServerTlsStream) -> 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()); Resource { rid } } diff --git a/cli/tests/tls/README.md b/cli/tests/tls/README.md new file mode 100644 index 0000000000..14399ae828 --- /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 0000000000..c2f84ceebc --- /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 0000000000..98ce53b0bd --- /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 0000000000..c2f84ceebc --- /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 0000000000..0bba95d334 --- /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 0000000000..a71ae9050f --- /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 0000000000..42774c9771 --- /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 4ee73eef90..1341c657a7 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;