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

[v18.x backport] crypto: use WebIDL converters in WebCryptoAPI #46252

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions doc/api/webcrypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/46067
description: Arguments are now coerced and validated as per their WebIDL
definitions like in other Web Crypto API implementations.
- version: v18.4.0
pr-url: https://github.com/nodejs/node/pull/43310
description: Removed proprietary `'node.keyObject'` import/export format.
Expand Down
28 changes: 11 additions & 17 deletions lib/internal/crypto/aes.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const {
} = internalBinding('crypto');

const {
getArrayBufferOrView,
hasAnyNotIn,
jobPromise,
validateByteLength,
Expand Down Expand Up @@ -112,13 +111,10 @@ function getVariant(name, length) {
}

function asyncAesCtrCipher(mode, key, data, { counter, length }) {
counter = getArrayBufferOrView(counter, 'algorithm.counter');
validateByteLength(counter, 'algorithm.counter', 16);
// The length must specify an integer between 1 and 128. While
// there is no default, this should typically be 64.
if (typeof length !== 'number' ||
length <= 0 ||
length > kMaxCounterLength) {
if (length === 0 || length > kMaxCounterLength) {
throw lazyDOMException(
'AES-CTR algorithm.length must be between 1 and 128',
'OperationError');
Expand All @@ -135,7 +131,6 @@ function asyncAesCtrCipher(mode, key, data, { counter, length }) {
}

function asyncAesCbcCipher(mode, key, data, { iv }) {
iv = getArrayBufferOrView(iv, 'algorithm.iv');
validateByteLength(iv, 'algorithm.iv', 16);
return jobPromise(() => new AESCipherJob(
kCryptoJobAsync,
Expand Down Expand Up @@ -166,12 +161,9 @@ function asyncAesGcmCipher(
'OperationError'));
}

iv = getArrayBufferOrView(iv, 'algorithm.iv');
validateMaxBufferLength(iv, 'algorithm.iv');

if (additionalData !== undefined) {
additionalData =
getArrayBufferOrView(additionalData, 'algorithm.additionalData');
validateMaxBufferLength(additionalData, 'algorithm.additionalData');
}

Expand Down Expand Up @@ -281,24 +273,26 @@ async function aesImportKey(
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');

if (keyData.kty !== 'oct')
throw lazyDOMException('Invalid key type', 'DataError');
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');

if (usagesSet.size > 0 &&
keyData.use !== undefined &&
keyData.use !== 'enc') {
throw lazyDOMException('Invalid use type', 'DataError');
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}

validateKeyOps(keyData.key_ops, usagesSet);

if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}

const handle = new KeyObjectHandle();
Expand All @@ -308,10 +302,10 @@ async function aesImportKey(
validateKeyLength(length);

if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
if (keyData.alg !== getAlgorithmName(algorithm.name, length))
throw lazyDOMException('Algorithm mismatch', 'DataError');
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}

keyObject = new SecretKeyObject(handle);
Expand Down
37 changes: 17 additions & 20 deletions lib/internal/crypto/cfrg.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const {
} = internalBinding('crypto');

const {
getArrayBufferOrView,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
Expand Down Expand Up @@ -73,7 +72,6 @@ function verifyAcceptableCfrgKeyUse(name, isPublic, usages) {

function createCFRGRawKey(name, keyData, isPublic) {
const handle = new KeyObjectHandle();
keyData = getArrayBufferOrView(keyData, 'keyData');

switch (name) {
case 'Ed25519':
Expand Down Expand Up @@ -237,12 +235,13 @@ async function cfrgImportKey(
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'OKP')
throw lazyDOMException('Invalid key type', 'DataError');
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
if (keyData.crv !== name)
throw lazyDOMException('Subtype mismatch', 'DataError');
throw lazyDOMException(
'JWK "crv" Parameter and algorithm name mismatch', 'DataError');
const isPublic = keyData.d === undefined;

if (usagesSet.size > 0 && keyData.use !== undefined) {
Expand All @@ -260,30 +259,32 @@ async function cfrgImportKey(
break;
}
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid use type', 'DataError');
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}

validateKeyOps(keyData.key_ops, usagesSet);

if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}

if (keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
if (
(name === 'Ed25519' || name === 'Ed448') &&
keyData.alg !== 'EdDSA'
) {
throw lazyDOMException('Invalid alg', 'DataError');
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
}

if (!isPublic && typeof keyData.x !== 'string') {
throw lazyDOMException('Invalid JWK keyData', 'DataError');
throw lazyDOMException('Invalid JWK', 'DataError');
}

verifyAcceptableCfrgKeyUse(
Expand All @@ -305,7 +306,7 @@ async function cfrgImportKey(
false);

if (!createPublicKey(keyObject).equals(publicKeyObject)) {
throw lazyDOMException('Invalid JWK keyData', 'DataError');
throw lazyDOMException('Invalid JWK', 'DataError');
}
}
break;
Expand Down Expand Up @@ -336,13 +337,9 @@ function eddsaSignVerify(key, data, { name, context }, signature) {
if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');

if (name === 'Ed448' && context !== undefined) {
context =
getArrayBufferOrView(context, 'algorithm.context');
if (context.byteLength !== 0) {
throw lazyDOMException(
'Non zero-length context is not yet supported.', 'NotSupportedError');
}
if (name === 'Ed448' && context?.byteLength) {
throw lazyDOMException(
'Non zero-length context is not yet supported.', 'NotSupportedError');
}

return jobPromise(() => new SignJob(
Expand Down
9 changes: 0 additions & 9 deletions lib/internal/crypto/diffiehellman.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const {
validateInt32,
validateObject,
validateString,
validateUint32,
} = require('internal/validators');

const {
Expand All @@ -48,7 +47,6 @@ const {

const {
KeyObject,
isCryptoKey,
} = require('internal/crypto/keys');

const {
Expand Down Expand Up @@ -320,13 +318,6 @@ function diffieHellman(options) {
async function ecdhDeriveBits(algorithm, baseKey, length) {
const { 'public': key } = algorithm;

// Null means that we're not asking for a specific number of bits, just
// give us everything that is generated.
if (length !== null)
validateUint32(length, 'length');
if (!isCryptoKey(key))
throw new ERR_INVALID_ARG_TYPE('algorithm.public', 'CryptoKey', key);

if (key.type !== 'public') {
throw lazyDOMException(
'algorithm.public must be a public key', 'InvalidAccessError');
Expand Down
39 changes: 16 additions & 23 deletions lib/internal/crypto/ec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ const {
} = internalBinding('crypto');

const {
codes: {
ERR_MISSING_OPTION,
},
} = require('internal/errors');

const {
getArrayBufferOrView,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
Expand Down Expand Up @@ -76,7 +69,6 @@ function verifyAcceptableEcKeyUse(name, isPublic, usages) {

function createECPublicKeyRaw(namedCurve, keyData) {
const handle = new KeyObjectHandle();
keyData = getArrayBufferOrView(keyData, 'keyData');

if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) {
throw lazyDOMException('Invalid keyData', 'DataError');
Expand Down Expand Up @@ -204,50 +196,53 @@ async function ecImportKey(
break;
}
case 'jwk': {
if (keyData == null || typeof keyData !== 'object')
throw lazyDOMException('Invalid JWK keyData', 'DataError');
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'EC')
throw lazyDOMException('Invalid key type', 'DataError');
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
if (keyData.crv !== namedCurve)
throw lazyDOMException('Named curve mismatch', 'DataError');
throw lazyDOMException(
'JWK "crv" does not match the requested algorithm',
'DataError');

verifyAcceptableEcKeyUse(
name,
keyData.d === undefined,
usagesSet);

if (usagesSet.size > 0 && keyData.use !== undefined) {
if (algorithm.name === 'ECDSA' && keyData.use !== 'sig')
throw lazyDOMException('Invalid use type', 'DataError');
if (algorithm.name === 'ECDH' && keyData.use !== 'enc')
throw lazyDOMException('Invalid use type', 'DataError');
const checkUse = name === 'ECDH' ? 'enc' : 'sig';
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}

validateKeyOps(keyData.key_ops, usagesSet);

if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException('JWK is not extractable', 'DataError');
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}

if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) {
if (typeof keyData.alg !== 'string')
throw lazyDOMException('Invalid alg', 'DataError');
let algNamedCurve;
switch (keyData.alg) {
case 'ES256': algNamedCurve = 'P-256'; break;
case 'ES384': algNamedCurve = 'P-384'; break;
case 'ES512': algNamedCurve = 'P-521'; break;
}
if (algNamedCurve !== namedCurve)
throw lazyDOMException('Named curve mismatch', 'DataError');
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}

const handle = new KeyObjectHandle();
const type = handle.initJwk(keyData, namedCurve);
if (type === undefined)
throw lazyDOMException('Invalid JWK keyData', 'DataError');
throw lazyDOMException('Invalid JWK', 'DataError');
keyObject = type === kKeyTypePrivate ?
new PrivateKeyObject(handle) :
new PublicKeyObject(handle);
Expand Down Expand Up @@ -289,8 +284,6 @@ function ecdsaSignVerify(key, data, { name, hash }, signature) {
if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');

if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const hashname = normalizeHashName(hash.name);

return jobPromise(() => new SignJob(
Expand Down
12 changes: 2 additions & 10 deletions lib/internal/crypto/hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ const {
} = internalBinding('crypto');

const {
getArrayBufferOrView,
getDefaultEncoding,
getStringOption,
jobPromise,
normalizeAlgorithm,
normalizeHashName,
validateMaxBufferLength,
kHandle,
Expand Down Expand Up @@ -168,13 +166,8 @@ Hmac.prototype._transform = Hash.prototype._transform;
// Implementation for WebCrypto subtle.digest()

async function asyncDigest(algorithm, data) {
algorithm = normalizeAlgorithm(algorithm);
data = getArrayBufferOrView(data, 'data');
validateMaxBufferLength(data, 'data');

if (algorithm.length !== undefined)
validateUint32(algorithm.length, 'algorithm.length');

switch (algorithm.name) {
case 'SHA-1':
// Fall through
Expand All @@ -186,11 +179,10 @@ async function asyncDigest(algorithm, data) {
return jobPromise(() => new HashJob(
kCryptoJobAsync,
normalizeHashName(algorithm.name),
data,
algorithm.length));
data));
}

throw lazyDOMException('Unrecognized name.', 'NotSupportedError');
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}

module.exports = {
Expand Down
8 changes: 1 addition & 7 deletions lib/internal/crypto/hkdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const {
const { kMaxLength } = require('buffer');

const {
getArrayBufferOrView,
normalizeHashName,
toBuf,
validateByteSource,
Expand All @@ -45,7 +44,6 @@ const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE,
ERR_MISSING_OPTION,
},
hideStackFrames,
} = require('internal/errors');
Expand Down Expand Up @@ -140,11 +138,7 @@ function hkdfSync(hash, key, salt, info, length) {

const hkdfPromise = promisify(hkdf);
async function hkdfDeriveBits(algorithm, baseKey, length) {
const { hash } = algorithm;
const salt = getArrayBufferOrView(algorithm.salt, 'algorithm.salt');
const info = getArrayBufferOrView(algorithm.info, 'algorithm.info');
if (hash === undefined)
throw new ERR_MISSING_OPTION('algorithm.hash');
const { hash, salt, info } = algorithm;

if (length === 0)
throw lazyDOMException('length cannot be zero', 'OperationError');
Expand Down
Loading