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:
parent
1f52c66ced
commit
6c5a981fd2
16 changed files with 646 additions and 22 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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:
|
||||||
|
|
24
cli/js/lib.deno_runtime.d.ts
vendored
24
cli/js/lib.deno_runtime.d.ts
vendored
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
184
cli/ops/tls.rs
184
cli/ops/tls.rs
|
@ -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)))
|
||||||
|
}
|
||||||
|
|
|
@ -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
47
cli/tests/tls/README.md
Normal 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
19
cli/tests/tls/RootCA.crt
Normal 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
28
cli/tests/tls/RootCA.key
Normal 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
19
cli/tests/tls/RootCA.pem
Normal 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-----
|
6
cli/tests/tls/domains.txt
Normal file
6
cli/tests/tls/domains.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
authorityKeyIdentifier=keyid,issuer
|
||||||
|
basicConstraints=CA:FALSE
|
||||||
|
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = localhost
|
21
cli/tests/tls/localhost.crt
Normal file
21
cli/tests/tls/localhost.crt
Normal 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-----
|
28
cli/tests/tls/localhost.key
Normal file
28
cli/tests/tls/localhost.key
Normal 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-----
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue