Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 106 additions & 15 deletions doc/api/quic.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,34 @@ added: v23.8.0

True if `endpoint.destroy()` has been called. Read only.

### `endpoint.setSNIContexts(entries[, options])`

<!-- YAML
added: REPLACEME
-->

* `entries` {object} An object mapping host names to TLS identity options.
Each entry must include `keys` and `certs`.
* `options` {object}
* `replace` {boolean} If `true`, replaces the entire SNI map. If `false`
(the default), merges the entries into the existing map.

Replaces or updates the SNI TLS contexts for this endpoint. This allows
changing the TLS identity (key/certificate) used for specific host names
without restarting the endpoint. Existing sessions are unaffected — only
new sessions will use the updated contexts.

```mjs
endpoint.setSNIContexts({
'api.example.com': { keys: [newApiKey], certs: [newApiCert] },
});

// Replace the entire SNI map
endpoint.setSNIContexts({
'api.example.com': { keys: [newApiKey], certs: [newApiCert] },
}, { replace: true });
```
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a good argument for the sni object form in the options, for performance & simplicity in the common case. I'm not so sure about the dynamic update method though?

We are eventually going to need a callback here (for dynamic cert generation, wildcard certificates, on-demand cert loading, etc). I would expect most setSNIContexts use cases would be subsumed by a SNICallback option, which means this method is largely redundant, and a callback would be more consistent with all our other TLS configuration anyway. Is there a use case I'm missing where this is API is preferable?

Not really opposed if you'd prefer to do this now and punt callbacks to later anyway, just want to make sure we're not aiming to implement this to replace SNI callbacks entirely, because I don't think it'll be sufficient.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, comes down to use cases. The current design keeps the implementation simple and easier to optimize. It's not immediately apparent if callbacks will definitely be needed later

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not immediately apparent if callbacks will definitely be needed later

It's a hard requirement for anywhere you need to generate certs on demand (where you don't know the cert subject until you read the SNI). That means localhost HTTPS setups for dev servers etc, mitm proxies (full disclosure - I make one of these), self-hosting homelabs, etc. If you don't know the hostnames involved before the connection arrives, you can't use the current API. Not a day 1 use case, but it'd be good to support this eventually.

Not strictly required but very useful for plenty of other cases, like wildcard certs (SNI is x.example.com, match that to a wildcard cert for *.example.com) or platforms with very large numbers of hosts available, or dynamic ACME setups where you need to read the cert from some store on demand.

Happy to avoid it for now if that helps, there's no long-term downside to this if it helps for now, but we will need a callback as well eventually.


### `endpoint.stats`

<!-- YAML
Expand Down Expand Up @@ -1113,22 +1141,37 @@ added: v23.8.0
#### `sessionOptions.alpn`

<!-- YAML
added: v23.8.0
added: REPLACEME
-->

* Type: {string}
* Type: {string} (client) | {string\[]} (server)

The ALPN (Application-Layer Protocol Negotiation) identifier(s).

For **client** sessions, this is a single string specifying the protocol
the client wants to use (e.g. `'h3'`).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we only support one ALPN value for clients? AFAICT there's no such restriction in QUIC itself, so we will want to support multiple values for clients fairly rapidly. It seems simpler to set the signature as string[] for everybody right from the start, and it looks like it'd make the implementation simpler and more consistent across the two sides.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is largely about use case... specifically, what use case will there be here the client doesn't know what protocol it wants to speak? Restricting to a single ALPN here simplifies the implementation flow and covers the majority case. But the overall api here is far from done... if there are use cases we need to cover, let's identify them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

specifically, what use case will there be here the client doesn't know what protocol it wants to speak?

There's a few examples of this in production use today:

  • DNS works over QUIC natively (DoQ) or over HTTP/3 (DoH3).
  • Media streaming has native protocols for QUIC (MoQT) and for WebTransport-over-QUIC
  • MQTT works over QUIC natively, and over websockets.

That's before we get into people versioning QUIC protocols (e.g. moqt has used moqt-NN for the each draft N of the protocol). Again, none of this is a first-launch issues, but we'll run into this eventually, just want to avoid hassle later when we have to change the public API.

For this case, it does simplify the API surface long-term if we just support this from day 1, and simplifies some of the implementation here too (granted, not all of it - but we can drop the separate client/server logic from processTlsOptions at least).


For **server** sessions, this is an array of protocol names in preference
order that the server supports (e.g. `['h3', 'h3-29']`). During the TLS
handshake, the server selects the first protocol from its list that the
client also supports.

The ALPN protocol identifier.
The negotiated ALPN determines which Application implementation is used
for the session. `'h3'` and `'h3-*'` variants select the HTTP/3
application; all other values select the default application.

#### `sessionOptions.ca`
Default: `'h3'`

#### `sessionOptions.ca` (client only)

<!-- YAML
added: v23.8.0
-->

* Type: {ArrayBuffer|ArrayBufferView|ArrayBuffer\[]|ArrayBufferView\[]}

The CA certificates to use for sessions.
The CA certificates to use for client sessions. For server sessions, CA
certificates are specified per-identity in the [`sessionOptions.sni`][] map.

#### `sessionOptions.cc`

Expand All @@ -1143,15 +1186,16 @@ Specifies the congestion control algorithm that will be used

This is an advanced option that users typically won't have need to specify.

#### `sessionOptions.certs`
#### `sessionOptions.certs` (client only)

<!-- YAML
added: v23.8.0
-->

* Type: {ArrayBuffer|ArrayBufferView|ArrayBuffer\[]|ArrayBufferView\[]}

The TLS certificates to use for sessions.
The TLS certificates to use for client sessions. For server sessions,
certificates are specified per-identity in the [`sessionOptions.sni`][] map.

#### `sessionOptions.ciphers`

Expand All @@ -1163,15 +1207,16 @@ added: v23.8.0

The list of supported TLS 1.3 cipher algorithms.

#### `sessionOptions.crl`
#### `sessionOptions.crl` (client only)

<!-- YAML
added: v23.8.0
-->

* Type: {ArrayBuffer|ArrayBufferView|ArrayBuffer\[]|ArrayBufferView\[]}

The CRL to use for sessions.
The CRL to use for client sessions. For server sessions, CRLs are specified
per-identity in the [`sessionOptions.sni`][] map.

#### `sessionOptions.groups`

Expand All @@ -1193,7 +1238,7 @@ added: v23.8.0

True to enable TLS keylogging output.

#### `sessionOptions.keys`
#### `sessionOptions.keys` (client only)

<!-- YAML
added: v23.8.0
Expand All @@ -1205,7 +1250,8 @@ changes:

* Type: {KeyObject|KeyObject\[]}

The TLS crypto keys to use for sessions.
The TLS crypto keys to use for client sessions. For server sessions,
keys are specified per-identity in the [`sessionOptions.sni`][] map.

#### `sessionOptions.maxPayloadSize`

Expand Down Expand Up @@ -1288,15 +1334,56 @@ added: v23.8.0
Specifies the maximum number of milliseconds a TLS handshake is permitted to take
to complete before timing out.

#### `sessionOptions.sni`
#### `sessionOptions.servername` (client only)

<!-- YAML
added: v23.8.0
-->

* Type: {string}

The peer server name to target.
The peer server name to target (SNI). Defaults to `'localhost'`.

#### `sessionOptions.sni` (server only)

<!-- YAML
added: REPLACEME
-->

* Type: {Object}

An object mapping host names to TLS identity options for Server Name
Indication (SNI) support. This is required for server sessions. The
special key `'*'` specifies the default/fallback identity used when
no other host name matches. Each entry may contain:

* `keys` {KeyObject|KeyObject\[]} The TLS private keys. **Required.**
* `certs` {ArrayBuffer|ArrayBufferView|ArrayBuffer\[]|ArrayBufferView\[]}
The TLS certificates. **Required.**
* `ca` {ArrayBuffer|ArrayBufferView|ArrayBuffer\[]|ArrayBufferView\[]}
Optional CA certificate overrides.
* `crl` {ArrayBuffer|ArrayBufferView|ArrayBuffer\[]|ArrayBufferView\[]}
Optional certificate revocation lists.
* `verifyPrivateKey` {boolean} Verify the private key. Default: `false`.

```mjs
const endpoint = await listen(callback, {
sni: {
'*': { keys: [defaultKey], certs: [defaultCert] },
'api.example.com': { keys: [apiKey], certs: [apiCert] },
'www.example.com': { keys: [wwwKey], certs: [wwwCert], ca: [customCA] },
},
});
```

Shared TLS options (such as `ciphers`, `groups`, `keylog`, and `verifyClient`)
are specified at the top level of the session options and apply to all
identities. Each SNI entry overrides only the per-identity certificate
fields.

The SNI map can be replaced at runtime using `endpoint.setSNIContexts()`,
which atomically swaps the map for new sessions while existing sessions
continue to use their original identity.

#### `sessionOptions.tlsTrace`

Expand Down Expand Up @@ -1338,15 +1425,17 @@ added: v23.8.0

True to require verification of TLS client certificate.

#### `sessionOptions.verifyPrivateKey`
#### `sessionOptions.verifyPrivateKey` (client only)

<!-- YAML
added: v23.8.0
-->

* Type: {boolean}

True to require private key verification.
True to require private key verification for client sessions. For server
sessions, this option is specified per-identity in the
[`sessionOptions.sni`][] map.

#### `sessionOptions.version`

Expand Down Expand Up @@ -1715,3 +1804,5 @@ added: v23.8.0
<!-- YAML
added: v23.8.0
-->

[`sessionOptions.sni`]: #sessionoptionssni-server-only
Loading
Loading