Skip to content

Commit adb88fc

Browse files
tniessendanielleadams
authored andcommitted
crypto: support RFC 2818 compatible checkHost
The 'subject' option should not only accept the values 'always' and 'never' because neither is compatible with RFC 2818, i.e., HTTPS. This change adds a third value 'default', which implies the behavior that HTTPS mandates. The new 'default' case matches the default behavior of OpenSSL for both DNS names and email addresses. Future Node.js versions should change the default option value from 'always' to 'default'. Refs: nodejs#36804 PR-URL: nodejs#41569 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent 9677da4 commit adb88fc

File tree

3 files changed

+60
-3
lines changed

3 files changed

+60
-3
lines changed

doc/api/crypto.md

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2472,6 +2472,9 @@ added: v15.6.0
24722472
<!-- YAML
24732473
added: v15.6.0
24742474
changes:
2475+
- version: REPLACEME
2476+
pr-url: https://github.com/nodejs/node/pull/41569
2477+
description: The subject option can now be set to `'default'`.
24752478
- version: v16.14.1
24762479
pr-url: https://github.com/nodejs/node/pull/41599
24772480
description: The `wildcards`, `partialWildcards`, `multiLabelWildcards`, and
@@ -2481,21 +2484,42 @@ changes:
24812484

24822485
* `email` {string}
24832486
* `options` {Object}
2484-
* `subject` {string} `'always'` or `'never'`. **Default:** `'always'`.
2487+
* `subject` {string} `'default'`, `'always'`, or `'never'`.
2488+
**Default:** `'always'`.
2489+
* `wildcards` {boolean} **Default:** `true`.
2490+
* `partialWildcards` {boolean} **Default:** `true`.
2491+
* `multiLabelWildcards` {boolean} **Default:** `false`.
2492+
* `singleLabelSubdomains` {boolean} **Default:** `false`.
24852493
* Returns: {string|undefined} Returns `email` if the certificate matches,
24862494
`undefined` if it does not.
24872495

24882496
Checks whether the certificate matches the given email address.
24892497

2498+
If the `'subject'` option is set to `'always'` and if the subject alternative
2499+
name extension either does not exist or does not contain a matching email
2500+
address, the certificate subject is considered.
2501+
2502+
If the `'subject'` option is set to `'default`', the certificate subject is only
2503+
considered if the subject alternative name extension either does not exist or
2504+
does not contain any email addresses.
2505+
2506+
If the `'subject'` option is set to `'never'`, the certificate subject is never
2507+
considered, even if the certificate contains no subject alternative names.
2508+
24902509
### `x509.checkHost(name[, options])`
24912510

24922511
<!-- YAML
24932512
added: v15.6.0
2513+
changes:
2514+
- version: REPLACEME
2515+
pr-url: https://github.com/nodejs/node/pull/41569
2516+
description: The subject option can now be set to `'default'`.
24942517
-->
24952518

24962519
* `name` {string}
24972520
* `options` {Object}
2498-
* `subject` {string} `'always'` or `'never'`. **Default:** `'always'`.
2521+
* `subject` {string} `'default'`, `'always'`, or `'never'`.
2522+
**Default:** `'always'`.
24992523
* `wildcards` {boolean} **Default:** `true`.
25002524
* `partialWildcards` {boolean} **Default:** `true`.
25012525
* `multiLabelWildcards` {boolean} **Default:** `false`.
@@ -2511,6 +2535,18 @@ or it might contain wildcards (e.g., `*.example.com`). Because host name
25112535
comparisons are case-insensitive, the returned subject name might also differ
25122536
from the given `name` in capitalization.
25132537

2538+
If the `'subject'` option is set to `'always'` and if the subject alternative
2539+
name extension either does not exist or does not contain a matching DNS name,
2540+
the certificate subject is considered.
2541+
2542+
If the `'subject'` option is set to `'default'`, the certificate subject is only
2543+
considered if the subject alternative name extension either does not exist or
2544+
does not contain any DNS names. This behavior is consistent with [RFC 2818][]
2545+
("HTTP Over TLS").
2546+
2547+
If the `'subject'` option is set to `'never'`, the certificate subject is never
2548+
considered, even if the certificate contains no subject alternative names.
2549+
25142550
### `x509.checkIP(ip)`
25152551

25162552
<!-- YAML
@@ -5896,6 +5932,7 @@ See the [list of SSL OP Flags][] for details.
58965932
[OpenSSL's SPKAC implementation]: https://www.openssl.org/docs/man1.1.0/apps/openssl-spkac.html
58975933
[RFC 1421]: https://www.rfc-editor.org/rfc/rfc1421.txt
58985934
[RFC 2412]: https://www.rfc-editor.org/rfc/rfc2412.txt
5935+
[RFC 2818]: https://www.rfc-editor.org/rfc/rfc2818.txt
58995936
[RFC 3526]: https://www.rfc-editor.org/rfc/rfc3526.txt
59005937
[RFC 3610]: https://www.rfc-editor.org/rfc/rfc3610.txt
59015938
[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt

lib/internal/crypto/x509.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ function isX509Certificate(value) {
6565
function getFlags(options = {}) {
6666
validateObject(options, 'options');
6767
const {
68-
subject = 'always', // Can be 'always' or 'never'
68+
// TODO(tniessen): change the default to 'default'
69+
subject = 'always', // Can be 'default', 'always', or 'never'
6970
wildcards = true,
7071
partialWildcards = true,
7172
multiLabelWildcards = false,
@@ -78,6 +79,7 @@ function getFlags(options = {}) {
7879
validateBoolean(multiLabelWildcards, 'options.multiLabelWildcards');
7980
validateBoolean(singleLabelSubdomains, 'options.singleLabelSubdomains');
8081
switch (subject) {
82+
case 'default': /* Matches OpenSSL's default, no flags. */ break;
8183
case 'always': flags |= X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT; break;
8284
case 'never': flags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT; break;
8385
default:

test/parallel/test-x509-escaping.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,15 @@ const { hasOpenSSL3 } = common;
424424
assert.strictEqual(certX509.subject, `CN=${servername}`);
425425
assert.strictEqual(certX509.subjectAltName, 'DNS:evil.example.com');
426426

427+
// The newer X509Certificate API allows customizing this behavior:
428+
assert.strictEqual(certX509.checkHost(servername), servername);
429+
assert.strictEqual(certX509.checkHost(servername, { subject: 'default' }),
430+
undefined);
431+
assert.strictEqual(certX509.checkHost(servername, { subject: 'always' }),
432+
servername);
433+
assert.strictEqual(certX509.checkHost(servername, { subject: 'never' }),
434+
undefined);
435+
427436
// Try connecting to a server that uses the self-signed certificate.
428437
const server = tls.createServer({ key, cert }, common.mustNotCall());
429438
server.listen(common.mustCall(() => {
@@ -454,6 +463,15 @@ const { hasOpenSSL3 } = common;
454463
assert.strictEqual(certX509.subject, `CN=${servername}`);
455464
assert.strictEqual(certX509.subjectAltName, 'IP Address:1.2.3.4');
456465

466+
// The newer X509Certificate API allows customizing this behavior:
467+
assert.strictEqual(certX509.checkHost(servername), servername);
468+
assert.strictEqual(certX509.checkHost(servername, { subject: 'default' }),
469+
servername);
470+
assert.strictEqual(certX509.checkHost(servername, { subject: 'always' }),
471+
servername);
472+
assert.strictEqual(certX509.checkHost(servername, { subject: 'never' }),
473+
undefined);
474+
457475
// Connect to a server that uses the self-signed certificate.
458476
const server = tls.createServer({ key, cert }, common.mustCall((socket) => {
459477
socket.destroy();

0 commit comments

Comments
 (0)