diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2019-10-21 20:38:28 +0200 |
---|---|---|
committer | Ry Dahl <ry@tinyclouds.org> | 2019-10-21 14:38:28 -0400 |
commit | 6c5a981fd2afad21af73a1345c4e30fb6b30b09a (patch) | |
tree | c6065fe502cc99f29d7f5554257729552920f7f4 /cli/ops/tls.rs | |
parent | 1f52c66ced9bed0cae6bff065dfa7563cbfaee29 (diff) |
feat: Deno.listenTLS (#3152)
Diffstat (limited to 'cli/ops/tls.rs')
-rw-r--r-- | cli/ops/tls.rs | 184 |
1 files changed, 181 insertions, 3 deletions
diff --git a/cli/ops/tls.rs b/cli/ops/tls.rs index 6528884af..a0f4197ba 100644 --- a/cli/ops/tls.rs +++ b/cli/ops/tls.rs @@ -1,28 +1,52 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use super::dispatch_json::{Deserialize, JsonOp, Value}; +use crate::deno_error::DenoError; +use crate::deno_error::ErrorKind; use crate::ops::json_op; use crate::resolve_addr::resolve_addr; use crate::resources; use crate::state::ThreadSafeState; +use crate::tokio_util; use deno::*; use futures::Future; use std; use std::convert::From; +use std::fs::File; +use std::io::BufReader; use std::sync::Arc; use tokio; +use tokio::net::TcpListener; use tokio::net::TcpStream; use tokio_rustls::{rustls::ClientConfig, TlsConnector}; +use tokio_rustls::{ + rustls::{ + internal::pemfile::{certs, pkcs8_private_keys, rsa_private_keys}, + Certificate, NoClientAuth, PrivateKey, ServerConfig, + }, + TlsAcceptor, +}; use webpki; use webpki::DNSNameRef; use webpki_roots; +pub fn init(i: &mut Isolate, s: &ThreadSafeState) { + i.register_op("dial_tls", s.core_op(json_op(s.stateful_op(op_dial_tls)))); + i.register_op( + "listen_tls", + s.core_op(json_op(s.stateful_op(op_listen_tls))), + ); + i.register_op( + "accept_tls", + s.core_op(json_op(s.stateful_op(op_accept_tls))), + ); +} + #[derive(Deserialize)] +#[serde(rename_all = "camelCase")] struct DialTLSArgs { hostname: String, port: u16, -} -pub fn init(i: &mut Isolate, s: &ThreadSafeState) { - i.register_op("dial_tls", s.core_op(json_op(s.stateful_op(op_dial_tls)))); + cert_file: Option<String>, } pub fn op_dial_tls( @@ -35,8 +59,12 @@ pub fn op_dial_tls( // TODO(ry) Using format! is suboptimal here. Better would be if // state.check_net and resolve_addr() took hostname and port directly. let address = format!("{}:{}", args.hostname, args.port); + let cert_file = args.cert_file; state.check_net(&address)?; + if let Some(path) = cert_file.clone() { + state.check_read(&path)?; + } let mut domain = args.hostname; if domain.is_empty() { @@ -53,6 +81,12 @@ pub fn op_dial_tls( .root_store .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + if let Some(path) = cert_file { + let key_file = File::open(path)?; + let reader = &mut BufReader::new(key_file); + config.root_store.add_pem_file(reader).unwrap(); + } + let tls_connector = TlsConnector::from(Arc::new(config)); Ok((tls_connector, tcp_stream, local_addr, remote_addr)) }) @@ -78,3 +112,147 @@ pub fn op_dial_tls( Ok(JsonOp::Async(Box::new(op))) } + +fn load_certs(path: &str) -> Result<Vec<Certificate>, ErrBox> { + let cert_file = File::open(path)?; + let reader = &mut BufReader::new(cert_file); + + let certs = certs(reader).map_err(|_| { + DenoError::new(ErrorKind::Other, "Unable to decode certificate".to_string()) + })?; + + if certs.is_empty() { + let e = DenoError::new( + ErrorKind::Other, + "No certificates found in cert file".to_string(), + ); + return Err(ErrBox::from(e)); + } + + Ok(certs) +} + +fn key_decode_err() -> DenoError { + DenoError::new(ErrorKind::Other, "Unable to decode key".to_string()) +} + +fn key_not_found_err() -> DenoError { + DenoError::new(ErrorKind::Other, "No keys found in key file".to_string()) +} + +/// Starts with -----BEGIN RSA PRIVATE KEY----- +fn load_rsa_keys(path: &str) -> Result<Vec<PrivateKey>, ErrBox> { + let key_file = File::open(path)?; + let reader = &mut BufReader::new(key_file); + let keys = rsa_private_keys(reader).map_err(|_| key_decode_err())?; + Ok(keys) +} + +/// Starts with -----BEGIN PRIVATE KEY----- +fn load_pkcs8_keys(path: &str) -> Result<Vec<PrivateKey>, ErrBox> { + let key_file = File::open(path)?; + let reader = &mut BufReader::new(key_file); + let keys = pkcs8_private_keys(reader).map_err(|_| key_decode_err())?; + Ok(keys) +} + +fn load_keys(path: &str) -> Result<Vec<PrivateKey>, ErrBox> { + let path = path.to_string(); + let mut keys = load_rsa_keys(&path)?; + + if keys.is_empty() { + keys = load_pkcs8_keys(&path)?; + } + + if keys.is_empty() { + return Err(ErrBox::from(key_not_found_err())); + } + + Ok(keys) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ListenTlsArgs { + transport: String, + hostname: String, + port: u16, + cert_file: String, + key_file: String, +} + +fn op_listen_tls( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: ListenTlsArgs = serde_json::from_value(args)?; + assert_eq!(args.transport, "tcp"); + + // TODO(ry) Using format! is suboptimal here. Better would be if + // state.check_net and resolve_addr() took hostname and port directly. + let address = format!("{}:{}", args.hostname, args.port); + let cert_file = args.cert_file; + let key_file = args.key_file; + + state.check_net(&address)?; + state.check_read(&cert_file)?; + state.check_read(&key_file)?; + + let mut config = ServerConfig::new(NoClientAuth::new()); + config + .set_single_cert(load_certs(&cert_file)?, load_keys(&key_file)?.remove(0)) + .expect("invalid key or certificate"); + let acceptor = TlsAcceptor::from(Arc::new(config)); + let addr = resolve_addr(&address).wait()?; + let listener = TcpListener::bind(&addr)?; + let local_addr = listener.local_addr()?; + let resource = resources::add_tls_listener(listener, acceptor); + + Ok(JsonOp::Sync(json!({ + "rid": resource.rid, + "localAddr": local_addr.to_string() + }))) +} + +#[derive(Deserialize)] +struct AcceptTlsArgs { + rid: i32, +} + +fn op_accept_tls( + _state: &ThreadSafeState, + args: Value, + _zero_copy: Option<PinnedBuf>, +) -> Result<JsonOp, ErrBox> { + let args: AcceptTlsArgs = serde_json::from_value(args)?; + let server_rid = args.rid as u32; + + let server_resource = resources::lookup(server_rid)?; + let op = tokio_util::accept(server_resource) + .and_then(move |(tcp_stream, _socket_addr)| { + let local_addr = tcp_stream.local_addr()?; + let remote_addr = tcp_stream.peer_addr()?; + Ok((tcp_stream, local_addr, remote_addr)) + }) + .and_then(move |(tcp_stream, local_addr, remote_addr)| { + let mut server_resource = resources::lookup(server_rid).unwrap(); + server_resource + .poll_accept_tls(tcp_stream) + .and_then(move |tls_stream| { + let tls_stream_resource = + resources::add_server_tls_stream(tls_stream); + Ok((tls_stream_resource, local_addr, remote_addr)) + }) + }) + .map_err(ErrBox::from) + .and_then(move |(tls_stream_resource, local_addr, remote_addr)| { + futures::future::ok(json!({ + "rid": tls_stream_resource.rid, + "localAddr": local_addr.to_string(), + "remoteAddr": remote_addr.to_string(), + })) + }); + + Ok(JsonOp::Async(Box::new(op))) +} |