deno icon indicating copy to clipboard operation
deno copied to clipboard

Support TLS SNI on listenTls and serve

Open mmastrac opened this issue 2 years ago • 2 comments

We'd like to support SNI on TLS connections for both raw TLS sockets and Deno.serve. The underlying TLS library (https://github.com/denoland/rustls-tokio-stream) supports SNI, but we need to expose this properly.

Here's the plan to make this work:

  • [ ] Plan the interface changes required. Likely interface changes are listed below. This method will be marked as unstable for the initial version
  • [ ] Implement a TlsKeyResolver in Rust with an async function for requesting key lookups and an async function for polling pending lookups: let (cert, key) = await resolver.resolve(sni) and resolver.poll(|sni| async { return (cert, key) })
  • [ ] Wrap Rc<TlsKeyResolver> in a CPPGC struct and hook up the poll function in an async op. Call this new op from an async IIFE and delegate the polled request to the JS function: async() => { while (sni = op_tls_poll_sni(resolver)) { let [cert, key] = await resFunc(); op_tls_poll_sni_result(resolver, sni, cert, key); } }
  • [ ] Provide Rc<TlsKeyResolver> to our TlsListener and call the resolve method in the SNI callback.
  • [ ] Plumb this infrastructure into Deno.serve (should be easy with the previous work)
  • [ ] (Optional) Implement a Deno.TlsKey for optimized TLS key management.

Interface changes

  • Add TlsCertifiedKeyOptions to lib.deno_net.d.ts with the cert and key properties from ListenTlsOptions. Remove these properties from ListenTlsOptions and inherit from TlsServerKeyOptions.

  • Change TlsCertifiedKeyOptions so that key is string | Deno.CertifiedKey | Deno.CertifiedKeyResolver.

  • Add Deno.CertifiedKey object. The object will not have any properties at this time -- it will just be a simple wrapper object:

const key = new Deno.CertifiedKey(Deno.readTextFileSync("certificate.cer"), Deno.readTextFileSync("private.key"));
  • Add Deno.CertifiedKeyResolver object. Add resolveCertificate as suggested by @lucacasonato to this object. Note that in the future we may wish to add a system mode to this certificate resolver.
const resolver = new Deno.CertifiedKeyResolver({ resolveCertificate: (name) {
  const cert = await (await fetch(`http://certs.local/certs/${name}`)).text();
  const key = await (await fetch(`http://certs.local/keys/${name}`)).text();
  return new Deno.CertifiedKey(cert, key); 
});
+    /** A function to return a certificate for a connection, based on the name
+     * specified in the TLS ClientHello packet (SNI). If the function throws or
+     * returns a rejected promise, the connection with the client will be aborted.
+     *
+     * The results of this function are cached by name, if the returned
+     * certificate and key are valid. Entries in the cache may be discarded at any
+     * time. This function will never be invoked in parallel for the same name.
+     *
+     * Multiple names may return the same certificate and key. */
+    resolveCertificate?(name: string): Promise<Deno.CertifiedKey>;
+  }

References:

https://github.com/denoland/deno/issues/9109 https://github.com/denoland/deno/pull/9125 https://github.com/denoland/deno/pull/20237

mmastrac avatar Apr 04 '24 22:04 mmastrac

Does TLS SNI is what nodejs provides with the servername attribute in the fetch API? If so, I think I need this. I'm working on a local proxy to intercept some traffic, but still pass it through. I do so, by routing a CDN to localhost via /etc/hosts where my proxy runs. Part of my solution is to make an outgoing request to fetch from the destination, but cache it completely locally, but since I reroute my traffic via /etc/hosts, I cannot do this (it's just an example):

console.log(await (await fetch("https://unpkg.com/[email protected]/dist/jquery.js", {
  method: "get",
  cache: "no-store",
})).text());

but instead I need to do this:

console.log(await (await fetch("https://104.17.249.203/[email protected]/dist/jquery.js", {
  method: "get",
  cache: "no-store",
  headers: {
    host: "unpkg.com",
  },
})).text());

but it gives me "error trying to connect: invalid peer certificate: NotValidForName". The reason why I try to skip the DNS resolve stage is so I can bypass the /etc/hosts entry, i.e. 127.0.0.1 unpkg.com.

I believe I run into this error due to the lack of the servername prop that would allow me to make an SNI request. Can somebody shed some more light onto this please?

Please note that I cannot change the URL, I literally try to intercept into a client that I cannot modify and I really want to store, cache and even modify everything that gets delivered through to improve performance and provide customizations.

It seems to work in Bruno just fine:

image

martin-braun avatar May 19 '24 09:05 martin-braun

This issue is preventing me from using Gel Cloud, unfortunately.

NetOpWibby avatar May 31 '24 20:05 NetOpWibby

Hello @mmastrac , please give us an update on the progress of this implementation. Thank you!

timonson avatar Aug 24 '24 10:08 timonson

This is still on the roadmap, but we can't provide an estimate when this feature will be implemented.

bartlomieju avatar Sep 03 '24 10:09 bartlomieju

Why did you remove it from the roadmap then?

bartlomieju removed this from the 2.0.0 milestone 28 minutes ago

timonson avatar Sep 03 '24 11:09 timonson

Because it will not be done in time for Deno 2 release.

bartlomieju avatar Sep 03 '24 11:09 bartlomieju

These docs are currently false:

Neither are implemented in Deno. Once again I'm forced to go with Node for my next project. I'm forced to abandon Deno until it prioritizes core features like SNI.

It is impossible to use Deno for full multi-domain HTTPS servers until SNI is implemented. You can make small single-domain HTTPS apps in Deno currently. You can only use one certificate.

Rocknerve avatar Dec 26 '24 22:12 Rocknerve

Just checking in to see if there’s any update on when SNI support might land in a stable release? Super excited about this feature — would love to know if it’s on the roadmap yet. Thank you!

timonson avatar Apr 21 '25 18:04 timonson