Skip to content

Commit

Permalink
crypto: validate this in all webcrypto methods and getters
Browse files Browse the repository at this point in the history
PR-URL: #42815
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Tobias Nießen <[email protected]>
  • Loading branch information
panva authored and danielleadams committed Jun 27, 2022
1 parent 931dcf4 commit d25f405
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 11 deletions.
58 changes: 47 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,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) {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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(
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}

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

Expand Down Expand Up @@ -799,7 +835,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());
}

0 comments on commit d25f405

Please sign in to comment.