tls: add min/max_version and their defaults
authorShigeki Ohtsu <ohtsu@ohtsu.org>
Sun, 6 May 2018 04:52:34 +0000 (13:52 +0900)
committerPeter Michael Green <plugwash@raspbian.org>
Thu, 31 Jan 2019 14:17:56 +0000 (14:17 +0000)
Gbp-Pq: Topic ssl
Gbp-Pq: Name a64ad8aaeab4ebb92e764dd88e3578453a5dbf2a.patch

doc/api/tls.md
lib/_tls_common.js
lib/_tls_wrap.js
lib/https.js
lib/tls.js
src/node_crypto.cc
test/parallel/test-https-agent-getname.js

index 30dfb9c31e01fe6b46152414f2b63f6dde9ee9d2..259e0bafdf477ad78dfc02920e0434882fe1a4af 100644 (file)
@@ -1031,6 +1031,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: XXX
+    pr-url: XXX
+    description: The `min_version` and `max_version` can be used to restrict
+                 the allowed TLS protocol versions.
 -->
 
 * `options` {Object}
@@ -1089,6 +1093,16 @@ changes:
     passphrase: <string>]}`. 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.
+  * `max_version`: Maximum TLS version to allow. One of `'TLSv1.3'`, `TLSv1.2'`,
+    `'TLSv1.1'`, or `'TLSv1'`. Optional, defaults to `'TLSv1.2'`. Note that
+    TLS1.3 is not currently supported, do not attempt to allow it. If
+    `secureProtocol` is used to select a specific protocol version,
+    `max_version` will be ignored.
+  * `min_version`: Minimum TLS version to allow. One of `'TLSv1.3'`, `TLSv1.2'`,
+    `'TLSv1.1'`, or `'TLSv1'`. Optional, defaults to `'TLSv1'`. Note that
+    TLS1.3 is not currently supported, do not attempt to allow it. If
+    `secureProtocol` is used to select a specific protocol version,
+    `min_version` will be ignored.
   * `passphrase` {string} Shared passphrase used for a single private key and/or
     a PFX.
   * `pfx` {string|string[]|Buffer|Buffer[]|Object[]} PFX or PKCS12 encoded
index 1073f5d520e0b92cd1eed425a303f3f934419571..a957ed720d7211583fe3a7884e6227d05d764ef7 100644 (file)
@@ -36,9 +36,11 @@ var crypto = null;
 
 const { SecureContext: NativeSecureContext } = internalBinding('crypto');
 
-function SecureContext(secureProtocol, secureOptions, context) {
+function SecureContext(secureProtocol, secureOptions, context,
+                       min_version, max_version) {
   if (!(this instanceof SecureContext)) {
-    return new SecureContext(secureProtocol, secureOptions, context);
+    return new SecureContext(secureProtocol, secureOptions, context,
+                             min_version, max_version);
   }
 
   if (context) {
@@ -47,9 +49,9 @@ function SecureContext(secureProtocol, secureOptions, context) {
     this.context = new NativeSecureContext();
 
     if (secureProtocol) {
-      this.context.init(secureProtocol);
+      this.context.init(min_version, max_version, secureProtocol);
     } else {
-      this.context.init();
+      this.context.init(min_version, max_version);
     }
   }
 
@@ -76,7 +78,9 @@ exports.createSecureContext = function createSecureContext(options, context) {
   if (options.honorCipherOrder)
     secureOptions |= SSL_OP_CIPHER_SERVER_PREFERENCE;
 
-  const c = new SecureContext(options.secureProtocol, secureOptions, context);
+  const c = new SecureContext(options.secureProtocol, secureOptions, context,
+                              options.min_version || tls.DEFAULT_MIN_VERSION,
+                              options.max_version || tls.DEFAULT_MAX_VERSION);
   var i;
   var val;
 
index 9049a830f805e4aa8212c1d3aa9bdd0904ae4a05..10e43708cb3a2723a95edfd222705abb9708297a 100644 (file)
@@ -875,6 +875,8 @@ function Server(options, listener) {
     ciphers: this.ciphers,
     ecdhCurve: this.ecdhCurve,
     dhparam: this.dhparam,
+    min_version: this.min_version,
+    max_version: this.max_version,
     secureProtocol: this.secureProtocol,
     secureOptions: this.secureOptions,
     honorCipherOrder: this.honorCipherOrder,
@@ -946,6 +948,8 @@ Server.prototype.setOptions = function(options) {
   if (options.clientCertEngine)
     this.clientCertEngine = options.clientCertEngine;
   if (options.ca) this.ca = options.ca;
+  if (options.min_version) this.min_version = options.min_version;
+  if (options.max_version) this.max_version = options.max_version;
   if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
   if (options.crl) this.crl = options.crl;
   if (options.ciphers) this.ciphers = options.ciphers;
index c1053da1a13eb6ea78a839185c57ba7ae0803323..f2f2ce8718e5cd3955a85cec31d3a8030f65dc1c 100644 (file)
@@ -188,6 +188,14 @@ Agent.prototype.getName = function getName(options) {
   if (options.servername && options.servername !== options.host)
     name += options.servername;
 
+  name += ':';
+  if (options.min_version)
+    name += options.min_version;
+
+  name += ':';
+  if (options.max_version)
+    name += options.max_version;
+
   name += ':';
   if (options.secureProtocol)
     name += options.secureProtocol;
index 0324f6db877d48623c71a375f341fbf3052e75fb..fe59ae967987b210bd702606748df77e4a5c6da9 100644 (file)
@@ -49,6 +49,13 @@ exports.DEFAULT_CIPHERS =
 
 exports.DEFAULT_ECDH_CURVE = 'auto';
 
+// Disable TLS1.3 by default. The only reason for enabling it for now is to work
+// on fixing cipher suite incompatibilities with TLS1.2 that prevent node from
+// working with TLS1.3 in OpenSSL 1.1.1.
+exports.DEFAULT_MAX_VERSION = 'TLSv1.2';
+
+exports.DEFAULT_MIN_VERSION = 'TLSv1';
+
 exports.getCiphers = internalUtil.cachedResult(
   () => internalUtil.filterDuplicateStrings(binding.getSSLCiphers(), true)
 );
index 8e65bd3c44b071592795e47ceefea63923bad961..f7b3201c2999f24b4f833763314fc25b92fceffe 100644 (file)
@@ -389,6 +389,24 @@ void SecureContext::New(const FunctionCallbackInfo<Value>& args) {
 }
 
 
+int string_to_tls_protocol(const char* version_str) {
+  int version;
+
+  if (strcmp(version_str, "TLSv1.3") == 0) {
+    version = TLS1_3_VERSION;
+  } else if (strcmp(version_str, "TLSv1.2") == 0) {
+    version = TLS1_2_VERSION;
+  } else if (strcmp(version_str, "TLSv1.1") == 0) {
+    version = TLS1_1_VERSION;
+  } else if (strcmp(version_str, "TLSv1") == 0) {
+    version = TLS1_VERSION;
+  } else {
+    version = 0;
+  }
+  return version;
+}
+
+
 void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
   SecureContext* sc;
   ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
@@ -396,10 +414,21 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
 
   int min_version = 0;
   int max_version = 0;
+
+  if (args[0]->IsString()) {
+    const node::Utf8Value min(env->isolate(), args[0]);
+    min_version = string_to_tls_protocol(*min);
+  }
+
+  if (args[1]->IsString()) {
+    const node::Utf8Value max(env->isolate(), args[1]);
+    max_version = string_to_tls_protocol(*max);
+  }
+
   const SSL_METHOD* method = TLS_method();
 
-  if (args.Length() == 1 && args[0]->IsString()) {
-    const node::Utf8Value sslmethod(env->isolate(), args[0]);
+  if (args.Length() == 3 && args[2]->IsString()) {
+    const node::Utf8Value sslmethod(env->isolate(), args[2]);
 
     // 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
index c29e09731df0b204b90409cbccb083ab7ef4666f..d27763c215c54025fc6d3ede806b5b087108ee70 100644 (file)
@@ -12,7 +12,7 @@ const agent = new https.Agent();
 // empty options
 assert.strictEqual(
   agent.getName({}),
-  'localhost:::::::::::::::::'
+  'localhost:::::::::::::::::::'
 );
 
 // pass all options arguments
@@ -39,5 +39,5 @@ const options = {
 assert.strictEqual(
   agent.getName(options),
   '0.0.0.0:443:192.168.1.1:ca:cert::ciphers:key:pfx:false:localhost:' +
-    'secureProtocol:c,r,l:false:ecdhCurve:dhparam:0:sessionIdContext'
+    '::secureProtocol:c,r,l:false:ecdhCurve:dhparam:0:sessionIdContext'
 );