Skip to content

Commit 1884f52

Browse files
panvajuanarbol
authored andcommitted
crypto: validate this in all webcrypto methods and getters
PR-URL: #42815 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
1 parent 8b92e59 commit 1884f52

File tree

2 files changed

+206
-11
lines changed

2 files changed

+206
-11
lines changed

lib/internal/crypto/webcrypto.js

+47-11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
JSONStringify,
77
ObjectDefineProperties,
88
ReflectApply,
9+
ReflectConstruct,
910
SafeSet,
1011
SymbolToStringTag,
1112
StringPrototypeRepeat,
@@ -31,6 +32,7 @@ const { TextDecoder, TextEncoder } = require('internal/encoding');
3132

3233
const {
3334
codes: {
35+
ERR_ILLEGAL_CONSTRUCTOR,
3436
ERR_INVALID_ARG_TYPE,
3537
ERR_INVALID_THIS,
3638
}
@@ -70,12 +72,21 @@ const {
7072
randomUUID: _randomUUID,
7173
} = require('internal/crypto/random');
7274

73-
const randomUUID = () => _randomUUID();
75+
async function digest(algorithm, data) {
76+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
77+
return ReflectApply(asyncDigest, this, arguments);
78+
}
79+
80+
function randomUUID() {
81+
if (this !== crypto) throw new ERR_INVALID_THIS('Crypto');
82+
return _randomUUID();
83+
}
7484

7585
async function generateKey(
7686
algorithm,
7787
extractable,
7888
keyUsages) {
89+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
7990
algorithm = normalizeAlgorithm(algorithm);
8091
validateBoolean(extractable, 'extractable');
8192
validateArray(keyUsages, 'keyUsages');
@@ -123,6 +134,7 @@ async function generateKey(
123134
}
124135

125136
async function deriveBits(algorithm, baseKey, length) {
137+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
126138
algorithm = normalizeAlgorithm(algorithm);
127139
if (!isCryptoKey(baseKey))
128140
throw new ERR_INVALID_ARG_TYPE('baseKey', 'CryptoKey', baseKey);
@@ -194,6 +206,7 @@ async function deriveKey(
194206
derivedKeyAlgorithm,
195207
extractable,
196208
keyUsages) {
209+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
197210
algorithm = normalizeAlgorithm(algorithm);
198211
derivedKeyAlgorithm = normalizeAlgorithm(derivedKeyAlgorithm);
199212
if (!isCryptoKey(baseKey))
@@ -238,7 +251,11 @@ async function deriveKey(
238251
throw lazyDOMException('Unrecognized name.');
239252
}
240253

241-
return importKey('raw', bits, derivedKeyAlgorithm, extractable, keyUsages);
254+
return ReflectApply(
255+
importKey,
256+
this,
257+
['raw', bits, derivedKeyAlgorithm, extractable, keyUsages],
258+
);
242259
}
243260

244261
async function exportKeySpki(key) {
@@ -415,6 +432,7 @@ async function exportKeyJWK(key) {
415432
}
416433

417434
async function exportKey(format, key) {
435+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
418436
validateString(format, 'format');
419437
validateOneOf(format, 'format', kExportFormats);
420438
if (!isCryptoKey(key))
@@ -496,6 +514,7 @@ async function importKey(
496514
algorithm,
497515
extractable,
498516
keyUsages) {
517+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
499518
validateString(format, 'format');
500519
validateOneOf(format, 'format', kExportFormats);
501520
if (format !== 'node.keyObject' && format !== 'jwk')
@@ -557,8 +576,9 @@ async function importKey(
557576
// subtle.wrapKey() is essentially a subtle.exportKey() followed
558577
// by a subtle.encrypt().
559578
async function wrapKey(format, key, wrappingKey, algorithm) {
579+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
560580
algorithm = normalizeAlgorithm(algorithm);
561-
let keyData = await exportKey(format, key);
581+
let keyData = await ReflectApply(exportKey, this, [format, key]);
562582

563583
if (format === 'jwk') {
564584
if (keyData == null || typeof keyData !== 'object')
@@ -586,6 +606,7 @@ async function unwrapKey(
586606
unwrappedKeyAlgo,
587607
extractable,
588608
keyUsages) {
609+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
589610
wrappedKey = getArrayBufferOrView(wrappedKey, 'wrappedKey');
590611
unwrapAlgo = normalizeAlgorithm(unwrapAlgo);
591612
let keyData = await cipherOrWrap(
@@ -607,7 +628,11 @@ async function unwrapKey(
607628
}
608629
}
609630

610-
return importKey(format, keyData, unwrappedKeyAlgo, extractable, keyUsages);
631+
return ReflectApply(
632+
importKey,
633+
this,
634+
[format, keyData, unwrappedKeyAlgo, extractable, keyUsages],
635+
);
611636
}
612637

613638
function signVerify(algorithm, key, data, signature) {
@@ -654,10 +679,12 @@ function signVerify(algorithm, key, data, signature) {
654679
}
655680

656681
async function sign(algorithm, key, data) {
682+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
657683
return signVerify(algorithm, key, data);
658684
}
659685

660686
async function verify(algorithm, key, signature, data) {
687+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
661688
return signVerify(algorithm, key, data, signature);
662689
}
663690

@@ -707,30 +734,39 @@ async function cipherOrWrap(mode, algorithm, key, data, op) {
707734
}
708735

709736
async function encrypt(algorithm, key, data) {
737+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
710738
return cipherOrWrap(kWebCryptoCipherEncrypt, algorithm, key, data, 'encrypt');
711739
}
712740

713741
async function decrypt(algorithm, key, data) {
742+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
714743
return cipherOrWrap(kWebCryptoCipherDecrypt, algorithm, key, data, 'decrypt');
715744
}
716745

717746
// The SubtleCrypto and Crypto classes are defined as part of the
718747
// Web Crypto API standard: https://www.w3.org/TR/WebCryptoAPI/
719748

720-
class SubtleCrypto {}
721-
const subtle = new SubtleCrypto();
749+
class SubtleCrypto {
750+
constructor() {
751+
throw new ERR_ILLEGAL_CONSTRUCTOR();
752+
}
753+
}
754+
const subtle = ReflectConstruct(function() {}, [], SubtleCrypto);
722755

723756
class Crypto {
757+
constructor() {
758+
throw new ERR_ILLEGAL_CONSTRUCTOR();
759+
}
760+
724761
get subtle() {
762+
if (this !== crypto) throw new ERR_INVALID_THIS('Crypto');
725763
return subtle;
726764
}
727765
}
728-
const crypto = new Crypto();
766+
const crypto = ReflectConstruct(function() {}, [], Crypto);
729767

730768
function getRandomValues(array) {
731-
if (!(this instanceof Crypto)) {
732-
throw new ERR_INVALID_THIS('Crypto');
733-
}
769+
if (this !== crypto) throw new ERR_INVALID_THIS('Crypto');
734770
return ReflectApply(_getRandomValues, this, arguments);
735771
}
736772

@@ -799,7 +835,7 @@ ObjectDefineProperties(
799835
enumerable: true,
800836
configurable: true,
801837
writable: true,
802-
value: asyncDigest,
838+
value: digest,
803839
},
804840
generateKey: {
805841
enumerable: true,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Flags: --experimental-global-webcrypto
2+
'use strict';
3+
4+
const common = require('../common');
5+
6+
if (!common.hasCrypto)
7+
common.skip('missing crypto');
8+
9+
const assert = require('assert');
10+
11+
// Test CryptoKey constructor
12+
{
13+
assert.throws(() => new CryptoKey(), {
14+
name: 'TypeError', message: 'Illegal constructor', code: 'ERR_ILLEGAL_CONSTRUCTOR'
15+
});
16+
}
17+
18+
// Test SubtleCrypto constructor
19+
{
20+
assert.throws(() => new SubtleCrypto(), {
21+
name: 'TypeError', message: 'Illegal constructor', code: 'ERR_ILLEGAL_CONSTRUCTOR'
22+
});
23+
}
24+
25+
// Test Crypto constructor
26+
{
27+
assert.throws(() => new Crypto(), {
28+
name: 'TypeError', message: 'Illegal constructor', code: 'ERR_ILLEGAL_CONSTRUCTOR'
29+
});
30+
}
31+
32+
const notCrypto = Reflect.construct(function() {}, [], Crypto);
33+
const notSubtle = Reflect.construct(function() {}, [], SubtleCrypto);
34+
35+
// Test Crypto.prototype.subtle
36+
{
37+
assert.throws(() => notCrypto.subtle, {
38+
name: 'TypeError', code: 'ERR_INVALID_THIS',
39+
});
40+
}
41+
42+
// Test Crypto.prototype.randomUUID
43+
{
44+
assert.throws(() => notCrypto.randomUUID(), {
45+
name: 'TypeError', code: 'ERR_INVALID_THIS',
46+
});
47+
}
48+
49+
// Test Crypto.prototype.getRandomValues
50+
{
51+
assert.throws(() => notCrypto.getRandomValues(new Uint8Array(12)), {
52+
name: 'TypeError', code: 'ERR_INVALID_THIS',
53+
});
54+
}
55+
56+
// Test SubtleCrypto.prototype.encrypt
57+
{
58+
assert.rejects(() => notSubtle.encrypt(), {
59+
name: 'TypeError', code: 'ERR_INVALID_THIS',
60+
}).then(common.mustCall());
61+
}
62+
63+
// Test SubtleCrypto.prototype.decrypt
64+
{
65+
assert.rejects(() => notSubtle.decrypt(), {
66+
name: 'TypeError', code: 'ERR_INVALID_THIS',
67+
}).then(common.mustCall());
68+
}
69+
70+
// Test SubtleCrypto.prototype.sign
71+
{
72+
assert.rejects(() => notSubtle.sign(), {
73+
name: 'TypeError', code: 'ERR_INVALID_THIS',
74+
}).then(common.mustCall());
75+
}
76+
77+
// Test SubtleCrypto.prototype.verify
78+
{
79+
assert.rejects(() => notSubtle.verify(), {
80+
name: 'TypeError', code: 'ERR_INVALID_THIS',
81+
}).then(common.mustCall());
82+
}
83+
84+
// Test SubtleCrypto.prototype.digest
85+
{
86+
assert.rejects(() => notSubtle.digest(), {
87+
name: 'TypeError', code: 'ERR_INVALID_THIS',
88+
}).then(common.mustCall());
89+
}
90+
91+
// Test SubtleCrypto.prototype.generateKey
92+
{
93+
assert.rejects(() => notSubtle.generateKey(), {
94+
name: 'TypeError', code: 'ERR_INVALID_THIS',
95+
}).then(common.mustCall());
96+
}
97+
98+
// Test SubtleCrypto.prototype.deriveKey
99+
{
100+
assert.rejects(() => notSubtle.deriveKey(), {
101+
name: 'TypeError', code: 'ERR_INVALID_THIS',
102+
}).then(common.mustCall());
103+
}
104+
105+
// Test SubtleCrypto.prototype.deriveBits
106+
{
107+
assert.rejects(() => notSubtle.deriveBits(), {
108+
name: 'TypeError', code: 'ERR_INVALID_THIS',
109+
}).then(common.mustCall());
110+
}
111+
112+
// Test SubtleCrypto.prototype.importKey
113+
{
114+
assert.rejects(() => notSubtle.importKey(), {
115+
name: 'TypeError', code: 'ERR_INVALID_THIS',
116+
}).then(common.mustCall());
117+
}
118+
119+
// Test SubtleCrypto.prototype.exportKey
120+
{
121+
assert.rejects(() => notSubtle.exportKey(), {
122+
name: 'TypeError', code: 'ERR_INVALID_THIS',
123+
}).then(common.mustCall());
124+
}
125+
126+
// Test SubtleCrypto.prototype.wrapKey
127+
{
128+
assert.rejects(() => notSubtle.wrapKey(), {
129+
name: 'TypeError', code: 'ERR_INVALID_THIS',
130+
}).then(common.mustCall());
131+
}
132+
133+
// Test SubtleCrypto.prototype.unwrapKey
134+
{
135+
assert.rejects(() => notSubtle.unwrapKey(), {
136+
name: 'TypeError', code: 'ERR_INVALID_THIS',
137+
}).then(common.mustCall());
138+
}
139+
140+
{
141+
globalThis.crypto.subtle.importKey(
142+
'raw',
143+
globalThis.crypto.getRandomValues(new Uint8Array(4)),
144+
'PBKDF2',
145+
false,
146+
['deriveKey'],
147+
).then((key) => {
148+
globalThis.crypto.subtle.importKey = common.mustNotCall();
149+
return globalThis.crypto.subtle.deriveKey({
150+
name: 'PBKDF2',
151+
hash: 'SHA-512',
152+
salt: globalThis.crypto.getRandomValues(new Uint8Array()),
153+
iterations: 5,
154+
}, key, {
155+
name: 'AES-GCM',
156+
length: 256
157+
}, true, ['encrypt', 'decrypt']);
158+
}).then(common.mustCall());
159+
}

0 commit comments

Comments
 (0)