Skip to content

Commit 6de2673

Browse files
committed
lib: enable global WebCrypto by default
Enables `--experimental-global-webcrypto` by default, and ensures that the classic `node:crypto` core module is still available in `--eval` or `--print` contexts. PR-URL: #42083 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Filip Skokan <[email protected]> Reviewed-By: Michaël Zasso <[email protected]>
1 parent 572d556 commit 6de2673

16 files changed

+203
-37
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ module.exports = {
322322
CompressionStream: 'readable',
323323
CountQueuingStrategy: 'readable',
324324
CustomEvent: 'readable',
325+
crypto: 'readable',
325326
Crypto: 'readable',
326327
CryptoKey: 'readable',
327328
DecompressionStream: 'readable',

doc/api/cli.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -351,16 +351,6 @@ added:
351351

352352
Expose the [CustomEvent Web API][] on the global scope.
353353

354-
### `--experimental-global-webcrypto`
355-
356-
<!-- YAML
357-
added:
358-
- v17.6.0
359-
- v16.15.0
360-
-->
361-
362-
Expose the [Web Crypto API][] on the global scope.
363-
364354
### `--experimental-import-meta-resolve`
365355

366356
<!-- YAML
@@ -413,6 +403,14 @@ added: v18.0.0
413403

414404
Disable experimental support for the [Fetch API][].
415405

406+
### `--no-experimental-global-webcrypto`
407+
408+
<!-- YAML
409+
added: REPLACEME
410+
-->
411+
412+
Disable exposition of [Web Crypto API][] on the global scope.
413+
416414
### `--no-experimental-repl-await`
417415

418416
<!-- YAML
@@ -1839,7 +1837,6 @@ Node.js options that are allowed are:
18391837
* `--enable-source-maps`
18401838
* `--experimental-abortcontroller`
18411839
* `--experimental-global-customevent`
1842-
* `--experimental-global-webcrypto`
18431840
* `--experimental-import-meta-resolve`
18441841
* `--experimental-json-modules`
18451842
* `--experimental-loader`
@@ -1872,6 +1869,7 @@ Node.js options that are allowed are:
18721869
* `--no-addons`
18731870
* `--no-deprecation`
18741871
* `--no-experimental-fetch`
1872+
* `--no-experimental-global-webcrypto`
18751873
* `--no-experimental-repl-await`
18761874
* `--no-extra-info-on-fatal-exception`
18771875
* `--no-force-async-hooks-checks`

doc/api/globals.md

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,14 @@ A browser-compatible implementation of [`CountQueuingStrategy`][].
345345
added:
346346
- v17.6.0
347347
- v16.15.0
348+
changes:
349+
- version: REPLACEME
350+
pr-url: https://github.com/nodejs/node/pull/42083
351+
description: No longer behind `--experimental-global-webcrypto` CLI flag.
348352
-->
349353

350-
> Stability: 1 - Experimental. Enable this API with the
351-
> [`--experimental-global-webcrypto`][] CLI flag.
354+
> Stability: 1 - Experimental. Disable this API with the
355+
> [`--no-experimental-global-webcrypto`][] CLI flag.
352356
353357
A browser-compatible implementation of {Crypto}. This global is available
354358
only if the Node.js binary was compiled with including support for the
@@ -360,10 +364,14 @@ only if the Node.js binary was compiled with including support for the
360364
added:
361365
- v17.6.0
362366
- v16.15.0
367+
changes:
368+
- version: REPLACEME
369+
pr-url: https://github.com/nodejs/node/pull/42083
370+
description: No longer behind `--experimental-global-webcrypto` CLI flag.
363371
-->
364372

365-
> Stability: 1 - Experimental. Enable this API with the
366-
> [`--experimental-global-webcrypto`][] CLI flag.
373+
> Stability: 1 - Experimental. Disable this API with the
374+
> [`--no-experimental-global-webcrypto`][] CLI flag.
367375
368376
A browser-compatible implementation of the [Web Crypto API][].
369377

@@ -373,10 +381,14 @@ A browser-compatible implementation of the [Web Crypto API][].
373381
added:
374382
- v17.6.0
375383
- v16.15.0
384+
changes:
385+
- version: REPLACEME
386+
pr-url: https://github.com/nodejs/node/pull/42083
387+
description: No longer behind `--experimental-global-webcrypto` CLI flag.
376388
-->
377389

378-
> Stability: 1 - Experimental. Enable this API with the
379-
> [`--experimental-global-webcrypto`][] CLI flag.
390+
> Stability: 1 - Experimental. Disable this API with the
391+
> [`--no-experimental-global-webcrypto`][] CLI flag.
380392
381393
A browser-compatible implementation of {CryptoKey}. This global is available
382394
only if the Node.js binary was compiled with including support for the
@@ -725,10 +737,14 @@ The WHATWG [`structuredClone`][] method.
725737
added:
726738
- v17.6.0
727739
- v16.15.0
740+
changes:
741+
- version: REPLACEME
742+
pr-url: https://github.com/nodejs/node/pull/42083
743+
description: No longer behind `--experimental-global-webcrypto` CLI flag.
728744
-->
729745

730-
> Stability: 1 - Experimental. Enable this API with the
731-
> [`--experimental-global-webcrypto`][] CLI flag.
746+
> Stability: 1 - Experimental. Disable this API with the
747+
> [`--no-experimental-global-webcrypto`][] CLI flag.
732748
733749
A browser-compatible implementation of {SubtleCrypto}. This global is available
734750
only if the Node.js binary was compiled with including support for the
@@ -870,8 +886,8 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
870886

871887
[Web Crypto API]: webcrypto.md
872888
[`--experimental-global-customevent`]: cli.md#--experimental-global-customevent
873-
[`--experimental-global-webcrypto`]: cli.md#--experimental-global-webcrypto
874889
[`--no-experimental-fetch`]: cli.md#--no-experimental-fetch
890+
[`--no-experimental-global-webcrypto`]: cli.md#--no-experimental-global-webcrypto
875891
[`AbortController`]: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
876892
[`ByteLengthQueuingStrategy`]: webstreams.md#class-bytelengthqueuingstrategy
877893
[`CompressionStream`]: webstreams.md#class-compressionstream

doc/node.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ Use this flag to enable ShadowRealm support.
165165
.It Fl -no-experimental-fetch
166166
Disable experimental support for the Fetch API.
167167
.
168+
.It Fl -no-experimental-global-webcrypto
169+
Disable exposition of the Web Crypto API on the global scope.
170+
.
168171
.It Fl -no-experimental-repl-await
169172
Disable top-level await keyword support in REPL.
170173
.

lib/internal/main/eval_string.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// `--interactive`.
55

66
const {
7+
ObjectDefineProperty,
8+
RegExpPrototypeExec,
79
globalThis,
810
} = primordials;
911

@@ -25,9 +27,31 @@ const print = getOptionValue('--print');
2527
const loadESM = getOptionValue('--import').length > 0;
2628
if (getOptionValue('--input-type') === 'module')
2729
evalModule(source, print);
28-
else
30+
else {
31+
// For backward compatibility, we want the identifier crypto to be the
32+
// `node:crypto` module rather than WebCrypto.
33+
const isUsingCryptoIdentifier =
34+
getOptionValue('--experimental-global-webcrypto') &&
35+
RegExpPrototypeExec(/\bcrypto\b/, source) !== null;
36+
const shouldDefineCrypto = isUsingCryptoIdentifier && internalBinding('config').hasOpenSSL;
37+
38+
if (isUsingCryptoIdentifier && !shouldDefineCrypto) {
39+
// This is taken from `addBuiltinLibsToObject`.
40+
const object = globalThis;
41+
const name = 'crypto';
42+
const setReal = (val) => {
43+
// Deleting the property before re-assigning it disables the
44+
// getter/setter mechanism.
45+
delete object[name];
46+
object[name] = val;
47+
};
48+
ObjectDefineProperty(object, name, { __proto__: null, set: setReal });
49+
}
2950
evalScript('[eval]',
30-
source,
51+
shouldDefineCrypto ? (
52+
print ? `let crypto=require("node:crypto");{${source}}` : `(crypto=>{{${source}}})(require('node:crypto'))`
53+
) : source,
3154
getOptionValue('--inspect-brk'),
3255
print,
3356
loadESM);
57+
}

lib/internal/process/pre_execution.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525

2626
const {
2727
ERR_MANIFEST_ASSERT_INTEGRITY,
28+
ERR_NO_CRYPTO,
2829
} = require('internal/errors').codes;
2930
const assert = require('internal/assert');
3031

@@ -247,23 +248,29 @@ function setupFetch() {
247248
// removed.
248249
function setupWebCrypto() {
249250
if (process.config.variables.node_no_browser_globals ||
250-
!getOptionValue('--experimental-global-webcrypto')) {
251+
getOptionValue('--no-experimental-global-webcrypto')) {
251252
return;
252253
}
253254

254-
let webcrypto;
255-
ObjectDefineProperty(globalThis, 'crypto',
256-
{ __proto__: null, ...ObjectGetOwnPropertyDescriptor({
257-
get crypto() {
258-
webcrypto ??= require('internal/crypto/webcrypto');
259-
return webcrypto.crypto;
260-
}
261-
}, 'crypto') });
262255
if (internalBinding('config').hasOpenSSL) {
263-
webcrypto ??= require('internal/crypto/webcrypto');
256+
const webcrypto = require('internal/crypto/webcrypto');
257+
ObjectDefineProperty(globalThis, 'crypto',
258+
{ __proto__: null, ...ObjectGetOwnPropertyDescriptor({
259+
get crypto() {
260+
return webcrypto.crypto;
261+
}
262+
}, 'crypto') });
264263
exposeInterface(globalThis, 'Crypto', webcrypto.Crypto);
265264
exposeInterface(globalThis, 'CryptoKey', webcrypto.CryptoKey);
266265
exposeInterface(globalThis, 'SubtleCrypto', webcrypto.SubtleCrypto);
266+
} else {
267+
ObjectDefineProperty(globalThis, 'crypto',
268+
{ __proto__: null, ...ObjectGetOwnPropertyDescriptor({
269+
get crypto() {
270+
throw new ERR_NO_CRYPTO();
271+
}
272+
}, 'crypto') });
273+
267274
}
268275
}
269276

src/node_options.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,8 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
370370
AddOption("--experimental-global-webcrypto",
371371
"expose experimental Web Crypto API on the global scope",
372372
&EnvironmentOptions::experimental_global_web_crypto,
373-
kAllowedInEnvironment);
373+
kAllowedInEnvironment,
374+
true);
374375
AddOption("--experimental-json-modules", "", NoOp{}, kAllowedInEnvironment);
375376
AddOption("--experimental-loader",
376377
"use the specified module as a custom loader",

src/node_options.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class EnvironmentOptions : public Options {
110110
bool enable_source_maps = false;
111111
bool experimental_fetch = true;
112112
bool experimental_global_customevent = false;
113-
bool experimental_global_web_crypto = false;
113+
bool experimental_global_web_crypto = true;
114114
bool experimental_https_modules = false;
115115
std::string experimental_specifier_resolution;
116116
bool experimental_wasm_modules = false;

test/common/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,9 @@ if (process.env.NODE_TEST_KNOWN_GLOBALS !== '0') {
357357
const leaked = [];
358358

359359
for (const val in global) {
360-
if (!knownGlobals.includes(global[val])) {
360+
// globalThis.crypto is a getter that throws if Node.js was compiled
361+
// without OpenSSL.
362+
if (val !== 'crypto' && !knownGlobals.includes(global[val])) {
361363
leaked.push(val);
362364
}
363365
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
if (!common.hasCrypto) {
7+
assert.fail('When Node.js is compiled without OpenSSL, overriding the global ' +
8+
'crypto is allowed on string eval');
9+
}
10+
11+
const child = require('child_process');
12+
const nodejs = `"${process.execPath}"`;
13+
14+
// Trying to define a variable named `crypto` using `var` triggers an exception.
15+
16+
child.exec(
17+
`${nodejs} ` +
18+
'-p "var crypto = {randomBytes:1};typeof crypto.randomBytes"',
19+
common.mustSucceed((stdout) => {
20+
assert.match(stdout, /^number/);
21+
}));

test/parallel/test-assert-checktag.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
'use strict';
2-
require('../common');
2+
const common = require('../common');
3+
4+
if (!common.hasCrypto) {
5+
common.skip('missing crypto');
6+
}
7+
38
const assert = require('assert');
49

510
// Disable colored output to prevent color codes from breaking assertion

test/parallel/test-bootstrap-modules.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,17 @@ if (common.hasIntl) {
206206
expectedModules.add('NativeModule url');
207207
}
208208

209+
if (common.hasCrypto) {
210+
expectedModules.add('Internal Binding crypto')
211+
.add('NativeModule internal/crypto/hash')
212+
.add('NativeModule internal/crypto/hashnames')
213+
.add('NativeModule internal/crypto/keys')
214+
.add('NativeModule internal/crypto/random')
215+
.add('NativeModule internal/crypto/util')
216+
.add('NativeModule internal/crypto/webcrypto')
217+
.add('NativeModule internal/streams/lazy_transform');
218+
}
219+
209220
if (process.features.inspector) {
210221
expectedModules.add('Internal Binding inspector');
211222
expectedModules.add('NativeModule internal/inspector_async_hook');

test/parallel/test-cli-eval.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,69 @@ child.exec(
288288
common.mustSucceed((stdout) => {
289289
assert.strictEqual(stdout, '.mjs file\n');
290290
}));
291+
292+
if (common.hasCrypto) {
293+
// Assert that calls to crypto utils work without require.
294+
child.exec(
295+
`${nodejs} ` +
296+
'-e "console.log(crypto.randomBytes(16).toString(\'hex\'))"',
297+
common.mustSucceed((stdout) => {
298+
assert.match(stdout, /[0-9a-f]{32}/i);
299+
}));
300+
child.exec(
301+
`${nodejs} ` +
302+
'-p "crypto.randomBytes(16).toString(\'hex\')"',
303+
common.mustSucceed((stdout) => {
304+
assert.match(stdout, /[0-9a-f]{32}/i);
305+
}));
306+
}
307+
// Assert that overriding crypto works.
308+
child.exec(
309+
`${nodejs} ` +
310+
'-p "crypto=Symbol(\'test\')"',
311+
common.mustSucceed((stdout) => {
312+
assert.match(stdout, /Symbol\(test\)/i);
313+
}));
314+
child.exec(
315+
`${nodejs} ` +
316+
'-e "crypto = {};console.log(\'randomBytes\', typeof crypto.randomBytes)"',
317+
common.mustSucceed((stdout) => {
318+
assert.match(stdout, /randomBytes\sundefined/);
319+
}));
320+
// Assert that overriding crypto with a local variable works.
321+
child.exec(
322+
`${nodejs} ` +
323+
'-e "const crypto = {};console.log(\'randomBytes\', typeof crypto.randomBytes)"',
324+
common.mustSucceed((stdout) => {
325+
assert.match(stdout, /randomBytes\sundefined/);
326+
}));
327+
child.exec(
328+
`${nodejs} ` +
329+
'-e "let crypto = {};console.log(\'randomBytes\', typeof crypto.randomBytes)"',
330+
common.mustSucceed((stdout) => {
331+
assert.match(stdout, /randomBytes\sundefined/);
332+
}));
333+
child.exec(
334+
`${nodejs} ` +
335+
'-e "var crypto = {};console.log(\'randomBytes\', typeof crypto.randomBytes)"',
336+
common.mustSucceed((stdout) => {
337+
assert.match(stdout, /randomBytes\sundefined/);
338+
}));
339+
child.exec(
340+
`${nodejs} ` +
341+
'-p "const crypto = {randomBytes:1};typeof crypto.randomBytes"',
342+
common.mustSucceed((stdout) => {
343+
assert.match(stdout, /^number/);
344+
}));
345+
child.exec(
346+
`${nodejs} ` +
347+
'-p "let crypto = {randomBytes:1};typeof crypto.randomBytes"',
348+
common.mustSucceed((stdout) => {
349+
assert.match(stdout, /^number/);
350+
}));
351+
child.exec(
352+
`${nodejs} --no-experimental-global-webcrypto ` +
353+
'-p "var crypto = {randomBytes:1};typeof crypto.randomBytes"',
354+
common.mustSucceed((stdout) => {
355+
assert.match(stdout, /^number/);
356+
}));

test/parallel/test-global-webcrypto-classes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Flags: --experimental-global-webcrypto --expose-internals
1+
// Flags: --expose-internals
22
'use strict';
33

44
const common = require('../common');

0 commit comments

Comments
 (0)