mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 08:09:08 -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 { 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 {
|
||||
|
|
|
@ -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:
|
||||
|
|
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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
import { sendAsync } from "./dispatch_json.ts";
|
||||
import { sendAsync, sendSync } from "./dispatch_json.ts";
|
||||
import * as dispatch from "./dispatch.ts";
|
||||
import { Conn, ConnImpl } from "./net.ts";
|
||||
import { Listener, Transport, Conn, ConnImpl, ListenerImpl } from "./net.ts";
|
||||
|
||||
// TODO(ry) There are many configuration options to add...
|
||||
// https://docs.rs/rustls/0.16.0/rustls/struct.ClientConfig.html
|
||||
interface DialTLSOptions {
|
||||
port: number;
|
||||
hostname?: string;
|
||||
certFile?: string;
|
||||
}
|
||||
const dialTLSDefaults = { hostname: "127.0.0.1", transport: "tcp" };
|
||||
|
||||
|
@ -19,3 +20,44 @@ export async function dialTLS(options: DialTLSOptions): Promise<Conn> {
|
|||
const res = await sendAsync(dispatch.OP_DIAL_TLS, options);
|
||||
return new ConnImpl(res.rid, res.remoteAddr!, res.localAddr!);
|
||||
}
|
||||
|
||||
class TLSListenerImpl extends ListenerImpl {
|
||||
async accept(): Promise<Conn> {
|
||||
const res = await sendAsync(dispatch.OP_ACCEPT_TLS, { rid: this.rid });
|
||||
return new ConnImpl(res.rid, res.remoteAddr, res.localAddr);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ListenTLSOptions {
|
||||
port: number;
|
||||
hostname?: string;
|
||||
transport?: Transport;
|
||||
certFile: string;
|
||||
keyFile: string;
|
||||
}
|
||||
|
||||
/** Listen announces on the local transport address over TLS (transport layer security).
|
||||
*
|
||||
* @param options
|
||||
* @param options.port The port to connect to. (Required.)
|
||||
* @param options.hostname A literal IP address or host name that can be
|
||||
* resolved to an IP address. If not specified, defaults to 0.0.0.0
|
||||
* @param options.certFile Server certificate file
|
||||
* @param options.keyFile Server public key file
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Deno.listenTLS({ port: 443, certFile: "./my_server.crt", keyFile: "./my_server.key" })
|
||||
*/
|
||||
export function listenTLS(options: ListenTLSOptions): Listener {
|
||||
const hostname = options.hostname || "0.0.0.0";
|
||||
const transport = options.transport || "tcp";
|
||||
const res = sendSync(dispatch.OP_LISTEN_TLS, {
|
||||
hostname,
|
||||
port: options.port,
|
||||
transport,
|
||||
certFile: options.certFile,
|
||||
keyFile: options.keyFile
|
||||
});
|
||||
return new TLSListenerImpl(res.rid, transport, res.localAddr);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ import { test, testPerm, assert, assertEquals } from "./test_util.ts";
|
|||
import { BufWriter, BufReader } from "../../std/io/bufio.ts";
|
||||
import { TextProtoReader } from "../../std/textproto/mod.ts";
|
||||
import { runIfMain } from "../../std/testing/mod.ts";
|
||||
// TODO(ry) The tests in this file use github.com:443, but it would be better to
|
||||
// not rely on an internet connection and rather use a localhost TLS server.
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
test(async function dialTLSNoPerm(): Promise<void> {
|
||||
let err;
|
||||
|
@ -17,15 +18,168 @@ test(async function dialTLSNoPerm(): Promise<void> {
|
|||
assertEquals(err.name, "PermissionDenied");
|
||||
});
|
||||
|
||||
testPerm({ net: true }, async function dialTLSBasic(): Promise<void> {
|
||||
const conn = await Deno.dialTLS({ hostname: "github.com", port: 443 });
|
||||
test(async function dialTLSCertFileNoReadPerm(): Promise<void> {
|
||||
let err;
|
||||
try {
|
||||
await Deno.dialTLS({
|
||||
hostname: "github.com",
|
||||
port: 443,
|
||||
certFile: "cli/tests/tls/RootCA.crt"
|
||||
});
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
assertEquals(err.kind, Deno.ErrorKind.PermissionDenied);
|
||||
assertEquals(err.name, "PermissionDenied");
|
||||
});
|
||||
|
||||
testPerm(
|
||||
{ read: true, net: true },
|
||||
async function listenTLSNonExistentCertKeyFiles(): Promise<void> {
|
||||
let err;
|
||||
const options = {
|
||||
hostname: "localhost",
|
||||
port: 4500,
|
||||
certFile: "cli/tests/tls/localhost.crt",
|
||||
keyFile: "cli/tests/tls/localhost.key"
|
||||
};
|
||||
|
||||
try {
|
||||
Deno.listenTLS({
|
||||
...options,
|
||||
certFile: "./non/existent/file"
|
||||
});
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
assertEquals(err.kind, Deno.ErrorKind.NotFound);
|
||||
assertEquals(err.name, "NotFound");
|
||||
|
||||
try {
|
||||
Deno.listenTLS({
|
||||
...options,
|
||||
keyFile: "./non/existent/file"
|
||||
});
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
assertEquals(err.kind, Deno.ErrorKind.NotFound);
|
||||
assertEquals(err.name, "NotFound");
|
||||
}
|
||||
);
|
||||
|
||||
testPerm({ net: true }, async function listenTLSNoReadPerm(): Promise<void> {
|
||||
let err;
|
||||
try {
|
||||
Deno.listenTLS({
|
||||
hostname: "localhost",
|
||||
port: 4500,
|
||||
certFile: "cli/tests/tls/localhost.crt",
|
||||
keyFile: "cli/tests/tls/localhost.key"
|
||||
});
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
assertEquals(err.kind, Deno.ErrorKind.PermissionDenied);
|
||||
assertEquals(err.name, "PermissionDenied");
|
||||
});
|
||||
|
||||
testPerm(
|
||||
{ read: true, write: true, net: true },
|
||||
async function listenTLSEmptyKeyFile(): Promise<void> {
|
||||
let err;
|
||||
const options = {
|
||||
hostname: "localhost",
|
||||
port: 4500,
|
||||
certFile: "cli/tests/tls/localhost.crt",
|
||||
keyFile: "cli/tests/tls/localhost.key"
|
||||
};
|
||||
|
||||
const testDir = Deno.makeTempDirSync();
|
||||
const keyFilename = testDir + "/key.pem";
|
||||
Deno.writeFileSync(keyFilename, new Uint8Array([]), {
|
||||
perm: 0o666
|
||||
});
|
||||
|
||||
try {
|
||||
Deno.listenTLS({
|
||||
...options,
|
||||
keyFile: keyFilename
|
||||
});
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
assertEquals(err.kind, Deno.ErrorKind.Other);
|
||||
assertEquals(err.name, "Other");
|
||||
}
|
||||
);
|
||||
|
||||
testPerm(
|
||||
{ read: true, write: true, net: true },
|
||||
async function listenTLSEmptyCertFile(): Promise<void> {
|
||||
let err;
|
||||
const options = {
|
||||
hostname: "localhost",
|
||||
port: 4500,
|
||||
certFile: "cli/tests/tls/localhost.crt",
|
||||
keyFile: "cli/tests/tls/localhost.key"
|
||||
};
|
||||
|
||||
const testDir = Deno.makeTempDirSync();
|
||||
const certFilename = testDir + "/cert.crt";
|
||||
Deno.writeFileSync(certFilename, new Uint8Array([]), {
|
||||
perm: 0o666
|
||||
});
|
||||
|
||||
try {
|
||||
Deno.listenTLS({
|
||||
...options,
|
||||
certFile: certFilename
|
||||
});
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
assertEquals(err.kind, Deno.ErrorKind.Other);
|
||||
assertEquals(err.name, "Other");
|
||||
}
|
||||
);
|
||||
|
||||
testPerm({ read: true, net: true }, async function dialAndListenTLS(): Promise<
|
||||
void
|
||||
> {
|
||||
const hostname = "localhost";
|
||||
const port = 4500;
|
||||
|
||||
const listener = Deno.listenTLS({
|
||||
hostname,
|
||||
port,
|
||||
certFile: "cli/tests/tls/localhost.crt",
|
||||
keyFile: "cli/tests/tls/localhost.key"
|
||||
});
|
||||
|
||||
const response = encoder.encode(
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n"
|
||||
);
|
||||
|
||||
listener.accept().then(
|
||||
async (conn): Promise<void> => {
|
||||
assert(conn.remoteAddr != null);
|
||||
assert(conn.localAddr != null);
|
||||
await conn.write(response);
|
||||
conn.close();
|
||||
}
|
||||
);
|
||||
|
||||
const conn = await Deno.dialTLS({
|
||||
hostname,
|
||||
port,
|
||||
certFile: "cli/tests/tls/RootCA.pem"
|
||||
});
|
||||
assert(conn.rid > 0);
|
||||
const w = new BufWriter(conn);
|
||||
const r = new BufReader(conn);
|
||||
let body = "GET / HTTP/1.1\r\n";
|
||||
body += "Host: github.com\r\n";
|
||||
body += "\r\n";
|
||||
const writeResult = await w.write(new TextEncoder().encode(body));
|
||||
const body = `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`;
|
||||
const writeResult = await w.write(encoder.encode(body));
|
||||
assertEquals(body.length, writeResult);
|
||||
await w.flush();
|
||||
const tpr = new TextProtoReader(r);
|
||||
|
@ -41,6 +195,7 @@ testPerm({ net: true }, async function dialTLSBasic(): Promise<void> {
|
|||
const contentLength = parseInt(headers.get("content-length"));
|
||||
const bodyBuf = new Uint8Array(contentLength);
|
||||
await r.readFull(bodyBuf);
|
||||
assertEquals(decoder.decode(bodyBuf), "Hello World\n");
|
||||
conn.close();
|
||||
});
|
||||
|
||||
|
|
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.
|
||||
use super::dispatch_json::{Deserialize, JsonOp, Value};
|
||||
use crate::deno_error::DenoError;
|
||||
use crate::deno_error::ErrorKind;
|
||||
use crate::ops::json_op;
|
||||
use crate::resolve_addr::resolve_addr;
|
||||
use crate::resources;
|
||||
use crate::state::ThreadSafeState;
|
||||
use crate::tokio_util;
|
||||
use deno::*;
|
||||
use futures::Future;
|
||||
use std;
|
||||
use std::convert::From;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::sync::Arc;
|
||||
use tokio;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_rustls::{rustls::ClientConfig, TlsConnector};
|
||||
use tokio_rustls::{
|
||||
rustls::{
|
||||
internal::pemfile::{certs, pkcs8_private_keys, rsa_private_keys},
|
||||
Certificate, NoClientAuth, PrivateKey, ServerConfig,
|
||||
},
|
||||
TlsAcceptor,
|
||||
};
|
||||
use webpki;
|
||||
use webpki::DNSNameRef;
|
||||
use webpki_roots;
|
||||
|
||||
pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
|
||||
i.register_op("dial_tls", s.core_op(json_op(s.stateful_op(op_dial_tls))));
|
||||
i.register_op(
|
||||
"listen_tls",
|
||||
s.core_op(json_op(s.stateful_op(op_listen_tls))),
|
||||
);
|
||||
i.register_op(
|
||||
"accept_tls",
|
||||
s.core_op(json_op(s.stateful_op(op_accept_tls))),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct DialTLSArgs {
|
||||
hostname: String,
|
||||
port: u16,
|
||||
}
|
||||
pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
|
||||
i.register_op("dial_tls", s.core_op(json_op(s.stateful_op(op_dial_tls))));
|
||||
cert_file: Option<String>,
|
||||
}
|
||||
|
||||
pub fn op_dial_tls(
|
||||
|
@ -35,8 +59,12 @@ pub fn op_dial_tls(
|
|||
// TODO(ry) Using format! is suboptimal here. Better would be if
|
||||
// state.check_net and resolve_addr() took hostname and port directly.
|
||||
let address = format!("{}:{}", args.hostname, args.port);
|
||||
let cert_file = args.cert_file;
|
||||
|
||||
state.check_net(&address)?;
|
||||
if let Some(path) = cert_file.clone() {
|
||||
state.check_read(&path)?;
|
||||
}
|
||||
|
||||
let mut domain = args.hostname;
|
||||
if domain.is_empty() {
|
||||
|
@ -53,6 +81,12 @@ pub fn op_dial_tls(
|
|||
.root_store
|
||||
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||
|
||||
if let Some(path) = cert_file {
|
||||
let key_file = File::open(path)?;
|
||||
let reader = &mut BufReader::new(key_file);
|
||||
config.root_store.add_pem_file(reader).unwrap();
|
||||
}
|
||||
|
||||
let tls_connector = TlsConnector::from(Arc::new(config));
|
||||
Ok((tls_connector, tcp_stream, local_addr, remote_addr))
|
||||
})
|
||||
|
@ -78,3 +112,147 @@ pub fn op_dial_tls(
|
|||
|
||||
Ok(JsonOp::Async(Box::new(op)))
|
||||
}
|
||||
|
||||
fn load_certs(path: &str) -> Result<Vec<Certificate>, ErrBox> {
|
||||
let cert_file = File::open(path)?;
|
||||
let reader = &mut BufReader::new(cert_file);
|
||||
|
||||
let certs = certs(reader).map_err(|_| {
|
||||
DenoError::new(ErrorKind::Other, "Unable to decode certificate".to_string())
|
||||
})?;
|
||||
|
||||
if certs.is_empty() {
|
||||
let e = DenoError::new(
|
||||
ErrorKind::Other,
|
||||
"No certificates found in cert file".to_string(),
|
||||
);
|
||||
return Err(ErrBox::from(e));
|
||||
}
|
||||
|
||||
Ok(certs)
|
||||
}
|
||||
|
||||
fn key_decode_err() -> DenoError {
|
||||
DenoError::new(ErrorKind::Other, "Unable to decode key".to_string())
|
||||
}
|
||||
|
||||
fn key_not_found_err() -> DenoError {
|
||||
DenoError::new(ErrorKind::Other, "No keys found in key file".to_string())
|
||||
}
|
||||
|
||||
/// Starts with -----BEGIN RSA PRIVATE KEY-----
|
||||
fn load_rsa_keys(path: &str) -> Result<Vec<PrivateKey>, ErrBox> {
|
||||
let key_file = File::open(path)?;
|
||||
let reader = &mut BufReader::new(key_file);
|
||||
let keys = rsa_private_keys(reader).map_err(|_| key_decode_err())?;
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
/// Starts with -----BEGIN PRIVATE KEY-----
|
||||
fn load_pkcs8_keys(path: &str) -> Result<Vec<PrivateKey>, ErrBox> {
|
||||
let key_file = File::open(path)?;
|
||||
let reader = &mut BufReader::new(key_file);
|
||||
let keys = pkcs8_private_keys(reader).map_err(|_| key_decode_err())?;
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
fn load_keys(path: &str) -> Result<Vec<PrivateKey>, ErrBox> {
|
||||
let path = path.to_string();
|
||||
let mut keys = load_rsa_keys(&path)?;
|
||||
|
||||
if keys.is_empty() {
|
||||
keys = load_pkcs8_keys(&path)?;
|
||||
}
|
||||
|
||||
if keys.is_empty() {
|
||||
return Err(ErrBox::from(key_not_found_err()));
|
||||
}
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ListenTlsArgs {
|
||||
transport: String,
|
||||
hostname: String,
|
||||
port: u16,
|
||||
cert_file: String,
|
||||
key_file: String,
|
||||
}
|
||||
|
||||
fn op_listen_tls(
|
||||
state: &ThreadSafeState,
|
||||
args: Value,
|
||||
_zero_copy: Option<PinnedBuf>,
|
||||
) -> Result<JsonOp, ErrBox> {
|
||||
let args: ListenTlsArgs = serde_json::from_value(args)?;
|
||||
assert_eq!(args.transport, "tcp");
|
||||
|
||||
// TODO(ry) Using format! is suboptimal here. Better would be if
|
||||
// state.check_net and resolve_addr() took hostname and port directly.
|
||||
let address = format!("{}:{}", args.hostname, args.port);
|
||||
let cert_file = args.cert_file;
|
||||
let key_file = args.key_file;
|
||||
|
||||
state.check_net(&address)?;
|
||||
state.check_read(&cert_file)?;
|
||||
state.check_read(&key_file)?;
|
||||
|
||||
let mut config = ServerConfig::new(NoClientAuth::new());
|
||||
config
|
||||
.set_single_cert(load_certs(&cert_file)?, load_keys(&key_file)?.remove(0))
|
||||
.expect("invalid key or certificate");
|
||||
let acceptor = TlsAcceptor::from(Arc::new(config));
|
||||
let addr = resolve_addr(&address).wait()?;
|
||||
let listener = TcpListener::bind(&addr)?;
|
||||
let local_addr = listener.local_addr()?;
|
||||
let resource = resources::add_tls_listener(listener, acceptor);
|
||||
|
||||
Ok(JsonOp::Sync(json!({
|
||||
"rid": resource.rid,
|
||||
"localAddr": local_addr.to_string()
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AcceptTlsArgs {
|
||||
rid: i32,
|
||||
}
|
||||
|
||||
fn op_accept_tls(
|
||||
_state: &ThreadSafeState,
|
||||
args: Value,
|
||||
_zero_copy: Option<PinnedBuf>,
|
||||
) -> Result<JsonOp, ErrBox> {
|
||||
let args: AcceptTlsArgs = serde_json::from_value(args)?;
|
||||
let server_rid = args.rid as u32;
|
||||
|
||||
let server_resource = resources::lookup(server_rid)?;
|
||||
let op = tokio_util::accept(server_resource)
|
||||
.and_then(move |(tcp_stream, _socket_addr)| {
|
||||
let local_addr = tcp_stream.local_addr()?;
|
||||
let remote_addr = tcp_stream.peer_addr()?;
|
||||
Ok((tcp_stream, local_addr, remote_addr))
|
||||
})
|
||||
.and_then(move |(tcp_stream, local_addr, remote_addr)| {
|
||||
let mut server_resource = resources::lookup(server_rid).unwrap();
|
||||
server_resource
|
||||
.poll_accept_tls(tcp_stream)
|
||||
.and_then(move |tls_stream| {
|
||||
let tls_stream_resource =
|
||||
resources::add_server_tls_stream(tls_stream);
|
||||
Ok((tls_stream_resource, local_addr, remote_addr))
|
||||
})
|
||||
})
|
||||
.map_err(ErrBox::from)
|
||||
.and_then(move |(tls_stream_resource, local_addr, remote_addr)| {
|
||||
futures::future::ok(json!({
|
||||
"rid": tls_stream_resource.rid,
|
||||
"localAddr": local_addr.to_string(),
|
||||
"remoteAddr": remote_addr.to_string(),
|
||||
}))
|
||||
});
|
||||
|
||||
Ok(JsonOp::Async(Box::new(op)))
|
||||
}
|
||||
|
|
|
@ -36,7 +36,9 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
|||
use tokio::net::TcpStream;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_process;
|
||||
use tokio_rustls::client::TlsStream;
|
||||
use tokio_rustls::client::TlsStream as ClientTlsStream;
|
||||
use tokio_rustls::server::TlsStream as ServerTlsStream;
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
|
||||
pub type ResourceId = u32; // Sometimes referred to RID.
|
||||
|
||||
|
@ -47,6 +49,7 @@ type ResourceTable = BTreeMap<ResourceId, Repr>;
|
|||
#[cfg(not(windows))]
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
use futures::future::Either;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::FromRawHandle;
|
||||
|
||||
|
@ -89,8 +92,14 @@ enum Repr {
|
|||
// Currently TcpListener itself does not take care of this issue.
|
||||
// See: https://github.com/tokio-rs/tokio/issues/846
|
||||
TcpListener(tokio::net::TcpListener, Option<futures::task::Task>),
|
||||
TlsListener(
|
||||
tokio::net::TcpListener,
|
||||
TlsAcceptor,
|
||||
Option<futures::task::Task>,
|
||||
),
|
||||
TcpStream(tokio::net::TcpStream),
|
||||
TlsStream(Box<TlsStream<TcpStream>>),
|
||||
ServerTlsStream(Box<ServerTlsStream<TcpStream>>),
|
||||
ClientTlsStream(Box<ClientTlsStream<TcpStream>>),
|
||||
HttpBody(HttpBody),
|
||||
Repl(Arc<Mutex<Repl>>),
|
||||
// Enum size is bounded by the largest variant.
|
||||
|
@ -135,8 +144,10 @@ fn inspect_repr(repr: &Repr) -> String {
|
|||
Repr::Stderr(_) => "stderr",
|
||||
Repr::FsFile(_) => "fsFile",
|
||||
Repr::TcpListener(_, _) => "tcpListener",
|
||||
Repr::TlsListener(_, _, _) => "tlsListener",
|
||||
Repr::TcpStream(_) => "tcpStream",
|
||||
Repr::TlsStream(_) => "tlsStream",
|
||||
Repr::ClientTlsStream(_) => "clientTlsStream",
|
||||
Repr::ServerTlsStream(_) => "serverTlsStream",
|
||||
Repr::HttpBody(_) => "httpBody",
|
||||
Repr::Repl(_) => "repl",
|
||||
Repr::Child(_) => "child",
|
||||
|
@ -168,6 +179,27 @@ impl Resource {
|
|||
)),
|
||||
Some(repr) => match repr {
|
||||
Repr::TcpListener(ref mut s, _) => s.poll_accept(),
|
||||
Repr::TlsListener(ref mut s, _, _) => s.poll_accept(),
|
||||
_ => panic!("Cannot accept"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_accept_tls(
|
||||
&mut self,
|
||||
tcp_stream: TcpStream,
|
||||
) -> impl Future<Item = ServerTlsStream<TcpStream>, Error = Error> {
|
||||
let mut table = RESOURCE_TABLE.lock().unwrap();
|
||||
let maybe_repr = table.get_mut(&self.rid);
|
||||
match maybe_repr {
|
||||
None => Either::A(futures::future::err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Listener has been closed",
|
||||
))),
|
||||
Some(repr) => match repr {
|
||||
Repr::TlsListener(_, ref mut acceptor, _) => {
|
||||
Either::B(acceptor.accept(tcp_stream))
|
||||
}
|
||||
_ => panic!("Cannot accept"),
|
||||
},
|
||||
}
|
||||
|
@ -252,7 +284,8 @@ impl DenoAsyncRead for Resource {
|
|||
Repr::FsFile(ref mut f) => f.poll_read(buf),
|
||||
Repr::Stdin(ref mut f) => f.poll_read(buf),
|
||||
Repr::TcpStream(ref mut f) => f.poll_read(buf),
|
||||
Repr::TlsStream(ref mut f) => f.poll_read(buf),
|
||||
Repr::ClientTlsStream(ref mut f) => f.poll_read(buf),
|
||||
Repr::ServerTlsStream(ref mut f) => f.poll_read(buf),
|
||||
Repr::HttpBody(ref mut f) => f.poll_read(buf),
|
||||
Repr::ChildStdout(ref mut f) => f.poll_read(buf),
|
||||
Repr::ChildStderr(ref mut f) => f.poll_read(buf),
|
||||
|
@ -293,7 +326,8 @@ impl DenoAsyncWrite for Resource {
|
|||
Repr::Stdout(ref mut f) => f.poll_write(buf),
|
||||
Repr::Stderr(ref mut f) => f.poll_write(buf),
|
||||
Repr::TcpStream(ref mut f) => f.poll_write(buf),
|
||||
Repr::TlsStream(ref mut f) => f.poll_write(buf),
|
||||
Repr::ClientTlsStream(ref mut f) => f.poll_write(buf),
|
||||
Repr::ServerTlsStream(ref mut f) => f.poll_write(buf),
|
||||
Repr::ChildStdin(ref mut f) => f.poll_write(buf),
|
||||
_ => {
|
||||
return Err(bad_resource());
|
||||
|
@ -329,6 +363,17 @@ pub fn add_tcp_listener(listener: tokio::net::TcpListener) -> Resource {
|
|||
Resource { rid }
|
||||
}
|
||||
|
||||
pub fn add_tls_listener(
|
||||
listener: tokio::net::TcpListener,
|
||||
acceptor: TlsAcceptor,
|
||||
) -> Resource {
|
||||
let rid = new_rid();
|
||||
let mut tg = RESOURCE_TABLE.lock().unwrap();
|
||||
let r = tg.insert(rid, Repr::TlsListener(listener, acceptor, None));
|
||||
assert!(r.is_none());
|
||||
Resource { rid }
|
||||
}
|
||||
|
||||
pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource {
|
||||
let rid = new_rid();
|
||||
let mut tg = RESOURCE_TABLE.lock().unwrap();
|
||||
|
@ -337,10 +382,18 @@ pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource {
|
|||
Resource { rid }
|
||||
}
|
||||
|
||||
pub fn add_tls_stream(stream: TlsStream<TcpStream>) -> Resource {
|
||||
pub fn add_tls_stream(stream: ClientTlsStream<TcpStream>) -> Resource {
|
||||
let rid = new_rid();
|
||||
let mut tg = RESOURCE_TABLE.lock().unwrap();
|
||||
let r = tg.insert(rid, Repr::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());
|
||||
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 {
|
||||
state: AcceptState,
|
||||
}
|
||||
|
||||
impl Future for Accept {
|
||||
type Item = (TcpStream, SocketAddr);
|
||||
type Error = io::Error;
|
||||
|
|
Loading…
Reference in a new issue