tls: support TLSv1.3

This introduces TLS1.3 support and makes it the default max protocol,
but also supports CLI/NODE_OPTIONS switches to disable it if necessary.

TLS1.3 is a major update to the TLS protocol, with many security
enhancements. It should be preferred over TLS1.2 whenever possible.

TLS1.3 is different enough that even though the OpenSSL APIs are
technically API/ABI compatible, that when TLS1.3 is negotiated, the
timing of protocol records and of callbacks broke assumptions hard-coded
into the 'tls' module.

This change introduces no API incompatibilities when TLS1.2 is
negotiated. It is the intention that it be backported to current and LTS
release lines with the default maximum TLS protocol reset to 'TLSv1.2'.
This will allow users of those lines to explicitly enable TLS1.3 if they
want.

API incompatibilities between TLS1.2 and TLS1.3 are:

- Renegotiation is not supported by TLS1.3 protocol, attempts to call
`.renegotiate()` will always fail.

- Compiling against a system OpenSSL lower than 1.1.1 is no longer
supported (OpenSSL-1.1.0 used to be supported with configure flags).

- Variations of `conn.write('data'); conn.destroy()` have undefined
behaviour according to the streams API. They may or may not send the
'data', and may or may not cause a ERR_STREAM_DESTROYED error to be
emitted. This has always been true, but conditions under which the write
suceeds is slightly but observably different when TLS1.3 is negotiated
vs when TLS1.2 or below is negotiated.

- If TLS1.3 is negotiated, and a server calls `conn.end()` in its
'secureConnection' listener without any data being written, the client
will not receive session tickets (no 'session' events will be emitted,
and `conn.getSession()` will never return a resumable session).

- The return value of `conn.getSession()` API may not return a resumable
session if called right after the handshake. The effect will be that
clients using the legacy `getSession()` API will resume sessions if
TLS1.2 is negotiated, but will do full handshakes if TLS1.3 is
negotiated.  See https://github.com/nodejs/node/pull/25831 for more
information.

PR-URL: https://github.com/nodejs/node/pull/26209
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Rod Vagg <rod@vagg.org>
This commit is contained in:
Sam Roberts 2018-11-28 17:58:08 -08:00
parent 4306300b5e
commit 42dbaed460
54 changed files with 990 additions and 222 deletions

View File

@ -443,21 +443,43 @@ added: v4.0.0
Specify an alternative default TLS cipher list. Requires Node.js to be built
with crypto support (default).
### `--tls-v1.0`
### `--tls-max-v1.2`
<!-- YAML
added: REPLACEME
-->
Enable TLSv1.0 and greater in default [secureProtocol][]. Use for compatibility
with old TLS clients or servers.
Set default [`maxVersion`][] to `'TLSv1.2'`. Use to disable support for TLSv1.3.
### `--tls-v1.1`
### `--tls-max-v1.3`
<!-- YAML
added: REPLACEME
-->
Enable TLSv1.1 and greater in default [secureProtocol][]. Use for compatibility
with old TLS clients or servers.
Set default [`maxVersion`][] to `'TLSv1.3'`. Use to enable support for TLSv1.3.
### `--tls-min-v1.0`
<!-- YAML
added: REPLACEME
-->
Set default [`minVersion`][] to `'TLSv1'`. Use for compatibility with old TLS
clients or servers.
### `--tls-min-v1.1`
<!-- YAML
added: REPLACEME
-->
Set default [`minVersion`][] to `'TLSv1.1'`. Use for compatibility with old TLS
clients or servers.
### `--tls-min-v1.3`
<!-- YAML
added: REPLACEME
-->
Set default [`minVersion`][] to `'TLSv1.3'`. Use to disable support for TLSv1.2
in favour of TLSv1.3, which is more secure.
### `--trace-deprecation`
<!-- YAML
@ -896,6 +918,8 @@ greater than `4` (its current default value). For more information, see the
[`--openssl-config`]: #cli_openssl_config_file
[`Buffer`]: buffer.html#buffer_class_buffer
[`SlowBuffer`]: buffer.html#buffer_class_slowbuffer
[`maxVersion`]: tls.html#tls_tls_createsecurecontext_options
[`minVersion`]: tls.html#tls_tls_createsecurecontext_options
[`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn
[Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/
[REPL]: repl.html
@ -907,4 +931,3 @@ greater than `4` (its current default value). For more information, see the
[experimental ECMAScript Module]: esm.html#esm_loader_hooks
[libuv threadpool documentation]: http://docs.libuv.org/en/latest/threadpool.html
[remote code execution]: https://www.owasp.org/index.php/Code_Injection
[secureProtocol]: tls.html#tls_tls_createsecurecontext_options

View File

@ -104,6 +104,9 @@ not required and a default ECDHE curve will be used. The `ecdhCurve` property
can be used when creating a TLS Server to specify the list of names of supported
curves to use, see [`tls.createServer()`] for more info.
Perfect Forward Secrecy was optional up to TLSv1.2, but it is not optional for
TLSv1.3, because all TLSv1.3 cipher suites use ECDHE.
### ALPN and SNI
<!-- type=misc -->
@ -136,6 +139,8 @@ threshold is exceeded. The limits are configurable:
The default renegotiation limits should not be modified without a full
understanding of the implications and risks.
TLSv1.3 does not support renegotiation.
### Session Resumption
Establishing a TLS session can be relatively slow. The process can be sped
@ -176,6 +181,10 @@ as for resumption with session tickets. For debugging, if
[`tls.TLSSocket.getTLSTicket()`][] returns a value, the session data contains a
ticket, otherwise it contains client-side session state.
With TLSv1.3, be aware that multiple tickets may be sent by the server,
resulting in multiple `'session'` events, see [`'session'`][] for more
information.
Single process servers need no specific implementation to use session tickets.
To use session tickets across server restarts or load balancers, servers must
all have the same ticket keys. There are three 16-byte keys internally, but the
@ -230,6 +239,9 @@ Node.js is built with a default suite of enabled and disabled TLS ciphers.
Currently, the default cipher suite is:
```txt
TLS_AES_256_GCM_SHA384:
TLS_CHACHA20_POLY1305_SHA256:
TLS_AES_128_GCM_SHA256:
ECDHE-RSA-AES128-GCM-SHA256:
ECDHE-ECDSA-AES128-GCM-SHA256:
ECDHE-RSA-AES256-GCM-SHA384:
@ -270,7 +282,19 @@ The default can also be replaced on a per client or server basis using the
in [`tls.createServer()`], [`tls.connect()`], and when creating new
[`tls.TLSSocket`]s.
Consult [OpenSSL cipher list format documentation][] for details on the format.
The ciphers list can contain a mixture of TLSv1.3 cipher suite names, the ones
that start with `'TLS_'`, and specifications for TLSv1.2 and below cipher
suites. The TLSv1.2 ciphers support a legacy specification format, consult
the OpenSSL [cipher list format][] documentation for details, but those
specifications do *not* apply to TLSv1.3 ciphers. The TLSv1.3 suites can only
be enabled by including their full name in the cipher list. They cannot, for
example, be enabled or disabled by using the legacy TLSv1.2 `'EECDH'` or
`'!EECDH'` specification.
Despite the relative order of TLSv1.3 and TLSv1.2 cipher suites, the TLSv1.3
protocol is significantly more secure than TLSv1.2, and will always be chosen
over TLSv1.2 if the handshake indicates it is supported, and if any TLSv1.3
cipher suites are enabled.
The default cipher suite included within Node.js has been carefully
selected to reflect current security best practices and risk mitigation.
@ -289,7 +313,18 @@ Old clients that rely on insecure and deprecated RC4 or DES-based ciphers
(like Internet Explorer 6) cannot complete the handshaking process with
the default configuration. If these clients _must_ be supported, the
[TLS recommendations] may offer a compatible cipher suite. For more details
on the format, see the [OpenSSL cipher list format documentation].
on the format, see the OpenSSL [cipher list format][] documentation.
There are only 5 TLSv1.3 cipher suites:
- `'TLS_AES_256_GCM_SHA384'`
- `'TLS_CHACHA20_POLY1305_SHA256'`
- `'TLS_AES_128_GCM_SHA256'`
- `'TLS_AES_128_CCM_SHA256'`
- `'TLS_AES_128_CCM_8_SHA256'`
The first 3 are enabled by default. The last 2 `CCM`-based suites are supported
by TLSv1.3 because they may be more performant on constrained systems, but they
are not enabled by default since they offer less security.
## Class: tls.Server
<!-- YAML
@ -634,11 +669,11 @@ On the client, the `session` can be provided to the `session` option of
See [Session Resumption][] for more information.
Note: For TLS1.2 and below, [`tls.TLSSocket.getSession()`][] can be called once
the handshake is complete. For TLS1.3, only ticket based resumption is allowed
Note: For TLSv1.2 and below, [`tls.TLSSocket.getSession()`][] can be called once
the handshake is complete. For TLSv1.3, only ticket based resumption is allowed
by the protocol, multiple tickets are sent, and the tickets aren't sent until
later, after the handshake completes, so it is necessary to wait for the
`'session'` event to get a resumable session. Future-proof applications are
`'session'` event to get a resumable session. Applications are
recommended to use the `'session'` event instead of `getSession()` to ensure
they will work for all TLS protocol versions. Applications that only expect to
get or use 1 session should listen for this event only once:
@ -731,7 +766,7 @@ changes:
Returns an object containing information on the negotiated cipher suite.
For example: `{ name: 'AES256-SHA', version: 'TLSv1/SSLv3' }`.
For example: `{ name: 'AES256-SHA', version: 'TLSv1.2' }`.
See
[OpenSSL](https://www.openssl.org/docs/man1.1.1/man3/SSL_CIPHER_get_name.html)
@ -904,12 +939,13 @@ be returned for server sockets or disconnected client sockets.
Protocol versions are:
* `'SSLv3'`
* `'TLSv1'`
* `'TLSv1.1'`
* `'TLSv1.2'`
* `'SSLv3'`
* `'TLSv1.3'`
See <https://www.openssl.org/docs/man1.1.0/ssl/SSL_get_version.html> for more
See <https://www.openssl.org/docs/man1.1.1/man3/SSL_get_version.html> for more
information.
### tlsSocket.getSession()
@ -926,8 +962,8 @@ for debugging.
See [Session Resumption][] for more information.
Note: `getSession()` works only for TLS1.2 and below. Future-proof applications
should use the [`'session'`][] event.
Note: `getSession()` works only for TLSv1.2 and below. For TLSv1.3, applications
must use the [`'session'`][] event (it also works for TLSv1.2 and below).
### tlsSocket.getTLSTicket()
<!-- YAML
@ -1009,8 +1045,12 @@ added: v0.11.8
verification fails; `err.code` contains the OpenSSL error code. **Default:**
`true`.
* `requestCert`
* `callback` {Function} A function that will be called when the renegotiation
request has been completed.
* `callback` {Function} If `renegotiate()` returned `true`, callback is
attached once to the `'secure'` event. If it returned `false`, it will be
called in the next tick with `ERR_TLS_RENEGOTIATE`, unless the `tlsSocket`
has been destroyed, in which case it will not be called at all.
* Returns: {boolean} `true` if renegotiation was initiated, `false` otherwise.
The `tlsSocket.renegotiate()` method initiates a TLS renegotiation process.
Upon completion, the `callback` function will be passed a single argument
@ -1022,6 +1062,9 @@ connection has been established.
When running as the server, the socket will be destroyed with an error after
`handshakeTimeout` timeout.
For TLSv1.3, renegotiation cannot be initiated, it is not supported by the
protocol.
### tlsSocket.setMaxSendFragment(size)
<!-- YAML
added: v0.11.11
@ -1220,6 +1263,9 @@ argument.
<!-- YAML
added: v0.11.13
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/26209
description: TLSv1.3 support added.
- version: v11.5.0
pr-url: https://github.com/nodejs/node/pull/24733
description: The `ca:` option now supports `BEGIN TRUSTED CERTIFICATE`.
@ -1310,15 +1356,22 @@ changes:
`object.passphrase` is optional. Encrypted keys will be decrypted with
`object.passphrase` if provided, or `options.passphrase` if it is not.
* `maxVersion` {string} Optionally set the maximum TLS version to allow. One
of `TLSv1.2'`, `'TLSv1.1'`, or `'TLSv1'`. Cannot be specified along with the
`secureProtocol` option, use one or the other. **Default:** `'TLSv1.2'`.
of `TLSv1.3`, `TLSv1.2'`, `'TLSv1.1'`, or `'TLSv1'`. Cannot be specified
along with the `secureProtocol` option, use one or the other.
**Default:** `'TLSv1.3'`, unless changed using CLI options. Using
`--tls-max-v1.2` sets the default to `'TLSv1.2`'. Using `--tls-max-v1.3`
sets the default to `'TLSv1.3'`. If multiple of the options are provided,
the highest maximum is used.
* `minVersion` {string} Optionally set the minimum TLS version to allow. One
of `TLSv1.2'`, `'TLSv1.1'`, or `'TLSv1'`. Cannot be specified along with the
`secureProtocol` option, use one or the other. It is not recommended to use
less than TLSv1.2, but it may be required for interoperability.
of `TLSv1.3`, `TLSv1.2'`, `'TLSv1.1'`, or `'TLSv1'`. Cannot be specified
along with the `secureProtocol` option, use one or the other. It is not
recommended to use less than TLSv1.2, but it may be required for
interoperability.
**Default:** `'TLSv1.2'`, unless changed using CLI options. Using
`--tls-v1.0` changes the default to `'TLSv1'`. Using `--tls-v1.1` changes
the default to `'TLSv1.1'`.
`--tls-min-v1.0` sets the default to `'TLSv1'`. Using `--tls-min-v1.1` sets
the default to `'TLSv1.1'`. Using `--tls-min-v1.3` sets the default to
`'TLSv1.3'`. If multiple of the options are provided, the lowest minimum is
used.
* `passphrase` {string} Shared passphrase used for a single private key and/or
a PFX.
* `pfx` {string|string[]|Buffer|Buffer[]|Object[]} PFX or PKCS12 encoded
@ -1334,12 +1387,15 @@ changes:
which is not usually necessary. This should be used carefully if at all!
Value is a numeric bitmask of the `SSL_OP_*` options from
[OpenSSL Options][].
* `secureProtocol` {string} The TLS protocol version to use. The possible
values are listed as [SSL_METHODS][], use the function names as strings. For
example, use `'TLSv1_1_method'` to force TLS version 1.1, or `'TLS_method'`
to allow any TLS protocol version. It is not recommended to use TLS versions
less than 1.2, but it may be required for interoperability. **Default:**
none, see `minVersion`.
* `secureProtocol` {string} Legacy mechanism to select the TLS protocol
version to use, it does not support independent control of the minimum and
maximum version, and does not support limiting the protocol to TLSv1.3. Use
`minVersion` and `maxVersion` instead. The possible values are listed as
[SSL_METHODS][], use the function names as strings. For example, use
`'TLSv1_1_method'` to force TLS version 1.1, or `'TLS_method'` to allow any
TLS protocol version up to TLSv1.3. It is not recommended to use TLS
versions less than 1.2, but it may be required for interoperability.
**Default:** none, see `minVersion`.
* `sessionIdContext` {string} Opaque identifier used by servers to ensure
session state is not shared between applications. Unused by clients.
@ -1457,10 +1513,15 @@ added: v0.10.2
* Returns: {string[]}
Returns an array with the names of the supported SSL ciphers.
Returns an array with the names of the supported TLS ciphers. The names are
lower-case for historical reasons, but must be uppercased to be used in
the `ciphers` option of [`tls.createSecureContext()`][].
Cipher names that start with `'tls_'` are for TLSv1.3, all the others are for
TLSv1.2 and below.
```js
console.log(tls.getCiphers()); // ['AES128-SHA', 'AES256-SHA', ...]
console.log(tls.getCiphers()); // ['aes128-gcm-sha256', 'aes128-sha', ...]
```
## tls.DEFAULT_ECDH_CURVE
@ -1619,16 +1680,16 @@ where `secureSocket` has the same API as `pair.cleartext`.
[Forward secrecy]: https://en.wikipedia.org/wiki/Perfect_forward_secrecy
[OCSP request]: https://en.wikipedia.org/wiki/OCSP_stapling
[OpenSSL Options]: crypto.html#crypto_openssl_options
[OpenSSL cipher list format documentation]: https://www.openssl.org/docs/man1.1.0/apps/ciphers.html#CIPHER-LIST-FORMAT
[Perfect Forward Secrecy]: #tls_perfect_forward_secrecy
[RFC 2246]: https://www.ietf.org/rfc/rfc2246.txt
[RFC 5077]: https://tools.ietf.org/html/rfc5077
[RFC 5929]: https://tools.ietf.org/html/rfc5929
[SSL_METHODS]: https://www.openssl.org/docs/man1.1.0/ssl/ssl.html#Dealing-with-Protocol-Methods
[SSL_METHODS]: https://www.openssl.org/docs/man1.1.1/man7/ssl.html#Dealing-with-Protocol-Methods
[Session Resumption]: #tls_session_resumption
[Stream]: stream.html#stream_stream
[TLS recommendations]: https://wiki.mozilla.org/Security/Server_Side_TLS
[asn1.js]: https://www.npmjs.com/package/asn1.js
[certificate object]: #tls_certificate_object
[cipher list format]: https://www.openssl.org/docs/man1.1.1/man1/ciphers.html#CIPHER-LIST-FORMAT
[modifying the default cipher suite]: #tls_modifying_the_default_tls_cipher_suite
[specific attacks affecting larger AES key sizes]: https://www.schneier.com/blog/archives/2009/07/another_new_aes.html

View File

@ -236,13 +236,23 @@ Specify process.title on startup.
Specify an alternative default TLS cipher list.
Requires Node.js to be built with crypto support. (Default)
.
.It Fl -tls-v1.0
Enable TLSv1.0 and greater in default secureProtocol. Use for compatibility
with old TLS clients or servers.
.It Fl -tls-max-v1.2
Set default maxVersion to 'TLSv1.2'. Use to disable support for TLSv1.3.
.
.It Fl -tls-v1.1
Enable TLSv1.1 and greater in default secureProtocol. Use for compatibility
with old TLS clients or servers.
.It Fl -tls-max-v1.3
Set default maxVersion to 'TLSv1.3'. Use to enable support for TLSv1.3.
.
.It Fl -tls-min-v1.0
Set default minVersion to 'TLSv1'. Use for compatibility with old TLS clients
or servers.
.
.It Fl -tls-min-v1.1
Set default minVersion to 'TLSv1.1'. Use for compatibility with old TLS clients
or servers.
.
.It Fl -tls-min-v1.3
Set default minVersion to 'TLSv1.3'. Use to disable support for TLSv1.2 in
favour of TLSv1.3, which is more secure.
.
.It Fl -trace-deprecation
Print stack traces for deprecations.

View File

@ -27,6 +27,7 @@ const tls = require('tls');
const {
ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_OPT_VALUE,
ERR_TLS_INVALID_PROTOCOL_VERSION,
ERR_TLS_PROTOCOL_VERSION_CONFLICT,
} = require('internal/errors').codes;
@ -35,6 +36,7 @@ const {
TLS1_VERSION,
TLS1_1_VERSION,
TLS1_2_VERSION,
TLS1_3_VERSION,
} = internalBinding('constants').crypto;
// Lazily loaded from internal/crypto/util.
@ -45,6 +47,7 @@ function toV(which, v, def) {
if (v === 'TLSv1') return TLS1_VERSION;
if (v === 'TLSv1.1') return TLS1_1_VERSION;
if (v === 'TLSv1.2') return TLS1_2_VERSION;
if (v === 'TLSv1.3') return TLS1_3_VERSION;
throw new ERR_TLS_INVALID_PROTOCOL_VERSION(v, which);
}
@ -148,10 +151,35 @@ exports.createSecureContext = function createSecureContext(options) {
}
}
if (options.ciphers)
c.context.setCiphers(options.ciphers);
else
c.context.setCiphers(tls.DEFAULT_CIPHERS);
if (options.ciphers && typeof options.ciphers !== 'string') {
throw new ERR_INVALID_ARG_TYPE(
'options.ciphers', 'string', options.ciphers);
}
// Work around an OpenSSL API quirk. cipherList is for TLSv1.2 and below,
// cipherSuites is for TLSv1.3 (and presumably any later versions). TLSv1.3
// cipher suites all have a standard name format beginning with TLS_, so split
// the ciphers and pass them to the appropriate API.
const ciphers = (options.ciphers || tls.DEFAULT_CIPHERS).split(':');
const cipherList = ciphers.filter((_) => !_.match(/^TLS_/)).join(':');
const cipherSuites = ciphers.filter((_) => _.match(/^TLS_/)).join(':');
if (cipherSuites === '' && cipherList === '') {
// Specifying empty cipher suites for both TLS1.2 and TLS1.3 is invalid, its
// not possible to handshake with no suites.
throw ERR_INVALID_OPT_VALUE('ciphers', ciphers);
}
c.context.setCipherSuites(cipherSuites);
c.context.setCiphers(cipherList);
if (cipherSuites === '' && c.context.getMaxProto() > TLS1_2_VERSION &&
c.context.getMinProto() < TLS1_3_VERSION)
c.context.setMaxProto(TLS1_2_VERSION);
if (cipherList === '' && c.context.getMinProto() < TLS1_3_VERSION &&
c.context.getMaxProto() > TLS1_2_VERSION)
c.context.setMinProto(TLS1_3_VERSION);
if (options.ecdhCurve === undefined)
c.context.setECDHCurve(tls.DEFAULT_ECDH_CURVE);

View File

@ -65,7 +65,7 @@ let ipServernameWarned = false;
// Server side times how long a handshake is taking to protect against slow
// handshakes being used for DoS.
function onhandshakestart(now) {
debug('onhandshakestart');
debug('server onhandshakestart');
const { lastHandshakeTime } = this;
assert(now >= lastHandshakeTime,
@ -83,6 +83,9 @@ function onhandshakestart(now) {
this.handshakes++;
const owner = this[owner_symbol];
assert(owner._tlsOptions.isServer);
if (this.handshakes > tls.CLIENT_RENEG_LIMIT) {
owner._emitTLSError(new ERR_TLS_SESSION_ATTACK());
return;
@ -93,9 +96,10 @@ function onhandshakestart(now) {
}
function onhandshakedone() {
debug('onhandshakedone');
debug('server onhandshakedone');
const owner = this[owner_symbol];
assert(owner._tlsOptions.isServer);
// `newSession` callback wasn't called yet
if (owner._newSessionPending) {
@ -108,10 +112,15 @@ function onhandshakedone() {
function loadSession(hello) {
debug('server onclienthello',
'sessionid.len', hello.sessionId.length,
'ticket?', hello.tlsTicket
);
const owner = this[owner_symbol];
var once = false;
function onSession(err, session) {
debug('server resumeSession callback(err %j, sess? %s)', err, !!session);
if (once)
return owner.destroy(new ERR_MULTIPLE_CALLBACK());
once = true;
@ -193,6 +202,8 @@ function requestOCSP(socket, info) {
let once = false;
const onOCSP = (err, response) => {
debug('server OCSPRequest done', 'handle?', !!socket._handle, 'once?', once,
'response?', !!response, 'err?', err);
if (once)
return socket.destroy(new ERR_MULTIPLE_CALLBACK());
once = true;
@ -208,6 +219,7 @@ function requestOCSP(socket, info) {
requestOCSPDone(socket);
};
debug('server oncertcb emit OCSPRequest');
socket.server.emit('OCSPRequest',
ctx.getCertificate(),
ctx.getIssuer(),
@ -215,16 +227,17 @@ function requestOCSP(socket, info) {
}
function requestOCSPDone(socket) {
debug('server certcb done');
try {
socket._handle.certCbDone();
} catch (e) {
debug('server certcb done errored', e);
socket.destroy(e);
}
}
function onnewsessionclient(sessionId, session) {
debug('client onnewsessionclient', sessionId, session);
debug('client emit session');
const owner = this[owner_symbol];
owner.emit('session', session);
}
@ -233,8 +246,9 @@ function onnewsession(sessionId, session) {
debug('onnewsession');
const owner = this[owner_symbol];
// XXX(sam) no server to emit the event on, but handshake won't continue
// unless newSessionDone() is called, should it be?
// TODO(@sam-github) no server to emit the event on, but handshake won't
// continue unless newSessionDone() is called, should it be, or is that
// situation unreachable, or only occurring during shutdown?
if (!owner.server)
return;
@ -263,11 +277,15 @@ function onnewsession(sessionId, session) {
function onocspresponse(resp) {
debug('client onocspresponse');
this[owner_symbol].emit('OCSPResponse', resp);
}
function onerror(err) {
const owner = this[owner_symbol];
debug('%s onerror %s had? %j',
owner._tlsOptions.isServer ? 'server' : 'client', err,
owner._hadError);
if (owner._hadError)
return;
@ -285,7 +303,7 @@ function onerror(err) {
// Ignore server's authorization errors
owner.destroy();
} else {
// Throw error
// Emit error
owner._emitTLSError(err);
}
}
@ -293,6 +311,11 @@ function onerror(err) {
// Used by both client and server TLSSockets to start data flowing from _handle,
// read(0) causes a StreamBase::ReadStart, via Socket._read.
function initRead(tlsSocket, socket) {
debug('%s initRead',
tlsSocket._tlsOptions.isServer ? 'server' : 'client',
'handle?', !!tlsSocket._handle,
'buffered?', !!socket && socket.readableLength
);
// If we were destroyed already don't bother reading
if (!tlsSocket._handle)
return;
@ -493,12 +516,17 @@ TLSSocket.prototype._destroySSL = function _destroySSL() {
this.ssl = null;
};
// Constructor guts, arbitrarily factored out.
TLSSocket.prototype._init = function(socket, wrap) {
var options = this._tlsOptions;
var ssl = this._handle;
this.server = options.server;
debug('%s _init',
options.isServer ? 'server' : 'client',
'handle?', !!ssl
);
// Clients (!isServer) always request a cert, servers request a client cert
// only on explicit configuration.
const requestCert = !!options.requestCert || !options.isServer;
@ -529,7 +557,10 @@ TLSSocket.prototype._init = function(socket, wrap) {
}
} else {
ssl.onhandshakestart = noop;
ssl.onhandshakedone = this._finishInit.bind(this);
ssl.onhandshakedone = () => {
debug('client onhandshakedone');
this._finishInit();
};
ssl.onocspresponse = onocspresponse;
if (options.session)
@ -600,6 +631,11 @@ TLSSocket.prototype.renegotiate = function(options, callback) {
if (callback !== undefined && typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK();
debug('%s renegotiate()',
this._tlsOptions.isServer ? 'server' : 'client',
'destroyed?', this.destroyed
);
if (this.destroyed)
return;
@ -667,9 +703,25 @@ TLSSocket.prototype._releaseControl = function() {
};
TLSSocket.prototype._finishInit = function() {
debug('secure established');
// Guard against getting onhandshakedone() after .destroy().
// * 1.2: If destroy() during onocspresponse(), then write of next handshake
// record fails, the handshake done info callbacks does not occur, and the
// socket closes.
// * 1.3: The OCSP response comes in the same record that finishes handshake,
// so even after .destroy(), the handshake done info callback occurs
// immediately after onocspresponse(). Ignore it.
if (!this._handle)
return;
this.alpnProtocol = this._handle.getALPNNegotiatedProtocol();
this.servername = this._handle.getServername();
debug('%s _finishInit',
this._tlsOptions.isServer ? 'server' : 'client',
'handle?', !!this._handle,
'alpn', this.alpnProtocol,
'servername', this.servername);
this._secureEstablished = true;
if (this._tlsOptions.handshakeTimeout > 0)
this.setTimeout(0, this._handleTimeout);
@ -677,6 +729,12 @@ TLSSocket.prototype._finishInit = function() {
};
TLSSocket.prototype._start = function() {
debug('%s _start',
this._tlsOptions.isServer ? 'server' : 'client',
'handle?', !!this._handle,
'connecting?', this.connecting,
'requestOCSP?', !!this._tlsOptions.requestOCSP,
);
if (this.connecting) {
this.once('connect', this._start);
return;
@ -686,7 +744,6 @@ TLSSocket.prototype._start = function() {
if (!this._handle)
return;
debug('start');
if (this._tlsOptions.requestOCSP)
this._handle.requestOCSP();
this._handle.start();
@ -765,13 +822,16 @@ function onServerSocketSecure() {
}
}
if (!this.destroyed && this._releaseControl())
if (!this.destroyed && this._releaseControl()) {
debug('server emit secureConnection');
this._tlsOptions.server.emit('secureConnection', this);
}
}
function onSocketTLSError(err) {
if (!this._controlReleased && !this[kErrorEmitted]) {
this[kErrorEmitted] = true;
debug('server emit tlsClientError:', err);
this._tlsOptions.server.emit('tlsClientError', err, this);
}
}
@ -792,6 +852,7 @@ function onSocketClose(err) {
}
function tlsConnectionListener(rawSocket) {
debug('net.Server.on(connection): new TLSSocket');
const socket = new TLSSocket(rawSocket, {
secureContext: this._sharedCreds,
isServer: true,
@ -1180,6 +1241,7 @@ function onConnectSecure() {
const ekeyinfo = this.getEphemeralKeyInfo();
if (ekeyinfo.type === 'DH' && ekeyinfo.size < options.minDHSize) {
const err = new ERR_TLS_DH_PARAM_SIZE(ekeyinfo.size);
debug('client emit:', err);
this.emit('error', err);
this.destroy();
return;
@ -1206,10 +1268,12 @@ function onConnectSecure() {
this.destroy(verifyError);
return;
} else {
debug('client emit secureConnect');
this.emit('secureConnect');
}
} else {
this.authorized = true;
debug('client emit secureConnect');
this.emit('secureConnect');
}

View File

@ -30,6 +30,8 @@ const kAfterAsyncWrite = Symbol('kAfterAsyncWrite');
const kHandle = Symbol('kHandle');
const kSession = Symbol('kSession');
const debug = require('util').debuglog('stream');
function handleWriteReq(req, data, encoding) {
const { handle } = req;
@ -66,6 +68,8 @@ function handleWriteReq(req, data, encoding) {
}
function onWriteComplete(status) {
debug('onWriteComplete', status, this.error);
const stream = this.handle[owner_symbol];
if (stream.destroyed) {

View File

@ -54,15 +54,25 @@ exports.DEFAULT_CIPHERS =
exports.DEFAULT_ECDH_CURVE = 'auto';
exports.DEFAULT_MAX_VERSION = 'TLSv1.2';
exports.DEFAULT_MAX_VERSION = 'TLSv1.3';
if (getOptionValue('--tls-v1.0'))
if (getOptionValue('--tls-min-v1.0'))
exports.DEFAULT_MIN_VERSION = 'TLSv1';
else if (getOptionValue('--tls-v1.1'))
else if (getOptionValue('--tls-min-v1.1'))
exports.DEFAULT_MIN_VERSION = 'TLSv1.1';
else if (getOptionValue('--tls-min-v1.3'))
exports.DEFAULT_MIN_VERSION = 'TLSv1.3';
else
exports.DEFAULT_MIN_VERSION = 'TLSv1.2';
if (getOptionValue('--tls-max-v1.3'))
exports.DEFAULT_MAX_VERSION = 'TLSv1.3';
else if (getOptionValue('--tls-max-v1.2'))
exports.DEFAULT_MAX_VERSION = 'TLSv1.2';
else
exports.DEFAULT_MAX_VERSION = 'TLSv1.3'; // Will depend on node version.
exports.getCiphers = internalUtil.cachedResult(
() => internalUtil.filterDuplicateStrings(binding.getSSLCiphers(), true)
);

View File

@ -1245,6 +1245,7 @@ void DefineCryptoConstants(Local<Object> target) {
NODE_DEFINE_CONSTANT(target, TLS1_VERSION);
NODE_DEFINE_CONSTANT(target, TLS1_1_VERSION);
NODE_DEFINE_CONSTANT(target, TLS1_2_VERSION);
NODE_DEFINE_CONSTANT(target, TLS1_3_VERSION);
#endif
NODE_DEFINE_CONSTANT(target, INT_MAX);
}

View File

@ -41,7 +41,13 @@
#define RSA_PSS_SALTLEN_AUTO -2
#endif
#define DEFAULT_CIPHER_LIST_CORE "ECDHE-RSA-AES128-GCM-SHA256:" \
// TLSv1.3 suites start with TLS_, and are the OpenSSL defaults, see:
// https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_ciphersuites.html
#define DEFAULT_CIPHER_LIST_CORE \
"TLS_AES_256_GCM_SHA384:" \
"TLS_CHACHA20_POLY1305_SHA256:" \
"TLS_AES_128_GCM_SHA256:" \
"ECDHE-RSA-AES128-GCM-SHA256:" \
"ECDHE-ECDSA-AES128-GCM-SHA256:" \
"ECDHE-RSA-AES256-GCM-SHA384:" \
"ECDHE-ECDSA-AES256-GCM-SHA384:" \

View File

@ -339,9 +339,14 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) {
env->SetProtoMethod(t, "addCACert", AddCACert);
env->SetProtoMethod(t, "addCRL", AddCRL);
env->SetProtoMethod(t, "addRootCerts", AddRootCerts);
env->SetProtoMethod(t, "setCipherSuites", SetCipherSuites);
env->SetProtoMethod(t, "setCiphers", SetCiphers);
env->SetProtoMethod(t, "setECDHCurve", SetECDHCurve);
env->SetProtoMethod(t, "setDHParam", SetDHParam);
env->SetProtoMethod(t, "setMaxProto", SetMaxProto);
env->SetProtoMethod(t, "setMinProto", SetMinProto);
env->SetProtoMethod(t, "getMaxProto", GetMaxProto);
env->SetProtoMethod(t, "getMinProto", GetMinProto);
env->SetProtoMethod(t, "setOptions", SetOptions);
env->SetProtoMethod(t, "setSessionIdContext", SetSessionIdContext);
env->SetProtoMethod(t, "setSessionTimeout", SetSessionTimeout);
@ -392,6 +397,9 @@ void SecureContext::New(const FunctionCallbackInfo<Value>& args) {
new SecureContext(env, args.This());
}
// A maxVersion of 0 means "any", but OpenSSL may support TLS versions that
// Node.js doesn't, so pin the max to what we do support.
const int MAX_SUPPORTED_VERSION = TLS1_3_VERSION;
void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
SecureContext* sc;
@ -406,13 +414,16 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
int max_version = args[2].As<Int32>()->Value();
const SSL_METHOD* method = TLS_method();
if (max_version == 0)
max_version = MAX_SUPPORTED_VERSION;
if (args[0]->IsString()) {
const node::Utf8Value sslmethod(env->isolate(), args[0]);
// Note that SSLv2 and SSLv3 are disallowed but SSLv23_method and friends
// are still accepted. They are OpenSSL's way of saying that all known
// protocols are supported unless explicitly disabled (which we do below
// for SSLv2 and SSLv3.)
// protocols below TLS 1.3 are supported unless explicitly disabled (which
// we do below for SSLv2 and SSLv3.)
if (strcmp(*sslmethod, "SSLv2_method") == 0) {
THROW_ERR_TLS_INVALID_PROTOCOL_METHOD(env, "SSLv2 methods disabled");
return;
@ -432,21 +443,23 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
THROW_ERR_TLS_INVALID_PROTOCOL_METHOD(env, "SSLv3 methods disabled");
return;
} else if (strcmp(*sslmethod, "SSLv23_method") == 0) {
// noop
max_version = TLS1_2_VERSION;
} else if (strcmp(*sslmethod, "SSLv23_server_method") == 0) {
max_version = TLS1_2_VERSION;
method = TLS_server_method();
} else if (strcmp(*sslmethod, "SSLv23_client_method") == 0) {
max_version = TLS1_2_VERSION;
method = TLS_client_method();
} else if (strcmp(*sslmethod, "TLS_method") == 0) {
min_version = 0;
max_version = 0;
max_version = MAX_SUPPORTED_VERSION;
} else if (strcmp(*sslmethod, "TLS_server_method") == 0) {
min_version = 0;
max_version = 0;
max_version = MAX_SUPPORTED_VERSION;
method = TLS_server_method();
} else if (strcmp(*sslmethod, "TLS_client_method") == 0) {
min_version = 0;
max_version = 0;
max_version = MAX_SUPPORTED_VERSION;
method = TLS_client_method();
} else if (strcmp(*sslmethod, "TLSv1_method") == 0) {
min_version = TLS1_VERSION;
@ -510,12 +523,6 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
SSL_SESS_CACHE_NO_AUTO_CLEAR);
SSL_CTX_set_min_proto_version(sc->ctx_.get(), min_version);
if (max_version == 0) {
// Selecting some secureProtocol methods allows the TLS version to be "any
// supported", but we don't support TLSv1.3, even if OpenSSL does.
max_version = TLS1_2_VERSION;
}
SSL_CTX_set_max_proto_version(sc->ctx_.get(), max_version);
// OpenSSL 1.1.0 changed the ticket key size, but the OpenSSL 1.0.x size was
@ -936,42 +943,54 @@ void SecureContext::AddRootCerts(const FunctionCallbackInfo<Value>& args) {
}
void SecureContext::SetCipherSuites(const FunctionCallbackInfo<Value>& args) {
// BoringSSL doesn't allow API config of TLS1.3 cipher suites.
#ifndef OPENSSL_IS_BORINGSSL
SecureContext* sc;
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
Environment* env = sc->env();
ClearErrorOnReturn clear_error_on_return;
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsString());
const node::Utf8Value ciphers(args.GetIsolate(), args[0]);
if (!SSL_CTX_set_ciphersuites(sc->ctx_.get(), *ciphers)) {
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
if (!err) {
// This would be an OpenSSL bug if it happened.
return env->ThrowError("Failed to set ciphers");
}
return ThrowCryptoError(env, err);
}
#endif
}
void SecureContext::SetCiphers(const FunctionCallbackInfo<Value>& args) {
SecureContext* sc;
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
Environment* env = sc->env();
ClearErrorOnReturn clear_error_on_return;
if (args.Length() != 1) {
return THROW_ERR_MISSING_ARGS(env, "Ciphers argument is mandatory");
}
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsString());
THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "Ciphers");
// Note: set_ciphersuites() is for TLSv1.3 and was introduced in openssl
// 1.1.1, set_cipher_list() is for TLSv1.2 and earlier.
//
// In openssl 1.1.0, set_cipher_list() would error if it resulted in no
// TLSv1.2 (and earlier) cipher suites, and there is no TLSv1.3 support.
//
// In openssl 1.1.1, set_cipher_list() will not error if it results in no
// TLSv1.2 cipher suites if there are any TLSv1.3 cipher suites, which there
// are by default. There will be an error later, during the handshake, but
// that results in an async error event, rather than a sync error thrown,
// which is a semver-major change for the tls API.
//
// Since we don't currently support TLSv1.3, work around this by removing the
// TLSv1.3 cipher suites, so we get backwards compatible synchronous errors.
const node::Utf8Value ciphers(args.GetIsolate(), args[0]);
if (
#if defined(TLS1_3_VERSION) && !defined(OPENSSL_IS_BORINGSSL)
!SSL_CTX_set_ciphersuites(sc->ctx_.get(), "") ||
#endif
!SSL_CTX_set_cipher_list(sc->ctx_.get(), *ciphers)) {
if (!SSL_CTX_set_cipher_list(sc->ctx_.get(), *ciphers)) {
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
if (!err) {
// This would be an OpenSSL bug if it happened.
return env->ThrowError("Failed to set ciphers");
}
if (strlen(*ciphers) == 0 && ERR_GET_REASON(err) == SSL_R_NO_CIPHER_MATCH) {
// TLS1.2 ciphers were deliberately cleared, so don't consider
// SSL_R_NO_CIPHER_MATCH to be an error (this is how _set_cipher_suites()
// works). If the user actually sets a value (like "no-such-cipher"), then
// that's actually an error.
return;
}
return ThrowCryptoError(env, err);
}
}
@ -1040,6 +1059,56 @@ void SecureContext::SetDHParam(const FunctionCallbackInfo<Value>& args) {
}
void SecureContext::SetMinProto(const FunctionCallbackInfo<Value>& args) {
SecureContext* sc;
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsInt32());
int version = args[0].As<Int32>()->Value();
CHECK(SSL_CTX_set_min_proto_version(sc->ctx_.get(), version));
}
void SecureContext::SetMaxProto(const FunctionCallbackInfo<Value>& args) {
SecureContext* sc;
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsInt32());
int version = args[0].As<Int32>()->Value();
CHECK(SSL_CTX_set_max_proto_version(sc->ctx_.get(), version));
}
void SecureContext::GetMinProto(const FunctionCallbackInfo<Value>& args) {
SecureContext* sc;
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
CHECK_EQ(args.Length(), 0);
long version = // NOLINT(runtime/int)
SSL_CTX_get_min_proto_version(sc->ctx_.get());
args.GetReturnValue().Set(static_cast<uint32_t>(version));
}
void SecureContext::GetMaxProto(const FunctionCallbackInfo<Value>& args) {
SecureContext* sc;
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
CHECK_EQ(args.Length(), 0);
long version = // NOLINT(runtime/int)
SSL_CTX_get_max_proto_version(sc->ctx_.get());
args.GetReturnValue().Set(static_cast<uint32_t>(version));
}
void SecureContext::SetOptions(const FunctionCallbackInfo<Value>& args) {
SecureContext* sc;
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
@ -1259,6 +1328,7 @@ void SecureContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
Environment* env = wrap->env();
// TODO(@sam-github) Move type and len check to js, and CHECK() in C++.
if (args.Length() < 1) {
return THROW_ERR_MISSING_ARGS(env, "Ticket keys argument is mandatory");
}
@ -2118,6 +2188,7 @@ void SSLWrap<Base>::LoadSession(const FunctionCallbackInfo<Value>& args) {
Base* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.Holder());
// TODO(@sam-github) check arg length and types in js, and CHECK in c++
if (args.Length() >= 1 && Buffer::HasInstance(args[0])) {
ArrayBufferViewContents<unsigned char> sbuf(args[0]);
@ -2154,7 +2225,7 @@ void SSLWrap<Base>::Renegotiate(const FunctionCallbackInfo<Value>& args) {
ClearErrorOnReturn clear_error_on_return;
// XXX(sam) Return/throw an error, don't discard the SSL error reason.
// TODO(@sam-github) Return/throw an error, don't discard the SSL error info.
bool yes = SSL_renegotiate(w->ssl_.get()) == 1;
args.GetReturnValue().Set(yes);
}
@ -2269,8 +2340,12 @@ void SSLWrap<Base>::GetEphemeralKeyInfo(
EVP_PKEY_bits(key.get()))).FromJust();
}
break;
default:
break;
}
}
// TODO(@sam-github) semver-major: else return ThrowCryptoError(env,
// ERR_get_error())
return args.GetReturnValue().Set(info);
}
@ -2484,7 +2559,10 @@ int SSLWrap<Base>::TLSExtStatusCallback(SSL* s, void* arg) {
w->MakeCallback(env->onocspresponse_string(), 1, &arg);
// Somehow, client is expecting different return value here
// No async acceptance is possible, so always return 1 to accept the
// response. The listener for 'OCSPResponse' event has no control over
// return value, but it can .destroy() the connection if the response is not
// acceptable.
return 1;
} else {
// Outgoing response
@ -2526,6 +2604,8 @@ int SSLWrap<Base>::SSLCertCallback(SSL* s, void* arg) {
return 1;
if (w->cert_cb_running_)
// Not an error. Suspend handshake with SSL_ERROR_WANT_X509_LOOKUP, and
// handshake will continue after certcb is done.
return -1;
Environment* env = w->env();
@ -2601,6 +2681,8 @@ void SSLWrap<Base>::CertCbDone(const FunctionCallbackInfo<Value>& args) {
if (rv)
rv = w->SetCACerts(sc);
if (!rv) {
// Not clear why sometimes we throw error, and sometimes we call
// onerror(). Both cause .destroy(), but onerror does a bit more.
unsigned long err = ERR_get_error(); // NOLINT(runtime/int)
if (!err)
return env->ThrowError("CertCbDone");
@ -5954,6 +6036,24 @@ void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
SSL_CIPHER_get_name(cipher))).FromJust();
}
// TLSv1.3 ciphers aren't listed by EVP. There are only 5, we could just
// document them, but since there are only 5, easier to just add them manually
// and not have to explain their absence in the API docs. They are lower-cased
// because the docs say they will be.
static const char* TLS13_CIPHERS[] = {
"tls_aes_256_gcm_sha384",
"tls_chacha20_poly1305_sha256",
"tls_aes_128_gcm_sha256",
"tls_aes_128_ccm_8_sha256",
"tls_aes_128_ccm_sha256"
};
for (unsigned i = 0; i < arraysize(TLS13_CIPHERS); ++i) {
const char* name = TLS13_CIPHERS[i];
arr->Set(env->context(),
arr->Length(), OneByteString(args.GetIsolate(), name)).FromJust();
}
args.GetReturnValue().Set(arr);
}

View File

@ -147,6 +147,7 @@ class SecureContext : public BaseObject {
static void AddCACert(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddCRL(const v8::FunctionCallbackInfo<v8::Value>& args);
static void AddRootCerts(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCipherSuites(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetCiphers(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetECDHCurve(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetDHParam(const v8::FunctionCallbackInfo<v8::Value>& args);
@ -155,6 +156,10 @@ class SecureContext : public BaseObject {
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSessionTimeout(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMinProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetMaxProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMinProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMaxProto(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Close(const v8::FunctionCallbackInfo<v8::Value>& args);
static void LoadPKCS12(const v8::FunctionCallbackInfo<v8::Value>& args);
#ifndef OPENSSL_NO_ENGINE

View File

@ -86,6 +86,8 @@ void ClientHelloParser::ParseHeader(const uint8_t* data, size_t avail) {
// (3,2) TLS v1.1
// (3,3) TLS v1.2
//
// Note that TLS v1.3 uses a TLS v1.2 handshake so requires no specific
// support here.
if (data[body_offset_ + 4] != 0x03 ||
data[body_offset_ + 5] < 0x01 ||
data[body_offset_ + 5] > 0x03) {

View File

@ -33,6 +33,12 @@ namespace crypto {
// Parse the client hello so we can do async session resumption. OpenSSL's
// session resumption uses synchronous callbacks, see SSL_CTX_sess_set_get_cb
// and get_session_cb.
//
// TLS1.3 handshakes masquerade as TLS1.2 session resumption, and to do this,
// they always include a session_id in the ClientHello, making up a bogus value
// if necessary. The parser can't know if its a bogus id, and will cause a
// 'newSession' event to be emitted. This should do no harm, the id won't be
// found, and the handshake will continue.
class ClientHelloParser {
public:
inline ClientHelloParser();

View File

@ -335,16 +335,30 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
AddOption("--napi-modules", "", NoOp{}, kAllowedInEnvironment);
#if HAVE_OPENSSL
AddOption("--tls-v1.0",
"enable TLSv1.0 and greater by default",
&EnvironmentOptions::tls_v1_0,
AddOption("--tls-min-v1.0",
"set default TLS minimum to TLSv1.0 (default: TLSv1.2)",
&EnvironmentOptions::tls_min_v1_0,
kAllowedInEnvironment);
AddOption("--tls-v1.1",
"enable TLSv1.1 and greater by default",
&EnvironmentOptions::tls_v1_1,
AddOption("--tls-min-v1.1",
"set default TLS minimum to TLSv1.1 (default: TLSv1.2)",
&EnvironmentOptions::tls_min_v1_1,
kAllowedInEnvironment);
AddOption("--tls-min-v1.3",
"set default TLS minimum to TLSv1.3 (default: TLSv1.2)",
&EnvironmentOptions::tls_min_v1_3,
kAllowedInEnvironment);
AddOption("--tls-max-v1.2",
"set default TLS maximum to TLSv1.2 (default: TLSv1.3)",
&EnvironmentOptions::tls_max_v1_2,
kAllowedInEnvironment);
// Current plan is:
// - 11.x and below: TLS1.3 is opt-in with --tls-max-v1.3
// - 12.x: TLS1.3 is opt-out with --tls-max-v1.2
// In either case, support both options they are uniformly available.
AddOption("--tls-max-v1.3",
"set default TLS maximum to TLSv1.3 (default: TLSv1.3)",
&EnvironmentOptions::tls_max_v1_3,
kAllowedInEnvironment);
#endif
}
PerIsolateOptionsParser::PerIsolateOptionsParser(

View File

@ -121,10 +121,11 @@ class EnvironmentOptions : public Options {
bool print_eval = false;
bool force_repl = false;
#if HAVE_OPENSSL
bool tls_v1_0 = false;
bool tls_v1_1 = false;
#endif
bool tls_min_v1_0 = false;
bool tls_min_v1_1 = false;
bool tls_min_v1_3 = false;
bool tls_max_v1_2 = false;
bool tls_max_v1_3 = false;
std::vector<std::string> preload_modules;

View File

@ -113,6 +113,12 @@ void TLSWrap::InitSSL() {
SSL_set_mode(ssl_.get(), SSL_MODE_RELEASE_BUFFERS);
#endif // SSL_MODE_RELEASE_BUFFERS
// This is default in 1.1.1, but set it anyway, Cycle() doesn't currently
// re-call ClearIn() if SSL_read() returns SSL_ERROR_WANT_READ, so data can be
// left sitting in the incoming enc_in_ and never get processed.
// - https://wiki.openssl.org/index.php/TLS1.3#Non-application_data_records
SSL_set_mode(ssl_.get(), SSL_MODE_AUTO_RETRY);
SSL_set_app_data(ssl_.get(), this);
// Using InfoCallback isn't how we are supposed to check handshake progress:
// https://github.com/openssl/openssl/issues/7199#issuecomment-420915993
@ -224,6 +230,8 @@ void TLSWrap::SSLInfoCallback(const SSL* ssl_, int where, int ret) {
Local<Object> object = c->object();
if (where & SSL_CB_HANDSHAKE_START) {
// Start is tracked to limit number and frequency of renegotiation attempts,
// since excessive renegotiation may be an attack.
Local<Value> callback;
if (object->Get(env->context(), env->onhandshakestart_string())
@ -237,6 +245,7 @@ void TLSWrap::SSLInfoCallback(const SSL* ssl_, int where, int ret) {
// sending HelloRequest in OpenSSL-1.1.1.
// We need to check whether this is in a renegotiation state or not.
if (where & SSL_CB_HANDSHAKE_DONE && !SSL_renegotiate_pending(ssl)) {
CHECK(!SSL_renegotiate_pending(ssl));
Local<Value> callback;
c->established_ = true;
@ -271,8 +280,23 @@ void TLSWrap::EncOut() {
// No encrypted output ready to write to the underlying stream.
if (BIO_pending(enc_out_) == 0) {
if (pending_cleartext_input_.empty())
InvokeQueued(0);
if (pending_cleartext_input_.empty()) {
if (!in_dowrite_) {
InvokeQueued(0);
} else {
// TODO(@sam-github, @addaleax) If in_dowrite_ is true, appdata was
// passed to SSL_write(). If we are here, the data was not encrypted to
// enc_out_ yet. Calling Done() "works", but since the write is not
// flushed, its too soon. Just returning and letting the next EncOut()
// call Done() passes the test suite, but without more careful analysis,
// its not clear if it is always correct. Not calling Done() could block
// data flow, so for now continue to call Done(), just do it in the next
// tick.
env()->SetImmediate([](Environment* env, void* data) {
static_cast<TLSWrap*>(data)->InvokeQueued(0);
}, this, object());
}
}
return;
}
@ -534,8 +558,8 @@ void TLSWrap::ClearIn() {
Local<Value> arg = GetSSLError(written, &err, &error_str);
if (!arg.IsEmpty()) {
write_callback_scheduled_ = true;
// XXX(sam) Should forward an error object with .code/.function/.etc, if
// possible.
// TODO(@sam-github) Should forward an error object with
// .code/.function/.etc, if possible.
InvokeQueued(UV_EPROTO, error_str.c_str());
} else {
// Push back the not-yet-written pending buffers into their queue.
@ -603,6 +627,7 @@ void TLSWrap::ClearError() {
// Called by StreamBase::Write() to request async write of clear text into SSL.
// TODO(@sam-github) Should there be a TLSWrap::DoTryWrite()?
int TLSWrap::DoWrite(WriteWrap* w,
uv_buf_t* bufs,
size_t count,
@ -688,7 +713,10 @@ int TLSWrap::DoWrite(WriteWrap* w,
}
// Write any encrypted/handshake output that may be ready.
// Guard against sync call of current_write_->Done(), its unsupported.
in_dowrite_ = true;
EncOut();
in_dowrite_ = false;
return 0;
}

View File

@ -173,6 +173,7 @@ class TLSWrap : public AsyncWrap,
std::vector<uv_buf_t> pending_cleartext_input_;
size_t write_size_ = 0;
WriteWrap* current_write_ = nullptr;
bool in_dowrite_ = false;
WriteWrap* current_empty_write_ = nullptr;
bool write_callback_scheduled_ = false;
bool started_ = false;

View File

@ -0,0 +1,11 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
require('./test-graph.tls-write.js');

View File

@ -39,8 +39,10 @@ function onlistening() {
function onsecureConnection() {}
function onsecureConnect() {
// Destroying client socket
this.destroy();
// end() client socket, which causes slightly different hook events than
// destroy(), but with TLS1.3 destroy() rips the connection down before the
// server completes the handshake.
this.end();
// Closing server
server.close(common.mustCall(onserverClosed));
@ -68,7 +70,8 @@ function onexit() {
{ type: 'WRITEWRAP', id: 'write:2', triggerAsyncId: null },
{ type: 'WRITEWRAP', id: 'write:3', triggerAsyncId: null },
{ type: 'WRITEWRAP', id: 'write:4', triggerAsyncId: null },
{ type: 'Immediate', id: 'immediate:1', triggerAsyncId: 'tcp:1' },
{ type: 'Immediate', id: 'immediate:2', triggerAsyncId: 'tcp:2' } ]
{ type: 'Immediate', id: 'immediate:1', triggerAsyncId: 'tcp:2' },
{ type: 'Immediate', id: 'immediate:2', triggerAsyncId: 'tcp:1' },
]
);
}

View File

@ -15,6 +15,10 @@ const { checkInvocations } = require('./hook-checks');
const hooks = initHooks();
hooks.enable();
// TODO(@sam-github) assumes server handshake completes before client, true for
// 1.2, not for 1.3. Might need a rewrite for TLS1.3.
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
//
// Creating server and listening on port
//
@ -52,6 +56,7 @@ function onsecureConnection() {
//
const as = hooks.activitiesOfTypes('TLSWRAP');
assert.strictEqual(as.length, 2);
// TODO(@sam-github) This happens after onsecureConnect, with TLS1.3.
client = as[1];
assert.strictEqual(client.type, 'TLSWRAP');
assert.strictEqual(typeof client.uid, 'number');
@ -78,7 +83,7 @@ function onsecureConnect() {
//
// Destroying client socket
//
this.destroy();
this.destroy(); // This destroys client before server handshakes, with TLS1.3
checkInvocations(svr, { init: 1, before: 2, after: 1 },
'server: when destroying client');
checkInvocations(client, { init: 1, before: 2, after: 2 },

View File

@ -129,6 +129,7 @@ validateList(cryptoCiphers);
// Assume that we have at least AES256-SHA.
const tlsCiphers = tls.getCiphers();
assert(tls.getCiphers().includes('aes256-sha'));
assert(tls.getCiphers().includes('tls_aes_128_ccm_8_sha256'));
// There should be no capital letters in any element.
const noCapitals = /^[^A-Z]+$/;
assert(tlsCiphers.every((value) => noCapitals.test(value)));

View File

@ -1,4 +1,4 @@
// Flags: --tls-v1.1
// Flags: --tls-min-v1.1
'use strict';
const common = require('../common');
if (!common.hasCrypto)

View File

@ -1,4 +1,4 @@
// Flags: --tls-v1.0
// Flags: --tls-min-v1.0
'use strict';
const common = require('../common');

View File

@ -32,6 +32,9 @@ const tls = require('tls');
const https = require('https');
const fixtures = require('../common/fixtures');
// Renegotiation as a protocol feature was dropped after TLS1.2.
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
// renegotiation limits to test
const LIMITS = [0, 1, 2, 3, 5, 10, 16];

View File

@ -55,7 +55,8 @@ server.listen(0, common.mustCall(function() {
'\r\n');
}));
client1.on('session', common.mustCall((session) => {
// TLS1.2 servers issue 1 ticket, TLS1.3 issues more, but only use the first.
client1.once('session', common.mustCall((session) => {
console.log('session');
const opts = {

View File

@ -64,7 +64,7 @@ function sendClient() {
}
client.end();
}, max_iter));
client.write('a');
client.write('a', common.mustCall());
client.on('error', common.mustNotCall());
client.on('close', common.mustCall(function() {
clientClosed = true;

View File

@ -14,7 +14,6 @@ const tls = require('tls');
// new and resume session events will never be emitted on the server.
const options = {
maxVersion: 'TLSv1.2',
secureOptions: SSL_OP_NO_TICKET,
key: fixtures.readSync('test_key.pem'),
cert: fixtures.readSync('test_cert.pem')
@ -38,6 +37,10 @@ server.on('resumeSession', common.mustCall((id, cb) => {
server.listen(0, common.mustCall(() => {
const clientOpts = {
// Don't send a TLS1.3/1.2 ClientHello, they contain a fake session_id,
// which triggers a 'resumeSession' event for client1. TLS1.2 ClientHello
// won't have a session_id until client2, which will have a valid session.
maxVersion: 'TLSv1.2',
port: server.address().port,
rejectUnauthorized: false,
session: false

View File

@ -12,7 +12,8 @@ common.expectsError(
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'Ciphers must be a string'
message: 'The "options.ciphers" property must be of type string.' +
' Received type number'
});
common.expectsError(
@ -20,7 +21,8 @@ common.expectsError(
{
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'Ciphers must be a string'
message: 'The "options.ciphers" property must be of type string.' +
' Received type number'
});
common.expectsError(

View File

@ -0,0 +1,15 @@
// Flags: --tls-max-v1.2
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
// Check that node `--tls-max-v1.2` is supported.
const assert = require('assert');
const tls = require('tls');
assert.strictEqual(tls.DEFAULT_MAX_VERSION, 'TLSv1.2');
assert.strictEqual(tls.DEFAULT_MIN_VERSION, 'TLSv1.2');
// Check the min-max version protocol versions against these CLI settings.
require('./test-tls-min-max-version.js');

View File

@ -0,0 +1,15 @@
// Flags: --tls-max-v1.3
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
// Check that node `--tls-max-v1.3` is supported.
const assert = require('assert');
const tls = require('tls');
assert.strictEqual(tls.DEFAULT_MAX_VERSION, 'TLSv1.3');
assert.strictEqual(tls.DEFAULT_MIN_VERSION, 'TLSv1.2');
// Check the min-max version protocol versions against these CLI settings.
require('./test-tls-min-max-version.js');

View File

@ -1,4 +1,4 @@
// Flags: --tls-v1.0 --tls-v1.1
// Flags: --tls-min-v1.0 --tls-min-v1.1
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
@ -8,7 +8,7 @@ if (!common.hasCrypto) common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
assert.strictEqual(tls.DEFAULT_MAX_VERSION, 'TLSv1.2');
assert.strictEqual(tls.DEFAULT_MAX_VERSION, 'TLSv1.3');
assert.strictEqual(tls.DEFAULT_MIN_VERSION, 'TLSv1');
// Check the min-max version protocol versions against these CLI settings.

View File

@ -1,4 +1,4 @@
// Flags: --tls-v1.1
// Flags: --tls-min-v1.1
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
@ -8,7 +8,7 @@ if (!common.hasCrypto) common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
assert.strictEqual(tls.DEFAULT_MAX_VERSION, 'TLSv1.2');
assert.strictEqual(tls.DEFAULT_MAX_VERSION, 'TLSv1.3');
assert.strictEqual(tls.DEFAULT_MIN_VERSION, 'TLSv1.1');
// Check the min-max version protocol versions against these CLI settings.

View File

@ -0,0 +1,15 @@
// Flags: --tls-min-v1.3
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
// Check that node `--tls-min-v1.3` is supported.
const assert = require('assert');
const tls = require('tls');
assert.strictEqual(tls.DEFAULT_MAX_VERSION, 'TLSv1.3');
assert.strictEqual(tls.DEFAULT_MIN_VERSION, 'TLSv1.3');
// Check the min-max version protocol versions against these CLI settings.
require('./test-tls-min-max-version.js');

View File

@ -1,10 +1,10 @@
'use strict';
require('../common');
const common = require('../common');
const fixtures = require('../common/fixtures');
const {
assert, connect, keys
assert, connect, keys, tls
} = require(fixtures.path('tls-connect'));
// Use ec10 and agent10, they are the only identities with intermediate CAs.
@ -63,8 +63,28 @@ connect({
return cleanup();
});
// Request cert from client that doesn't have one.
// Request cert from TLS1.2 client that doesn't have one.
connect({
client: {
maxVersion: 'TLSv1.2',
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.strictEqual(pair.server.err.code,
'ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE');
assert.strictEqual(pair.client.err.code, 'ECONNRESET');
return cleanup();
});
// Request cert from TLS1.3 client that doesn't have one.
if (tls.DEFAULT_MAX_VERSION === 'TLSv1.3') connect({
client: {
ca: server.ca,
checkServerIdentity,
@ -76,8 +96,17 @@ connect({
requestCert: true,
},
}, function(err, pair, cleanup) {
assert.strictEqual(err.code, 'ECONNRESET');
return cleanup();
assert.strictEqual(pair.server.err.code,
'ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE');
// TLS1.3 client completes handshake before server, and its only after the
// server handshakes, requests certs, gets back a zero-length list of certs,
// and sends a fatal Alert to the client that the client discovers there has
// been a fatal error.
pair.client.conn.once('error', common.mustCall((err) => {
assert.strictEqual(err.code, 'ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED');
cleanup();
}));
});
// Typical configuration error, incomplete cert chains sent, we have to know the

View File

@ -10,6 +10,9 @@ const tls = require('tls');
const key = fixtures.readKey('agent2-key.pem');
const cert = fixtures.readKey('agent2-cert.pem');
// TODO(@sam-github) test works with TLS1.3, rework test to add
// 'ECDH' with 'TLS_AES_128_GCM_SHA256',
function loadDHParam(n) {
return fixtures.readKey(`dh${n}.pem`);
}

View File

@ -0,0 +1,13 @@
'use strict';
// test-tls-client-reject specifically for TLS1.2.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
require('./test-tls-client-reject.js');

View File

@ -35,6 +35,7 @@ const options = {
const server = tls.createServer(options, function(socket) {
socket.pipe(socket);
// Pipe already ends... but leaving this here tests .end() after .end().
socket.on('end', () => socket.end());
}).listen(0, common.mustCall(function() {
unauthorized();
@ -47,13 +48,19 @@ function unauthorized() {
servername: 'localhost',
rejectUnauthorized: false
}, common.mustCall(function() {
console.log('... unauthorized');
let _data;
assert(!socket.authorized);
socket.on('data', common.mustCall((data) => {
assert.strictEqual(data.toString(), 'ok');
_data = data;
}));
socket.on('end', common.mustCall(() => {
assert(_data, 'data failed to echo!');
}));
socket.on('end', () => rejectUnauthorized());
}));
socket.once('session', common.mustCall(() => {
}));
socket.on('error', common.mustNotCall());
socket.end('ok');
}
@ -65,7 +72,6 @@ function rejectUnauthorized() {
}, common.mustNotCall());
socket.on('data', common.mustNotCall());
socket.on('error', common.mustCall(function(err) {
console.log('... rejected:', err);
authorized();
}));
socket.end('ng');

View File

@ -0,0 +1,37 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
// Confirm that for TLSv1.3, renegotiate() is disallowed.
const {
assert, connect, keys
} = require(fixtures.path('tls-connect'));
const server = keys.agent10;
connect({
client: {
ca: server.ca,
checkServerIdentity: common.mustCall(),
},
server: {
key: server.key,
cert: server.cert,
},
}, function(err, pair, cleanup) {
assert.ifError(err);
const client = pair.client.conn;
assert.strictEqual(client.getProtocol(), 'TLSv1.3');
const ok = client.renegotiate({}, common.mustCall((err) => {
assert(err.code, 'ERR_TLS_RENEGOTIATE');
assert(err.message, 'Attempt to renegotiate TLS session failed');
cleanup();
}));
assert.strictEqual(ok, false);
});

View File

@ -31,6 +31,9 @@ const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
// Renegotiation as a protocol feature was dropped after TLS1.2.
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
// renegotiation limits to test
const LIMITS = [0, 1, 2, 3, 5, 10, 16];

View File

@ -0,0 +1,13 @@
'use strict';
// test-tls-client-resume specifically for TLS1.2.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
require('./test-tls-client-resume.js');

View File

@ -44,32 +44,59 @@ const server = tls.Server(options, common.mustCall((socket) => {
// start listening
server.listen(0, common.mustCall(function() {
let sessionx = null;
let session1 = null;
let sessionx = null; // From right after connect, invalid for TLS1.3
let session1 = null; // Delivered by the session event, always valid.
let sessions = 0;
let tls13;
const client1 = tls.connect({
port: this.address().port,
rejectUnauthorized: false
}, common.mustCall(() => {
console.log('connect1');
tls13 = client1.getProtocol() === 'TLSv1.3';
assert.strictEqual(client1.isSessionReused(), false);
sessionx = client1.getSession();
assert(sessionx);
if (session1)
reconnect();
}));
client1.on('data', common.mustCall((d) => {
}));
client1.once('session', common.mustCall((session) => {
console.log('session1');
session1 = session;
assert(session1);
if (sessionx)
reconnect();
}));
client1.on('close', common.mustCall(() => {
client1.on('session', () => {
console.log('client1 session#', ++sessions);
});
client1.on('close', () => {
console.log('client1 close');
assert.strictEqual(sessions, tls13 ? 2 : 1);
});
function reconnect() {
assert(sessionx);
assert(session1);
assert.strictEqual(sessionx.compare(session1), 0);
if (tls13)
// For TLS1.3, the session immediately after handshake is a dummy,
// unresumable session. The one delivered later in session event is
// resumable.
assert.notStrictEqual(sessionx.compare(session1), 0);
else
// For TLS1.2, they are identical.
assert.strictEqual(sessionx.compare(session1), 0);
const opts = {
port: server.address().port,
rejectUnauthorized: false,
session: session1
session: session1,
};
const client2 = tls.connect(opts, common.mustCall(() => {
@ -83,7 +110,7 @@ server.listen(0, common.mustCall(function() {
}));
client2.resume();
}));
}
client1.resume();
}));

View File

@ -0,0 +1,13 @@
'use strict';
// test-tls-destroy-stream specifically for TLS1.2.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
require('./test-tls-destroy-stream.js');

View File

@ -9,6 +9,8 @@ const net = require('net');
const assert = require('assert');
const tls = require('tls');
tls.DEFAULT_MAX_VERSION = 'TLSv1.3';
// This test ensures that an instance of StreamWrap should emit "end" and
// "close" when the socket on the other side call `destroy()` instead of
// `end()`.
@ -21,10 +23,17 @@ const tlsServer = tls.createServer(
ca: [fixtures.readSync('test_ca.pem')],
},
(socket) => {
socket.on('error', common.mustNotCall());
socket.on('close', common.mustCall());
socket.write(CONTENT);
socket.destroy();
socket.on('error', (err) => {
// destroy() is sync, write() is async, whether write completes depends
// on the protocol, it is not guaranteed by stream API.
if (err.code === 'ERR_STREAM_DESTROYED')
return;
assert.ifError(err);
});
},
);
@ -57,13 +66,12 @@ const server = net.createServer((conn) => {
server.listen(0, () => {
const port = server.address().port;
const conn = tls.connect({ port, rejectUnauthorized: false }, () => {
conn.on('data', common.mustCall((data) => {
// Whether the server's write() completed before its destroy() is
// indeterminate, but if data was written, we should receive it correctly.
conn.on('data', (data) => {
assert.strictEqual(data.toString('utf8'), CONTENT);
}));
});
conn.on('error', common.mustNotCall());
conn.on(
'close',
common.mustCall(() => server.close()),
);
conn.on('close', common.mustCall(() => server.close()));
});
});

View File

@ -10,6 +10,9 @@ if (!common.hasCrypto)
const tls = require('tls');
// Renegotiation as a protocol feature was dropped after TLS1.2.
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
const options = {
key: fixtures.readKey('agent1-key.pem'),
cert: fixtures.readKey('agent1-cert.pem'),
@ -86,5 +89,9 @@ server.listen(0, common.mustCall(() => {
}));
}));
assert.strictEqual(ok, true);
client.on('secureConnect', common.mustCall(() => {
}));
client.on('secure', common.mustCall(() => {
}));
}));
}));

View File

@ -60,7 +60,7 @@ server.listen(0, '127.0.0.1', common.mustCall(function() {
tls.connect({
host: '127.0.0.1',
port: this.address().port,
cipher: 'ECDHE-RSA-AES128-GCM-SHA256',
ciphers: 'ECDHE-RSA-AES128-GCM-SHA256',
rejectUnauthorized: false
}, common.mustCall(function() {
const cipher = this.getCipher();
@ -69,3 +69,24 @@ server.listen(0, '127.0.0.1', common.mustCall(function() {
this.end();
}));
}));
tls.createServer({
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'),
ciphers: 'TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_8_SHA256',
maxVersion: 'TLSv1.3',
}, common.mustCall(function() {
this.close();
})).listen(0, common.mustCall(function() {
const client = tls.connect({
port: this.address().port,
ciphers: 'TLS_AES_128_CCM_8_SHA256',
maxVersion: 'TLSv1.3',
rejectUnauthorized: false
}, common.mustCall(() => {
const cipher = client.getCipher();
assert.strictEqual(cipher.name, 'TLS_AES_128_CCM_8_SHA256');
assert.strictEqual(cipher.version, 'TLSv1.3');
client.end();
}));
}));

View File

@ -13,6 +13,10 @@ const DEFAULT_MAX_VERSION = tls.DEFAULT_MAX_VERSION;
function test(cmin, cmax, cprot, smin, smax, sprot, proto, cerr, serr) {
assert(proto || cerr || serr, 'test missing any expectations');
// Report where test was called from. Strip leading garbage from
// at Object.<anonymous> (file:line)
// from the stack location, we only want the file:line part.
const where = (new Error()).stack.split('\n')[2].replace(/[^(]*/, '');
connect({
client: {
checkServerIdentity: (servername, cert) => { },
@ -32,6 +36,7 @@ function test(cmin, cmax, cprot, smin, smax, sprot, proto, cerr, serr) {
function u(_) { return _ === undefined ? 'U' : _; }
console.log('test:', u(cmin), u(cmax), u(cprot), u(smin), u(smax), u(sprot),
'expect', u(proto), u(cerr), u(serr));
console.log(' ', where);
if (!proto) {
console.log('client', pair.client.err ? pair.client.err.code : undefined);
console.log('server', pair.server.err ? pair.server.err.code : undefined);
@ -64,8 +69,8 @@ function test(cmin, cmax, cprot, smin, smax, sprot, proto, cerr, serr) {
const U = undefined;
// Default protocol is TLSv1.2.
test(U, U, U, U, U, U, 'TLSv1.2');
// Default protocol is the max version.
test(U, U, U, U, U, U, DEFAULT_MAX_VERSION);
// Insecure or invalid protocols cannot be enabled.
test(U, U, U, U, U, 'SSLv2_method',
@ -101,7 +106,23 @@ test(U, U, 'TLS_method', U, U, 'TLSv1_method', 'TLSv1');
// SSLv23 also means "any supported protocol" greater than the default
// minimum (which is configurable via command line).
test(U, U, 'TLSv1_2_method', U, U, 'SSLv23_method', 'TLSv1.2');
if (DEFAULT_MIN_VERSION === 'TLSv1.3') {
test(U, U, 'TLSv1_2_method', U, U, 'SSLv23_method',
U, 'ECONNRESET', 'ERR_SSL_INTERNAL_ERROR');
} else {
test(U, U, 'TLSv1_2_method', U, U, 'SSLv23_method', 'TLSv1.2');
}
if (DEFAULT_MIN_VERSION === 'TLSv1.3') {
test(U, U, 'TLSv1_1_method', U, U, 'SSLv23_method',
U, 'ECONNRESET', 'ERR_SSL_INTERNAL_ERROR');
test(U, U, 'TLSv1_method', U, U, 'SSLv23_method',
U, 'ECONNRESET', 'ERR_SSL_INTERNAL_ERROR');
test(U, U, 'SSLv23_method', U, U, 'TLSv1_1_method',
U, 'ERR_SSL_NO_PROTOCOLS_AVAILABLE', 'ERR_SSL_UNEXPECTED_MESSAGE');
test(U, U, 'SSLv23_method', U, U, 'TLSv1_method',
U, 'ERR_SSL_NO_PROTOCOLS_AVAILABLE', 'ERR_SSL_UNEXPECTED_MESSAGE');
}
if (DEFAULT_MIN_VERSION === 'TLSv1.2') {
test(U, U, 'TLSv1_1_method', U, U, 'SSLv23_method',
@ -149,7 +170,11 @@ if (DEFAULT_MIN_VERSION === 'TLSv1.2') {
test(U, U, U, U, U, 'TLSv1_method',
U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', 'ERR_SSL_WRONG_VERSION_NUMBER');
} else {
assert(false, 'unreachable');
// TLS1.3 client hellos are are not understood by TLS1.1 or below.
test(U, U, U, U, U, 'TLSv1_1_method',
U, 'ECONNRESET', 'ERR_SSL_UNSUPPORTED_PROTOCOL');
test(U, U, U, U, U, 'TLSv1_method',
U, 'ECONNRESET', 'ERR_SSL_UNSUPPORTED_PROTOCOL');
}
}
@ -164,7 +189,9 @@ if (DEFAULT_MIN_VERSION === 'TLSv1.1') {
test(U, U, U, U, U, 'TLSv1_method',
U, 'ERR_SSL_UNSUPPORTED_PROTOCOL', 'ERR_SSL_WRONG_VERSION_NUMBER');
} else {
assert(false, 'unreachable');
// TLS1.3 client hellos are are not understood by TLS1.1 or below.
test(U, U, U, U, U, 'TLSv1_method',
U, 'ECONNRESET', 'ERR_SSL_UNSUPPORTED_PROTOCOL');
}
}
@ -180,14 +207,32 @@ if (DEFAULT_MIN_VERSION === 'TLSv1') {
test('TLSv1', 'TLSv1.2', U, U, U, 'TLSv1_method', 'TLSv1');
test('TLSv1', 'TLSv1.2', U, U, U, 'TLSv1_1_method', 'TLSv1.1');
test('TLSv1', 'TLSv1.2', U, U, U, 'TLSv1_2_method', 'TLSv1.2');
test('TLSv1', 'TLSv1.2', U, U, U, 'TLS_method', 'TLSv1.2');
test(U, U, 'TLSv1_method', 'TLSv1', 'TLSv1.2', U, 'TLSv1');
test(U, U, 'TLSv1_1_method', 'TLSv1', 'TLSv1.2', U, 'TLSv1.1');
test(U, U, 'TLSv1_2_method', 'TLSv1', 'TLSv1.2', U, 'TLSv1.2');
test('TLSv1', 'TLSv1.1', U, 'TLSv1', 'TLSv1.3', U, 'TLSv1.1');
test('TLSv1', 'TLSv1.1', U, 'TLSv1', 'TLSv1.2', U, 'TLSv1.1');
test('TLSv1', 'TLSv1.2', U, 'TLSv1', 'TLSv1.1', U, 'TLSv1.1');
test('TLSv1', 'TLSv1.3', U, 'TLSv1', 'TLSv1.1', U, 'TLSv1.1');
test('TLSv1', 'TLSv1', U, 'TLSv1', 'TLSv1.1', U, 'TLSv1');
test('TLSv1', 'TLSv1.2', U, 'TLSv1', 'TLSv1', U, 'TLSv1');
test('TLSv1', 'TLSv1.3', U, 'TLSv1', 'TLSv1', U, 'TLSv1');
test('TLSv1.1', 'TLSv1.1', U, 'TLSv1', 'TLSv1.2', U, 'TLSv1.1');
test('TLSv1', 'TLSv1.2', U, 'TLSv1.1', 'TLSv1.1', U, 'TLSv1.1');
test('TLSv1', 'TLSv1.2', U, 'TLSv1', 'TLSv1.3', U, 'TLSv1.2');
// v-any client can connect to v-specific server
test('TLSv1', 'TLSv1.3', U, 'TLSv1.3', 'TLSv1.3', U, 'TLSv1.3');
test('TLSv1', 'TLSv1.3', U, 'TLSv1.2', 'TLSv1.3', U, 'TLSv1.3');
test('TLSv1', 'TLSv1.3', U, 'TLSv1.2', 'TLSv1.2', U, 'TLSv1.2');
test('TLSv1', 'TLSv1.3', U, 'TLSv1.1', 'TLSv1.1', U, 'TLSv1.1');
test('TLSv1', 'TLSv1.3', U, 'TLSv1', 'TLSv1', U, 'TLSv1');
// v-specific client can connect to v-any server
test('TLSv1.3', 'TLSv1.3', U, 'TLSv1', 'TLSv1.3', U, 'TLSv1.3');
test('TLSv1.2', 'TLSv1.2', U, 'TLSv1', 'TLSv1.3', U, 'TLSv1.2');
test('TLSv1.1', 'TLSv1.1', U, 'TLSv1', 'TLSv1.3', U, 'TLSv1.1');
test('TLSv1', 'TLSv1', U, 'TLSv1', 'TLSv1.3', U, 'TLSv1');

View File

@ -0,0 +1,13 @@
'use strict';
// test-tls-net-socket-keepalive specifically for TLS1.2.
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const tls = require('tls');
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
require('./test-tls-net-socket-keepalive.js');

View File

@ -20,8 +20,11 @@ const options = {
};
const server = tls.createServer(options, common.mustCall((conn) => {
conn.write('hello');
conn.write('hello', common.mustCall());
conn.on('data', common.mustCall());
conn.on('end', common.mustCall());
conn.on('data', common.mustCall());
conn.on('close', common.mustCall());
conn.end();
})).listen(0, common.mustCall(() => {
const netSocket = new net.Socket({
@ -42,6 +45,7 @@ const server = tls.createServer(options, common.mustCall((conn) => {
address,
});
socket.on('secureConnect', common.mustCall());
socket.on('end', common.mustCall());
socket.on('data', common.mustCall());
socket.on('close', common.mustCall(() => {

View File

@ -272,6 +272,8 @@ function runTest(port, testIndex) {
if (tcase.renegotiate) {
serverOptions.secureOptions =
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
// Renegotiation as a protocol feature was dropped after TLS1.2.
serverOptions.maxVersion = 'TLSv1.2';
}
let renegotiated = false;

View File

@ -19,4 +19,7 @@ const fixtures = require('../common/fixtures');
options.ciphers = 'FOOBARBAZ';
assert.throws(() => tls.createServer(options, common.mustNotCall()),
/no cipher match/i);
options.ciphers = 'TLS_not_a_cipher';
assert.throws(() => tls.createServer(options, common.mustNotCall()),
/no cipher match/i);
}

View File

@ -1,62 +1,93 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.opensslCli)
common.skip('node compiled without OpenSSL CLI.');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const exec = require('child_process').exec;
const tls = require('tls');
if (!common.hasCrypto) common.skip('missing crypto');
const fixtures = require('../common/fixtures');
const options = {
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem'),
ciphers: 'AES256-SHA'
};
// Test cipher: option for TLS.
const reply = 'I AM THE WALRUS'; // something recognizable
let response = '';
const {
assert, connect, keys
} = require(fixtures.path('tls-connect'));
process.on('exit', function() {
assert.ok(response.includes(reply));
});
const server = tls.createServer(options, common.mustCall(function(conn) {
conn.end(reply);
}));
function test(cciphers, sciphers, cipher, cerr, serr) {
assert(cipher || cerr || serr, 'test missing any expectations');
const where = (new Error()).stack.split('\n')[2].replace(/[^(]*/, '');
connect({
client: {
checkServerIdentity: (servername, cert) => { },
ca: `${keys.agent1.cert}\n${keys.agent6.ca}`,
ciphers: cciphers,
},
server: {
cert: keys.agent6.cert,
key: keys.agent6.key,
ciphers: sciphers,
},
}, common.mustCall((err, pair, cleanup) => {
function u(_) { return _ === undefined ? 'U' : _; }
console.log('test:', u(cciphers), u(sciphers),
'expect', u(cipher), u(cerr), u(serr));
console.log(' ', where);
if (!cipher) {
console.log('client', pair.client.err ? pair.client.err.code : undefined);
console.log('server', pair.server.err ? pair.server.err.code : undefined);
if (cerr) {
assert(pair.client.err);
assert.strictEqual(pair.client.err.code, cerr);
}
if (serr) {
assert(pair.server.err);
assert.strictEqual(pair.server.err.code, serr);
}
return cleanup();
}
server.listen(0, '127.0.0.1', function() {
const cmd = `"${common.opensslCli}" s_client -cipher ${
options.ciphers} -connect 127.0.0.1:${this.address().port}`;
const reply = 'So long and thanks for all the fish.';
exec(cmd, function(err, stdout, stderr) {
assert.ifError(err);
response = stdout;
server.close();
});
});
assert.ifError(pair.server.err);
assert.ifError(pair.client.err);
assert(pair.server.conn);
assert(pair.client.conn);
assert.strictEqual(pair.client.conn.getCipher().name, cipher);
assert.strictEqual(pair.server.conn.getCipher().name, cipher);
pair.server.conn.write(reply);
pair.client.conn.on('data', common.mustCall((data) => {
assert.strictEqual(data.toString(), reply);
return cleanup();
}));
}));
}
const U = undefined;
// Have shared ciphers.
test(U, 'AES256-SHA', 'AES256-SHA');
test('AES256-SHA', U, 'AES256-SHA');
test(U, 'TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384');
test('TLS_AES_256_GCM_SHA384', U, 'TLS_AES_256_GCM_SHA384');
// Do not have shared ciphers.
test('TLS_AES_256_GCM_SHA384', 'TLS_CHACHA20_POLY1305_SHA256',
U, 'ECONNRESET', 'ERR_SSL_NO_SHARED_CIPHER');
test('AES128-SHA', 'AES256-SHA', U, 'ECONNRESET', 'ERR_SSL_NO_SHARED_CIPHER');
test('AES128-SHA:TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256:AES256-SHA',
U, 'ECONNRESET', 'ERR_SSL_NO_SHARED_CIPHER');
// Cipher order ignored, TLS1.3 chosen before TLS1.2.
test('AES256-SHA:TLS_AES_256_GCM_SHA384', U, 'TLS_AES_256_GCM_SHA384');
test(U, 'AES256-SHA:TLS_AES_256_GCM_SHA384', 'TLS_AES_256_GCM_SHA384');
// TLS_AES_128_CCM_8_SHA256 & TLS_AES_128_CCM_SHA256 are not enabled by
// default, but work.
test('TLS_AES_128_CCM_8_SHA256', U,
U, 'ECONNRESET', 'ERR_SSL_NO_SHARED_CIPHER');
test('TLS_AES_128_CCM_8_SHA256', 'TLS_AES_128_CCM_8_SHA256',
'TLS_AES_128_CCM_8_SHA256');

View File

@ -0,0 +1,12 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
// Run test-tls-ticket.js with TLS1.2
const tls = require('tls');
tls.DEFAULT_MAX_VERSION = 'TLSv1.2';
require('./test-tls-ticket.js');

View File

@ -40,13 +40,17 @@ if (cluster.isMaster) {
let workerPort = null;
function shoot() {
console.error('[master] connecting', workerPort);
console.error('[master] connecting', workerPort, 'session?', !!lastSession);
const c = tls.connect(workerPort, {
session: lastSession,
rejectUnauthorized: false
}, () => {
c.end();
}).on('close', () => {
// Wait for close to shoot off another connection. We don't want to shoot
// until a new session is allocated, if one will be. The new session is
// not guaranteed on secureConnect (it depends on TLS1.2 vs TLS1.3), but
// it is guaranteed to happen before the connection is closed.
if (++reqCount === expectedReqCount) {
Object.keys(cluster.workers).forEach(function(id) {
cluster.workers[id].send('die');
@ -55,8 +59,11 @@ if (cluster.isMaster) {
shoot();
}
}).once('session', (session) => {
assert(!lastSession);
lastSession = session;
});
c.resume(); // See close_notify comment in server
}
function fork() {
@ -93,12 +100,15 @@ const cert = fixtures.readSync('agent.crt');
const options = { key, cert };
const server = tls.createServer(options, (c) => {
console.error('[worker] connection reused?', c.isSessionReused());
if (c.isSessionReused()) {
process.send({ msg: 'reused' });
} else {
process.send({ msg: 'not-reused' });
}
c.end();
// Used to just .end(), but that means client gets close_notify before
// NewSessionTicket. Send data until that problem is solved.
c.end('x');
});
server.listen(0, () => {

View File

@ -34,6 +34,8 @@ const keys = crypto.randomBytes(48);
const serverLog = [];
const ticketLog = [];
let s;
let serverCount = 0;
function createServer() {
const id = serverCount++;
@ -47,16 +49,37 @@ function createServer() {
ticketKeys: keys
}, function(c) {
serverLog.push(id);
c.end();
// TODO(@sam-github) Triggers close_notify before NewSessionTicket bug.
// c.end();
c.end('x');
counter++;
// Rotate ticket keys
//
// Take especial care to account for TLS1.2 and TLS1.3 differences around
// when ticket keys are encrypted. In TLS1.2, they are encrypted before the
// handshake complete callback, but in TLS1.3, they are encrypted after.
// There is no callback or way for us to know when they were sent, so hook
// the client's reception of the keys, and use it as proof that the current
// keys were used, and its safe to rotate them.
//
// Rotation can occur right away if the session was reused, the keys were
// already decrypted or we wouldn't have a reused session.
function setTicketKeys(keys) {
if (c.isSessionReused())
server.setTicketKeys(keys);
else
s.once('session', () => {
server.setTicketKeys(keys);
});
}
if (counter === 1) {
previousKey = server.getTicketKeys();
server.setTicketKeys(crypto.randomBytes(48));
assert.strictEqual(previousKey.compare(keys), 0);
setTicketKeys(crypto.randomBytes(48));
} else if (counter === 2) {
server.setTicketKeys(previousKey);
setTicketKeys(previousKey);
} else if (counter === 3) {
// Use keys from counter=2
} else {
@ -95,12 +118,15 @@ function start(callback) {
let left = servers.length;
function connect() {
const s = tls.connect(shared.address().port, {
s = tls.connect(shared.address().port, {
session: sess,
rejectUnauthorized: false
}, function() {
sess = sess || s.getSession();
ticketLog.push(s.getTLSTicket().toString('hex'));
if (s.isSessionReused())
ticketLog.push(s.getTLSTicket().toString('hex'));
});
s.on('data', () => {
s.end();
});
s.on('close', function() {
if (--left === 0)
@ -108,7 +134,11 @@ function start(callback) {
else
connect();
});
s.on('session', (session) => {
sess = sess || session;
});
s.once('session', (session) => onNewSession(s, session));
s.once('session', () => ticketLog.push(s.getTLSTicket().toString('hex')));
}
connect();