test,crypto: update WebCryptoAPI WPT

PR-URL: https://github.com/nodejs/node/pull/46575
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Filip Skokan 2023-02-08 18:09:53 +01:00
parent 2787e2dfc2
commit eb2a1ab38a
10 changed files with 125 additions and 62 deletions

View File

@ -32,7 +32,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

View File

@ -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");
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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"])

View File

@ -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.

View File

@ -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);

View File

@ -88,7 +88,7 @@
"path": "wasm/webapi"
},
"WebCryptoAPI": {
"commit": "450f829d2567ed9c44bd9b10f7e8f34a2ad15315",
"commit": "238d9d9bac54d4f1ae8844fc8dd4389b1ad99b4e",
"path": "WebCryptoAPI"
},
"webidl/ecmascript-binding/es-exceptions": {