diff --git a/doc/api/dtls.md b/doc/api/dtls.md index df0e5b50a428..fe81bab7dbad 100644 --- a/doc/api/dtls.md +++ b/doc/api/dtls.md @@ -74,20 +74,73 @@ added: REPLACEME * `options` {Object} * `cert` {string|Buffer} Server certificate in PEM format. **Required.** * `key` {string|Buffer} Server private key in PEM format. **Required.** + * `secureContext` {DTLSSecureContext} A context from + [`dtls.createSecureContext()`][] to use instead of building one from the + credential options below. Must have been created with `isServer: true`. + Cannot be combined with any option the context already carries. + * `sni` {Object|Function} Server Name Indication. A map of host names to the + identity to serve them with, or a function returning one. Cannot be + combined with `secureContext`; set it on the context instead. See + [Server Name Indication][]. + * `passphrase` {string} Passphrase to decrypt `key`, if it is encrypted. + Ignored when `key` is not encrypted. Unlike `key` and `cert`, this must be + a string, matching [`tls.createSecureContext()`][]. * `port` {number} Port to bind to. **Required.** * `host` {string} Address to bind to. **Default:** `'0.0.0.0'`. * `ca` {string|Buffer|string\[]|Buffer\[]} CA certificates in PEM format. * `ciphers` {string} OpenSSL cipher list string. - * `alpn` {string\[]|Buffer} ALPN protocol names. + * `alpn` {string\[]|Buffer} ALPN protocol names. Each name must be between + 1 and 255 bytes. A `Buffer` must already be in ALPN wire format: one + length byte followed by that many bytes, repeated. * `srtp` {string} Colon-separated SRTP protection profile names (e.g., `'SRTP_AES128_CM_SHA1_80:SRTP_AEAD_AES_128_GCM'`). - * `requestCert` {boolean} Request client certificate. **Default:** `false`. - * `mtu` {number} Maximum transmission unit for DTLS records. - **Default:** `1200`. + * `requestCert` {boolean} Request a certificate from the client. + **Default:** `false`. + * `rejectUnauthorized` {boolean} Only has an effect together with + `requestCert`. When `true`, a client that presents no certificate, or one + that does not chain to a trusted CA, is rejected during the handshake and + receives a TLS alert. When `false`, the certificate is still requested and + verified but the handshake completes regardless, leaving the decision to + the application via [`session.authorized`][]. **Default:** `true`. + * `mtu` {number} Maximum size in bytes of a DTLS datagram. **Default:** + `1200`. + * `handshakeTimeout` {number} Milliseconds a handshake may take before it is + abandoned. `0` disables it. **Default:** `60000`. See + [Handshake timeout][]. + * `ipv6Only` {boolean} When `true`, an IPv6 endpoint serves IPv6 only. When + `false`, binding `'::'` also accepts IPv4 peers, which arrive with mapped + addresses such as `'::ffff:203.0.113.1'` -- anything keyed on the peer + address, including `maxSessionsPerHost`, sees them in that form. Has no + effect on an IPv4 endpoint. **Default:** `false`. + * `reusePort` {boolean} When `true`, sets `SO_REUSEPORT`, so several + processes may bind the same port and the kernel spreads arriving + datagrams between them. Every one of them must set it. **Default:** + `false`. + * `udpReceiveBufferSize` {number} Size in bytes for the socket's receive + buffer (`SO_RCVBUF`). Raising it gives the endpoint room for bursts that + the default would drop. The kernel clamps this to its own maximum. + **Default:** the system default. + * `udpSendBufferSize` {number} Size in bytes for the socket's send buffer + (`SO_SNDBUF`). Clamped as above. **Default:** the system default. + * `udpTTL` {number} IP time-to-live for outgoing datagrams, from `1` to + `255`. **Default:** the system default. + * `maxSessions` {number} The maximum number of concurrent sessions the + endpoint will hold. Set to `0` for no limit. **Default:** `10000`. + * `maxSessionsPerHost` {number} The maximum number of concurrent sessions + from any single source IP address, ignoring port. Set to `0` for no limit. + **Default:** `1000`. + * `sessionIdContext` {string} Opaque identifier scoping resumable sessions + to this server, at most 32 bytes. **Default:** a value derived from + `process.argv`, as in `tls.createServer()`. * Returns: {DTLSEndpoint} Creates a DTLS server bound to the specified address and port. The server -uses automatic HMAC-based cookie exchange for DoS protection. +uses automatic HMAC-based cookie exchange for DoS protection. See +[Denial of service][]. + +Binding failures are thrown with the code the operating system gave, as in +`net` and `dgram`: an address already in use throws an error whose `code` is +`'EADDRINUSE'`, with `errno` and `syscall` set. ```mjs import { listen } from 'node:dtls'; @@ -117,26 +170,50 @@ console.log('DTLS server listening on', endpoint.address); added: REPLACEME --> -* `host` {string} Remote host to connect to. +* `host` {string} Remote host to connect to, as an IPv4 or IPv6 literal. + Host names are not resolved. * `port` {number} Remote port to connect to. * `options` {Object} * `ca` {string|Buffer|string\[]|Buffer\[]} CA certificates in PEM format. * `cert` {string|Buffer} Client certificate in PEM format. * `key` {string|Buffer} Client private key in PEM format. + * `secureContext` {DTLSSecureContext} A context from + [`dtls.createSecureContext()`][] to use instead of building one from the + credential options below. Must **not** have been created with + `isServer: true`. Cannot be combined with any option the context already + carries. + * `psk` {Object|Function} A pre-shared key as `{ identity, key }`, or a + function returning one. See [Pre-shared keys][]. + * `session` {Buffer} A session from [`session.session`][] on an earlier + connection, to resume rather than handshake in full. See + [Session resumption][]. + * `passphrase` {string} Passphrase to decrypt `key`, if it is encrypted. + Ignored when `key` is not encrypted. Unlike `key` and `cert`, this must be + a string, matching [`tls.createSecureContext()`][]. * `rejectUnauthorized` {boolean} When `true`, the server's certificate must both chain to a trusted CA and match the expected identity (`servername`, or `host` when `servername` is not set); otherwise the handshake is - aborted and `session.opened` rejects. When `false`, the certificate is not - verified. **Default:** `true`. + aborted and `session.opened` rejects. When `false`, the certificate is + still verified and the handshake completes regardless, leaving the + decision to the application via [`session.authorized`][] and + [`session.authorizationError`][]. **Default:** `true`. * `servername` {string} Server name used for the SNI (Server Name Indication) extension and as the identity checked during certificate verification. **Default:** the `host` argument. Set to `''` to disable SNI. SNI is never sent for IP address literals. - * `bindHost` {string} Local bind address. **Default:** `'0.0.0.0'`. + * `bindHost` {string} Local bind address. **Default:** `'::'` when `host` is an + IPv6 literal, otherwise `'0.0.0.0'`. The local socket must be in the same + address family as the peer. * `bindPort` {number} Local bind port. **Default:** `0` (ephemeral). - * `alpn` {string\[]|Buffer} ALPN protocol names. + * `alpn` {string\[]|Buffer} ALPN protocol names. Each name must be between + 1 and 255 bytes. A `Buffer` must already be in ALPN wire format: one + length byte followed by that many bytes, repeated. * `srtp` {string} SRTP protection profile names. - * `mtu` {number} Maximum transmission unit. **Default:** `1200`. + * `mtu` {number} Maximum size in bytes of a DTLS datagram. **Default:** + `1200`. + * `handshakeTimeout` {number} Milliseconds a handshake may take before it is + abandoned and `session.opened` rejects. `0` disables it. **Default:** + `60000`. See [Handshake timeout][]. * Returns: {DTLSSession} Connects to a DTLS server. Returns a `DTLSSession` whose `opened` property @@ -146,7 +223,7 @@ is a `Promise` that resolves when the handshake completes. import { connect } from 'node:dtls'; import { readFileSync } from 'node:fs'; -const session = connect('localhost', 4433, { +const session = connect('127.0.0.1', 4433, { ca: [readFileSync('ca-cert.pem')], }); @@ -158,6 +235,388 @@ session.onmessage = (data) => { }; ``` +## `dtls.createSecureContext([options])` + + + +* `options` {Object} + * `alpn` {string\[]} ALPN protocols. + * `ca` {string|Buffer|Array} CA certificates in PEM format. When omitted, + the bundled default certificate authorities are used. + * `cert` {string|Buffer} Certificate in PEM format. + * `ciphers` {string} OpenSSL cipher suite list. + * `ecdhCurve` {string} Named curve or curve list for ECDH. + * `isServer` {boolean} Build a context for a server. **Default:** `false`. + * `key` {string|Buffer} Private key in PEM format. + * `passphrase` {string} Passphrase for `key`, if it is encrypted. + * `rejectUnauthorized` {boolean} Verification behaviour, as for + [`dtls.listen()`][] and [`dtls.connect()`][]. + * `requestCert` {boolean} Request a certificate from the peer. Servers only. + * `sessionIdContext` {string} Session id context. Servers only. + * `sni` {Object|Function} Server Name Indication. Servers only. See + [Server Name Indication][]. + * `psk` {Object|Function} Pre-shared keys. See [Pre-shared keys][]. + * `pskIdentityHint` {string} Identity hint to advertise, naming which key a + client should pick. Requires `psk`. Servers only. + * `srtp` {string} SRTP profile list. + * `ticketKeys` {Buffer} Session ticket keys, for resuming sessions across + endpoints and restarts. Servers only. See [Session resumption][]. +* Returns: {DTLSSecureContext} + +Options marked "Servers only" require `isServer: true`. Passing one to a +client context throws `ERR_INVALID_ARG_VALUE`, rather than being ignored or +applied where it can have no effect. + +Creates a reusable secure context. Pass it to [`dtls.listen()`][] or +[`dtls.connect()`][] as `secureContext` in place of the credential options. + +A context holds a parsed certificate and key and, when `ca` is given, its own +certificate store; roughly 28 KiB in total. Building one per connection is +therefore expensive in memory rather than in time -- two thousand of them cost +about 54 MiB, against 2 MiB when a single context is shared. Clients opening +many connections should build the context once. + +The peer identity checked during verification is **not** part of the context. +It is bound to each connection from `servername` (or the host), so one context +can be used against different peers and still reject the wrong certificate. + +`isServer` is fixed when the context is created, because it selects the +underlying OpenSSL method. Passing a server context to [`dtls.connect()`][], +or a client context to [`dtls.listen()`][], throws. + +```mjs +import { connect, createSecureContext, listen } from 'node:dtls'; +import { readFileSync } from 'node:fs'; + +const serverContext = createSecureContext({ + cert: readFileSync('server-cert.pem'), + key: readFileSync('server-key.pem'), + isServer: true, +}); + +// One context, several endpoints. +const a = listen(onsession, { secureContext: serverContext, port: 5684 }); +const b = listen(onsession, { secureContext: serverContext, port: 5685 }); + +const clientContext = createSecureContext({ + ca: readFileSync('ca-cert.pem'), +}); + +// One context, many connections, each verified against its own name. +const s1 = connect('192.0.2.1', 5684, { + secureContext: clientContext, + servername: 'a.example.com', +}); +const s2 = connect('192.0.2.2', 5684, { + secureContext: clientContext, + servername: 'b.example.com', +}); +``` + +## Server Name Indication + +An endpoint can serve more than one identity by giving `listen()` an `sni` +map, or a function. Each key of a map is a host name and each value is either +a +[`DTLSSecureContext`][] created with `isServer: true`, or a plain object of +the same options [`dtls.createSecureContext()`][] takes: + +```mjs +import { createSecureContext, listen } from 'node:dtls'; +import { readFileSync } from 'node:fs'; + +const endpoint = listen(onsession, { + cert: readFileSync('default-cert.pem'), + key: readFileSync('default-key.pem'), + port: 5684, + sni: { + 'api.example.com': { + cert: readFileSync('api-cert.pem'), + key: readFileSync('api-key.pem'), + }, + 'www.example.com': createSecureContext({ + cert: readFileSync('www-cert.pem'), + key: readFileSync('www-key.pem'), + isServer: true, + }), + '*': { + cert: readFileSync('default-cert.pem'), + key: readFileSync('default-key.pem'), + }, + }, +}); +``` + +The `'*'` key is the fallback, used when the client's name matches nothing and +when the client sends no name at all. **Without it, an unmatched name is +refused with an `unrecognized_name` alert** rather than falling back to the +endpoint's own `cert` and `key`; providing an `sni` map is taken to mean that +only the names in it are served. [`tls.createServer()`][] differs here: its +`SNICallback` falls back to the default identity silently. + +Verification follows the selected identity, so an entry carrying its own `ca` +accepts only client certificates issued under it. `requestCert` and +`rejectUnauthorized` are not per-identity: they belong to the endpoint and +apply to every name it serves. + +A function may be given instead of a map, for identities that are chosen +rather than enumerated: + +```mjs +listen(onsession, { + port: 5684, + cert, + key, + sni: (servername) => contexts.get(servername), +}); +``` + +It is called with the name the client asked for, or `undefined` if the client +sent no SNI extension, and returns what a map entry holds: a +[`dtls.createSecureContext()`][] result or the options to build one. Returning +nothing declines the name, which is refused exactly as an unmatched map with no +`'*'` entry is, rather than falling back to the endpoint's own certificate. + +The function runs during the handshake and must return synchronously, so it +cannot consult a database. Returning a prepared context is worth doing: +building one from options parses the certificate again on every handshake. + +An exception thrown by the function fails that handshake and is reported to the +session's error handler, like any other handshake failure. It does not reach +the process as an uncaught exception. + +The certificate and the cipher list both follow the selected context. +Pre-shared keys do not. OpenSSL installs the PSK callbacks on the connection +when it is created, before any name is known, and selecting an identity does +not replace them, so the keys a server accepts are always the endpoint's own. +A `psk` given on an SNI identity is never consulted, and an identity cannot be +served over PSK alone. + +`sni` belongs to the secure context rather than to the endpoint, so it can be +given to [`dtls.createSecureContext()`][] and cannot be combined with a +`secureContext` that already exists. Applying it to a prepared context would +reconfigure that context for every endpoint sharing it, and the identities a +server serves are part of what its context is. + +A connection refused for an unrecognized name still reaches the `listen()` +callback: the session exists once the client's address is validated, which +happens before the name is examined. It then fails like any other handshake +failure. + +## Denial of service + +Cookie exchange proves a peer can receive at its claimed address, but it does +not limit how many sessions that peer may then establish, and each session +holds a TLS state machine, two buffers and a timer. `maxSessions` bounds the +total; `maxSessionsPerHost` is what prevents one peer from taking all of it. +A peer refused by either cap is answered with silence rather than an alert, +because replying to an address that has not completed cookie exchange would +create an amplification vector; a legitimate client retransmits and is +admitted once there is room. Refusals are counted by +[`endpointStats.serverRefusedCount`][]. + +Deployments serving many clients behind a single NAT may need to raise +`maxSessionsPerHost`. + +## Handshake timeout + +A handshake that never finishes is abandoned after `handshakeTimeout` +milliseconds, and its session error is `DTLS handshake timeout`. + +OpenSSL already gives up on its own, but only after twelve retransmits on a +doubling backoff capped at 60 seconds -- around eight minutes in total. Until +then the session holds its place against `maxSessions` (see +[Denial of service][]), +so handshakes that are started and abandoned can occupy an endpoint for the +cost of starting them. That needs no spoofing: the peer completes the cookie +exchange and then simply stops. + +The two limits coexist and whichever comes first ends the handshake. The +retransmit schedule itself is untouched, deliberately -- compressing it to +force earlier failure would cause spurious retransmissions on exactly the +lossy links DTLS is meant for. + +The timeout covers resumed and PSK handshakes as well, and stops applying once +the handshake completes; it is not an idle timeout. + +A handshake can stall without either peer being at fault or aware. +DTLS discards records it cannot authenticate rather than answering them +(RFC 6347 section 4.1.2.1), so a mismatched pre-shared key or a cipher list +with nothing in common produces silence rather than an alert. This timeout is +what ends those. + +## Pre-shared keys + +DTLS can authenticate with a key both peers already hold instead of a +certificate (RFC 4279). This is how it is usually deployed to constrained +devices, which frequently have no certificate at all. + +A server gives the identities it accepts; a client gives the one it is. No +certificate is needed on either side: + +```mjs +import { connect, listen } from 'node:dtls'; + +const endpoint = listen(onsession, { + port: 5684, + psk: { 'device-42': deviceKey }, +}); + +const client = connect('192.0.2.1', 5684, { + psk: { identity: 'device-42', key: deviceKey }, +}); +``` + +Either side may pass a function instead, for keys that are looked up or +derived rather than known up front. A server's is called with the identity the +client offered and returns the key, or nothing to refuse it. A client's is +called with the server's identity hint, if it sent one, and returns +`{ identity, key }`: + +```mjs +listen(onsession, { + port: 5684, + psk: (identity) => deriveKey(masterSecret, identity), +}); +``` + +The callback runs during the handshake and must return synchronously, so it +cannot consult a database. Where both are given, the map is checked first and +the callback is only reached when the map has no answer -- a configuration +using only the map never runs JavaScript inside the handshake. + +An exception thrown by the callback fails that handshake and is reported to +the session's error handler. It does not reach the process as an uncaught +exception. + +### Cipher suites + +The default cipher list excludes PSK, so giving `psk` without `ciphers` +enables the PSK suites. Supplying `ciphers` disables that and uses exactly +what was asked for. + +A server keeps the certificate suites as well, since it may serve both kinds +of client on one port. A client does not: a client that configured a +pre-shared key and no CA wants the key, and leaving the certificate suites +enabled would let a server choose one, failing the handshake while verifying a +certificate the caller never meant to rely on. + +Forward-secret PSK key exchanges are preferred over plain PSK of the same +strength. Plain PSK derives its keys from the shared secret alone, so anyone +who later learns that key can decrypt traffic they recorded earlier. `RSA-PSK` +is excluded: it needs a certificate and adds no forward secrecy. + +CoAP requires `TLS_PSK_WITH_AES_128_CCM_8` (RFC 7252), whose 64-bit +authentication tag OpenSSL rejects at security level 1 and above. Node.js +default is above it, so that suite has to be asked for explicitly and with the +security level lowered: + +```mjs +listen(onsession, { port: 5684, psk, ciphers: 'PSK-AES128-CCM8@SECLEVEL=0' }); +``` + +### Failure modes + +A wrong key does not produce an error. The identity only names the key, so the +handshake proceeds and the two sides derive different secrets; the first +record that fails authentication is then discarded rather than answered, since +DTLS discards invalid records instead of replying to them (RFC 6347 section +4.1.2.1). Neither peer is told anything and both retransmit. + +A cipher list with nothing in common behaves the same way, which is what makes +the `CCM8` case above present as a stall rather than a rejection. Both are +ended by [`handshakeTimeout`][], after 60 seconds by default. + +An identity the server does not recognise is refused outright, and the client +sees the handshake fail. + +## Session resumption + +A resumed handshake skips the server's certificate, which matters more here +than it does over TCP: the `Certificate` flight is fragmented across several +datagrams, and losing any one of them costs a retransmission timeout. Measured +on loopback, a full handshake has the server send 1850 bytes in 4 packets +against 280 bytes in 3 for a resumed one. + +A client reads [`session.session`][] once the session is open and passes it to +a later [`dtls.connect()`][]: + +```mjs +import { connect } from 'node:dtls'; + +const first = connect('192.0.2.1', 5684, { ca, servername: 'device.example' }); +await first.opened; +const ticket = first.session; // Buffer. +await first.close(); + +const second = connect('192.0.2.1', 5684, { + ca, + servername: 'device.example', + session: ticket, +}); +await second.opened; +console.log(second.reused); // True. +``` + +A session that the server will not accept -- expired, or issued by a different +endpoint -- is not an error. The handshake simply proceeds in full, and +[`session.reused`][] is `false`. + +The cookie exchange still happens for a resumed handshake, so resumption is not +a way around the address validation described under [Denial of service][]. + +### Binding to the authenticated host + +A session may only be resumed against the identity it was authenticated for -- +the `servername`, or the host when there is none. Reusing it for anything else +throws. + +This is not a convenience check. A resumed handshake does not re-send or +re-verify the peer's certificate; it inherits the authenticated identity of the +original session. Replaying a session against a different host would therefore +skip verification while appearing to succeed. For the same reason a `session` +that did not come from [`session.session`][] is rejected outright: nothing +records which identity it belongs to, so it cannot be checked. + +### Ticket keys + +The key that encrypts session tickets is generated at random for each context, +so by default a ticket is only good for the endpoint that issued it and only +until the process restarts. Give every endpoint the same `ticketKeys` to let +tickets be resumed across a restart or a cluster: + +```mjs +import { listen } from 'node:dtls'; +import { randomBytes } from 'node:crypto'; + +const ticketKeys = randomBytes(80); // Share this between processes. +const endpoint = listen(onsession, { cert, key, port: 5684, ticketKeys }); +``` + +The length is OpenSSL's: a key name followed by an HMAC key and an AES key. It +differs from the 48 bytes [`tls.createServer()`][] uses, which is a layout +`node:tls` defines for itself. Supplying the wrong length throws and reports +the length expected. + +Ticket keys are long-lived secrets. Anyone holding them can decrypt tickets and +recover the sessions they protect, so treat them as key material and rotate +them. + +## Class: `DTLSSecureContext` + + + +An opaque, reusable bundle of credentials and TLS settings, created by +[`dtls.createSecureContext()`][]. It cannot be constructed directly. + +### `secureContext.isServer` + +* Returns: {boolean} `true` if the context was created for a server. + ## Class: `DTLSEndpoint` + +* Type: {bigint} The number of datagrams discarded before a handshake was + attempted because they could not be a ClientHello. Read only. + +Datagrams arriving at a listening endpoint that do not match an existing +session are screened for the shape of a DTLS ClientHello record before any +state is allocated for them. A steadily rising value indicates junk or scan +traffic rather than failing clients, which are counted as sessions that never +complete. + +### `endpointStats.serverRefusedCount` + + + +* Type: {bigint} The number of otherwise valid handshake attempts refused + because the endpoint was at `maxSessions` or the peer was at + `maxSessionsPerHost`. Read only. + ### `endpointStats.isConnected` + +* Returns: {X509Certificate|undefined} The peer's certificate, or `undefined` + if the peer sent none. + +An [`X509Certificate`][] for the peer's leaf certificate. The issuer chain is +reachable through its `issuerCertificate` property, and the parsed fields -- +`subject`, `issuer`, `validFrom`, `validTo`, `fingerprint256`, `serialNumber` +and the rest -- are properties of that object. + +Where [`tls.TLSSocket.getPeerCertificate()`][] returns a plain dictionary with +`valid_from`, `valid_to` and a chain walked through `issuerCertificate`, this +returns the same `X509Certificate` class that +[`tls.TLSSocket.getPeerX509Certificate()`][] does. Call `toLegacyObject()` on +it to get the dictionary form. + +The same object is returned on every access once the peer's certificate is +available. + +### `session.session` + + + +* Returns: {Buffer|undefined} An opaque session for resuming this connection + later, or `undefined` on a server session or before the handshake completes. + +Pass it as the `session` option to a later [`dtls.connect()`][]. It is bound to +the host this connection authenticated against and is refused elsewhere; see +[Session resumption][]. + +Server sessions return `undefined`: a server has no identity to bind the value +to, and it is the client that carries a session between connections. + +### `session.reused` + + + +* Returns: {boolean} `true` if this connection resumed an earlier session + rather than performing a full handshake. + +Like [`session.authorized`][], this reads `false` once the session is closed. + +### `session.authorized` + + + +* Returns: {boolean} `true` if the peer presented a certificate chain that + verified against the configured certificate authorities, and, for a client, + matched the requested identity. `false` before the handshake completes. + +### `session.authorizationError` + + + +* Returns: {string|undefined} The short X509 verification error code, for + example `'CERT_HAS_EXPIRED'` or `'HOSTNAME_MISMATCH'`, or `undefined` if the + peer's chain verified. + +A peer that presented no certificate at all reports +`'UNABLE_TO_GET_ISSUER_CERT'`, so this can be used to distinguish "no +certificate" from "a certificate that failed to verify". + +The chain is verified even when `rejectUnauthorized` is `false`; the result is +simply not enforced. That makes these two properties the way to apply a custom +authorization policy: + +```mjs +import { connect } from 'node:dtls'; + +const session = connect('192.0.2.1', 4433, { + ca: [caCert], + servername: 'example.com', + rejectUnauthorized: false, +}); + +await session.opened; + +if (!session.authorized && session.authorizationError !== 'CERT_HAS_EXPIRED') { + await session.close(); +} +``` ### `session.alpnProtocol` -* Returns: {string|undefined} The negotiated ALPN protocol. +* Returns: {string|undefined} The negotiated ALPN protocol, or `undefined` if + ALPN was not used. + +If a server has `alpn` configured and a client offers only protocols the +server does not support, the server sends a fatal `no_application_protocol` +alert and the handshake fails, as required by [RFC 7301][] section 3.2. A +server with no `alpn` configured declines the extension instead, and the +handshake completes with no protocol negotiated. ### `session.srtpProfile` @@ -388,7 +1012,8 @@ live and updated as data flows through the session. ### `session.exportKeyingMaterial(length, label[, context])` -* `length` {number} Number of bytes to export. +* `length` {number} Number of bytes to export. Must be an integer between + `1` and `65536`. * `label` {string} The label for the exported keying material. * `context` {Buffer} Optional context value. * Returns: {Buffer} @@ -397,6 +1022,45 @@ Exports keying material from the DTLS session, as defined in [RFC 5705][]. This is commonly used with DTLS-SRTP to derive encryption keys for media streams. +Throws `ERR_OUT_OF_RANGE` if `length` is outside the accepted range. The upper +bound is not imposed by [RFC 5705][]; it exists so that a caller cannot request +an arbitrarily large allocation, and is far above what any defined exporter +needs (DTLS-SRTP uses 60 bytes). + +### Callback properties + +#### `session.onmessage` + +* {Function} + * `data` {Buffer} + +Set to receive application data from the peer. + +#### `session.onerror` + +* {Function} + * `error` {Error} + +Set to receive error notifications. + +#### `session.onhandshake` + +* {Function} + * `protocol` {string} + +Set to receive handshake completion notifications. + +#### `session.onkeylog` + +* {Function} + * `line` {string} + +Set to receive TLS key log lines (for debugging with Wireshark). + +### `session[Symbol.asyncDispose]()` + +Equivalent to calling `session.close()`. + ## Class: `DTLSSession.Stats`