diff --git a/doc/api/errors.md b/doc/api/errors.md
index 500fb7801b61fd..96ff284c0a4054 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1655,6 +1655,17 @@ recommended to use 2048 bits or larger for stronger security.
A TLS/SSL handshake timed out. In this case, the server must also abort the
connection.
+
+### ERR_TLS_INVALID_PROTOCOL_VERSION
+
+Valid TLS protocol versions are `'TLSv1'`, `'TLSv1.1'`, or `'TLSv1.2'`.
+
+
+### ERR_TLS_PROTOCOL_VERSION_CONFLICT
+
+Attempting to set a TLS protocol `minVersion` or `maxVersion` conflicts with an
+attempt to set the `secureProtocol` explicitly. Use one mechanism or the other.
+
### ERR_TLS_RENEGOTIATE
diff --git a/doc/api/tls.md b/doc/api/tls.md
index fe3282f97efc39..45d7fbfa2baa9f 100644
--- a/doc/api/tls.md
+++ b/doc/api/tls.md
@@ -1070,6 +1070,10 @@ changes:
pr-url: /~https://github.com/nodejs/node/pull/4099
description: The `ca` option can now be a single string containing multiple
CA certificates.
+ - version: REPLACEME
+ pr-url: REPLACEME
+ description: The `minVersion` and `maxVersion` can be used to restrict
+ the allowed TLS protocol versions.
-->
* `options` {Object}
@@ -1130,6 +1134,16 @@ changes:
passphrase: ]}`. The object form can only occur in an array.
`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'`.
+ * `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.
+ **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'`.
* `passphrase` {string} Shared passphrase used for a single private key and/or
a PFX.
* `pfx` {string|string[]|Buffer|Buffer[]|Object[]} PFX or PKCS12 encoded
@@ -1150,10 +1164,7 @@ changes:
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:**
- `'TLSv1_2_method'`, unless changed using CLI options. Using the `--tlsv1.0`
- CLI option is like `'TLS_method'` except protocols earlier than TLSv1.0 are
- not allowed, and using the `--tlsv1.1` CLI option is like `'TLS_method'`
- except that protocols earlier than TLSv1.1 are not allowed.
+ none, see `minVersion`.
* `sessionIdContext` {string} Opaque identifier used by servers to ensure
session state is not shared between applications. Unused by clients.
diff --git a/lib/_tls_common.js b/lib/_tls_common.js
index 4028b02be28e17..7ddb0d4757eb80 100644
--- a/lib/_tls_common.js
+++ b/lib/_tls_common.js
@@ -26,22 +26,46 @@ const { isArrayBufferView } = require('internal/util/types');
const tls = require('tls');
const {
ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED,
- ERR_INVALID_ARG_TYPE
+ ERR_INVALID_ARG_TYPE,
+ ERR_TLS_INVALID_PROTOCOL_VERSION,
+ ERR_TLS_PROTOCOL_VERSION_CONFLICT,
} = require('internal/errors').codes;
-
-const { SSL_OP_CIPHER_SERVER_PREFERENCE } = internalBinding('constants').crypto;
+const {
+ SSL_OP_CIPHER_SERVER_PREFERENCE,
+ TLS1_VERSION,
+ TLS1_1_VERSION,
+ TLS1_2_VERSION,
+} = internalBinding('constants').crypto;
// Lazily loaded from internal/crypto/util.
let toBuf = null;
+function toV(which, v, def) {
+ if (v == null) v = def;
+ if (v === 'TLSv1') return TLS1_VERSION;
+ if (v === 'TLSv1.1') return TLS1_1_VERSION;
+ if (v === 'TLSv1.2') return TLS1_2_VERSION;
+ throw new ERR_TLS_INVALID_PROTOCOL_VERSION(v, which);
+}
+
const { SecureContext: NativeSecureContext } = internalBinding('crypto');
-function SecureContext(secureProtocol, secureOptions) {
+function SecureContext(secureProtocol, secureOptions, minVersion, maxVersion) {
if (!(this instanceof SecureContext)) {
- return new SecureContext(secureProtocol, secureOptions);
+ return new SecureContext(secureProtocol, secureOptions, minVersion,
+ maxVersion);
+ }
+
+ if (secureProtocol) {
+ if (minVersion != null)
+ throw new ERR_TLS_PROTOCOL_VERSION_CONFLICT(minVersion, secureProtocol);
+ if (maxVersion != null)
+ throw new ERR_TLS_PROTOCOL_VERSION_CONFLICT(maxVersion, secureProtocol);
}
this.context = new NativeSecureContext();
- this.context.init(secureProtocol);
+ this.context.init(secureProtocol,
+ toV('minimum', minVersion, tls.DEFAULT_MIN_VERSION),
+ toV('maximum', maxVersion, tls.DEFAULT_MAX_VERSION));
if (secureOptions) this.context.setOptions(secureOptions);
}
@@ -66,7 +90,8 @@ exports.createSecureContext = function createSecureContext(options) {
if (options.honorCipherOrder)
secureOptions |= SSL_OP_CIPHER_SERVER_PREFERENCE;
- const c = new SecureContext(options.secureProtocol, secureOptions);
+ const c = new SecureContext(options.secureProtocol, secureOptions,
+ options.minVersion, options.maxVersion);
var i;
var val;
diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js
index 9931691464ec14..1d6ccbb8a9cbaf 100644
--- a/lib/_tls_wrap.js
+++ b/lib/_tls_wrap.js
@@ -918,6 +918,16 @@ Server.prototype.setSecureContext = function(options) {
else
this.ca = undefined;
+ if (options.minVersion)
+ this.minVersion = options.minVersion;
+ else
+ this.minVersion = undefined;
+
+ if (options.maxVersion)
+ this.maxVersion = options.maxVersion;
+ else
+ this.maxVersion = undefined;
+
if (options.secureProtocol)
this.secureProtocol = options.secureProtocol;
else
@@ -974,6 +984,8 @@ Server.prototype.setSecureContext = function(options) {
ciphers: this.ciphers,
ecdhCurve: this.ecdhCurve,
dhparam: this.dhparam,
+ minVersion: this.minVersion,
+ maxVersion: this.maxVersion,
secureProtocol: this.secureProtocol,
secureOptions: this.secureOptions,
honorCipherOrder: this.honorCipherOrder,
@@ -1026,6 +1038,8 @@ Server.prototype.setOptions = util.deprecate(function(options) {
if (options.clientCertEngine)
this.clientCertEngine = options.clientCertEngine;
if (options.ca) this.ca = options.ca;
+ if (options.minVersion) this.minVersion = options.minVersion;
+ if (options.maxVersion) this.maxVersion = options.maxVersion;
if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
if (options.crl) this.crl = options.crl;
if (options.ciphers) this.ciphers = options.ciphers;
diff --git a/lib/https.js b/lib/https.js
index 12db2f452c17fa..66e76c1f050935 100644
--- a/lib/https.js
+++ b/lib/https.js
@@ -186,6 +186,14 @@ Agent.prototype.getName = function getName(options) {
if (options.servername && options.servername !== options.host)
name += options.servername;
+ name += ':';
+ if (options.minVersion)
+ name += options.minVersion;
+
+ name += ':';
+ if (options.maxVersion)
+ name += options.maxVersion;
+
name += ':';
if (options.secureProtocol)
name += options.secureProtocol;
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 5fec67d6c721af..0ed23a943805a8 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -861,6 +861,10 @@ E('ERR_TLS_CERT_ALTNAME_INVALID',
'Hostname/IP does not match certificate\'s altnames: %s', Error);
E('ERR_TLS_DH_PARAM_SIZE', 'DH parameter size %s is less than 2048', Error);
E('ERR_TLS_HANDSHAKE_TIMEOUT', 'TLS handshake timeout', Error);
+E('ERR_TLS_INVALID_PROTOCOL_VERSION',
+ '%j is not a valid %s TLS protocol version', TypeError);
+E('ERR_TLS_PROTOCOL_VERSION_CONFLICT',
+ 'TLS protocol version %j conflicts with secureProtocol %j', TypeError);
E('ERR_TLS_RENEGOTIATE', 'Attempt to renegotiate TLS session failed', Error);
E('ERR_TLS_RENEGOTIATION_DISABLED',
'TLS session renegotiation disabled for this socket', Error);
diff --git a/lib/tls.js b/lib/tls.js
index d6b86a410374b7..898e204c539de8 100644
--- a/lib/tls.js
+++ b/lib/tls.js
@@ -31,6 +31,7 @@ internalUtil.assertCrypto();
const { isArrayBufferView } = require('internal/util/types');
const net = require('net');
+const { getOptionValue } = require('internal/options');
const url = require('url');
const binding = internalBinding('crypto');
const { Buffer } = require('buffer');
@@ -53,6 +54,15 @@ exports.DEFAULT_CIPHERS =
exports.DEFAULT_ECDH_CURVE = 'auto';
+exports.DEFAULT_MAX_VERSION = 'TLSv1.2';
+
+if (getOptionValue('--tls-v1.0'))
+ exports.DEFAULT_MIN_VERSION = 'TLSv1';
+else if (getOptionValue('--tls-v1.1'))
+ exports.DEFAULT_MIN_VERSION = 'TLSv1.1';
+else
+ exports.DEFAULT_MIN_VERSION = 'TLSv1.2';
+
exports.getCiphers = internalUtil.cachedResult(
() => internalUtil.filterDuplicateStrings(binding.getSSLCiphers(), true)
);
diff --git a/src/node_constants.cc b/src/node_constants.cc
index 3b028b52aa75cc..7530d6d0a34780 100644
--- a/src/node_constants.cc
+++ b/src/node_constants.cc
@@ -1237,6 +1237,10 @@ void DefineCryptoConstants(Local