diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 88b7f59285..4ca0616f71 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -1785,3 +1785,13 @@ mod permissions { exit_code: 1, }); } + +itest!(tls_starttls { + args: "run --quiet --reload --allow-net --allow-read --unstable --cert tls/RootCA.pem tls_starttls.js", + output: "tls.out", +}); + +itest!(tls_connecttls { + args: "run --quiet --reload --allow-net --allow-read --cert tls/RootCA.pem tls_connecttls.js", + output: "tls.out", +}); diff --git a/cli/tests/tls.out b/cli/tests/tls.out new file mode 100644 index 0000000000..c8e8a135c9 --- /dev/null +++ b/cli/tests/tls.out @@ -0,0 +1 @@ +DONE diff --git a/cli/tests/tls_connecttls.js b/cli/tests/tls_connecttls.js new file mode 100644 index 0000000000..1ef6b99ee8 --- /dev/null +++ b/cli/tests/tls_connecttls.js @@ -0,0 +1,67 @@ +import { deferred } from "../../test_util/std/async/deferred.ts"; +import { assert, assertEquals } from "../../test_util/std/testing/asserts.ts"; +import { BufReader, BufWriter } from "../../test_util/std/io/bufio.ts"; +import { TextProtoReader } from "../../test_util/std/textproto/mod.ts"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +const resolvable = deferred(); +const hostname = "localhost"; +const port = 3505; + +const listener = Deno.listenTls({ + hostname, + port, + certFile: "./tls/localhost.crt", + keyFile: "./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) => { + assert(conn.remoteAddr != null); + assert(conn.localAddr != null); + await conn.write(response); + // TODO(bartlomieju): this might be a bug + setTimeout(() => { + conn.close(); + resolvable.resolve(); + }, 0); + }, +); + +const conn = await Deno.connectTls({ + hostname, + port, +}); +assert(conn.rid > 0); +const w = new BufWriter(conn); +const r = new BufReader(conn); +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); +const statusLine = await tpr.readLine(); +assert(statusLine !== null, `line must be read: ${String(statusLine)}`); +const m = statusLine.match(/^(.+?) (.+?) (.+?)$/); +assert(m !== null, "must be matched"); +const [_, proto, status, ok] = m; +assertEquals(proto, "HTTP/1.1"); +assertEquals(status, "200"); +assertEquals(ok, "OK"); +const headers = await tpr.readMIMEHeader(); +assert(headers !== null); +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(); +listener.close(); +await resolvable; + +console.log("DONE"); diff --git a/cli/tests/tls_starttls.js b/cli/tests/tls_starttls.js new file mode 100644 index 0000000000..652ba869fb --- /dev/null +++ b/cli/tests/tls_starttls.js @@ -0,0 +1,65 @@ +import { deferred } from "../../test_util/std/async/deferred.ts"; +import { assert, assertEquals } from "../../test_util/std/testing/asserts.ts"; +import { BufReader, BufWriter } from "../../test_util/std/io/bufio.ts"; +import { TextProtoReader } from "../../test_util/std/textproto/mod.ts"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +const resolvable = deferred(); +const hostname = "localhost"; +const port = 3504; + +const listener = Deno.listenTls({ + hostname, + port, + certFile: "./tls/localhost.crt", + keyFile: "./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) => { + assert(conn.remoteAddr != null); + assert(conn.localAddr != null); + await conn.write(response); + // TODO(bartlomieju): this might be a bug + setTimeout(() => { + conn.close(); + resolvable.resolve(); + }, 0); + }, +); + +let conn = await Deno.connect({ hostname, port }); +conn = await Deno.startTls(conn, { hostname }); +assert(conn.rid > 0); +const w = new BufWriter(conn); +const r = new BufReader(conn); +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); +const statusLine = await tpr.readLine(); +assert(statusLine !== null, `line must be read: ${String(statusLine)}`); +const m = statusLine.match(/^(.+?) (.+?) (.+?)$/); +assert(m !== null, "must be matched"); +const [_, proto, status, ok] = m; +assertEquals(proto, "HTTP/1.1"); +assertEquals(status, "200"); +assertEquals(ok, "OK"); +const headers = await tpr.readMIMEHeader(); +assert(headers !== null); +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(); +listener.close(); +await resolvable; + +console.log("DONE"); diff --git a/extensions/net/lib.rs b/extensions/net/lib.rs index f3281a2fb6..11d0b44936 100644 --- a/extensions/net/lib.rs +++ b/extensions/net/lib.rs @@ -88,12 +88,22 @@ pub fn get_unstable_declaration() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_net.unstable.d.ts") } -pub fn init(unstable: bool) -> Extension { +#[derive(Clone)] +pub struct DefaultTlsOptions { + pub ca_data: Option>, +} + +pub fn init( + ca_data: Option>, + unstable: bool, +) -> Extension { let mut ops_to_register = vec![]; ops_to_register.extend(io::init()); ops_to_register.extend(ops::init::

()); ops_to_register.extend(ops_tls::init::

()); + let default_tls_options = DefaultTlsOptions { ca_data }; + Extension::builder() .js(include_js_files!( prefix "deno:extensions/net", @@ -103,6 +113,7 @@ pub fn init(unstable: bool) -> Extension { )) .ops(ops_to_register) .state(move |state| { + state.put(default_tls_options.clone()); state.put(UnstableChecker { unstable }); Ok(()) }) diff --git a/extensions/net/ops_tls.rs b/extensions/net/ops_tls.rs index 092c74a697..a082f7f620 100644 --- a/extensions/net/ops_tls.rs +++ b/extensions/net/ops_tls.rs @@ -10,6 +10,7 @@ use crate::ops::OpAddr; use crate::ops::OpConn; use crate::resolve_addr::resolve_addr; use crate::resolve_addr::resolve_addr_sync; +use crate::DefaultTlsOptions; use crate::NetPermissions; use deno_core::error::bad_resource; use deno_core::error::bad_resource_id; @@ -60,6 +61,7 @@ use std::convert::From; use std::fs::File; use std::io; use std::io::BufReader; +use std::io::Cursor; use std::io::ErrorKind; use std::ops::Deref; use std::ops::DerefMut; @@ -702,6 +704,7 @@ where }; let cert_file = args.cert_file.as_deref(); + let default_tls_options; { super::check_unstable2(&state, "Deno.startTls"); let mut s = state.borrow_mut(); @@ -710,6 +713,7 @@ where if let Some(path) = cert_file { permissions.check_read(Path::new(path))?; } + default_tls_options = s.borrow::().clone(); } let hostname_dns = DNSNameRef::try_from_ascii_str(hostname) @@ -733,6 +737,10 @@ where tls_config .root_store .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + if let Some(ca_data) = default_tls_options.ca_data { + let reader = &mut Cursor::new(ca_data); + tls_config.root_store.add_pem_file(reader).unwrap(); + }; if let Some(path) = cert_file { let key_file = File::open(path)?; let reader = &mut BufReader::new(key_file); @@ -779,6 +787,7 @@ where let port = args.port; let cert_file = args.cert_file.as_deref(); + let default_tls_options; { let mut s = state.borrow_mut(); let permissions = s.borrow_mut::(); @@ -786,6 +795,7 @@ where if let Some(path) = cert_file { permissions.check_read(Path::new(path))?; } + default_tls_options = s.borrow::().clone(); } let hostname_dns = DNSNameRef::try_from_ascii_str(hostname) @@ -804,6 +814,10 @@ where tls_config .root_store .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + if let Some(ca_data) = default_tls_options.ca_data { + let reader = &mut Cursor::new(ca_data); + tls_config.root_store.add_pem_file(reader).unwrap(); + }; if let Some(path) = cert_file { let key_file = File::open(path)?; let reader = &mut BufReader::new(key_file); diff --git a/runtime/build.rs b/runtime/build.rs index 8c5772c67d..0d1cf3bce6 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -59,7 +59,7 @@ fn create_runtime_snapshot(snapshot_path: &Path, files: Vec) { deno_broadcast_channel::InMemoryBroadcastChannel::default(), false, // No --unstable. ), - deno_net::init::(false), // No --unstable. + deno_net::init::(None, false), // No --unstable. deno_http::init(), ]; diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index acafb086b8..bcbf716929 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -332,7 +332,10 @@ impl WebWorker { vec![ ops::fs_events::init(), ops::fs::init(), - deno_net::init::(options.unstable), + deno_net::init::( + options.ca_data.clone(), + options.unstable, + ), ops::os::init(), ops::permissions::init(), ops::plugin::init(), diff --git a/runtime/worker.rs b/runtime/worker.rs index 41e63914df..18ba348ff5 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -123,7 +123,7 @@ impl MainWorker { ops::fs::init(), ops::io::init(), ops::io::init_stdio(), - deno_net::init::(options.unstable), + deno_net::init::(options.ca_data.clone(), options.unstable), ops::os::init(), ops::permissions::init(), ops::plugin::init(),