Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: validate this in all webcrypto methods and getters #42815

Merged
merged 5 commits into from
Apr 23, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions lib/internal/crypto/webcrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
JSONStringify,
ObjectDefineProperties,
ReflectApply,
ReflectConstruct,
SafeSet,
SymbolToStringTag,
StringPrototypeRepeat,
Expand All @@ -31,6 +32,7 @@ const { TextDecoder, TextEncoder } = require('internal/encoding');

const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_THIS,
}
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -238,7 +251,7 @@ async function deriveKey(
throw lazyDOMException('Unrecognized name.');
}

return importKey('raw', bits, derivedKeyAlgorithm, extractable, keyUsages);
return importKey.call(this, 'raw', bits, derivedKeyAlgorithm, extractable, keyUsages);
panva marked this conversation as resolved.
Show resolved Hide resolved
}

async function exportKeySpki(key) {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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 exportKey.call(this, format, key);

if (format === 'jwk') {
if (keyData == null || typeof keyData !== 'object')
Expand Down Expand Up @@ -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(
Expand All @@ -607,7 +624,7 @@ async function unwrapKey(
}
}

return importKey(format, keyData, unwrappedKeyAlgo, extractable, keyUsages);
return importKey.call(this, format, keyData, unwrappedKeyAlgo, extractable, keyUsages);
}

function signVerify(algorithm, key, data, signature) {
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -799,7 +827,7 @@ ObjectDefineProperties(
enumerable: true,
configurable: true,
writable: true,
value: asyncDigest,
value: digest,
},
generateKey: {
enumerable: true,
Expand Down
159 changes: 159 additions & 0 deletions test/parallel/test-webcrypto-constructors.js
Original file line number Diff line number Diff line change
@@ -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());
}