summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilip Skokan <panva.ip@gmail.com>2022-04-23 19:42:45 +0200
committerGitHub <noreply@github.com>2022-04-23 18:42:45 +0100
commit603803ecb776ca5d9644c6b16c1c2a45b99b27fe (patch)
tree36a00f4c67864a4e1d1999a230355f877e432478
parent9cd2c277d87c6a232f7758966db718fd4b3929de (diff)
downloadnode-new-603803ecb776ca5d9644c6b16c1c2a45b99b27fe.tar.gz
crypto: validate `this` in all webcrypto methods and getters
PR-URL: https://github.com/nodejs/node/pull/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>
-rw-r--r--lib/internal/crypto/webcrypto.js58
-rw-r--r--test/parallel/test-webcrypto-constructors.js159
2 files changed, 206 insertions, 11 deletions
diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js
index cf440ebf8f..3b7e4d13d8 100644
--- a/lib/internal/crypto/webcrypto.js
+++ b/lib/internal/crypto/webcrypto.js
@@ -6,6 +6,7 @@ const {
JSONStringify,
ObjectDefineProperties,
ReflectApply,
+ ReflectConstruct,
SafeSet,
SymbolToStringTag,
StringPrototypeRepeat,
@@ -31,6 +32,7 @@ const { TextDecoder, TextEncoder } = require('internal/encoding');
const {
codes: {
+ ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_THIS,
}
@@ -70,12 +72,21 @@ const {
randomUUID: _randomUUID,
} = require('internal/crypto/random');
-const randomUUID = () => _randomUUID();
+async function digest(algorithm, data) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
+ return ReflectApply(asyncDigest, this, arguments);
+}
+
+function randomUUID() {
+ if (this !== crypto) throw new ERR_INVALID_THIS('Crypto');
+ return _randomUUID();
+}
async function generateKey(
algorithm,
extractable,
keyUsages) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
algorithm = normalizeAlgorithm(algorithm);
validateBoolean(extractable, 'extractable');
validateArray(keyUsages, 'keyUsages');
@@ -123,6 +134,7 @@ async function generateKey(
}
async function deriveBits(algorithm, baseKey, length) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
algorithm = normalizeAlgorithm(algorithm);
if (!isCryptoKey(baseKey))
throw new ERR_INVALID_ARG_TYPE('baseKey', 'CryptoKey', baseKey);
@@ -194,6 +206,7 @@ async function deriveKey(
derivedKeyAlgorithm,
extractable,
keyUsages) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
algorithm = normalizeAlgorithm(algorithm);
derivedKeyAlgorithm = normalizeAlgorithm(derivedKeyAlgorithm);
if (!isCryptoKey(baseKey))
@@ -238,7 +251,11 @@ async function deriveKey(
throw lazyDOMException('Unrecognized name.');
}
- return importKey('raw', bits, derivedKeyAlgorithm, extractable, keyUsages);
+ return ReflectApply(
+ importKey,
+ this,
+ ['raw', bits, derivedKeyAlgorithm, extractable, keyUsages],
+ );
}
async function exportKeySpki(key) {
@@ -415,6 +432,7 @@ async function exportKeyJWK(key) {
}
async function exportKey(format, key) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
validateString(format, 'format');
validateOneOf(format, 'format', kExportFormats);
if (!isCryptoKey(key))
@@ -496,6 +514,7 @@ async function importKey(
algorithm,
extractable,
keyUsages) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
validateString(format, 'format');
validateOneOf(format, 'format', kExportFormats);
if (format !== 'node.keyObject' && format !== 'jwk')
@@ -557,8 +576,9 @@ async function importKey(
// subtle.wrapKey() is essentially a subtle.exportKey() followed
// by a subtle.encrypt().
async function wrapKey(format, key, wrappingKey, algorithm) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
algorithm = normalizeAlgorithm(algorithm);
- let keyData = await exportKey(format, key);
+ let keyData = await ReflectApply(exportKey, this, [format, key]);
if (format === 'jwk') {
if (keyData == null || typeof keyData !== 'object')
@@ -586,6 +606,7 @@ async function unwrapKey(
unwrappedKeyAlgo,
extractable,
keyUsages) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
wrappedKey = getArrayBufferOrView(wrappedKey, 'wrappedKey');
unwrapAlgo = normalizeAlgorithm(unwrapAlgo);
let keyData = await cipherOrWrap(
@@ -607,7 +628,11 @@ async function unwrapKey(
}
}
- return importKey(format, keyData, unwrappedKeyAlgo, extractable, keyUsages);
+ return ReflectApply(
+ importKey,
+ this,
+ [format, keyData, unwrappedKeyAlgo, extractable, keyUsages],
+ );
}
function signVerify(algorithm, key, data, signature) {
@@ -654,10 +679,12 @@ function signVerify(algorithm, key, data, signature) {
}
async function sign(algorithm, key, data) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
return signVerify(algorithm, key, data);
}
async function verify(algorithm, key, signature, data) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
return signVerify(algorithm, key, data, signature);
}
@@ -707,30 +734,39 @@ async function cipherOrWrap(mode, algorithm, key, data, op) {
}
async function encrypt(algorithm, key, data) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
return cipherOrWrap(kWebCryptoCipherEncrypt, algorithm, key, data, 'encrypt');
}
async function decrypt(algorithm, key, data) {
+ if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
return cipherOrWrap(kWebCryptoCipherDecrypt, algorithm, key, data, 'decrypt');
}
// The SubtleCrypto and Crypto classes are defined as part of the
// Web Crypto API standard: https://www.w3.org/TR/WebCryptoAPI/
-class SubtleCrypto {}
-const subtle = new SubtleCrypto();
+class SubtleCrypto {
+ constructor() {
+ throw new ERR_ILLEGAL_CONSTRUCTOR();
+ }
+}
+const subtle = ReflectConstruct(function() {}, [], SubtleCrypto);
class Crypto {
+ constructor() {
+ throw new ERR_ILLEGAL_CONSTRUCTOR();
+ }
+
get subtle() {
+ if (this !== crypto) throw new ERR_INVALID_THIS('Crypto');
return subtle;
}
}
-const crypto = new Crypto();
+const crypto = ReflectConstruct(function() {}, [], Crypto);
function getRandomValues(array) {
- if (!(this instanceof Crypto)) {
- throw new ERR_INVALID_THIS('Crypto');
- }
+ if (this !== crypto) throw new ERR_INVALID_THIS('Crypto');
return ReflectApply(_getRandomValues, this, arguments);
}
@@ -799,7 +835,7 @@ ObjectDefineProperties(
enumerable: true,
configurable: true,
writable: true,
- value: asyncDigest,
+ value: digest,
},
generateKey: {
enumerable: true,
diff --git a/test/parallel/test-webcrypto-constructors.js b/test/parallel/test-webcrypto-constructors.js
new file mode 100644
index 0000000000..ddc4e8cb07
--- /dev/null
+++ b/test/parallel/test-webcrypto-constructors.js
@@ -0,0 +1,159 @@
+// Flags: --experimental-global-webcrypto
+'use strict';
+
+const common = require('../common');
+
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+
+// Test CryptoKey constructor
+{
+ assert.throws(() => new CryptoKey(), {
+ name: 'TypeError', message: 'Illegal constructor', code: 'ERR_ILLEGAL_CONSTRUCTOR'
+ });
+}
+
+// Test SubtleCrypto constructor
+{
+ assert.throws(() => new SubtleCrypto(), {
+ name: 'TypeError', message: 'Illegal constructor', code: 'ERR_ILLEGAL_CONSTRUCTOR'
+ });
+}
+
+// Test Crypto constructor
+{
+ assert.throws(() => new Crypto(), {
+ name: 'TypeError', message: 'Illegal constructor', code: 'ERR_ILLEGAL_CONSTRUCTOR'
+ });
+}
+
+const notCrypto = Reflect.construct(function() {}, [], Crypto);
+const notSubtle = Reflect.construct(function() {}, [], SubtleCrypto);
+
+// Test Crypto.prototype.subtle
+{
+ assert.throws(() => notCrypto.subtle, {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ });
+}
+
+// Test Crypto.prototype.randomUUID
+{
+ assert.throws(() => notCrypto.randomUUID(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ });
+}
+
+// Test Crypto.prototype.getRandomValues
+{
+ assert.throws(() => notCrypto.getRandomValues(new Uint8Array(12)), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ });
+}
+
+// Test SubtleCrypto.prototype.encrypt
+{
+ assert.rejects(() => notSubtle.encrypt(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.decrypt
+{
+ assert.rejects(() => notSubtle.decrypt(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.sign
+{
+ assert.rejects(() => notSubtle.sign(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.verify
+{
+ assert.rejects(() => notSubtle.verify(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.digest
+{
+ assert.rejects(() => notSubtle.digest(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.generateKey
+{
+ assert.rejects(() => notSubtle.generateKey(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.deriveKey
+{
+ assert.rejects(() => notSubtle.deriveKey(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.deriveBits
+{
+ assert.rejects(() => notSubtle.deriveBits(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.importKey
+{
+ assert.rejects(() => notSubtle.importKey(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.exportKey
+{
+ assert.rejects(() => notSubtle.exportKey(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.wrapKey
+{
+ assert.rejects(() => notSubtle.wrapKey(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+// Test SubtleCrypto.prototype.unwrapKey
+{
+ assert.rejects(() => notSubtle.unwrapKey(), {
+ name: 'TypeError', code: 'ERR_INVALID_THIS',
+ }).then(common.mustCall());
+}
+
+{
+ globalThis.crypto.subtle.importKey(
+ 'raw',
+ globalThis.crypto.getRandomValues(new Uint8Array(4)),
+ 'PBKDF2',
+ false,
+ ['deriveKey'],
+ ).then((key) => {
+ globalThis.crypto.subtle.importKey = common.mustNotCall();
+ return globalThis.crypto.subtle.deriveKey({
+ name: 'PBKDF2',
+ hash: 'SHA-512',
+ salt: globalThis.crypto.getRandomValues(new Uint8Array()),
+ iterations: 5,
+ }, key, {
+ name: 'AES-GCM',
+ length: 256
+ }, true, ['encrypt', 'decrypt']);
+ }).then(common.mustCall());
+}