From 5fb69a8a46cc519e7797522689822c43af044334 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 21 Apr 2022 18:23:22 +0200 Subject: [PATCH 1/5] crypto: validate `this` in all webcrypto methods and getters --- lib/internal/crypto/webcrypto.js | 50 +++++-- test/parallel/test-webcrypto-constructors.js | 138 +++++++++++++++++++ 2 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 test/parallel/test-webcrypto-constructors.js diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index cf440ebf8ff39d..470aeb485dd679 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,7 @@ async function deriveKey( throw lazyDOMException('Unrecognized name.'); } - return importKey('raw', bits, derivedKeyAlgorithm, extractable, keyUsages); + return subtle.importKey('raw', bits, derivedKeyAlgorithm, extractable, keyUsages); } async function exportKeySpki(key) { @@ -415,6 +428,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 +510,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 +572,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 subtle.exportKey(format, key); if (format === 'jwk') { if (keyData == null || typeof keyData !== 'object') @@ -586,6 +602,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 +624,7 @@ async function unwrapKey( } } - return importKey(format, keyData, unwrappedKeyAlgo, extractable, keyUsages); + return subtle.importKey(format, keyData, unwrappedKeyAlgo, extractable, keyUsages); } function signVerify(algorithm, key, data, signature) { @@ -654,10 +671,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 +726,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 +827,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 00000000000000..2a34b612f3dfdc --- /dev/null +++ b/test/parallel/test-webcrypto-constructors.js @@ -0,0 +1,138 @@ +// 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()); +} From b0b5572ce920b4eee28a930c9a07a899e63b6934 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 22 Apr 2022 07:00:57 +0200 Subject: [PATCH 2/5] fixup! crypto: validate this in all webcrypto methods and getters Co-authored-by: Antoine du Hamel --- lib/internal/crypto/webcrypto.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 470aeb485dd679..6f45cd69816513 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -251,7 +251,7 @@ async function deriveKey( throw lazyDOMException('Unrecognized name.'); } - return subtle.importKey('raw', bits, derivedKeyAlgorithm, extractable, keyUsages); + return this.importKey('raw', bits, derivedKeyAlgorithm, extractable, keyUsages); } async function exportKeySpki(key) { @@ -574,7 +574,7 @@ async function importKey( async function wrapKey(format, key, wrappingKey, algorithm) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); algorithm = normalizeAlgorithm(algorithm); - let keyData = await subtle.exportKey(format, key); + let keyData = await this.exportKey(format, key); if (format === 'jwk') { if (keyData == null || typeof keyData !== 'object') @@ -624,7 +624,7 @@ async function unwrapKey( } } - return subtle.importKey(format, keyData, unwrappedKeyAlgo, extractable, keyUsages); + return this.importKey(format, keyData, unwrappedKeyAlgo, extractable, keyUsages); } function signVerify(algorithm, key, data, signature) { From af7273bce4102d40edc6fb739daaca1dbf93657a Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 22 Apr 2022 16:22:43 +0200 Subject: [PATCH 3/5] fixup! crypto: validate this in all webcrypto methods and getters --- lib/internal/crypto/webcrypto.js | 6 +++--- test/parallel/test-webcrypto-constructors.js | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 6f45cd69816513..28adcce38bbf84 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -251,7 +251,7 @@ async function deriveKey( throw lazyDOMException('Unrecognized name.'); } - return this.importKey('raw', bits, derivedKeyAlgorithm, extractable, keyUsages); + return importKey.call(this, 'raw', bits, derivedKeyAlgorithm, extractable, keyUsages); } async function exportKeySpki(key) { @@ -574,7 +574,7 @@ async function importKey( async function wrapKey(format, key, wrappingKey, algorithm) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); algorithm = normalizeAlgorithm(algorithm); - let keyData = await this.exportKey(format, key); + let keyData = await exportKey.call(this, format, key); if (format === 'jwk') { if (keyData == null || typeof keyData !== 'object') @@ -624,7 +624,7 @@ async function unwrapKey( } } - return this.importKey(format, keyData, unwrappedKeyAlgo, extractable, keyUsages); + return importKey.call(this, format, keyData, unwrappedKeyAlgo, extractable, keyUsages); } function signVerify(algorithm, key, data, signature) { diff --git a/test/parallel/test-webcrypto-constructors.js b/test/parallel/test-webcrypto-constructors.js index 2a34b612f3dfdc..c80602f74b223b 100644 --- a/test/parallel/test-webcrypto-constructors.js +++ b/test/parallel/test-webcrypto-constructors.js @@ -136,3 +136,23 @@ const notSubtle = Reflect.construct(function() {}, [], SubtleCrypto); name: 'TypeError', code: 'ERR_INVALID_THIS', }).then(common.mustCall()); } + +{ + crypto.subtle.importKey( + 'raw', + crypto.getRandomValues(new Uint8Array(4)), + 'PBKDF2', + false, + ['deriveKey']).then(key => { + crypto.subtle.importKey = common.mustNotCall(); + return crypto.subtle.deriveKey({ + name: 'PBKDF2', + hash: 'SHA-512', + salt: crypto.getRandomValues(new Uint8Array()), + iterations: 5 + }, key, { + name: 'AES-GCM', + length: 256 + }, true, ['encrypt', 'decrypt']); + }).then(common.mustCall()); +} From 6296ceaa32f416d01095dff932fe97bd1365cc43 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 22 Apr 2022 16:26:08 +0200 Subject: [PATCH 4/5] fixup! crypto: validate this in all webcrypto methods and getters --- test/parallel/test-webcrypto-constructors.js | 29 ++++++++++---------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/test/parallel/test-webcrypto-constructors.js b/test/parallel/test-webcrypto-constructors.js index c80602f74b223b..ddc4e8cb0796b1 100644 --- a/test/parallel/test-webcrypto-constructors.js +++ b/test/parallel/test-webcrypto-constructors.js @@ -138,21 +138,22 @@ const notSubtle = Reflect.construct(function() {}, [], SubtleCrypto); } { - crypto.subtle.importKey( + globalThis.crypto.subtle.importKey( 'raw', - crypto.getRandomValues(new Uint8Array(4)), + globalThis.crypto.getRandomValues(new Uint8Array(4)), 'PBKDF2', false, - ['deriveKey']).then(key => { - crypto.subtle.importKey = common.mustNotCall(); - return crypto.subtle.deriveKey({ - name: 'PBKDF2', - hash: 'SHA-512', - salt: crypto.getRandomValues(new Uint8Array()), - iterations: 5 - }, key, { - name: 'AES-GCM', - length: 256 - }, true, ['encrypt', 'decrypt']); - }).then(common.mustCall()); + ['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()); } From 2e46c9d7ef1797851d7124df036413019d72f528 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 22 Apr 2022 16:32:39 +0200 Subject: [PATCH 5/5] fixup! crypto: validate this in all webcrypto methods and getters --- lib/internal/crypto/webcrypto.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 28adcce38bbf84..3b7e4d13d8b1c0 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -251,7 +251,11 @@ async function deriveKey( throw lazyDOMException('Unrecognized name.'); } - return importKey.call(this, 'raw', bits, derivedKeyAlgorithm, extractable, keyUsages); + return ReflectApply( + importKey, + this, + ['raw', bits, derivedKeyAlgorithm, extractable, keyUsages], + ); } async function exportKeySpki(key) { @@ -574,7 +578,7 @@ async function importKey( async function wrapKey(format, key, wrappingKey, algorithm) { if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto'); algorithm = normalizeAlgorithm(algorithm); - let keyData = await exportKey.call(this, format, key); + let keyData = await ReflectApply(exportKey, this, [format, key]); if (format === 'jwk') { if (keyData == null || typeof keyData !== 'object') @@ -624,7 +628,11 @@ async function unwrapKey( } } - return importKey.call(this, format, keyData, unwrappedKeyAlgo, extractable, keyUsages); + return ReflectApply( + importKey, + this, + [format, keyData, unwrappedKeyAlgo, extractable, keyUsages], + ); } function signVerify(algorithm, key, data, signature) {