From 99bfbed78a676ae69ec2272def299678a82a3ce5 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 8 Feb 2023 18:09:53 +0100 Subject: [PATCH] test,crypto: update WebCryptoAPI WPT PR-URL: https://github.com/nodejs/node/pull/46575 Reviewed-By: Yagiz Nizipli Reviewed-By: Luigi Pinca Reviewed-By: Mohammed Keyvanzadeh Reviewed-By: James M Snell --- test/fixtures/wpt/README.md | 2 +- .../wpt/WebCryptoAPI/generateKey/successes.js | 2 +- .../import_export/ec_importKey.https.any.js | 2 + .../import_export/okp_importKey.https.any.js | 2 + .../import_export/rsa_importKey.https.any.js | 2 + .../symmetric_importKey.https.any.js | 6 + .../wpt/WebCryptoAPI/sign_verify/rsa.js | 29 ++++ .../fixtures/wpt/WebCryptoAPI/util/helpers.js | 9 +- .../wrapKey_unwrapKey.https.any.js | 131 ++++++++++-------- test/fixtures/wpt/versions.json | 2 +- 10 files changed, 125 insertions(+), 62 deletions(-) diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index 6bcfa59464683f..875ac7ab0a3e7b 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -31,7 +31,7 @@ Last update: - user-timing: https://github.com/web-platform-tests/wpt/tree/df24fb604e/user-timing - wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/d8dbe6990b/wasm/jsapi - wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi -- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/450f829d25/WebCryptoAPI +- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/238d9d9bac/WebCryptoAPI - webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/a370aad338/webidl/ecmascript-binding/es-exceptions [Web Platform Tests]: https://github.com/web-platform-tests/wpt diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/successes.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes.js index 4a047aa0609d7a..e1c1665b511030 100644 --- a/test/fixtures/wpt/WebCryptoAPI/generateKey/successes.js +++ b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes.js @@ -64,7 +64,7 @@ function run_test(algorithmNames, slowTest) { .then(function(result) { if (resultType === "CryptoKeyPair") { assert_goodCryptoKey(result.privateKey, algorithm, extractable, usages, "private"); - assert_goodCryptoKey(result.publicKey, algorithm, extractable, usages, "public"); + assert_goodCryptoKey(result.publicKey, algorithm, true, usages, "public"); } else { assert_goodCryptoKey(result, algorithm, extractable, usages, "secret"); } diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js index 66be9ff65d6f8b..c70583bf12eba9 100644 --- a/test/fixtures/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js @@ -1,5 +1,6 @@ // META: title=WebCryptoAPI: importKey() for EC keys // META: timeout=long +// META: script=../util/helpers.js // Test importKey and exportKey for EC algorithms. Only "happy paths" are // currently tested - those where the operation should succeed. @@ -110,6 +111,7 @@ return subtle.importKey(format, keyData, algorithm, extractable, usages). then(function(key) { assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object"); + assert_goodCryptoKey(key, algorithm, extractable, usages, (format === 'pkcs8' || (format === 'jwk' && keyData.d)) ? 'private' : 'public'); if (!extractable) { return; } diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey.https.any.js index 0bd460b62f66ae..2257a5fa418ad5 100644 --- a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey.https.any.js @@ -1,5 +1,6 @@ // META: title=WebCryptoAPI: importKey() for OKP keys // META: timeout=long +// META: script=../util/helpers.js // Test importKey and exportKey for OKP algorithms. Only "happy paths" are // currently tested - those where the operation should succeed. @@ -104,6 +105,7 @@ return subtle.importKey(format, keyData[format], algorithm, extractable, usages). then(function(key) { assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object"); + assert_goodCryptoKey(key, algorithm, extractable, usages, (format === 'pkcs8' || (format === 'jwk' && keyData[format].d)) ? 'private' : 'public'); if (!extractable) { return; } diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/rsa_importKey.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/rsa_importKey.https.any.js index 9f9d38a9554be9..41d25da89c6505 100644 --- a/test/fixtures/wpt/WebCryptoAPI/import_export/rsa_importKey.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/rsa_importKey.https.any.js @@ -1,5 +1,6 @@ // META: title=WebCryptoAPI: importKey() for RSA keys // META: timeout=long +// META: script=../util/helpers.js // Test importKey and exportKey for RSA algorithms. Only "happy paths" are // currently tested - those where the operation should succeed. @@ -113,6 +114,7 @@ return subtle.importKey(format, keyData[format], algorithm, extractable, usages). then(function(key) { assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object"); + assert_goodCryptoKey(key, algorithm, extractable, usages, (format === 'pkcs8' || (format === 'jwk' && keyData[format].d)) ? 'private' : 'public'); if (!extractable) { return; } diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/symmetric_importKey.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/symmetric_importKey.https.any.js index 3d76daac1e28a1..404b66ac0022ac 100644 --- a/test/fixtures/wpt/WebCryptoAPI/import_export/symmetric_importKey.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/import_export/symmetric_importKey.https.any.js @@ -1,5 +1,6 @@ // META: title=WebCryptoAPI: importKey() for symmetric keys // META: timeout=long +// META: script=../util/helpers.js // Test importKey and exportKey for non-PKC algorithms. Only "happy paths" are // currently tested - those where the operation should succeed. @@ -57,6 +58,10 @@ }); }); + function hasLength(algorithm) { + return algorithm.name === 'HMAC' || algorithm.name.startsWith('AES'); + } + // Test importKey with a given key format and other parameters. If // extrable is true, export the key and verify that it matches the input. function testFormat(format, algorithm, keyData, keySize, usages, extractable) { @@ -64,6 +69,7 @@ return subtle.importKey(format, keyData, algorithm, extractable, usages). then(function(key) { assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object"); + assert_goodCryptoKey(key, hasLength(key.algorithm) ? { length: keySize, ...algorithm } : algorithm, extractable, usages, 'secret'); if (!extractable) { return; } diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/rsa.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/rsa.js index 3eb79fb0131d25..5abadd3d4b8629 100644 --- a/test/fixtures/wpt/WebCryptoAPI/sign_verify/rsa.js +++ b/test/fixtures/wpt/WebCryptoAPI/sign_verify/rsa.js @@ -306,6 +306,35 @@ function run_test() { all_promises.push(promise); }); + // [RSA-PSS] Verification should fail with wrong saltLength + testVectors.forEach(function(vector) { + if (vector.algorithm.name === "RSA-PSS") { + var promise = importVectorKeys(vector, ["verify"], ["sign"]) + .then(function(vectors) { + promise_test(function(test) { + const saltLength = vector.algorithm.saltLength === 32 ? 48 : 32; + var operation = subtle.verify({ ...vector.algorithm, saltLength }, vector.publicKey, vector.signature, vector.plaintext) + .then(function(is_verified) { + assert_false(is_verified, "Signature NOT verified"); + }, function(err) { + assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); + }); + + return operation; + }, vector.name + " verification failure with wrong saltLength"); + + }, function(err) { + // We need a failed test if the importVectorKey operation fails, so + // we know we never tested verification. + promise_test(function(test) { + assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); + }, "importVectorKeys step: " + vector.name + " verification failure with wrong saltLength"); + }); + + all_promises.push(promise); + } + }); + // Verification should fail with wrong plaintext testVectors.forEach(function(vector) { var promise = importVectorKeys(vector, ["verify"], ["sign"]) diff --git a/test/fixtures/wpt/WebCryptoAPI/util/helpers.js b/test/fixtures/wpt/WebCryptoAPI/util/helpers.js index ee9d09d1251237..ce240a1549cb21 100644 --- a/test/fixtures/wpt/WebCryptoAPI/util/helpers.js +++ b/test/fixtures/wpt/WebCryptoAPI/util/helpers.js @@ -19,7 +19,7 @@ var registeredAlgorithmNames = [ "SHA-256", "SHA-384", "SHA-512", - "HKDF-CTR", + "HKDF", "PBKDF2", "Ed25519", "Ed448", @@ -104,9 +104,6 @@ function assert_goodCryptoKey(key, algorithm, extractable, usages, kind) { assert_equals(key.constructor, CryptoKey, "Is a CryptoKey"); assert_equals(key.type, kind, "Is a " + kind + " key"); - if (key.type === "public") { - extractable = true; // public keys are always extractable - } assert_equals(key.extractable, extractable, "Extractability is correct"); assert_equals(key.algorithm.name, registeredAlgorithmName, "Correct algorithm name"); @@ -130,6 +127,10 @@ function assert_goodCryptoKey(key, algorithm, extractable, usages, kind) { assert_equals(key.algorithm.hash.name.toUpperCase(), algorithm.hash.toUpperCase(), "Correct hash function"); } + if (/^(?:Ed|X)(?:25519|448)$/.test(key.algorithm.name)) { + assert_false('namedCurve' in key.algorithm, "Does not have a namedCurve property"); + } + // usages is expected to be provided for a key pair, but we are checking // only a single key. The publicKey and privateKey portions of a key pair // recognize only some of the usages appropriate for a key pair. diff --git a/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js b/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js index 65e640a258452f..95102495fe3a5b 100644 --- a/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js @@ -7,12 +7,11 @@ var wrappers = []; // Things we wrap (and upwrap) keys with var keys = []; // Things to wrap and unwrap - var ecdhPeerKey; // ECDH peer public key needed for non-extractable ECDH key comparison // Generate all the keys needed, then iterate over all combinations // to test wrapping and unwrapping. promise_test(function() { - return Promise.all([generateWrappingKeys(), generateKeysToWrap(), generateEcdhPeerKey()]) + return Promise.all([generateWrappingKeys(), generateKeysToWrap()]) .then(function(results) { var promises = []; wrappers.forEach(function(wrapper) { @@ -83,6 +82,9 @@ {algorithm: {name: "ECDSA", namedCurve: "P-256"}, privateUsages: ["sign"], publicUsages: ["verify"]}, {algorithm: {name: "ECDH", namedCurve: "P-256"}, privateUsages: ["deriveBits"], publicUsages: []}, {algorithm: {name: "Ed25519" }, privateUsages: ["sign"], publicUsages: ["verify"]}, + {algorithm: {name: "Ed448" }, privateUsages: ["sign"], publicUsages: ["verify"]}, + {algorithm: {name: "X25519" }, privateUsages: ["deriveBits"], publicUsages: []}, + {algorithm: {name: "X448" }, privateUsages: ["deriveBits"], publicUsages: []}, {algorithm: {name: "AES-CTR", length: 128}, usages: ["encrypt", "decrypt"]}, {algorithm: {name: "AES-CBC", length: 128}, usages: ["encrypt", "decrypt"]}, {algorithm: {name: "AES-GCM", length: 128}, usages: ["encrypt", "decrypt"]}, @@ -112,13 +114,6 @@ })); } - function generateEcdhPeerKey() { - return subtle.generateKey({name: "ECDH", namedCurve: "P-256"},true,["deriveBits"]) - .then(function(result){ - ecdhPeerKey = result.publicKey; - }); - } - // Can we successfully "round-trip" (wrap, then unwrap, a key)? function testWrapping(wrapper, toWrap) { var formats; @@ -135,58 +130,72 @@ var originalExport; return subtle.exportKey(fmt, toWrap.key).then(function(exportedKey) { originalExport = exportedKey; - if (wrappingIsPossible(originalExport, wrapper.parameters.name)) { - promise_test(function(test) { + const isPossible = wrappingIsPossible(originalExport, wrapper.parameters.name); + promise_test(function(test) { + if (!isPossible) { + return Promise.resolve().then(() => { + assert_false(false, "Wrapping is not possible"); + }) + } + return subtle.wrapKey(fmt, toWrap.key, wrapper.wrappingKey, wrapper.parameters.wrapParameters) + .then(function(wrappedResult) { + return subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages); + }).then(function(unwrappedResult) { + assert_true(unwrappedResult.extractable, "Unwrapped result is extractable"); + return subtle.exportKey(fmt, unwrappedResult) + }).then(function(roundTripExport) { + assert_true(equalExport(originalExport, roundTripExport), "Post-wrap export matches original export"); + }, function(err) { + assert_unreached("Round trip for extractable key threw an error - " + err.name + ': "' + err.message + '"'); + }); + }, "Can wrap and unwrap " + toWrap.name + " keys using " + fmt + " and " + wrapper.parameters.name); + + if (canCompareNonExtractableKeys(toWrap.key)) { + promise_test(function(test){ + if (!isPossible) { + return Promise.resolve().then(() => { + assert_false(false, "Wrapping is not possible"); + }) + } return subtle.wrapKey(fmt, toWrap.key, wrapper.wrappingKey, wrapper.parameters.wrapParameters) .then(function(wrappedResult) { - return subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages); - }).then(function(unwrappedResult) { - assert_true(unwrappedResult.extractable, "Unwrapped result is extractable"); - return subtle.exportKey(fmt, unwrappedResult) - }).then(function(roundTripExport) { - assert_true(equalExport(originalExport, roundTripExport), "Post-wrap export matches original export"); - }, function(err) { - assert_unreached("Round trip for extractable key threw an error - " + err.name + ': "' + err.message + '"'); + return subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages); + }).then(function(unwrappedResult){ + assert_false(unwrappedResult.extractable, "Unwrapped result is non-extractable"); + return equalKeys(toWrap.key, unwrappedResult); + }).then(function(result){ + assert_true(result, "Unwrapped key matches original"); + }).catch(function(err){ + assert_unreached("Round trip for key unwrapped non-extractable threw an error - " + err.name + ': "' + err.message + '"'); }); - }, "Can wrap and unwrap " + toWrap.name + " keys using " + fmt + " and " + wrapper.parameters.name); + }, "Can wrap and unwrap " + toWrap.name + " keys as non-extractable using " + fmt + " and " + wrapper.parameters.name); - if (canCompareNonExtractableKeys(toWrap.key)) { + if (fmt === "jwk") { promise_test(function(test){ - return subtle.wrapKey(fmt, toWrap.key, wrapper.wrappingKey, wrapper.parameters.wrapParameters) - .then(function(wrappedResult) { - return subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages); + if (!isPossible) { + return Promise.resolve().then(() => { + assert_false(false, "Wrapping is not possible"); + }) + } + var wrappedKey; + return wrapAsNonExtractableJwk(toWrap.key,wrapper).then(function(wrappedResult){ + wrappedKey = wrappedResult; + return subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages); }).then(function(unwrappedResult){ - assert_false(unwrappedResult.extractable, "Unwrapped result is non-extractable"); - return equalKeys(toWrap.key, unwrappedResult); + assert_false(unwrappedResult.extractable, "Unwrapped key is non-extractable"); + return equalKeys(toWrap.key,unwrappedResult); }).then(function(result){ assert_true(result, "Unwrapped key matches original"); }).catch(function(err){ - assert_unreached("Round trip for key unwrapped non-extractable threw an error - " + err.name + ': "' + err.message + '"'); + assert_unreached("Round trip for non-extractable key threw an error - " + err.name + ': "' + err.message + '"'); + }).then(function(){ + return subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages); + }).then(function(unwrappedResult){ + assert_unreached("Unwrapping a non-extractable JWK as extractable should fail"); + }).catch(function(err){ + assert_equals(err.name, "DataError", "Unwrapping a non-extractable JWK as extractable fails with DataError"); }); - }, "Can wrap and unwrap " + toWrap.name + " keys as non-extractable using " + fmt + " and " + wrapper.parameters.name); - - if (fmt === "jwk") { - promise_test(function(test){ - var wrappedKey; - return wrapAsNonExtractableJwk(toWrap.key,wrapper).then(function(wrappedResult){ - wrappedKey = wrappedResult; - return subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages); - }).then(function(unwrappedResult){ - assert_false(unwrappedResult.extractable, "Unwrapped key is non-extractable"); - return equalKeys(toWrap.key,unwrappedResult); - }).then(function(result){ - assert_true(result, "Unwrapped key matches original"); - }).catch(function(err){ - assert_unreached("Round trip for non-extractable key threw an error - " + err.name + ': "' + err.message + '"'); - }).then(function(){ - return subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages); - }).then(function(unwrappedResult){ - assert_unreached("Unwrapping a non-extractable JWK as extractable should fail"); - }).catch(function(err){ - assert_equals(err.name, "DataError", "Unwrapping a non-extractable JWK as extractable fails with DataError"); - }); - }, "Can unwrap " + toWrap.name + " non-extractable keys using jwk and " + wrapper.parameters.name); - } + }, "Can unwrap " + toWrap.name + " non-extractable keys using jwk and " + wrapper.parameters.name); } } }); @@ -386,6 +395,15 @@ case "Ed25519" : signParams = {name: "Ed25519"}; break; + case "Ed448" : + signParams = {name: "Ed448"}; + break; + case "X25519" : + deriveParams = {name: "X25519"}; + break; + case "X448" : + deriveParams = {name: "X448"}; + break; case "HMAC" : signParams = {name: "HMAC"}; break; @@ -393,7 +411,7 @@ wrapParams = {name: "AES-KW"}; break; case "ECDH" : - deriveParams = {name: "ECDH", public: ecdhPeerKey}; + deriveParams = {name: "ECDH"}; break; default: throw new Error("Unsupported algorithm for key comparison"); @@ -422,7 +440,7 @@ if (expected.algorithm.name === "RSA-PSS" || expected.algorithm.name === "RSASSA-PKCS1-v1_5") { ["d","p","q","dp","dq","qi","oth"].forEach(function(field){ delete jwkExpectedKey[field]; }); } - if (expected.algorithm.name === "ECDSA" || expected.algorithm.name === "Ed25519") { + if (expected.algorithm.name === "ECDSA" || expected.algorithm.name.startsWith("Ed")) { delete jwkExpectedKey["d"]; } jwkExpectedKey.key_ops = ["verify"]; @@ -446,9 +464,12 @@ var wrappedWithGot = Array.from((new Uint8Array(wrapResult)).values()); return wrappedWithGot.every((x,i) => x === wrappedWithExpected[i]); }); - } else { + } else if (deriveParams) { var expectedDerivedBits; - return subtle.deriveBits(deriveParams, expected, 128) + return subtle.generateKey(expected.algorithm, true, ['deriveBits']).then(({ publicKey }) => { + deriveParams.public = publicKey; + return subtle.deriveBits(deriveParams, expected, 128) + }) .then(function(result){ expectedDerivedBits = Array.from((new Uint8Array(result)).values()); return subtle.deriveBits(deriveParams, got, 128); diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 58fe167a78bed2..ca53416f2671b9 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -84,7 +84,7 @@ "path": "wasm/webapi" }, "WebCryptoAPI": { - "commit": "450f829d2567ed9c44bd9b10f7e8f34a2ad15315", + "commit": "238d9d9bac54d4f1ae8844fc8dd4389b1ad99b4e", "path": "WebCryptoAPI" }, "webidl/ecmascript-binding/es-exceptions": {