Skip to content

Commit

Permalink
refactor(ext/tls): Implement required functionality for later SNI sup…
Browse files Browse the repository at this point in the history
…port (#23686)

Precursor to #23236 

This implements the SNI features, but uses private symbols to avoid
exposing the functionality at this time. Note that to properly test this
feature, we need to add a way for `connectTls` to specify a hostname.
This is something that should be pushed into that API at a later time as
well.

```ts
Deno.test(
  { permissions: { net: true, read: true } },
  async function listenResolver() {
    let sniRequests = [];
    const listener = Deno.listenTls({
      hostname: "localhost",
      port: 0,
      [resolverSymbol]: (sni: string) => {
        sniRequests.push(sni);
        return {
          cert,
          key,
        };
      },
    });

    {
      const conn = await Deno.connectTls({
        hostname: "localhost",
        [serverNameSymbol]: "server-1",
        port: listener.addr.port,
      });
      const [_handshake, serverConn] = await Promise.all([
        conn.handshake(),
        listener.accept(),
      ]);
      conn.close();
      serverConn.close();
    }

    {
      const conn = await Deno.connectTls({
        hostname: "localhost",
        [serverNameSymbol]: "server-2",
        port: listener.addr.port,
      });
      const [_handshake, serverConn] = await Promise.all([
        conn.handshake(),
        listener.accept(),
      ]);
      conn.close();
      serverConn.close();
    }

    assertEquals(sniRequests, ["server-1", "server-2"]);
    listener.close();
  },
);
```

---------

Signed-off-by: Matt Mastracci <matthew@mastracci.com>
  • Loading branch information
mmastrac committed May 9, 2024
1 parent dc29986 commit 684377c
Show file tree
Hide file tree
Showing 16 changed files with 615 additions and 102 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ ring = "^0.17.0"
rusqlite = { version = "=0.29.0", features = ["unlock_notify", "bundled"] }
rustls = "0.21.11"
rustls-pemfile = "1.0.0"
rustls-tokio-stream = "=0.2.17"
rustls-tokio-stream = "=0.2.23"
rustls-webpki = "0.101.4"
rustyline = "=13.0.0"
saffron = "=0.1.0"
Expand Down
22 changes: 11 additions & 11 deletions ext/fetch/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use deno_tls::RootCertStoreProvider;
use data_url::DataUrl;
use deno_tls::TlsKey;
use deno_tls::TlsKeys;
use deno_tls::TlsKeysHolder;
use http_v02::header::CONTENT_LENGTH;
use http_v02::Uri;
use reqwest::header::HeaderMap;
Expand Down Expand Up @@ -80,7 +81,7 @@ pub struct Options {
pub request_builder_hook:
Option<fn(RequestBuilder) -> Result<RequestBuilder, AnyError>>,
pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
pub client_cert_chain_and_key: Option<TlsKey>,
pub client_cert_chain_and_key: TlsKeys,
pub file_fetch_handler: Rc<dyn FetchHandler>,
}

Expand All @@ -101,7 +102,7 @@ impl Default for Options {
proxy: None,
request_builder_hook: None,
unsafely_ignore_certificate_errors: None,
client_cert_chain_and_key: None,
client_cert_chain_and_key: TlsKeys::Null,
file_fetch_handler: Rc::new(DefaultFileFetchHandler),
}
}
Expand Down Expand Up @@ -205,7 +206,11 @@ pub fn create_client_from_options(
unsafely_ignore_certificate_errors: options
.unsafely_ignore_certificate_errors
.clone(),
client_cert_chain_and_key: options.client_cert_chain_and_key.clone(),
client_cert_chain_and_key: options
.client_cert_chain_and_key
.clone()
.try_into()
.unwrap_or_default(),
pool_max_idle_per_host: None,
pool_idle_timeout: None,
http1: true,
Expand Down Expand Up @@ -821,7 +826,7 @@ fn default_true() -> bool {
pub fn op_fetch_custom_client<FP>(
state: &mut OpState,
#[serde] args: CreateHttpClientArgs,
#[cppgc] tls_keys: &deno_tls::TlsKeys,
#[cppgc] tls_keys: &TlsKeysHolder,
) -> Result<ResourceId, AnyError>
where
FP: FetchPermissions + 'static,
Expand All @@ -832,11 +837,6 @@ where
permissions.check_net_url(&url, "Deno.createHttpClient()")?;
}

let client_cert_chain_and_key = match tls_keys {
TlsKeys::Null => None,
TlsKeys::Static(key) => Some(key.clone()),
};

let options = state.borrow::<Options>();
let ca_certs = args
.ca_certs
Expand All @@ -853,7 +853,7 @@ where
unsafely_ignore_certificate_errors: options
.unsafely_ignore_certificate_errors
.clone(),
client_cert_chain_and_key,
client_cert_chain_and_key: tls_keys.take().try_into().unwrap(),
pool_max_idle_per_host: args.pool_max_idle_per_host,
pool_idle_timeout: args.pool_idle_timeout.and_then(
|timeout| match timeout {
Expand Down Expand Up @@ -915,7 +915,7 @@ pub fn create_http_client(
options.root_cert_store,
options.ca_certs,
options.unsafely_ignore_certificate_errors,
options.client_cert_chain_and_key,
options.client_cert_chain_and_key.into(),
deno_tls::SocketUse::Http,
)?;

Expand Down
10 changes: 7 additions & 3 deletions ext/kv/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use deno_fetch::CreateHttpClientOptions;
use deno_tls::rustls::RootCertStore;
use deno_tls::Proxy;
use deno_tls::RootCertStoreProvider;
use deno_tls::TlsKey;
use deno_tls::TlsKeys;
use denokv_remote::MetadataEndpoint;
use denokv_remote::Remote;
use url::Url;
Expand All @@ -27,7 +27,7 @@ pub struct HttpOptions {
pub root_cert_store_provider: Option<Arc<dyn RootCertStoreProvider>>,
pub proxy: Option<Proxy>,
pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
pub client_cert_chain_and_key: Option<TlsKey>,
pub client_cert_chain_and_key: TlsKeys,
}

impl HttpOptions {
Expand Down Expand Up @@ -135,7 +135,11 @@ impl<P: RemoteDbHandlerPermissions + 'static> DatabaseHandler
unsafely_ignore_certificate_errors: options
.unsafely_ignore_certificate_errors
.clone(),
client_cert_chain_and_key: options.client_cert_chain_and_key.clone(),
client_cert_chain_and_key: options
.client_cert_chain_and_key
.clone()
.try_into()
.unwrap(),
pool_max_idle_per_host: None,
pool_idle_timeout: None,
http1: false,
Expand Down
50 changes: 49 additions & 1 deletion ext/net/02_tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {
op_net_accept_tls,
op_net_connect_tls,
op_net_listen_tls,
op_tls_cert_resolver_create,
op_tls_cert_resolver_poll,
op_tls_cert_resolver_resolve,
op_tls_cert_resolver_resolve_error,
op_tls_handshake,
op_tls_key_null,
op_tls_key_static,
Expand All @@ -16,6 +20,7 @@ const {
Number,
ObjectDefineProperty,
TypeError,
SymbolFor,
} = primordials;

import { Conn, Listener } from "ext:deno_net/01_net.js";
Expand Down Expand Up @@ -87,9 +92,12 @@ async function connectTls({
keyFile,
privateKey,
});
// TODO(mmastrac): We only expose this feature via symbol for now. This should actually be a feature
// in Deno.connectTls, however.
const serverName = arguments[0][serverNameSymbol] ?? null;
const { 0: rid, 1: localAddr, 2: remoteAddr } = await op_net_connect_tls(
{ hostname, port },
{ certFile: deprecatedCertFile, caCerts, alpnProtocols },
{ certFile: deprecatedCertFile, caCerts, alpnProtocols, serverName },
keyPair,
);
localAddr.transport = "tcp";
Expand Down Expand Up @@ -133,6 +141,10 @@ class TlsListener extends Listener {
* interfaces.
*/
function hasTlsKeyPairOptions(options) {
// TODO(mmastrac): remove this temporary symbol when the API lands
if (options[resolverSymbol] !== undefined) {
return true;
}
return (options.cert !== undefined || options.key !== undefined ||
options.certFile !== undefined ||
options.keyFile !== undefined || options.privateKey !== undefined ||
Expand All @@ -159,6 +171,11 @@ function loadTlsKeyPair(api, {
privateKey = undefined;
}

// TODO(mmastrac): remove this temporary symbol when the API lands
if (arguments[1][resolverSymbol] !== undefined) {
return createTlsKeyResolver(arguments[1][resolverSymbol]);
}

// Check for "pem" format
if (keyFormat !== undefined && keyFormat !== "pem") {
throw new TypeError('If `keyFormat` is specified, it must be "pem"');
Expand Down Expand Up @@ -275,6 +292,37 @@ async function startTls(
return new TlsConn(rid, remoteAddr, localAddr);
}

const resolverSymbol = SymbolFor("unstableSniResolver");
const serverNameSymbol = SymbolFor("unstableServerName");

function createTlsKeyResolver(callback) {
const { 0: resolver, 1: lookup } = op_tls_cert_resolver_create();
(async () => {
while (true) {
const sni = await op_tls_cert_resolver_poll(lookup);
if (typeof sni !== "string") {
break;
}
try {
const key = await callback(sni);
if (!hasTlsKeyPairOptions(key)) {
op_tls_cert_resolver_resolve_error(lookup, sni, "Invalid key");
} else {
const resolved = loadTlsKeyPair("Deno.listenTls", key);
op_tls_cert_resolver_resolve(lookup, sni, resolved);
}
} catch (e) {
op_tls_cert_resolver_resolve_error(lookup, sni, e.message);
}
}
})();
return resolver;
}

internals.resolverSymbol = resolverSymbol;
internals.serverNameSymbol = serverNameSymbol;
internals.createTlsKeyResolver = createTlsKeyResolver;

export {
connectTls,
hasTlsKeyPairOptions,
Expand Down
4 changes: 4 additions & 0 deletions ext/net/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ deno_core::extension!(deno_net,
ops_tls::op_tls_key_null,
ops_tls::op_tls_key_static,
ops_tls::op_tls_key_static_from_file<P>,
ops_tls::op_tls_cert_resolver_create,
ops_tls::op_tls_cert_resolver_poll,
ops_tls::op_tls_cert_resolver_resolve,
ops_tls::op_tls_cert_resolver_resolve_error,
ops_tls::op_tls_start<P>,
ops_tls::op_net_connect_tls<P>,
ops_tls::op_net_listen_tls<P>,
Expand Down

0 comments on commit 684377c

Please sign in to comment.