summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/js/deno.ts2
-rw-r--r--cli/js/lib.deno.ns.d.ts27
-rw-r--r--cli/js/ops/tls.ts14
-rw-r--r--cli/js/tests/tls_test.ts51
-rw-r--r--cli/js/tls.ts17
-rw-r--r--cli/ops/io.rs8
-rw-r--r--cli/ops/net.rs10
-rw-r--r--cli/ops/tls.rs80
8 files changed, 197 insertions, 12 deletions
diff --git a/cli/js/deno.ts b/cli/js/deno.ts
index 89795119b..0322bac27 100644
--- a/cli/js/deno.ts
+++ b/cli/js/deno.ts
@@ -106,7 +106,7 @@ export { resources, close } from "./ops/resources.ts";
export { signal, signals, Signal, SignalStream } from "./signals.ts";
export { FileInfo, statSync, lstatSync, stat, lstat } from "./ops/fs/stat.ts";
export { symlinkSync, symlink } from "./ops/fs/symlink.ts";
-export { connectTLS, listenTLS } from "./tls.ts";
+export { connectTLS, listenTLS, startTLS } from "./tls.ts";
export { truncateSync, truncate } from "./ops/fs/truncate.ts";
export { isatty, setRaw } from "./ops/tty.ts";
export { umask } from "./ops/fs/umask.ts";
diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts
index cdaff073b..9ab40248f 100644
--- a/cli/js/lib.deno.ns.d.ts
+++ b/cli/js/lib.deno.ns.d.ts
@@ -2038,6 +2038,33 @@ declare namespace Deno {
*/
export function connectTLS(options: ConnectTLSOptions): Promise<Conn>;
+ export interface StartTLSOptions {
+ /** A literal IP address or host name that can be resolved to an IP address.
+ * If not specified, defaults to `127.0.0.1`. */
+ hostname?: string;
+ /** Server certificate file. */
+ certFile?: string;
+ }
+
+ /** **UNSTABLE**: new API, yet to be vetted.
+ *
+ * Start TLS handshake from an existing connection using
+ * an optional cert file, hostname (default is "127.0.0.1"). The
+ * cert file is optional and if not included Mozilla's root certificates will
+ * be used (see also https://github.com/ctz/webpki-roots for specifics)
+ * Using this function requires that the other end of the connection is
+ * prepared for TLS handshake.
+ *
+ * const conn = await Deno.connect({ port: 80, hostname: "127.0.0.1" });
+ * const tlsConn = await Deno.startTLS(conn, { certFile: "./certs/my_custom_root_CA.pem", hostname: "127.0.0.1", port: 80 });
+ *
+ * Requires `allow-net` permission.
+ */
+ export function startTLS(
+ conn: Conn,
+ options?: StartTLSOptions
+ ): Promise<Conn>;
+
/** **UNSTABLE**: not sure if broken or not */
export interface Metrics {
opsDispatched: number;
diff --git a/cli/js/ops/tls.ts b/cli/js/ops/tls.ts
index 234e569dd..3964c44bb 100644
--- a/cli/js/ops/tls.ts
+++ b/cli/js/ops/tls.ts
@@ -8,7 +8,7 @@ export interface ConnectTLSRequest {
certFile?: string;
}
-interface ConnectTLSResponse {
+interface EstablishTLSResponse {
rid: number;
localAddr: {
hostname: string;
@@ -24,7 +24,7 @@ interface ConnectTLSResponse {
export function connectTLS(
args: ConnectTLSRequest
-): Promise<ConnectTLSResponse> {
+): Promise<EstablishTLSResponse> {
return sendAsync("op_connect_tls", args);
}
@@ -66,3 +66,13 @@ interface ListenTLSResponse {
export function listenTLS(args: ListenTLSRequest): ListenTLSResponse {
return sendSync("op_listen_tls", args);
}
+
+export interface StartTLSRequest {
+ rid: number;
+ hostname: string;
+ certFile?: string;
+}
+
+export function startTLS(args: StartTLSRequest): Promise<EstablishTLSResponse> {
+ return sendAsync("op_start_tls", args);
+}
diff --git a/cli/js/tests/tls_test.ts b/cli/js/tests/tls_test.ts
index 019b81652..e42e477c3 100644
--- a/cli/js/tests/tls_test.ts
+++ b/cli/js/tests/tls_test.ts
@@ -209,3 +209,54 @@ unitTest(
await resolvable;
}
);
+
+unitTest(
+ { perms: { read: true, net: true } },
+ async function startTLS(): Promise<void> {
+ const hostname = "smtp.gmail.com";
+ const port = 587;
+ const encoder = new TextEncoder();
+
+ let conn = await Deno.connect({
+ hostname,
+ port,
+ });
+
+ let writer = new BufWriter(conn);
+ let reader = new TextProtoReader(new BufReader(conn));
+
+ let line: string | Deno.EOF = (await reader.readLine()) as string;
+ assert(line.startsWith("220"));
+
+ await writer.write(encoder.encode(`EHLO ${hostname}\r\n`));
+ await writer.flush();
+
+ while ((line = (await reader.readLine()) as string)) {
+ assert(line.startsWith("250"));
+ if (line.startsWith("250 ")) break;
+ }
+
+ await writer.write(encoder.encode("STARTTLS\r\n"));
+ await writer.flush();
+
+ line = await reader.readLine();
+
+ // Received the message that the server is ready to establish TLS
+ assertEquals(line, "220 2.0.0 Ready to start TLS");
+
+ conn = await Deno.startTLS(conn, { hostname });
+ writer = new BufWriter(conn);
+ reader = new TextProtoReader(new BufReader(conn));
+
+ // After that use TLS communication again
+ await writer.write(encoder.encode(`EHLO ${hostname}\r\n`));
+ await writer.flush();
+
+ while ((line = (await reader.readLine()) as string)) {
+ assert(line.startsWith("250"));
+ if (line.startsWith("250 ")) break;
+ }
+
+ conn.close();
+ }
+);
diff --git a/cli/js/tls.ts b/cli/js/tls.ts
index ef87b5aa1..f60eb24cb 100644
--- a/cli/js/tls.ts
+++ b/cli/js/tls.ts
@@ -57,3 +57,20 @@ export function listenTLS({
});
return new TLSListenerImpl(res.rid, res.localAddr);
}
+
+interface StartTLSOptions {
+ hostname?: string;
+ certFile?: string;
+}
+
+export async function startTLS(
+ conn: Conn,
+ { hostname = "127.0.0.1", certFile = undefined }: StartTLSOptions = {}
+): Promise<Conn> {
+ const res = await tlsOps.startTLS({
+ rid: conn.rid,
+ hostname,
+ certFile,
+ });
+ return new ConnImpl(res.rid, res.remoteAddr!, res.localAddr!);
+}
diff --git a/cli/ops/io.rs b/cli/ops/io.rs
index e045eddfb..9c228ffad 100644
--- a/cli/ops/io.rs
+++ b/cli/ops/io.rs
@@ -157,7 +157,7 @@ impl StreamResourceHolder {
pub enum StreamResource {
Stdin(tokio::io::Stdin, TTYMetadata),
FsFile(Option<(tokio::fs::File, FileMetadata)>),
- TcpStream(tokio::net::TcpStream),
+ TcpStream(Option<tokio::net::TcpStream>),
#[cfg(not(windows))]
UnixStream(tokio::net::UnixStream),
ServerTlsStream(Box<ServerTlsStream<TcpStream>>),
@@ -195,7 +195,7 @@ impl DenoAsyncRead for StreamResource {
FsFile(Some((f, _))) => f,
FsFile(None) => return Poll::Ready(Err(OpError::resource_unavailable())),
Stdin(f, _) => f,
- TcpStream(f) => f,
+ TcpStream(Some(f)) => f,
#[cfg(not(windows))]
UnixStream(f) => f,
ClientTlsStream(f) => f,
@@ -297,7 +297,7 @@ impl DenoAsyncWrite for StreamResource {
let f: &mut dyn UnpinAsyncWrite = match self {
FsFile(Some((f, _))) => f,
FsFile(None) => return Poll::Pending,
- TcpStream(f) => f,
+ TcpStream(Some(f)) => f,
#[cfg(not(windows))]
UnixStream(f) => f,
ClientTlsStream(f) => f,
@@ -315,7 +315,7 @@ impl DenoAsyncWrite for StreamResource {
let f: &mut dyn UnpinAsyncWrite = match self {
FsFile(Some((f, _))) => f,
FsFile(None) => return Poll::Pending,
- TcpStream(f) => f,
+ TcpStream(Some(f)) => f,
#[cfg(not(windows))]
UnixStream(f) => f,
ClientTlsStream(f) => f,
diff --git a/cli/ops/net.rs b/cli/ops/net.rs
index 2636a2c2d..101fc5130 100644
--- a/cli/ops/net.rs
+++ b/cli/ops/net.rs
@@ -81,9 +81,9 @@ fn accept_tcp(
let mut state = state_.borrow_mut();
let rid = state.resource_table.add(
"tcpStream",
- Box::new(StreamResourceHolder::new(StreamResource::TcpStream(
+ Box::new(StreamResourceHolder::new(StreamResource::TcpStream(Some(
tcp_stream,
- ))),
+ )))),
);
Ok(json!({
"rid": rid,
@@ -280,9 +280,9 @@ fn op_connect(
let mut state = state_.borrow_mut();
let rid = state.resource_table.add(
"tcpStream",
- Box::new(StreamResourceHolder::new(StreamResource::TcpStream(
+ Box::new(StreamResourceHolder::new(StreamResource::TcpStream(Some(
tcp_stream,
- ))),
+ )))),
);
Ok(json!({
"rid": rid,
@@ -367,7 +367,7 @@ fn op_shutdown(
.get_mut::<StreamResourceHolder>(rid)
.ok_or_else(OpError::bad_resource_id)?;
match resource_holder.resource {
- StreamResource::TcpStream(ref mut stream) => {
+ StreamResource::TcpStream(Some(ref mut stream)) => {
TcpStream::shutdown(stream, shutdown_mode).map_err(OpError::from)?;
}
#[cfg(unix)]
diff --git a/cli/ops/tls.rs b/cli/ops/tls.rs
index 60338f7fc..dca2d8012 100644
--- a/cli/ops/tls.rs
+++ b/cli/ops/tls.rs
@@ -28,6 +28,7 @@ use tokio_rustls::{
use webpki::DNSNameRef;
pub fn init(i: &mut Isolate, s: &State) {
+ i.register_op("op_start_tls", s.stateful_json_op(op_start_tls));
i.register_op("op_connect_tls", s.stateful_json_op(op_connect_tls));
i.register_op("op_listen_tls", s.stateful_json_op(op_listen_tls));
i.register_op("op_accept_tls", s.stateful_json_op(op_accept_tls));
@@ -42,6 +43,85 @@ struct ConnectTLSArgs {
cert_file: Option<String>,
}
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct StartTLSArgs {
+ rid: u32,
+ cert_file: Option<String>,
+ hostname: String,
+}
+
+pub fn op_start_tls(
+ state: &State,
+ args: Value,
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<JsonOp, OpError> {
+ let args: StartTLSArgs = serde_json::from_value(args)?;
+ let rid = args.rid as u32;
+ let cert_file = args.cert_file.clone();
+ let state_ = state.clone();
+
+ let mut domain = args.hostname;
+ if domain.is_empty() {
+ domain.push_str("localhost");
+ }
+
+ let op = async move {
+ let mut state = state_.borrow_mut();
+
+ let mut resource_holder =
+ match state.resource_table.remove::<StreamResourceHolder>(rid) {
+ Some(resource) => *resource,
+ None => return Err(OpError::bad_resource_id()),
+ };
+
+ if let StreamResource::TcpStream(ref mut tcp_stream) =
+ resource_holder.resource
+ {
+ let tcp_stream = tcp_stream.take().unwrap();
+ let local_addr = tcp_stream.local_addr()?;
+ let remote_addr = tcp_stream.peer_addr()?;
+ let mut config = ClientConfig::new();
+ config
+ .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));
+ let dnsname =
+ DNSNameRef::try_from_ascii_str(&domain).expect("Invalid DNS lookup");
+ let tls_stream = tls_connector.connect(dnsname, tcp_stream).await?;
+
+ let rid = state.resource_table.add(
+ "clientTlsStream",
+ Box::new(StreamResourceHolder::new(StreamResource::ClientTlsStream(
+ Box::new(tls_stream),
+ ))),
+ );
+ Ok(json!({
+ "rid": rid,
+ "localAddr": {
+ "hostname": local_addr.ip().to_string(),
+ "port": local_addr.port(),
+ "transport": "tcp",
+ },
+ "remoteAddr": {
+ "hostname": remote_addr.ip().to_string(),
+ "port": remote_addr.port(),
+ "transport": "tcp",
+ }
+ }))
+ } else {
+ Err(OpError::bad_resource_id())
+ }
+ };
+ Ok(JsonOp::Async(op.boxed_local()))
+}
+
pub fn op_connect_tls(
state: &State,
args: Value,