Skip to content

Commit 782f7bd

Browse files
committed
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
1 parent 3f0bcfb commit 782f7bd

File tree

3 files changed

+57
-3
lines changed

3 files changed

+57
-3
lines changed

doc/api/crypto.md

+36-2
Original file line numberDiff line numberDiff line change
@@ -2471,11 +2471,16 @@ added: v15.6.0
24712471

24722472
<!-- YAML
24732473
added: v15.6.0
2474+
changes:
2475+
- version: REPLACEME
2476+
pr-url: https://github.com/nodejs-private/node-private/pull/00000
2477+
description: The subject option can now be set to `'default'`.
24742478
-->
24752479

24762480
* `email` {string}
24772481
* `options` {Object}
2478-
* `subject` {string} `'always'` or `'never'`. **Default:** `'always'`.
2482+
* `subject` {string} `'default'`, `'always'`, or `'never'`.
2483+
**Default:** `'always'`.
24792484
* `wildcards` {boolean} **Default:** `true`.
24802485
* `partialWildcards` {boolean} **Default:** `true`.
24812486
* `multiLabelWildcards` {boolean} **Default:** `false`.
@@ -2485,15 +2490,31 @@ added: v15.6.0
24852490

24862491
Checks whether the certificate matches the given email address.
24872492

2493+
If the `'subject'` option is set to '`always`' and if the subject alternative
2494+
name extension either does not exist or does not contain a matching email
2495+
address, the certificate subject is considered.
2496+
2497+
If the `'subject'` option is set to `'default`', the certificate subject is only
2498+
considered if the subject alternative name extension either does not exist or
2499+
does not contain any email addresses.
2500+
2501+
If the `'subject'` option is set to `'never'`, the certificate subject is never
2502+
considered, even if the certificate contains no subject alternative names.
2503+
24882504
### `x509.checkHost(name[, options])`
24892505

24902506
<!-- YAML
24912507
added: v15.6.0
2508+
changes:
2509+
- version: REPLACEME
2510+
pr-url: https://github.com/nodejs-private/node-private/pull/00000
2511+
description: The subject option can now be set to `'default'`.
24922512
-->
24932513

24942514
* `name` {string}
24952515
* `options` {Object}
2496-
* `subject` {string} `'always'` or `'never'`. **Default:** `'always'`.
2516+
* `subject` {string} `'default'`, `'always'`, or `'never'`.
2517+
**Default:** `'always'`.
24972518
* `wildcards` {boolean} **Default:** `true`.
24982519
* `partialWildcards` {boolean} **Default:** `true`.
24992520
* `multiLabelWildcards` {boolean} **Default:** `false`.
@@ -2509,6 +2530,18 @@ or it might contain wildcards (e.g., `*.example.com`). Because host name
25092530
comparisons are case-insensitive, the returned subject name might also differ
25102531
from the given `name` in capitalization.
25112532

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

25142547
<!-- YAML
@@ -5937,6 +5970,7 @@ See the [list of SSL OP Flags][] for details.
59375970
[OpenSSL's SPKAC implementation]: https://www.openssl.org/docs/man1.1.0/apps/openssl-spkac.html
59385971
[RFC 1421]: https://www.rfc-editor.org/rfc/rfc1421.txt
59395972
[RFC 2412]: https://www.rfc-editor.org/rfc/rfc2412.txt
5973+
[RFC 2818]: https://www.rfc-editor.org/rfc/rfc2818.txt
59405974
[RFC 3526]: https://www.rfc-editor.org/rfc/rfc3526.txt
59415975
[RFC 3610]: https://www.rfc-editor.org/rfc/rfc3610.txt
59425976
[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt

lib/internal/crypto/x509.js

+3-1
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

+18
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)