2017-01-03 21:16:48 +00:00
|
|
|
// 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.
|
|
|
|
|
2014-11-22 15:59:48 +00:00
|
|
|
'use strict';
|
|
|
|
|
2018-10-12 18:44:10 +00:00
|
|
|
const {
|
|
|
|
ERR_TLS_CERT_ALTNAME_INVALID,
|
|
|
|
ERR_OUT_OF_RANGE
|
|
|
|
} = require('internal/errors').codes;
|
2016-06-08 15:18:26 +00:00
|
|
|
const internalUtil = require('internal/util');
|
2017-09-08 07:58:54 +00:00
|
|
|
const internalTLS = require('internal/tls');
|
2017-01-16 09:19:32 +00:00
|
|
|
internalUtil.assertCrypto();
|
2018-10-02 02:52:30 +00:00
|
|
|
const { isArrayBufferView } = require('internal/util/types');
|
2016-03-08 23:31:31 +00:00
|
|
|
|
2015-01-21 16:36:59 +00:00
|
|
|
const net = require('net');
|
|
|
|
const url = require('url');
|
2018-08-21 06:54:02 +00:00
|
|
|
const binding = internalBinding('crypto');
|
2018-04-16 09:54:23 +00:00
|
|
|
const { Buffer } = require('buffer');
|
2017-12-23 04:55:37 +00:00
|
|
|
const EventEmitter = require('events');
|
2018-04-25 00:37:43 +00:00
|
|
|
const { URL } = require('internal/url');
|
2017-12-23 04:55:37 +00:00
|
|
|
const DuplexPair = require('internal/streams/duplexpair');
|
2018-08-23 13:28:41 +00:00
|
|
|
const { canonicalizeIP } = internalBinding('cares_wrap');
|
2018-04-17 13:07:34 +00:00
|
|
|
const _tls_common = require('_tls_common');
|
|
|
|
const _tls_wrap = require('_tls_wrap');
|
2013-05-22 18:32:54 +00:00
|
|
|
|
2012-02-15 18:26:43 +00:00
|
|
|
// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
|
|
|
|
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
|
2017-06-17 13:11:45 +00:00
|
|
|
// renegotiations are seen. The settings are applied to all remote client
|
2012-02-15 18:26:43 +00:00
|
|
|
// connections.
|
2012-02-18 23:01:35 +00:00
|
|
|
exports.CLIENT_RENEG_LIMIT = 3;
|
2012-02-15 18:26:43 +00:00
|
|
|
exports.CLIENT_RENEG_WINDOW = 600;
|
|
|
|
|
2016-05-02 17:27:12 +00:00
|
|
|
exports.DEFAULT_CIPHERS =
|
2018-10-14 23:41:32 +00:00
|
|
|
internalBinding('constants').crypto.defaultCipherList;
|
2014-03-06 23:27:01 +00:00
|
|
|
|
2017-11-06 23:08:22 +00:00
|
|
|
exports.DEFAULT_ECDH_CURVE = 'auto';
|
2014-03-06 23:27:01 +00:00
|
|
|
|
2017-01-13 22:28:35 +00:00
|
|
|
exports.getCiphers = internalUtil.cachedResult(
|
|
|
|
() => internalUtil.filterDuplicateStrings(binding.getSSLCiphers(), true)
|
|
|
|
);
|
2013-03-18 23:16:55 +00:00
|
|
|
|
2011-04-14 03:53:39 +00:00
|
|
|
// Convert protocols array into valid OpenSSL protocols list
|
|
|
|
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
|
2015-04-23 06:25:15 +00:00
|
|
|
function convertProtocols(protocols) {
|
2016-09-13 10:38:29 +00:00
|
|
|
const lens = new Array(protocols.length);
|
2016-06-08 22:47:18 +00:00
|
|
|
const buff = Buffer.allocUnsafe(protocols.reduce((p, c, i) => {
|
|
|
|
var len = Buffer.byteLength(c);
|
2018-10-12 18:44:10 +00:00
|
|
|
if (len > 255) {
|
|
|
|
throw new ERR_OUT_OF_RANGE('The byte length of the protocol at index ' +
|
|
|
|
`${i} exceeds the maximum length.`, '<= 255', len, true);
|
|
|
|
}
|
2016-06-08 22:47:18 +00:00
|
|
|
lens[i] = len;
|
|
|
|
return p + 1 + len;
|
2015-04-23 06:25:15 +00:00
|
|
|
}, 0));
|
2011-04-14 03:53:39 +00:00
|
|
|
|
2016-06-08 22:47:18 +00:00
|
|
|
var offset = 0;
|
|
|
|
for (var i = 0, c = protocols.length; i < c; i++) {
|
|
|
|
buff[offset++] = lens[i];
|
|
|
|
buff.write(protocols[i], offset);
|
|
|
|
offset += lens[i];
|
|
|
|
}
|
2011-04-14 03:53:39 +00:00
|
|
|
|
2015-04-23 06:25:15 +00:00
|
|
|
return buff;
|
2016-01-15 08:53:11 +00:00
|
|
|
}
|
2015-04-23 06:25:15 +00:00
|
|
|
|
2018-07-11 10:36:33 +00:00
|
|
|
exports.convertALPNProtocols = function convertALPNProtocols(protocols, out) {
|
2015-04-23 06:25:15 +00:00
|
|
|
// If protocols is Array - translate it into buffer
|
|
|
|
if (Array.isArray(protocols)) {
|
2016-08-10 19:16:06 +00:00
|
|
|
out.ALPNProtocols = convertProtocols(protocols);
|
2018-10-02 02:52:30 +00:00
|
|
|
} else if (isArrayBufferView(protocols)) {
|
2016-08-10 19:16:06 +00:00
|
|
|
// Copy new buffer not to be modified by user.
|
2016-01-25 23:00:06 +00:00
|
|
|
out.ALPNProtocols = Buffer.from(protocols);
|
2011-04-14 03:53:39 +00:00
|
|
|
}
|
2013-06-13 13:36:00 +00:00
|
|
|
};
|
2012-07-05 19:50:21 +00:00
|
|
|
|
2016-08-15 16:46:27 +00:00
|
|
|
function unfqdn(host) {
|
|
|
|
return host.replace(/[.]$/, '');
|
|
|
|
}
|
2012-07-11 19:54:20 +00:00
|
|
|
|
2016-08-15 16:46:27 +00:00
|
|
|
function splitHost(host) {
|
|
|
|
// String#toLowerCase() is locale-sensitive so we use
|
|
|
|
// a conservative version that only lowercases A-Z.
|
|
|
|
const replacer = (c) => String.fromCharCode(32 + c.charCodeAt(0));
|
|
|
|
return unfqdn(host).replace(/[A-Z]/g, replacer).split('.');
|
|
|
|
}
|
|
|
|
|
|
|
|
function check(hostParts, pattern, wildcards) {
|
|
|
|
// Empty strings, null, undefined, etc. never match.
|
|
|
|
if (!pattern)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const patternParts = splitHost(pattern);
|
|
|
|
|
|
|
|
if (hostParts.length !== patternParts.length)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Pattern has empty components, e.g. "bad..example.com".
|
|
|
|
if (patternParts.includes(''))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// RFC 6125 allows IDNA U-labels (Unicode) in names but we have no
|
|
|
|
// good way to detect their encoding or normalize them so we simply
|
|
|
|
// reject them. Control characters and blanks are rejected as well
|
|
|
|
// because nothing good can come from accepting them.
|
|
|
|
const isBad = (s) => /[^\u0021-\u007F]/u.test(s);
|
|
|
|
if (patternParts.some(isBad))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Check host parts from right to left first.
|
2017-06-20 21:37:00 +00:00
|
|
|
for (var i = hostParts.length - 1; i > 0; i -= 1) {
|
2016-08-15 16:46:27 +00:00
|
|
|
if (hostParts[i] !== patternParts[i])
|
|
|
|
return false;
|
2017-06-20 21:37:00 +00:00
|
|
|
}
|
2016-08-15 16:46:27 +00:00
|
|
|
|
|
|
|
const hostSubdomain = hostParts[0];
|
|
|
|
const patternSubdomain = patternParts[0];
|
|
|
|
const patternSubdomainParts = patternSubdomain.split('*');
|
|
|
|
|
|
|
|
// Short-circuit when the subdomain does not contain a wildcard.
|
|
|
|
// RFC 6125 does not allow wildcard substitution for components
|
|
|
|
// containing IDNA A-labels (Punycode) so match those verbatim.
|
|
|
|
if (patternSubdomainParts.length === 1 || patternSubdomain.includes('xn--'))
|
|
|
|
return hostSubdomain === patternSubdomain;
|
|
|
|
|
|
|
|
if (!wildcards)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// More than one wildcard is always wrong.
|
|
|
|
if (patternSubdomainParts.length > 2)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// *.tld wildcards are not allowed.
|
|
|
|
if (patternParts.length <= 2)
|
|
|
|
return false;
|
2012-07-11 19:54:20 +00:00
|
|
|
|
2016-08-15 16:46:27 +00:00
|
|
|
const [prefix, suffix] = patternSubdomainParts;
|
|
|
|
|
|
|
|
if (prefix.length + suffix.length > hostSubdomain.length)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!hostSubdomain.startsWith(prefix))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!hostSubdomain.endsWith(suffix))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-04-25 00:37:43 +00:00
|
|
|
let urlWarningEmitted = false;
|
2018-05-24 11:14:27 +00:00
|
|
|
exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
|
2016-08-15 16:46:27 +00:00
|
|
|
const subject = cert.subject;
|
|
|
|
const altNames = cert.subjectaltname;
|
|
|
|
const dnsNames = [];
|
|
|
|
const uriNames = [];
|
2016-01-12 21:04:50 +00:00
|
|
|
const ips = [];
|
2016-08-15 16:46:27 +00:00
|
|
|
|
2018-05-24 11:14:27 +00:00
|
|
|
hostname = '' + hostname;
|
2016-08-15 16:46:27 +00:00
|
|
|
|
|
|
|
if (altNames) {
|
|
|
|
for (const name of altNames.split(', ')) {
|
|
|
|
if (name.startsWith('DNS:')) {
|
|
|
|
dnsNames.push(name.slice(4));
|
|
|
|
} else if (name.startsWith('URI:')) {
|
2018-04-25 00:37:43 +00:00
|
|
|
let uri;
|
|
|
|
try {
|
|
|
|
uri = new URL(name.slice(4));
|
2018-10-12 16:50:41 +00:00
|
|
|
} catch {
|
2018-04-25 00:37:43 +00:00
|
|
|
uri = url.parse(name.slice(4));
|
|
|
|
if (!urlWarningEmitted && !process.noDeprecation) {
|
|
|
|
urlWarningEmitted = true;
|
|
|
|
process.emitWarning(
|
|
|
|
`The URI ${name.slice(4)} found in cert.subjectaltname ` +
|
|
|
|
'is not a valid URI, and is supported in the tls module ' +
|
|
|
|
'solely for compatibility.',
|
|
|
|
'DeprecationWarning', 'DEP0109');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-15 16:46:27 +00:00
|
|
|
uriNames.push(uri.hostname); // TODO(bnoordhuis) Also use scheme.
|
|
|
|
} else if (name.startsWith('IP Address:')) {
|
2017-08-10 13:11:19 +00:00
|
|
|
ips.push(canonicalizeIP(name.slice(11)));
|
2012-07-11 19:54:20 +00:00
|
|
|
}
|
2016-08-15 16:46:27 +00:00
|
|
|
}
|
2012-07-11 19:54:20 +00:00
|
|
|
}
|
|
|
|
|
2016-08-15 16:46:27 +00:00
|
|
|
let valid = false;
|
|
|
|
let reason = 'Unknown reason';
|
|
|
|
|
2018-05-24 11:14:27 +00:00
|
|
|
if (net.isIP(hostname)) {
|
|
|
|
valid = ips.includes(canonicalizeIP(hostname));
|
2016-08-15 16:46:27 +00:00
|
|
|
if (!valid)
|
2018-05-24 11:14:27 +00:00
|
|
|
reason = `IP: ${hostname} is not in the cert's list: ${ips.join(', ')}`;
|
2016-08-15 16:46:27 +00:00
|
|
|
// TODO(bnoordhuis) Also check URI SANs that are IP addresses.
|
|
|
|
} else if (subject) {
|
2018-05-24 11:14:27 +00:00
|
|
|
hostname = unfqdn(hostname); // Remove trailing dot for error messages.
|
|
|
|
const hostParts = splitHost(hostname);
|
2016-08-15 16:46:27 +00:00
|
|
|
const wildcard = (pattern) => check(hostParts, pattern, true);
|
|
|
|
const noWildcard = (pattern) => check(hostParts, pattern, false);
|
|
|
|
|
|
|
|
// Match against Common Name only if no supported identifiers are present.
|
|
|
|
if (dnsNames.length === 0 && ips.length === 0 && uriNames.length === 0) {
|
|
|
|
const cn = subject.CN;
|
|
|
|
|
|
|
|
if (Array.isArray(cn))
|
|
|
|
valid = cn.some(wildcard);
|
|
|
|
else if (cn)
|
|
|
|
valid = wildcard(cn);
|
|
|
|
|
|
|
|
if (!valid)
|
2018-05-24 11:14:27 +00:00
|
|
|
reason = `Host: ${hostname}. is not cert's CN: ${cn}`;
|
2016-08-15 16:46:27 +00:00
|
|
|
} else {
|
|
|
|
valid = dnsNames.some(wildcard) || uriNames.some(noWildcard);
|
|
|
|
if (!valid)
|
2018-05-24 11:14:27 +00:00
|
|
|
reason =
|
|
|
|
`Host: ${hostname}. is not in the cert's altnames: ${altNames}`;
|
2014-04-14 16:08:38 +00:00
|
|
|
}
|
2015-08-10 15:55:37 +00:00
|
|
|
} else {
|
|
|
|
reason = 'Cert is empty';
|
2012-07-11 19:54:20 +00:00
|
|
|
}
|
|
|
|
|
2014-04-14 16:08:38 +00:00
|
|
|
if (!valid) {
|
2018-02-27 13:55:32 +00:00
|
|
|
const err = new ERR_TLS_CERT_ALTNAME_INVALID(reason);
|
2014-04-14 16:08:38 +00:00
|
|
|
err.reason = reason;
|
2018-05-24 11:14:27 +00:00
|
|
|
err.host = hostname;
|
2014-04-14 16:08:38 +00:00
|
|
|
err.cert = cert;
|
|
|
|
return err;
|
|
|
|
}
|
2010-12-09 08:10:16 +00:00
|
|
|
};
|
|
|
|
|
2017-12-23 04:55:37 +00:00
|
|
|
|
|
|
|
class SecurePair extends EventEmitter {
|
|
|
|
constructor(secureContext = exports.createSecureContext(),
|
|
|
|
isServer = false,
|
|
|
|
requestCert = !isServer,
|
|
|
|
rejectUnauthorized = false,
|
|
|
|
options = {}) {
|
|
|
|
super();
|
|
|
|
const { socket1, socket2 } = new DuplexPair();
|
|
|
|
|
|
|
|
this.server = options.server;
|
|
|
|
this.credentials = secureContext;
|
|
|
|
|
|
|
|
this.encrypted = socket1;
|
|
|
|
this.cleartext = new exports.TLSSocket(socket2, Object.assign({
|
|
|
|
secureContext, isServer, requestCert, rejectUnauthorized
|
|
|
|
}, options));
|
|
|
|
this.cleartext.once('secure', () => this.emit('secure'));
|
|
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
|
|
|
this.cleartext.destroy();
|
|
|
|
this.encrypted.destroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-08 07:58:54 +00:00
|
|
|
exports.parseCertString = internalUtil.deprecate(
|
|
|
|
internalTLS.parseCertString,
|
|
|
|
'tls.parseCertString() is deprecated. ' +
|
|
|
|
'Please use querystring.parse() instead.',
|
2017-09-21 16:42:06 +00:00
|
|
|
'DEP0076');
|
2011-01-07 18:57:39 +00:00
|
|
|
|
2018-04-17 13:07:34 +00:00
|
|
|
exports.createSecureContext = _tls_common.createSecureContext;
|
|
|
|
exports.SecureContext = _tls_common.SecureContext;
|
|
|
|
exports.TLSSocket = _tls_wrap.TLSSocket;
|
|
|
|
exports.Server = _tls_wrap.Server;
|
|
|
|
exports.createServer = _tls_wrap.createServer;
|
|
|
|
exports.connect = _tls_wrap.connect;
|
2017-02-16 22:30:29 +00:00
|
|
|
|
2017-12-23 04:55:37 +00:00
|
|
|
exports.createSecurePair = internalUtil.deprecate(
|
|
|
|
function createSecurePair(...args) {
|
|
|
|
return new SecurePair(...args);
|
|
|
|
},
|
|
|
|
'tls.createSecurePair() is deprecated. Please use ' +
|
|
|
|
'tls.TLSSocket instead.', 'DEP0064');
|