From c1f6762f4552ddb5b38924b075ebffd1d9145fc8 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Fri, 12 Aug 2022 20:29:14 +0100 Subject: [PATCH] crypto: fix webcrypto operation errors to be OperationError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/44171 Reviewed-By: James M Snell Reviewed-By: Tobias Nießen Backport-PR-URL: https://github.com/nodejs/node/pull/44872 --- lib/internal/crypto/aes.js | 32 +++-- lib/internal/crypto/cfrg.js | 116 +++++++++--------- lib/internal/crypto/ec.js | 78 ++++++------ lib/internal/crypto/mac.js | 30 ++--- lib/internal/crypto/rsa.js | 98 ++++++++------- lib/internal/crypto/util.js | 7 +- .../test-webcrypto-encrypt-decrypt-aes.js | 6 +- .../test-webcrypto-encrypt-decrypt-rsa.js | 2 +- test/wpt/status/WebCryptoAPI.json | 21 ---- 9 files changed, 185 insertions(+), 205 deletions(-) diff --git a/lib/internal/crypto/aes.js b/lib/internal/crypto/aes.js index 48cde6b31972e0..79ca61c5ff2097 100644 --- a/lib/internal/crypto/aes.js +++ b/lib/internal/crypto/aes.js @@ -7,7 +7,6 @@ const { ArrayPrototypeIncludes, ArrayPrototypePush, MathFloor, - Promise, SafeSet, TypedArrayPrototypeSlice, } = primordials; @@ -46,6 +45,7 @@ const { const { lazyDOMException, + promisify, } = require('internal/util'); const { PromiseReject } = primordials; @@ -57,11 +57,12 @@ const { } = require('internal/crypto/keys'); const { - generateKey, + generateKey: _generateKey, } = require('internal/crypto/keygen'); const kMaxCounterLength = 128; const kTagLengths = [32, 64, 96, 104, 112, 120, 128]; +const generateKey = promisify(_generateKey); function getAlgorithmName(name, length) { switch (name) { @@ -239,22 +240,19 @@ async function aesGenerateKey(algorithm, extractable, keyUsages) { 'SyntaxError'); } - return new Promise((resolve, reject) => { - generateKey('aes', { length }, (err, key) => { - if (err) { - return reject(lazyDOMException( - 'The operation failed for an operation-specific reason ' + - `[${err.message}]`, - 'OperationError')); - } - - resolve(new InternalCryptoKey( - key, - { name, length }, - ArrayFrom(usagesSet), - extractable)); - }); + const key = await generateKey('aes', { length }).catch((err) => { + // TODO(@panva): add err as cause to DOMException + throw lazyDOMException( + 'The operation failed for an operation-specific reason' + + `[${err.message}]`, + 'OperationError'); }); + + return new InternalCryptoKey( + key, + { name, length }, + ArrayFrom(usagesSet), + extractable); } async function aesImportKey( diff --git a/lib/internal/crypto/cfrg.js b/lib/internal/crypto/cfrg.js index ba39bc117b89ff..17358c7ccca23d 100644 --- a/lib/internal/crypto/cfrg.js +++ b/lib/internal/crypto/cfrg.js @@ -1,7 +1,6 @@ 'use strict'; const { - Promise, SafeSet, } = primordials; @@ -31,10 +30,11 @@ const { const { emitExperimentalWarning, lazyDOMException, + promisify, } = require('internal/util'); const { - generateKeyPair, + generateKeyPair: _generateKeyPair, } = require('internal/crypto/keygen'); const { @@ -45,6 +45,8 @@ const { createPublicKey, } = require('internal/crypto/keys'); +const generateKeyPair = promisify(_generateKeyPair); + function verifyAcceptableCfrgKeyUse(name, type, usages) { let checkSet; switch (name) { @@ -131,65 +133,63 @@ async function cfrgGenerateKey(algorithm, extractable, keyUsages) { } break; } - return new Promise((resolve, reject) => { - let genKeyType; - switch (name) { - case 'Ed25519': - genKeyType = 'ed25519'; - break; - case 'Ed448': - genKeyType = 'ed448'; - break; - case 'X25519': - genKeyType = 'x25519'; - break; - case 'X448': - genKeyType = 'x448'; - break; - } - generateKeyPair(genKeyType, undefined, (err, pubKey, privKey) => { - if (err) { - return reject(lazyDOMException( - 'The operation failed for an operation-specific reason', - 'OperationError')); - } + let genKeyType; + switch (name) { + case 'Ed25519': + genKeyType = 'ed25519'; + break; + case 'Ed448': + genKeyType = 'ed448'; + break; + case 'X25519': + genKeyType = 'x25519'; + break; + case 'X448': + genKeyType = 'x448'; + break; + } - const algorithm = { name }; + const keyPair = await generateKeyPair(genKeyType).catch((err) => { + // TODO(@panva): add err as cause to DOMException + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + 'OperationError'); + }); - let publicUsages; - let privateUsages; - switch (name) { - case 'Ed25519': - // Fall through - case 'Ed448': - publicUsages = getUsagesUnion(usageSet, 'verify'); - privateUsages = getUsagesUnion(usageSet, 'sign'); - break; - case 'X25519': - // Fall through - case 'X448': - publicUsages = []; - privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits'); - break; - } + let publicUsages; + let privateUsages; + switch (name) { + case 'Ed25519': + // Fall through + case 'Ed448': + publicUsages = getUsagesUnion(usageSet, 'verify'); + privateUsages = getUsagesUnion(usageSet, 'sign'); + break; + case 'X25519': + // Fall through + case 'X448': + publicUsages = []; + privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits'); + break; + } - const publicKey = - new InternalCryptoKey( - pubKey, - algorithm, - publicUsages, - true); - - const privateKey = - new InternalCryptoKey( - privKey, - algorithm, - privateUsages, - extractable); - - resolve({ publicKey, privateKey }); - }); - }); + const keyAlgorithm = { name }; + + const publicKey = + new InternalCryptoKey( + keyPair.publicKey, + keyAlgorithm, + publicUsages, + true); + + const privateKey = + new InternalCryptoKey( + keyPair.privateKey, + keyAlgorithm, + privateUsages, + extractable); + + return { privateKey, publicKey }; } function cfrgExportKey(key, format) { diff --git a/lib/internal/crypto/ec.js b/lib/internal/crypto/ec.js index 809f531069bf8b..719aa94f95f82c 100644 --- a/lib/internal/crypto/ec.js +++ b/lib/internal/crypto/ec.js @@ -2,7 +2,6 @@ const { ObjectKeys, - Promise, SafeSet, } = primordials; @@ -42,10 +41,11 @@ const { const { lazyDOMException, + promisify, } = require('internal/util'); const { - generateKeyPair, + generateKeyPair: _generateKeyPair, } = require('internal/crypto/keygen'); const { @@ -56,6 +56,8 @@ const { createPublicKey, } = require('internal/crypto/keys'); +const generateKeyPair = promisify(_generateKeyPair); + function verifyAcceptableEcKeyUse(name, type, usages) { let checkSet; switch (name) { @@ -111,46 +113,44 @@ async function ecGenerateKey(algorithm, extractable, keyUsages) { } // Fall through } - return new Promise((resolve, reject) => { - generateKeyPair('ec', { namedCurve }, (err, pubKey, privKey) => { - if (err) { - return reject(lazyDOMException( - 'The operation failed for an operation-specific reason', - 'OperationError')); - } - const algorithm = { name, namedCurve }; + const keypair = await generateKeyPair('ec', { namedCurve }).catch((err) => { + // TODO(@panva): add err as cause to DOMException + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + 'OperationError'); + }); - let publicUsages; - let privateUsages; - switch (name) { - case 'ECDSA': - publicUsages = getUsagesUnion(usageSet, 'verify'); - privateUsages = getUsagesUnion(usageSet, 'sign'); - break; - case 'ECDH': - publicUsages = []; - privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits'); - break; - } + let publicUsages; + let privateUsages; + switch (name) { + case 'ECDSA': + publicUsages = getUsagesUnion(usageSet, 'verify'); + privateUsages = getUsagesUnion(usageSet, 'sign'); + break; + case 'ECDH': + publicUsages = []; + privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits'); + break; + } - const publicKey = - new InternalCryptoKey( - pubKey, - algorithm, - publicUsages, - true); - - const privateKey = - new InternalCryptoKey( - privKey, - algorithm, - privateUsages, - extractable); - - resolve({ publicKey, privateKey }); - }); - }); + const keyAlgorithm = { name, namedCurve }; + + const publicKey = + new InternalCryptoKey( + keypair.publicKey, + keyAlgorithm, + publicUsages, + true); + + const privateKey = + new InternalCryptoKey( + keypair.privateKey, + keyAlgorithm, + privateUsages, + extractable); + + return { publicKey, privateKey }; } function ecExportKey(key, format) { diff --git a/lib/internal/crypto/mac.js b/lib/internal/crypto/mac.js index 3054f66b27d4f2..15b3378e2eda64 100644 --- a/lib/internal/crypto/mac.js +++ b/lib/internal/crypto/mac.js @@ -2,7 +2,6 @@ const { ArrayFrom, - Promise, SafeSet, } = primordials; @@ -27,6 +26,7 @@ const { const { lazyDOMException, + promisify, } = require('internal/util'); const { @@ -36,7 +36,7 @@ const { } = require('internal/errors'); const { - generateKey, + generateKey: _generateKey, } = require('internal/crypto/keygen'); const { @@ -45,6 +45,8 @@ const { createSecretKey, } = require('internal/crypto/keys'); +const generateKey = promisify(_generateKey); + async function hmacGenerateKey(algorithm, extractable, keyUsages) { const { hash, name } = algorithm; let { length } = algorithm; @@ -62,21 +64,19 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) { 'Unsupported key usage for an HMAC key', 'SyntaxError'); } - return new Promise((resolve, reject) => { - generateKey('hmac', { length }, (err, key) => { - if (err) { - return reject(lazyDOMException( - 'The operation failed for an operation-specific reason', - 'OperationError')); - } - resolve(new InternalCryptoKey( - key, - { name, length, hash: { name: hash.name } }, - ArrayFrom(usageSet), - extractable)); - }); + const key = await generateKey('hmac', { length }).catch((err) => { + // TODO(@panva): add err as cause to DOMException + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + 'OperationError'); }); + + return new InternalCryptoKey( + key, + { name, length, hash: { name: hash.name } }, + ArrayFrom(usageSet), + extractable); } async function hmacImportKey( diff --git a/lib/internal/crypto/rsa.js b/lib/internal/crypto/rsa.js index 728e4bd951cdc5..fa6928e4d78f06 100644 --- a/lib/internal/crypto/rsa.js +++ b/lib/internal/crypto/rsa.js @@ -1,7 +1,6 @@ 'use strict'; const { - Promise, SafeSet, Uint8Array, } = primordials; @@ -49,6 +48,7 @@ const { const { lazyDOMException, + promisify, } = require('internal/util'); const { @@ -64,7 +64,7 @@ const { } = require('internal/crypto/keys'); const { - generateKeyPair, + generateKeyPair: _generateKeyPair, } = require('internal/crypto/keygen'); const kRsaVariants = { @@ -72,6 +72,7 @@ const kRsaVariants = { 'RSA-PSS': kKeyVariantRSA_PSS, 'RSA-OAEP': kKeyVariantRSA_OAEP, }; +const generateKeyPair = promisify(_generateKeyPair); function verifyAcceptableRsaKeyUse(name, type, usages) { let checkSet; @@ -173,56 +174,53 @@ async function rsaKeyGenerate( } } - return new Promise((resolve, reject) => { - generateKeyPair('rsa', { - modulusLength, - publicExponent: publicExponentConverted, - }, (err, pubKey, privKey) => { - if (err) { - return reject(lazyDOMException( - 'The operation failed for an operation-specific reason', - 'OperationError')); - } + const keypair = await generateKeyPair('rsa', { + modulusLength, + publicExponent: publicExponentConverted, + }).catch((err) => { + // TODO(@panva): add err as cause to DOMException + throw lazyDOMException( + 'The operation failed for an operation-specific reason', + 'OperationError'); + }); - const algorithm = { - name, - modulusLength, - publicExponent, - hash: { name: hash.name } - }; - - let publicUsages; - let privateUsages; - switch (name) { - case 'RSA-OAEP': { - publicUsages = getUsagesUnion(usageSet, 'encrypt', 'wrapKey'); - privateUsages = getUsagesUnion(usageSet, 'decrypt', 'unwrapKey'); - break; - } - default: { - publicUsages = getUsagesUnion(usageSet, 'verify'); - privateUsages = getUsagesUnion(usageSet, 'sign'); - break; - } - } + const keyAlgorithm = { + name, + modulusLength, + publicExponent, + hash: { name: hash.name } + }; - const publicKey = - new InternalCryptoKey( - pubKey, - algorithm, - publicUsages, - true); - - const privateKey = - new InternalCryptoKey( - privKey, - algorithm, - privateUsages, - extractable); - - resolve({ publicKey, privateKey }); - }); - }); + let publicUsages; + let privateUsages; + switch (name) { + case 'RSA-OAEP': { + publicUsages = getUsagesUnion(usageSet, 'encrypt', 'wrapKey'); + privateUsages = getUsagesUnion(usageSet, 'decrypt', 'unwrapKey'); + break; + } + default: { + publicUsages = getUsagesUnion(usageSet, 'verify'); + privateUsages = getUsagesUnion(usageSet, 'sign'); + break; + } + } + + const publicKey = + new InternalCryptoKey( + keypair.publicKey, + keyAlgorithm, + publicUsages, + true); + + const privateKey = + new InternalCryptoKey( + keypair.privateKey, + keyAlgorithm, + privateUsages, + extractable); + + return { publicKey, privateKey }; } function rsaExportKey(key, format) { diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js index 40a677606bf3f0..dbd816cff99e9f 100644 --- a/lib/internal/crypto/util.js +++ b/lib/internal/crypto/util.js @@ -278,7 +278,12 @@ const validateByteSource = hideStackFrames((val, name) => { }); function onDone(resolve, reject, err, result) { - if (err) return reject(err); + if (err) { + // TODO(@panva): add err as cause to DOMException + return reject(lazyDOMException( + 'The operation failed for an operation-specific reason', + 'OperationError')); + } resolve(result); } diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-aes.js b/test/parallel/test-webcrypto-encrypt-decrypt-aes.js index 885cded906b079..5dfae1c5d55385 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt-aes.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt-aes.js @@ -119,7 +119,7 @@ async function testDecrypt({ keyBuffer, algorithm, result }) { decryptionFailing.forEach((vector) => { variations.push(assert.rejects(testDecrypt(vector), { - message: /bad decrypt/ + name: 'OperationError' })); }); @@ -158,7 +158,7 @@ async function testDecrypt({ keyBuffer, algorithm, result }) { decryptionFailing.forEach((vector) => { variations.push(assert.rejects(testDecrypt(vector), { - message: /bad decrypt/ + name: 'OperationError' })); }); @@ -195,7 +195,7 @@ async function testDecrypt({ keyBuffer, algorithm, result }) { decryptionFailing.forEach((vector) => { variations.push(assert.rejects(testDecrypt(vector), { - message: /bad decrypt/ + name: 'OperationError' })); }); diff --git a/test/parallel/test-webcrypto-encrypt-decrypt-rsa.js b/test/parallel/test-webcrypto-encrypt-decrypt-rsa.js index 151eebd36c9765..6af0fa727969d8 100644 --- a/test/parallel/test-webcrypto-encrypt-decrypt-rsa.js +++ b/test/parallel/test-webcrypto-encrypt-decrypt-rsa.js @@ -127,7 +127,7 @@ async function testEncryptionLongPlaintext({ algorithm, return assert.rejects( subtle.encrypt(algorithm, publicKey, newplaintext), { - message: /data too large/ + name: 'OperationError' }); } diff --git a/test/wpt/status/WebCryptoAPI.json b/test/wpt/status/WebCryptoAPI.json index 65a03fa6a93a3b..f4c964631a4550 100644 --- a/test/wpt/status/WebCryptoAPI.json +++ b/test/wpt/status/WebCryptoAPI.json @@ -2654,33 +2654,12 @@ "encrypt_decrypt/aes_cbc.https.any.js": { "fail": { "expected": [ - "AES-CBC 128-bit key, zeroPadChar", - "AES-CBC 128-bit key, bigPadChar", - "AES-CBC 128-bit key, inconsistentPadChars", - "AES-CBC 192-bit key, zeroPadChar", - "AES-CBC 192-bit key, bigPadChar", - "AES-CBC 192-bit key, inconsistentPadChars", - "AES-CBC 256-bit key, zeroPadChar", - "AES-CBC 256-bit key, bigPadChar", - "AES-CBC 256-bit key, inconsistentPadChars" ] } }, "encrypt_decrypt/rsa_oaep.https.any.js": { "fail": { "expected": [ - "RSA-OAEP with SHA-1 and no label too long plaintext", - "RSA-OAEP with SHA-256 and no label too long plaintext", - "RSA-OAEP with SHA-384 and no label too long plaintext", - "RSA-OAEP with SHA-512 and no label too long plaintext", - "RSA-OAEP with SHA-1 and empty label too long plaintext", - "RSA-OAEP with SHA-256 and empty label too long plaintext", - "RSA-OAEP with SHA-384 and empty label too long plaintext", - "RSA-OAEP with SHA-512 and empty label too long plaintext", - "RSA-OAEP with SHA-1 and a label too long plaintext", - "RSA-OAEP with SHA-256 and a label too long plaintext", - "RSA-OAEP with SHA-384 and a label too long plaintext", - "RSA-OAEP with SHA-512 and a label too long plaintext" ] } },