Skip to content

Commit a5c7843

Browse files
mhdawsonkumarak
authored andcommitted
src: add cve reverts and associated tests
Co-authored-by: Akshay K <iit.akshay@gmail.com> Backport-PR-URL: nodejs-private/node-private#306 PR-URL: nodejs-private/node-private#300 Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 8c2db2c commit a5c7843

File tree

6 files changed

+556
-3
lines changed

6 files changed

+556
-3
lines changed

lib/tls.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ const { isArrayBufferView } = require('internal/util/types');
4747

4848
const net = require('net');
4949
const { getOptionValue } = require('internal/options');
50+
const url = require('url');
5051
const { getRootCertificates, getSSLCiphers } = internalBinding('crypto');
5152
const { Buffer } = require('buffer');
5253
const EventEmitter = require('events');
54+
const { URL } = require('internal/url');
5355
const DuplexPair = require('internal/streams/duplexpair');
5456
const { canonicalizeIP } = internalBinding('cares_wrap');
5557
const _tls_common = require('_tls_common');
@@ -252,10 +254,12 @@ function splitEscapedAltNames(altNames) {
252254
return result;
253255
}
254256

257+
let urlWarningEmitted = false;
255258
exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
256259
const subject = cert.subject;
257260
const altNames = cert.subjectaltname;
258261
const dnsNames = [];
262+
const uriNames = [];
259263
const ips = [];
260264

261265
hostname = '' + hostname;
@@ -267,6 +271,22 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
267271
for (const name of splitAltNames) {
268272
if (name.startsWith('DNS:')) {
269273
dnsNames.push(name.slice(4));
274+
} else if (process.REVERT_CVE_2021_44531 && name.startsWith('URI:')) {
275+
let uri;
276+
try {
277+
uri = new URL(name.slice(4));
278+
} catch {
279+
uri = url.parse(name.slice(4));
280+
if (!urlWarningEmitted && !process.noDeprecation) {
281+
urlWarningEmitted = true;
282+
process.emitWarning(
283+
`The URI ${name.slice(4)} found in cert.subjectaltname ` +
284+
'is not a valid URI, and is supported in the tls module ' +
285+
'solely for compatibility.',
286+
'DeprecationWarning', 'DEP0109');
287+
}
288+
}
289+
uriNames.push(uri.hostname); // TODO(bnoordhuis) Also use scheme.
270290
} else if (name.startsWith('IP Address:')) {
271291
ips.push(canonicalizeIP(name.slice(11)));
272292
}
@@ -276,19 +296,25 @@ exports.checkServerIdentity = function checkServerIdentity(hostname, cert) {
276296
let valid = false;
277297
let reason = 'Unknown reason';
278298

299+
const hasAltNames =
300+
dnsNames.length > 0 || ips.length > 0 || uriNames.length > 0;
301+
279302
hostname = unfqdn(hostname); // Remove trailing dot for error messages.
280303

281304
if (net.isIP(hostname)) {
282305
valid = ips.includes(canonicalizeIP(hostname));
283306
if (!valid)
284307
reason = `IP: ${hostname} is not in the cert's list: ${ips.join(', ')}`;
285308
// TODO(bnoordhuis) Also check URI SANs that are IP addresses.
286-
} else if (dnsNames.length > 0 || (subject && subject.CN)) {
309+
} else if ((process.REVERT_CVE_2021_44531 && (hasAltNames || subject)) ||
310+
(dnsNames.length > 0 || (subject && subject.CN))) {
287311
const hostParts = splitHost(hostname);
288312
const wildcard = (pattern) => check(hostParts, pattern, true);
289313

290-
if (dnsNames.length > 0) {
291-
valid = dnsNames.some(wildcard);
314+
if ((process.REVERT_CVE_2021_44531 && hasAltNames) ||
315+
(dnsNames.length > 0)) {
316+
const noWildcard = (pattern) => check(hostParts, pattern, false);
317+
valid = dnsNames.some(wildcard) || uriNames.some(noWildcard);
292318
if (!valid)
293319
reason =
294320
`Host: ${hostname}. is not in the cert's altnames: ${altNames}`;

src/node_crypto_common.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "node_crypto_common.h"
66
#include "node.h"
77
#include "node_internals.h"
8+
#include "node_revert.h"
89
#include "node_url.h"
910
#include "string_bytes.h"
1011
#include "v8.h"
@@ -481,6 +482,44 @@ void AddFingerprintDigest(
481482
}
482483
}
483484

485+
// deprecated, only used for security revert
486+
bool SafeX509ExtPrint(const BIOPointer& out, X509_EXTENSION* ext) {
487+
const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext);
488+
489+
if (method != X509V3_EXT_get_nid(NID_subject_alt_name))
490+
return false;
491+
492+
GENERAL_NAMES* names = static_cast<GENERAL_NAMES*>(X509V3_EXT_d2i(ext));
493+
if (names == nullptr)
494+
return false;
495+
496+
for (int i = 0; i < sk_GENERAL_NAME_num(names); i++) {
497+
GENERAL_NAME* gen = sk_GENERAL_NAME_value(names, i);
498+
499+
if (i != 0)
500+
BIO_write(out.get(), ", ", 2);
501+
502+
if (gen->type == GEN_DNS) {
503+
ASN1_IA5STRING* name = gen->d.dNSName;
504+
505+
BIO_write(out.get(), "DNS:", 4);
506+
BIO_write(out.get(), name->data, name->length);
507+
} else {
508+
STACK_OF(CONF_VALUE)* nval = i2v_GENERAL_NAME(
509+
const_cast<X509V3_EXT_METHOD*>(method), gen, nullptr);
510+
if (nval == nullptr) {
511+
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
512+
return false;
513+
}
514+
X509V3_EXT_val_prn(out.get(), nval, 0, 0);
515+
sk_CONF_VALUE_pop_free(nval, X509V3_conf_free);
516+
}
517+
}
518+
sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
519+
520+
return true;
521+
}
522+
484523
static inline bool IsSafeAltName(const char* name, size_t length, bool utf8) {
485524
for (size_t i = 0; i < length; i++) {
486525
char c = name[i];
@@ -705,6 +744,10 @@ bool SafeX509SubjectAltNamePrint(const BIOPointer& out, X509_EXTENSION* ext) {
705744
const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext);
706745
CHECK(method == X509V3_EXT_get_nid(NID_subject_alt_name));
707746

747+
if (IsReverted(SECURITY_REVERT_CVE_2021_44532)) {
748+
return SafeX509ExtPrint(out, ext);
749+
}
750+
708751
GENERAL_NAMES* names = static_cast<GENERAL_NAMES*>(X509V3_EXT_d2i(ext));
709752
if (names == nullptr)
710753
return false;
@@ -730,6 +773,10 @@ bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext) {
730773
const X509V3_EXT_METHOD* method = X509V3_EXT_get(ext);
731774
CHECK(method == X509V3_EXT_get_nid(NID_info_access));
732775

776+
if (IsReverted(SECURITY_REVERT_CVE_2021_44532)) {
777+
return (X509V3_EXT_print(out.get(), ext, 0, 0) == 1);
778+
}
779+
733780
AUTHORITY_INFO_ACCESS* descs =
734781
static_cast<AUTHORITY_INFO_ACCESS*>(X509V3_EXT_d2i(ext));
735782
if (descs == nullptr)

src/node_revert.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ namespace node {
2020
XX(CVE_2019_9514, "CVE-2019-9514", "HTTP/2 Reset Flood") \
2121
XX(CVE_2019_9516, "CVE-2019-9516", "HTTP/2 0-Length Headers Leak") \
2222
XX(CVE_2019_9518, "CVE-2019-9518", "HTTP/2 Empty DATA Frame Flooding") \
23+
XX(CVE_2021_44531, "CVE-2021-44531", "Cert Verif Bypass via URI SAN") \
24+
XX(CVE_2021_44532, "CVE-2021-44532", "Cert Verif Bypass via Str Inject") \
2325
// XX(CVE_2016_PEND, "CVE-2016-PEND", "Vulnerability Title")
2426
// TODO(addaleax): Remove all of the above before Node.js 13 as the comment
2527
// at the start of the file indicates.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Flags: --security-revert=CVE-2021-44531
2+
'use strict';
3+
const common = require('../common');
4+
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
8+
const assert = require('assert');
9+
const util = require('util');
10+
11+
const tls = require('tls');
12+
13+
common.expectWarning('DeprecationWarning', [
14+
['The URI http://[a.b.a.com]/ found in cert.subjectaltname ' +
15+
'is not a valid URI, and is supported in the tls module ' +
16+
'solely for compatibility.',
17+
'DEP0109'],
18+
]);
19+
20+
const tests = [
21+
// Likewise for "URI:" Subject Alternative Names.
22+
// See also https://github.com/nodejs/node/issues/8108.
23+
{
24+
host: '8.8.8.8',
25+
cert: { subject: { CN: '8.8.8.8' }, subjectaltname: 'URI:http://8.8.8.8/' },
26+
error: 'IP: 8.8.8.8 is not in the cert\'s list: '
27+
},
28+
// Empty Subject w/URI name
29+
{
30+
host: 'a.b.a.com', cert: {
31+
subjectaltname: 'URI:http://a.b.a.com/',
32+
}
33+
},
34+
// URI names
35+
{
36+
host: 'a.b.a.com', cert: {
37+
subjectaltname: 'URI:http://a.b.a.com/',
38+
subject: {}
39+
}
40+
},
41+
{
42+
host: 'a.b.a.com', cert: {
43+
subjectaltname: 'URI:http://*.b.a.com/',
44+
subject: {}
45+
},
46+
error: 'Host: a.b.a.com. is not in the cert\'s altnames: ' +
47+
'URI:http://*.b.a.com/'
48+
},
49+
// Invalid URI
50+
{
51+
host: 'a.b.a.com', cert: {
52+
subjectaltname: 'URI:http://[a.b.a.com]/',
53+
subject: {}
54+
}
55+
},
56+
];
57+
58+
tests.forEach(function(test, i) {
59+
const err = tls.checkServerIdentity(test.host, test.cert);
60+
assert.strictEqual(err && err.reason,
61+
test.error,
62+
`Test# ${i} failed: ${util.inspect(test)} \n` +
63+
`${test.error} != ${(err && err.reason)}`);
64+
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright Joyent, Inc. and other Node contributors.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a
4+
// copy of this software and associated documentation files (the
5+
// "Software"), to deal in the Software without restriction, including
6+
// without limitation the rights to use, copy, modify, merge, publish,
7+
// distribute, sublicense, and/or sell copies of the Software, and to permit
8+
// persons to whom the Software is furnished to do so, subject to the
9+
// following conditions:
10+
//
11+
// The above copyright notice and this permission notice shall be included
12+
// in all copies or substantial portions of the Software.
13+
//
14+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
// Flags: --security-revert=CVE-2021-44532
22+
'use strict';
23+
const common = require('../common');
24+
if (!common.hasCrypto)
25+
common.skip('missing crypto');
26+
27+
// Check getPeerCertificate can properly handle '\0' for fix CVE-2009-2408.
28+
29+
const assert = require('assert');
30+
const tls = require('tls');
31+
const fixtures = require('../common/fixtures');
32+
33+
const server = tls.createServer({
34+
key: fixtures.readSync(['0-dns', '0-dns-key.pem']),
35+
cert: fixtures.readSync(['0-dns', '0-dns-cert.pem'])
36+
}, common.mustCall((c) => {
37+
c.once('data', common.mustCall(() => {
38+
c.destroy();
39+
server.close();
40+
}));
41+
})).listen(0, common.mustCall(() => {
42+
const c = tls.connect(server.address().port, {
43+
rejectUnauthorized: false
44+
}, common.mustCall(() => {
45+
const cert = c.getPeerCertificate();
46+
assert.strictEqual(cert.subjectaltname,
47+
'DNS:good.example.org\0.evil.example.com, ' +
48+
'DNS:just-another.example.com, ' +
49+
'IP Address:8.8.8.8, ' +
50+
'IP Address:8.8.4.4, ' +
51+
'DNS:last.example.com');
52+
c.write('ok');
53+
}));
54+
}));

0 commit comments

Comments
 (0)