summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcjihrig <cjihrig@gmail.com>2018-10-13 14:18:31 -0400
committerJames M Snell <jasnell@gmail.com>2018-10-21 12:27:11 -0700
commitbed4a8c6e056c9ea74215875269e457f8f4ac268 (patch)
tree0c01020d59fe0728d3bd79049ea96c428834675f
parent7db4281e526aec23b42eac19c7f50ed8e20bd962 (diff)
downloadnode-new-bed4a8c6e056c9ea74215875269e457f8f4ac268.tar.gz
tls: support changing credentials dynamically
This commit adds a setSecureContext() method to TLS servers. In order to maintain backwards compatibility, the method takes the options needed to create a new SecureContext, rather than an instance of SecureContext. Fixes: https://github.com/nodejs/node/issues/4464 Refs: https://github.com/nodejs/node/issues/10349 Refs: https://github.com/nodejs/help/issues/603 Refs: https://github.com/nodejs/node/issues/15115 PR-URL: https://github.com/nodejs/node/pull/23644 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
-rw-r--r--doc/api/tls.md12
-rw-r--r--lib/_tls_wrap.js138
-rw-r--r--test/parallel/test-tls-set-secure-context.js88
3 files changed, 214 insertions, 24 deletions
diff --git a/doc/api/tls.md b/doc/api/tls.md
index 4f3430854e..f9a313f914 100644
--- a/doc/api/tls.md
+++ b/doc/api/tls.md
@@ -411,6 +411,18 @@ encryption/decryption of the [TLS Session Tickets][].
Starts the server listening for encrypted connections.
This method is identical to [`server.listen()`][] from [`net.Server`][].
+### server.setSecureContext(options)
+<!-- YAML
+added: REPLACEME
+-->
+
+* `options` {Object} An object containing any of the possible properties from
+ the [`tls.createSecureContext()`][] `options` arguments (e.g. `key`, `cert`,
+ `ca`, etc).
+
+The `server.setSecureContext()` method replaces the secure context of an
+existing server. Existing connections to the server are not interrupted.
+
### server.setTicketKeys(keys)
<!-- YAML
added: v3.0.0
diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js
index b28e3b62b7..aa8b66b715 100644
--- a/lib/_tls_wrap.js
+++ b/lib/_tls_wrap.js
@@ -833,22 +833,11 @@ function Server(options, listener) {
// Handle option defaults:
this.setOptions(options);
- this._sharedCreds = tls.createSecureContext({
- pfx: this.pfx,
- key: this.key,
- passphrase: this.passphrase,
- cert: this.cert,
- clientCertEngine: this.clientCertEngine,
- ca: this.ca,
- ciphers: this.ciphers,
- ecdhCurve: this.ecdhCurve,
- dhparam: this.dhparam,
- secureProtocol: this.secureProtocol,
- secureOptions: this.secureOptions,
- honorCipherOrder: this.honorCipherOrder,
- crl: this.crl,
- sessionIdContext: this.sessionIdContext
- });
+ // setSecureContext() overlaps with setOptions() quite a bit. setOptions()
+ // is an undocumented API that was probably never intended to be exposed
+ // publicly. Unfortunately, it would be a breaking change to just remove it,
+ // and there is at least one test that depends on it.
+ this.setSecureContext(options);
this[kHandshakeTimeout] = options.handshakeTimeout || (120 * 1000);
this[kSNICallback] = options.SNICallback;
@@ -863,14 +852,6 @@ function Server(options, listener) {
'options.SNICallback', 'function', options.SNICallback);
}
- if (this.sessionTimeout) {
- this._sharedCreds.context.setSessionTimeout(this.sessionTimeout);
- }
-
- if (this.ticketKeys) {
- this._sharedCreds.context.setTicketKeys(this.ticketKeys);
- }
-
// constructor call
net.Server.call(this, tlsConnectionListener);
@@ -886,6 +867,115 @@ exports.createServer = function createServer(options, listener) {
};
+Server.prototype.setSecureContext = function(options) {
+ if (options === null || typeof options !== 'object')
+ throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
+
+ if (options.pfx)
+ this.pfx = options.pfx;
+ else
+ this.pfx = undefined;
+
+ if (options.key)
+ this.key = options.key;
+ else
+ this.key = undefined;
+
+ if (options.passphrase)
+ this.passphrase = options.passphrase;
+ else
+ this.passphrase = undefined;
+
+ if (options.cert)
+ this.cert = options.cert;
+ else
+ this.cert = undefined;
+
+ if (options.clientCertEngine)
+ this.clientCertEngine = options.clientCertEngine;
+ else
+ this.clientCertEngine = undefined;
+
+ if (options.ca)
+ this.ca = options.ca;
+ else
+ this.ca = undefined;
+
+ if (options.secureProtocol)
+ this.secureProtocol = options.secureProtocol;
+ else
+ this.secureProtocol = undefined;
+
+ if (options.crl)
+ this.crl = options.crl;
+ else
+ this.crl = undefined;
+
+ if (options.ciphers)
+ this.ciphers = options.ciphers;
+ else
+ this.ciphers = undefined;
+
+ if (options.ecdhCurve !== undefined)
+ this.ecdhCurve = options.ecdhCurve;
+ else
+ this.ecdhCurve = undefined;
+
+ if (options.dhparam)
+ this.dhparam = options.dhparam;
+ else
+ this.dhparam = undefined;
+
+ if (options.honorCipherOrder !== undefined)
+ this.honorCipherOrder = !!options.honorCipherOrder;
+ else
+ this.honorCipherOrder = true;
+
+ const secureOptions = options.secureOptions || 0;
+
+ if (secureOptions)
+ this.secureOptions = secureOptions;
+ else
+ this.secureOptions = undefined;
+
+ if (options.sessionIdContext) {
+ this.sessionIdContext = options.sessionIdContext;
+ } else {
+ this.sessionIdContext = crypto.createHash('sha1')
+ .update(process.argv.join(' '))
+ .digest('hex')
+ .slice(0, 32);
+ }
+
+ this._sharedCreds = tls.createSecureContext({
+ pfx: this.pfx,
+ key: this.key,
+ passphrase: this.passphrase,
+ cert: this.cert,
+ clientCertEngine: this.clientCertEngine,
+ ca: this.ca,
+ ciphers: this.ciphers,
+ ecdhCurve: this.ecdhCurve,
+ dhparam: this.dhparam,
+ secureProtocol: this.secureProtocol,
+ secureOptions: this.secureOptions,
+ honorCipherOrder: this.honorCipherOrder,
+ crl: this.crl,
+ sessionIdContext: this.sessionIdContext
+ });
+
+ if (this.sessionTimeout)
+ this._sharedCreds.context.setSessionTimeout(this.sessionTimeout);
+
+ if (options.ticketKeys) {
+ this.ticketKeys = options.ticketKeys;
+ this.setTicketKeys(this.ticketKeys);
+ } else {
+ this.setTicketKeys(this.getTicketKeys());
+ }
+};
+
+
Server.prototype._getServerData = function() {
return {
ticketKeys: this.getTicketKeys().toString('hex')
diff --git a/test/parallel/test-tls-set-secure-context.js b/test/parallel/test-tls-set-secure-context.js
new file mode 100644
index 0000000000..51ab3af10b
--- /dev/null
+++ b/test/parallel/test-tls-set-secure-context.js
@@ -0,0 +1,88 @@
+'use strict';
+const common = require('../common');
+
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const https = require('https');
+const fixtures = require('../common/fixtures');
+const credentialOptions = [
+ {
+ key: fixtures.readKey('agent1-key.pem'),
+ cert: fixtures.readKey('agent1-cert.pem'),
+ ca: fixtures.readKey('ca1-cert.pem')
+ },
+ {
+ key: fixtures.readKey('agent2-key.pem'),
+ cert: fixtures.readKey('agent2-cert.pem'),
+ ca: fixtures.readKey('ca2-cert.pem')
+ }
+];
+let requestsCount = 0;
+let firstResponse;
+
+const server = https.createServer(credentialOptions[0], (req, res) => {
+ requestsCount++;
+
+ if (requestsCount === 1) {
+ firstResponse = res;
+ firstResponse.write('multi-');
+ return;
+ } else if (requestsCount === 3) {
+ firstResponse.write('success-');
+ }
+
+ res.end('success');
+});
+
+server.listen(0, common.mustCall(async () => {
+ const { port } = server.address();
+ const firstRequest = makeRequest(port);
+
+ assert.strictEqual(await makeRequest(port), 'success');
+
+ server.setSecureContext(credentialOptions[1]);
+ firstResponse.write('request-');
+ await assert.rejects(async () => {
+ await makeRequest(port);
+ }, /^Error: self signed certificate$/);
+
+ server.setSecureContext(credentialOptions[0]);
+ assert.strictEqual(await makeRequest(port), 'success');
+
+ server.setSecureContext(credentialOptions[1]);
+ firstResponse.end('fun!');
+ await assert.rejects(async () => {
+ await makeRequest(port);
+ }, /^Error: self signed certificate$/);
+
+ assert.strictEqual(await firstRequest, 'multi-request-success-fun!');
+ server.close();
+}));
+
+function makeRequest(port) {
+ return new Promise((resolve, reject) => {
+ const options = {
+ rejectUnauthorized: true,
+ ca: credentialOptions[0].ca,
+ servername: 'agent1'
+ };
+
+ https.get(`https://localhost:${port}`, options, (res) => {
+ let response = '';
+
+ res.setEncoding('utf8');
+
+ res.on('data', (chunk) => {
+ response += chunk;
+ });
+
+ res.on('end', common.mustCall(() => {
+ resolve(response);
+ }));
+ }).on('error', (err) => {
+ reject(err);
+ });
+ });
+}