test: update WPT for WebCryptoAPI to edd42c005c
PR-URL: https://github.com/nodejs/node/pull/57365 Backport-PR-URL: https://github.com/nodejs/node/pull/58589 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jason Zhang <xzha4350@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
This commit is contained in:
parent
f56e62851a
commit
3cf7cfb695
@ -274,6 +274,14 @@ async function cfrgImportKey(
|
|||||||
'DataError');
|
'DataError');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keyData.alg !== undefined && (name === 'Ed25519' || name === 'Ed448')) {
|
||||||
|
if (keyData.alg !== name && keyData.alg !== 'EdDSA') {
|
||||||
|
throw lazyDOMException(
|
||||||
|
'JWK "alg" does not match the requested algorithm',
|
||||||
|
'DataError');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isPublic && typeof keyData.x !== 'string') {
|
if (!isPublic && typeof keyData.x !== 'string') {
|
||||||
throw lazyDOMException('Invalid JWK', 'DataError');
|
throw lazyDOMException('Invalid JWK', 'DataError');
|
||||||
}
|
}
|
||||||
|
@ -475,6 +475,7 @@ async function exportKeyJWK(key) {
|
|||||||
// Fall through
|
// Fall through
|
||||||
case 'Ed448':
|
case 'Ed448':
|
||||||
jwk.crv ||= key.algorithm.name;
|
jwk.crv ||= key.algorithm.name;
|
||||||
|
jwk.alg = key.algorithm.name;
|
||||||
return jwk;
|
return jwk;
|
||||||
case 'AES-CTR':
|
case 'AES-CTR':
|
||||||
// Fall through
|
// Fall through
|
||||||
|
2
test/fixtures/wpt/README.md
vendored
2
test/fixtures/wpt/README.md
vendored
@ -31,7 +31,7 @@ Last update:
|
|||||||
- user-timing: https://github.com/web-platform-tests/wpt/tree/5ae85bf826/user-timing
|
- user-timing: https://github.com/web-platform-tests/wpt/tree/5ae85bf826/user-timing
|
||||||
- wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi
|
- wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi
|
||||||
- wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi
|
- wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi
|
||||||
- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/b81831169b/WebCryptoAPI
|
- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/edd42c005c/WebCryptoAPI
|
||||||
- webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/a370aad338/webidl/ecmascript-binding/es-exceptions
|
- webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/a370aad338/webidl/ecmascript-binding/es-exceptions
|
||||||
- webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/e97fac4791/webmessaging/broadcastchannel
|
- webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/e97fac4791/webmessaging/broadcastchannel
|
||||||
|
|
||||||
|
44
test/fixtures/wpt/WebCryptoAPI/crypto_key_cached_slots.https.any.js
vendored
Normal file
44
test/fixtures/wpt/WebCryptoAPI/crypto_key_cached_slots.https.any.js
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// META: title=WebCryptoAPI: CryptoKey cached ECMAScript objects
|
||||||
|
|
||||||
|
// https://w3c.github.io/webcrypto/#dom-cryptokey-algorithm
|
||||||
|
// https://github.com/servo/servo/issues/33908
|
||||||
|
|
||||||
|
promise_test(function() {
|
||||||
|
return self.crypto.subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: "AES-CTR",
|
||||||
|
length: 256,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["encrypt"],
|
||||||
|
).then(
|
||||||
|
function(key) {
|
||||||
|
let a = key.algorithm;
|
||||||
|
let b = key.algorithm;
|
||||||
|
assert_true(a === b);
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
assert_unreached("generateKey threw an unexpected error: " + err.toString());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, "CryptoKey.algorithm getter returns cached object");
|
||||||
|
|
||||||
|
promise_test(function() {
|
||||||
|
return self.crypto.subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: "AES-CTR",
|
||||||
|
length: 256,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["encrypt"],
|
||||||
|
).then(
|
||||||
|
function(key) {
|
||||||
|
let a = key.usages;
|
||||||
|
let b = key.usages;
|
||||||
|
assert_true(a === b);
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
assert_unreached("generateKey threw an unexpected error: " + err.toString());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, "CryptoKey.usages getter returns cached object");
|
@ -1,24 +0,0 @@
|
|||||||
// META: title=WebCryptoAPI: CryptoKey.algorithm getter returns cached object
|
|
||||||
|
|
||||||
// https://w3c.github.io/webcrypto/#dom-cryptokey-algorithm
|
|
||||||
// https://github.com/servo/servo/issues/33908
|
|
||||||
|
|
||||||
promise_test(function() {
|
|
||||||
return self.crypto.subtle.generateKey(
|
|
||||||
{
|
|
||||||
name: "AES-CTR",
|
|
||||||
length: 256,
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
["encrypt"],
|
|
||||||
).then(
|
|
||||||
function(key) {
|
|
||||||
let a = key.algorithm;
|
|
||||||
let b = key.algorithm;
|
|
||||||
assert_true(a === b);
|
|
||||||
},
|
|
||||||
function(err) {
|
|
||||||
assert_unreached("generateKey threw an unexpected error: " + err.toString());
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}, "CryptoKey.algorithm getter returns cached object");
|
|
@ -118,6 +118,20 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Call digest() with empty algorithm object
|
||||||
|
Object.keys(sourceData).forEach(function(size) {
|
||||||
|
promise_test(function(test) {
|
||||||
|
var promise = subtle.digest({}, sourceData[size])
|
||||||
|
.then(function(result) {
|
||||||
|
assert_unreached("digest() with missing algorithm name should have thrown a TypeError");
|
||||||
|
}, function(err) {
|
||||||
|
assert_equals(err.name, "TypeError", "Missing algorithm name should cause TypeError")
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}, "empty algorithm object with " + size);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
|
||||||
|
@ -166,6 +166,14 @@ function run_test(algorithmNames) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Empty algorithm should fail with TypeError
|
||||||
|
allValidUsages(["decrypt", "sign", "deriveBits"], true, []) // Small search space, shouldn't matter because should fail before used
|
||||||
|
.forEach(function(usages) {
|
||||||
|
[false, true, "RED", 7].forEach(function(extractable){
|
||||||
|
testError({}, extractable, usages, "TypeError", "Empty algorithm");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Algorithms normalize okay, but usages bad (though not empty).
|
// Algorithms normalize okay, but usages bad (though not empty).
|
||||||
// It shouldn't matter what other extractable is. Should fail
|
// It shouldn't matter what other extractable is. Should fail
|
||||||
|
@ -19,6 +19,14 @@ function getMismatchedJWKKeyData(algorithm) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMismatchedKtyField(algorithm) {
|
||||||
|
return mismatchedKtyField[algorithm.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMismatchedCrvField(algorithm) {
|
||||||
|
return mismatchedCrvField[algorithm.name];
|
||||||
|
}
|
||||||
|
|
||||||
var validKeyData = {
|
var validKeyData = {
|
||||||
"P-521": [
|
"P-521": [
|
||||||
{
|
{
|
||||||
@ -201,3 +209,17 @@ var missingJWKFieldKeyData = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The 'kty' field doesn't match the key algorithm.
|
||||||
|
var mismatchedKtyField = {
|
||||||
|
"P-521": "OKP",
|
||||||
|
"P-256": "OKP",
|
||||||
|
"P-384": "OKP",
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 'kty' field doesn't match the key algorithm.
|
||||||
|
var mismatchedCrvField = {
|
||||||
|
"P-521": "P-256",
|
||||||
|
"P-256": "P-384",
|
||||||
|
"P-384": "P-521",
|
||||||
|
}
|
||||||
|
@ -192,4 +192,79 @@ function run_test(algorithmNames) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Missing mandatory "name" field on algorithm
|
||||||
|
testVectors.forEach(function(vector) {
|
||||||
|
var name = vector.name;
|
||||||
|
// We just need *some* valid keydata, so pick the first available algorithm.
|
||||||
|
var algorithm = allAlgorithmSpecifiersFor(name)[0];
|
||||||
|
getValidKeyData(algorithm).forEach(function(test) {
|
||||||
|
validUsages(vector, test.format, test.data).forEach(function(usages) {
|
||||||
|
[true, false].forEach(function(extractable) {
|
||||||
|
testError(test.format, {}, test.data, name, usages, extractable, "TypeError", "Missing algorithm name");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// The 'kty' field is not correct.
|
||||||
|
testVectors.forEach(function(vector) {
|
||||||
|
var name = vector.name;
|
||||||
|
allAlgorithmSpecifiersFor(name).forEach(function(algorithm) {
|
||||||
|
getValidKeyData(algorithm).forEach(function(test) {
|
||||||
|
if (test.format === "jwk") {
|
||||||
|
var data = {crv: test.data.crv, kty: test.data.kty, d: test.data.d, x: test.data.x, d: test.data.d};
|
||||||
|
data.kty = getMismatchedKtyField(algorithm);
|
||||||
|
var usages = validUsages(vector, 'jwk', test.data);
|
||||||
|
testError('jwk', algorithm, data, name, usages, true, "DataError", "Invalid 'kty' field");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// The 'ext' field is not correct.
|
||||||
|
testVectors.forEach(function(vector) {
|
||||||
|
var name = vector.name;
|
||||||
|
allAlgorithmSpecifiersFor(name).forEach(function(algorithm) {
|
||||||
|
getValidKeyData(algorithm).forEach(function(test) {
|
||||||
|
if (test.format === "jwk") {
|
||||||
|
var data = {crv: test.data.crv, kty: test.data.kty, d: test.data.d, x: test.data.x, d: test.data.d};
|
||||||
|
data.ext = false;
|
||||||
|
var usages = validUsages(vector, 'jwk', test.data);
|
||||||
|
testError('jwk', algorithm, data, name, usages, true, "DataError", "Import from a non-extractable");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// The 'use' field is incorrect.
|
||||||
|
testVectors.forEach(function(vector) {
|
||||||
|
var name = vector.name;
|
||||||
|
allAlgorithmSpecifiersFor(name).forEach(function(algorithm) {
|
||||||
|
getValidKeyData(algorithm).forEach(function(test) {
|
||||||
|
if (test.format === "jwk") {
|
||||||
|
var data = {crv: test.data.crv, kty: test.data.kty, d: test.data.d, x: test.data.x, d: test.data.d};
|
||||||
|
data.use = "invalid";
|
||||||
|
var usages = validUsages(vector, 'jwk', test.data);
|
||||||
|
if (usages.length !== 0)
|
||||||
|
testError('jwk', algorithm, data, name, usages, true, "DataError", "Invalid 'use' field");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// The 'crv' field is incorrect.
|
||||||
|
testVectors.forEach(function(vector) {
|
||||||
|
var name = vector.name;
|
||||||
|
allAlgorithmSpecifiersFor(name).forEach(function(algorithm) {
|
||||||
|
getValidKeyData(algorithm).forEach(function(test) {
|
||||||
|
if (test.format === "jwk") {
|
||||||
|
var data = {crv: test.data.crv, kty: test.data.kty, d: test.data.d, x: test.data.x, d: test.data.d};
|
||||||
|
data.crv = getMismatchedCrvField(algorithm)
|
||||||
|
var usages = validUsages(vector, 'jwk', test.data);
|
||||||
|
testError('jwk', algorithm, data, name, usages, true, "DataError", "Invalid 'crv' field");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ function runTests(algorithmName) {
|
|||||||
['spki', 'jwk', 'raw'].forEach(function(format) {
|
['spki', 'jwk', 'raw'].forEach(function(format) {
|
||||||
if (format === "jwk") { // Not all fields used for public keys
|
if (format === "jwk") { // Not all fields used for public keys
|
||||||
testFormat(format, algorithm, jwkData, algorithmName, usages, extractable);
|
testFormat(format, algorithm, jwkData, algorithmName, usages, extractable);
|
||||||
// Test for https://github.com/WICG/webcrypto-secure-curves/pull/24
|
// Test for https://github.com/w3c/webcrypto/pull/401
|
||||||
if (extractable) {
|
if (extractable) {
|
||||||
testJwkAlgBehaviours(algorithm, jwkData.jwk, algorithmName, usages);
|
testJwkAlgBehaviours(algorithm, jwkData.jwk, algorithmName, usages);
|
||||||
}
|
}
|
||||||
@ -27,7 +27,7 @@ function runTests(algorithmName) {
|
|||||||
['pkcs8', 'jwk'].forEach(function(format) {
|
['pkcs8', 'jwk'].forEach(function(format) {
|
||||||
testFormat(format, algorithm, data, algorithmName, usages, extractable);
|
testFormat(format, algorithm, data, algorithmName, usages, extractable);
|
||||||
|
|
||||||
// Test for https://github.com/WICG/webcrypto-secure-curves/pull/24
|
// Test for https://github.com/w3c/webcrypto/pull/401
|
||||||
if (format === "jwk" && extractable) {
|
if (format === "jwk" && extractable) {
|
||||||
testJwkAlgBehaviours(algorithm, data.jwk, algorithmName, usages);
|
testJwkAlgBehaviours(algorithm, data.jwk, algorithmName, usages);
|
||||||
}
|
}
|
||||||
@ -67,19 +67,25 @@ function testFormat(format, algorithm, keyData, keySize, usages, extractable) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test importKey/exportKey "alg" behaviours, alg is ignored upon import and alg is missing for Ed25519 and Ed448 JWK export
|
// Test importKey/exportKey "alg" behaviours (https://github.com/w3c/webcrypto/pull/401)
|
||||||
// https://github.com/WICG/webcrypto-secure-curves/pull/24
|
// - alg is ignored for ECDH import
|
||||||
|
// - TODO: alg is checked to be the algorithm.name or EdDSA for Ed25519 and Ed448 import
|
||||||
|
// - alg is missing for ECDH export
|
||||||
|
// - alg is the algorithm name for Ed25519 and Ed448 export
|
||||||
function testJwkAlgBehaviours(algorithm, keyData, crv, usages) {
|
function testJwkAlgBehaviours(algorithm, keyData, crv, usages) {
|
||||||
[algorithm, algorithm.name].forEach((alg) => {
|
[algorithm, algorithm.name].forEach((alg) => {
|
||||||
|
(crv.startsWith('Ed') ? [algorithm.name, 'EdDSA'] : ['this is ignored']).forEach((jwkAlg) => {
|
||||||
promise_test(function(test) {
|
promise_test(function(test) {
|
||||||
return subtle.importKey('jwk', { ...keyData, alg: 'this is ignored' }, alg, true, usages).
|
return subtle.importKey('jwk', { ...keyData, alg: jwkAlg }, alg, true, usages).
|
||||||
then(function(key) {
|
then(function(key) {
|
||||||
assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object");
|
assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object");
|
||||||
|
|
||||||
return subtle.exportKey('jwk', key).
|
return subtle.exportKey('jwk', key).
|
||||||
then(function(result) {
|
then(function(result) {
|
||||||
assert_equals(Object.keys(result).length, keyData.d ? 6 : 5, "Correct number of JWK members");
|
let expectedKeys = crv.startsWith('Ed') ? 6 : 5
|
||||||
assert_equals(result.alg, undefined, 'No JWK "alg" member is present');
|
if (keyData.d) expectedKeys++
|
||||||
|
assert_equals(Object.keys(result).length, expectedKeys, "Correct number of JWK members");
|
||||||
|
assert_equals(result.alg, crv.startsWith('Ed') ? algorithm.name : undefined, 'Expected JWK "alg" member');
|
||||||
assert_true(equalJwk(keyData, result), "Round trip works");
|
assert_true(equalJwk(keyData, result), "Round trip works");
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
assert_unreached("Threw an unexpected error: " + err.toString());
|
assert_unreached("Threw an unexpected error: " + err.toString());
|
||||||
@ -87,7 +93,8 @@ function testJwkAlgBehaviours(algorithm, keyData, crv, usages) {
|
|||||||
}, function(err) {
|
}, function(err) {
|
||||||
assert_unreached("Threw an unexpected error: " + err.toString());
|
assert_unreached("Threw an unexpected error: " + err.toString());
|
||||||
});
|
});
|
||||||
}, "Good parameters with ignored JWK alg: " + crv.toString() + " " + parameterString('jwk', keyData, alg, true, usages));
|
}, 'Good parameters with JWK alg' + (crv.startsWith('Ed') ? ` ${jwkAlg}: ` : ': ') + crv.toString() + " " + parameterString('jwk', keyData, alg, true, usages, jwkAlg));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,14 @@ function getMismatchedJWKKeyData(algorithm) {
|
|||||||
return mismatchedJWKKeyData[algorithm.name];
|
return mismatchedJWKKeyData[algorithm.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMismatchedKtyField(algorithm) {
|
||||||
|
return mismatchedKtyField[algorithm.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMismatchedCrvField(algorithm) {
|
||||||
|
return mismatchedCrvField[algorithm.name];
|
||||||
|
}
|
||||||
|
|
||||||
var validKeyData = {
|
var validKeyData = {
|
||||||
"Ed25519": [
|
"Ed25519": [
|
||||||
{
|
{
|
||||||
@ -412,3 +420,19 @@ var mismatchedJWKKeyData = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The 'kty' field doesn't match the key algorithm.
|
||||||
|
var mismatchedKtyField = {
|
||||||
|
"Ed25519": "EC",
|
||||||
|
"X25519": "EC",
|
||||||
|
"Ed448": "EC",
|
||||||
|
"X448": "EC",
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 'kty' field doesn't match the key algorithm.
|
||||||
|
var mismatchedCrvField = {
|
||||||
|
"Ed25519": "X25519",
|
||||||
|
"X25519": "Ed448",
|
||||||
|
"Ed448": "X25519",
|
||||||
|
"X448": "Ed25519",
|
||||||
|
}
|
||||||
|
@ -1,85 +1,52 @@
|
|||||||
// META: title=WebCryptoAPI: wrapKey() and unwrapKey()
|
// META: title=WebCryptoAPI: wrapKey() and unwrapKey()
|
||||||
// META: timeout=long
|
// META: timeout=long
|
||||||
// META: script=../util/helpers.js
|
// META: script=../util/helpers.js
|
||||||
|
// META: script=wrapKey_unwrapKey_vectors.js
|
||||||
|
|
||||||
// Tests for wrapKey and unwrapKey round tripping
|
// Tests for wrapKey and unwrapKey round tripping
|
||||||
|
|
||||||
var subtle = self.crypto.subtle;
|
var subtle = self.crypto.subtle;
|
||||||
|
|
||||||
var wrappers = []; // Things we wrap (and upwrap) keys with
|
var wrappers = {}; // Things we wrap (and upwrap) keys with
|
||||||
var keys = []; // Things to wrap and unwrap
|
var keys = {}; // Things to wrap and unwrap
|
||||||
|
|
||||||
// Generate all the keys needed, then iterate over all combinations
|
|
||||||
// to test wrapping and unwrapping.
|
|
||||||
promise_test(function() {
|
|
||||||
return Promise.all([generateWrappingKeys(), generateKeysToWrap()])
|
|
||||||
.then(function(results) {
|
|
||||||
var promises = [];
|
|
||||||
wrappers.forEach(function(wrapper) {
|
|
||||||
keys.forEach(function(key) {
|
|
||||||
promises.push(testWrapping(wrapper, key));
|
|
||||||
})
|
|
||||||
});
|
|
||||||
return Promise.allSettled(promises);
|
|
||||||
});
|
|
||||||
}, "setup");
|
|
||||||
|
|
||||||
function generateWrappingKeys() {
|
|
||||||
// There are five algorithms that can be used for wrapKey/unwrapKey.
|
// There are five algorithms that can be used for wrapKey/unwrapKey.
|
||||||
// Generate one key with typical parameters for each kind.
|
// Generate one key with typical parameters for each kind.
|
||||||
//
|
//
|
||||||
// Note: we don't need cryptographically strong parameters for things
|
// Note: we don't need cryptographically strong parameters for things
|
||||||
// like IV - just any legal value will do.
|
// like IV - just any legal value will do.
|
||||||
var parameters = [
|
var wrappingKeysParameters = [
|
||||||
{
|
{
|
||||||
name: "RSA-OAEP",
|
name: "RSA-OAEP",
|
||||||
generateParameters: {name: "RSA-OAEP", modulusLength: 4096, publicExponent: new Uint8Array([1,0,1]), hash: "SHA-256"},
|
importParameters: {name: "RSA-OAEP", hash: "SHA-256"},
|
||||||
wrapParameters: {name: "RSA-OAEP", label: new Uint8Array(8)}
|
wrapParameters: {name: "RSA-OAEP", label: new Uint8Array(8)}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AES-CTR",
|
name: "AES-CTR",
|
||||||
generateParameters: {name: "AES-CTR", length: 128},
|
importParameters: {name: "AES-CTR", length: 128},
|
||||||
wrapParameters: {name: "AES-CTR", counter: new Uint8Array(16), length: 64}
|
wrapParameters: {name: "AES-CTR", counter: new Uint8Array(16), length: 64}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AES-CBC",
|
name: "AES-CBC",
|
||||||
generateParameters: {name: "AES-CBC", length: 128},
|
importParameters: {name: "AES-CBC", length: 128},
|
||||||
wrapParameters: {name: "AES-CBC", iv: new Uint8Array(16)}
|
wrapParameters: {name: "AES-CBC", iv: new Uint8Array(16)}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AES-GCM",
|
name: "AES-GCM",
|
||||||
generateParameters: {name: "AES-GCM", length: 128},
|
importParameters: {name: "AES-GCM", length: 128},
|
||||||
wrapParameters: {name: "AES-GCM", iv: new Uint8Array(16), additionalData: new Uint8Array(16), tagLength: 128}
|
wrapParameters: {name: "AES-GCM", iv: new Uint8Array(16), additionalData: new Uint8Array(16), tagLength: 128}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AES-KW",
|
name: "AES-KW",
|
||||||
generateParameters: {name: "AES-KW", length: 128},
|
importParameters: {name: "AES-KW", length: 128},
|
||||||
wrapParameters: {name: "AES-KW"}
|
wrapParameters: {name: "AES-KW"}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Using allSettled to skip unsupported test cases.
|
var keysToWrapParameters = [
|
||||||
return Promise.allSettled(parameters.map(function(params) {
|
{algorithm: {name: "RSASSA-PKCS1-v1_5", hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
|
||||||
return subtle.generateKey(params.generateParameters, true, ["wrapKey", "unwrapKey"])
|
{algorithm: {name: "RSA-PSS", hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
|
||||||
.then(function(key) {
|
{algorithm: {name: "RSA-OAEP", hash: "SHA-256"}, privateUsages: ["decrypt"], publicUsages: ["encrypt"]},
|
||||||
var wrapper;
|
|
||||||
if (params.name === "RSA-OAEP") { // we have a key pair, not just a key
|
|
||||||
wrapper = {wrappingKey: key.publicKey, unwrappingKey: key.privateKey, parameters: params};
|
|
||||||
} else {
|
|
||||||
wrapper = {wrappingKey: key, unwrappingKey: key, parameters: params};
|
|
||||||
}
|
|
||||||
wrappers.push(wrapper);
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function generateKeysToWrap() {
|
|
||||||
var parameters = [
|
|
||||||
{algorithm: {name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, publicExponent: new Uint8Array([1,0,1]), hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
|
|
||||||
{algorithm: {name: "RSA-PSS", modulusLength: 1024, publicExponent: new Uint8Array([1,0,1]), hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
|
|
||||||
{algorithm: {name: "RSA-OAEP", modulusLength: 1024, publicExponent: new Uint8Array([1,0,1]), hash: "SHA-256"}, privateUsages: ["decrypt"], publicUsages: ["encrypt"]},
|
|
||||||
{algorithm: {name: "ECDSA", namedCurve: "P-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
|
{algorithm: {name: "ECDSA", namedCurve: "P-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
|
||||||
{algorithm: {name: "ECDH", namedCurve: "P-256"}, privateUsages: ["deriveBits"], publicUsages: []},
|
{algorithm: {name: "ECDH", namedCurve: "P-256"}, privateUsages: ["deriveBits"], publicUsages: []},
|
||||||
{algorithm: {name: "Ed25519" }, privateUsages: ["sign"], publicUsages: ["verify"]},
|
{algorithm: {name: "Ed25519" }, privateUsages: ["sign"], publicUsages: ["verify"]},
|
||||||
@ -93,125 +60,177 @@
|
|||||||
{algorithm: {name: "HMAC", length: 128, hash: "SHA-256"}, usages: ["sign", "verify"]}
|
{algorithm: {name: "HMAC", length: 128, hash: "SHA-256"}, usages: ["sign", "verify"]}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Import all the keys needed, then iterate over all combinations
|
||||||
|
// to test wrapping and unwrapping.
|
||||||
|
promise_test(function() {
|
||||||
|
return Promise.all([importWrappingKeys(), importKeysToWrap()])
|
||||||
|
.then(function(results) {
|
||||||
|
wrappingKeysParameters.filter((param) => Object.keys(wrappers).includes(param.name)).forEach(function(wrapperParam) {
|
||||||
|
var wrapper = wrappers[wrapperParam.name];
|
||||||
|
keysToWrapParameters.filter((param) => Object.keys(keys).includes(param.algorithm.name)).forEach(function(toWrapParam) {
|
||||||
|
var keyData = keys[toWrapParam.algorithm.name];
|
||||||
|
["raw", "spki", "pkcs8"].filter((fmt) => Object.keys(keyData).includes(fmt)).forEach(function(keyDataFormat) {
|
||||||
|
var toWrap = keyData[keyDataFormat];
|
||||||
|
[keyDataFormat, "jwk"].forEach(function(format) {
|
||||||
|
if (wrappingIsPossible(toWrap.originalExport[format], wrapper.parameters.name)) {
|
||||||
|
testWrapping(wrapper, toWrap, format);
|
||||||
|
if (canCompareNonExtractableKeys(toWrap.key)) {
|
||||||
|
testWrappingNonExtractable(wrapper, toWrap, format);
|
||||||
|
if (format === "jwk") {
|
||||||
|
testWrappingNonExtractableAsExtractable(wrapper, toWrap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return Promise.resolve("setup done");
|
||||||
|
}, function(err) {
|
||||||
|
return Promise.reject("setup failed: " + err.name + ': "' + err.message + '"');
|
||||||
|
});
|
||||||
|
}, "setup");
|
||||||
|
|
||||||
|
function importWrappingKeys() {
|
||||||
// Using allSettled to skip unsupported test cases.
|
// Using allSettled to skip unsupported test cases.
|
||||||
return Promise.allSettled(parameters.map(function(params) {
|
var promises = [];
|
||||||
var usages;
|
wrappingKeysParameters.forEach(function(params) {
|
||||||
if ("usages" in params) {
|
if (params.name === "RSA-OAEP") { // we have a key pair, not just a key
|
||||||
usages = params.usages;
|
var algorithm = {name: "RSA-OAEP", hash: "SHA-256"};
|
||||||
|
wrappers[params.name] = {wrappingKey: undefined, unwrappingKey: undefined, parameters: params};
|
||||||
|
promises.push(subtle.importKey("spki", wrappingKeyData["RSA"].spki, algorithm, true, ["wrapKey"])
|
||||||
|
.then(function(key) {
|
||||||
|
wrappers["RSA-OAEP"].wrappingKey = key;
|
||||||
|
}));
|
||||||
|
promises.push(subtle.importKey("pkcs8", wrappingKeyData["RSA"].pkcs8, algorithm, true, ["unwrapKey"])
|
||||||
|
.then(function(key) {
|
||||||
|
wrappers["RSA-OAEP"].unwrappingKey = key;
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
usages = params.publicUsages.concat(params.privateUsages);
|
var algorithm = {name: params.name};
|
||||||
|
promises.push(subtle.importKey("raw", wrappingKeyData["SYMMETRIC"].raw, algorithm, true, ["wrapKey", "unwrapKey"])
|
||||||
|
.then(function(key) {
|
||||||
|
wrappers[params.name] = {wrappingKey: key, unwrappingKey: key, parameters: params};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Using allSettled to skip unsupported test cases.
|
||||||
|
return Promise.allSettled(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
return subtle.generateKey(params.algorithm, true, usages)
|
async function importAndExport(format, keyData, algorithm, keyUsages, keyType) {
|
||||||
.then(function(result) {
|
var importedKey;
|
||||||
if (result.constructor === CryptoKey) {
|
try {
|
||||||
keys.push({name: params.algorithm.name, algorithm: params.algorithm, usages: params.usages, key: result});
|
importedKey = await subtle.importKey(format, keyData, algorithm, true, keyUsages);
|
||||||
} else {
|
keys[algorithm.name][format] = { name: algorithm.name + " " + keyType, algorithm: algorithm, usages: keyUsages, key: importedKey, originalExport: {} };
|
||||||
keys.push({name: params.algorithm.name + " public key", algorithm: params.algorithm, usages: params.publicUsages, key: result.publicKey});
|
} catch (err) {
|
||||||
keys.push({name: params.algorithm.name + " private key", algorithm: params.algorithm, usages: params.privateUsages, key: result.privateKey});
|
delete keys[algorithm.name][format];
|
||||||
|
throw("Error importing " + algorithm.name + " " + keyType + " key in '" + format + "' -" + err.name + ': "' + err.message + '"');
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
var exportedKey = await subtle.exportKey(format, importedKey);
|
||||||
|
keys[algorithm.name][format].originalExport[format] = exportedKey;
|
||||||
|
} catch (err) {
|
||||||
|
delete keys[algorithm.name][format];
|
||||||
|
throw("Error exporting " + algorithm.name + " '" + format + "' key -" + err.name + ': "' + err.message + '"');
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
var jwkExportedKey = await subtle.exportKey("jwk", importedKey);
|
||||||
|
keys[algorithm.name][format].originalExport["jwk"] = jwkExportedKey;
|
||||||
|
} catch (err) {
|
||||||
|
delete keys[algorithm.name][format];
|
||||||
|
throw("Error exporting " + algorithm.name + " '" + format + "' key to 'jwk' -" + err.name + ': "' + err.message + '"');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function importKeysToWrap() {
|
||||||
|
var promises = [];
|
||||||
|
keysToWrapParameters.forEach(function(params) {
|
||||||
|
if ("publicUsages" in params) {
|
||||||
|
keys[params.algorithm.name] = {};
|
||||||
|
var keyData = toWrapKeyDataFromAlg(params.algorithm.name);
|
||||||
|
promises.push(importAndExport("spki", keyData.spki, params.algorithm, params.publicUsages, "public key "));
|
||||||
|
promises.push(importAndExport("pkcs8", keyData.pkcs8, params.algorithm, params.privateUsages, "private key "));
|
||||||
|
} else {
|
||||||
|
keys[params.algorithm.name] = {};
|
||||||
|
promises.push(importAndExport("raw", toWrapKeyData["SYMMETRIC"].raw, params.algorithm, params.usages, ""));
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
}));
|
// Using allSettled to skip unsupported test cases.
|
||||||
|
return Promise.allSettled(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can we successfully "round-trip" (wrap, then unwrap, a key)?
|
// Can we successfully "round-trip" (wrap, then unwrap, a key)?
|
||||||
function testWrapping(wrapper, toWrap) {
|
function testWrapping(wrapper, toWrap, fmt) {
|
||||||
var formats;
|
promise_test(async() => {
|
||||||
|
try {
|
||||||
if (toWrap.name.includes("private")) {
|
var wrappedResult = await subtle.wrapKey(fmt, toWrap.key, wrapper.wrappingKey, wrapper.parameters.wrapParameters);
|
||||||
formats = ["pkcs8", "jwk"];
|
var unwrappedResult = await subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages);
|
||||||
} else if (toWrap.name.includes("public")) {
|
|
||||||
formats = ["spki", "jwk"]
|
|
||||||
} else {
|
|
||||||
formats = ["raw", "jwk"]
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(formats.map(function(fmt) {
|
|
||||||
var originalExport;
|
|
||||||
return subtle.exportKey(fmt, toWrap.key).then(function(exportedKey) {
|
|
||||||
originalExport = exportedKey;
|
|
||||||
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_goodCryptoKey(unwrappedResult, toWrap.algorithm, true, toWrap.usages, toWrap.key.type);
|
assert_goodCryptoKey(unwrappedResult, toWrap.algorithm, true, toWrap.usages, toWrap.key.type);
|
||||||
return subtle.exportKey(fmt, unwrappedResult)
|
var roundTripExport = await subtle.exportKey(fmt, unwrappedResult);
|
||||||
}).then(function(roundTripExport) {
|
assert_true(equalExport(toWrap.originalExport[fmt], roundTripExport), "Post-wrap export matches original export");
|
||||||
assert_true(equalExport(originalExport, roundTripExport), "Post-wrap export matches original export");
|
} catch (err) {
|
||||||
}, function(err) {
|
if (err instanceof AssertionError) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
assert_unreached("Round trip for extractable key threw an error - " + err.name + ': "' + err.message + '"');
|
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)
|
}, "Can wrap and unwrap " + toWrap.name + "keys using " + fmt + " and " + wrapper.parameters.name);
|
||||||
.then(function(wrappedResult) {
|
}
|
||||||
return subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages);
|
|
||||||
}).then(function(unwrappedResult){
|
function testWrappingNonExtractable(wrapper, toWrap, fmt) {
|
||||||
|
promise_test(async() => {
|
||||||
|
try {
|
||||||
|
var wrappedResult = await subtle.wrapKey(fmt, toWrap.key, wrapper.wrappingKey, wrapper.parameters.wrapParameters);
|
||||||
|
var unwrappedResult = await subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages);
|
||||||
assert_goodCryptoKey(unwrappedResult, toWrap.algorithm, false, toWrap.usages, toWrap.key.type);
|
assert_goodCryptoKey(unwrappedResult, toWrap.algorithm, false, toWrap.usages, toWrap.key.type);
|
||||||
return equalKeys(toWrap.key, unwrappedResult);
|
var result = await equalKeys(toWrap.key, unwrappedResult);
|
||||||
}).then(function(result){
|
|
||||||
assert_true(result, "Unwrapped key matches original");
|
assert_true(result, "Unwrapped key matches original");
|
||||||
}).catch(function(err){
|
} catch (err) {
|
||||||
|
if (err instanceof AssertionError) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
assert_unreached("Round trip for key unwrapped non-extractable threw an error - " + err.name + ': "' + err.message + '"');
|
assert_unreached("Round trip for key unwrapped non-extractable threw an error - " + err.name + ': "' + err.message + '"');
|
||||||
});
|
};
|
||||||
}, "Can wrap and unwrap " + toWrap.name + " keys as non-extractable using " + fmt + " and " + wrapper.parameters.name);
|
}, "Can wrap and unwrap " + toWrap.name + "keys as non-extractable using " + fmt + " and " + wrapper.parameters.name);
|
||||||
|
}
|
||||||
|
|
||||||
if (fmt === "jwk") {
|
function testWrappingNonExtractableAsExtractable(wrapper, toWrap) {
|
||||||
promise_test(function(test){
|
promise_test(async() => {
|
||||||
if (!isPossible) {
|
|
||||||
return Promise.resolve().then(() => {
|
|
||||||
assert_false(false, "Wrapping is not possible");
|
|
||||||
})
|
|
||||||
}
|
|
||||||
var wrappedKey;
|
var wrappedKey;
|
||||||
return wrapAsNonExtractableJwk(toWrap.key,wrapper).then(function(wrappedResult){
|
try {
|
||||||
|
var wrappedResult = await wrapAsNonExtractableJwk(toWrap.key,wrapper);
|
||||||
wrappedKey = wrappedResult;
|
wrappedKey = wrappedResult;
|
||||||
return subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages);
|
var unwrappedResult = await 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");
|
assert_false(unwrappedResult.extractable, "Unwrapped key is non-extractable");
|
||||||
return equalKeys(toWrap.key,unwrappedResult);
|
var result = await equalKeys(toWrap.key,unwrappedResult);
|
||||||
}).then(function(result){
|
|
||||||
assert_true(result, "Unwrapped key matches original");
|
assert_true(result, "Unwrapped key matches original");
|
||||||
}).catch(function(err){
|
} catch (err) {
|
||||||
|
if (err instanceof AssertionError) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
assert_unreached("Round trip for non-extractable key 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);
|
try {
|
||||||
}).then(function(unwrappedResult){
|
var unwrappedResult = await subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages);
|
||||||
assert_unreached("Unwrapping a non-extractable JWK as extractable should fail");
|
assert_unreached("Unwrapping a non-extractable JWK as extractable should fail");
|
||||||
}).catch(function(err){
|
} catch (err) {
|
||||||
|
if (err instanceof AssertionError) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
assert_equals(err.name, "DataError", "Unwrapping a non-extractable JWK as extractable fails with DataError");
|
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);
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement key wrapping by hand to wrap a key as non-extractable JWK
|
// Implement key wrapping by hand to wrap a key as non-extractable JWK
|
||||||
function wrapAsNonExtractableJwk(key, wrapper){
|
async function wrapAsNonExtractableJwk(key, wrapper) {
|
||||||
var wrappingKey = wrapper.wrappingKey,
|
var wrappingKey = wrapper.wrappingKey,
|
||||||
encryptKey;
|
encryptKey;
|
||||||
|
|
||||||
return subtle.exportKey("jwk",wrappingKey)
|
var jwkWrappingKey = await subtle.exportKey("jwk",wrappingKey);
|
||||||
.then(function(jwkWrappingKey){
|
|
||||||
// Update the key generation parameters to work as key import parameters
|
// Update the key generation parameters to work as key import parameters
|
||||||
var params = Object.create(wrapper.parameters.generateParameters);
|
var params = Object.create(wrapper.parameters.importParameters);
|
||||||
if(params.name === "AES-KW") {
|
if(params.name === "AES-KW") {
|
||||||
params.name = "AES-CBC";
|
params.name = "AES-CBC";
|
||||||
jwkWrappingKey.alg = "A"+params.length+"CBC";
|
jwkWrappingKey.alg = "A"+params.length+"CBC";
|
||||||
@ -220,19 +239,18 @@
|
|||||||
params.publicExponent = undefined;
|
params.publicExponent = undefined;
|
||||||
}
|
}
|
||||||
jwkWrappingKey.key_ops = ["encrypt"];
|
jwkWrappingKey.key_ops = ["encrypt"];
|
||||||
return subtle.importKey("jwk", jwkWrappingKey, params, true, ["encrypt"]);
|
var importedWrappingKey = await subtle.importKey("jwk", jwkWrappingKey, params, true, ["encrypt"]);
|
||||||
}).then(function(importedWrappingKey){
|
|
||||||
encryptKey = importedWrappingKey;
|
encryptKey = importedWrappingKey;
|
||||||
return subtle.exportKey("jwk",key);
|
var exportedKey = await subtle.exportKey("jwk",key);
|
||||||
}).then(function(exportedKey){
|
|
||||||
exportedKey.ext = false;
|
exportedKey.ext = false;
|
||||||
var jwk = JSON.stringify(exportedKey)
|
var jwk = JSON.stringify(exportedKey)
|
||||||
|
var result;
|
||||||
if (wrappingKey.algorithm.name === "AES-KW") {
|
if (wrappingKey.algorithm.name === "AES-KW") {
|
||||||
return aeskw(encryptKey, str2ab(jwk.slice(0,-1) + " ".repeat(jwk.length%8 ? 8-jwk.length%8 : 0) + "}"));
|
result = await aeskw(encryptKey, str2ab(jwk.slice(0,-1) + " ".repeat(jwk.length%8 ? 8-jwk.length%8 : 0) + "}"));
|
||||||
} else {
|
} else {
|
||||||
return subtle.encrypt(wrapper.parameters.wrapParameters,encryptKey,str2ab(jwk));
|
result = await subtle.encrypt(wrapper.parameters.wrapParameters,encryptKey,str2ab(jwk));
|
||||||
}
|
}
|
||||||
});
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -365,9 +383,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compare two keys by using them (works for non-extractable keys)
|
// Compare two keys by using them (works for non-extractable keys)
|
||||||
function equalKeys(expected, got){
|
async function equalKeys(expected, got){
|
||||||
if ( expected.algorithm.name !== got.algorithm.name ) {
|
if ( expected.algorithm.name !== got.algorithm.name ) {
|
||||||
return Promise.resolve(false);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cryptParams, signParams, wrapParams, deriveParams;
|
var cryptParams, signParams, wrapParams, deriveParams;
|
||||||
@ -419,25 +437,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cryptParams) {
|
if (cryptParams) {
|
||||||
return subtle.exportKey("jwk",expected)
|
var jwkExpectedKey = await subtle.exportKey("jwk", expected);
|
||||||
.then(function(jwkExpectedKey){
|
|
||||||
if (expected.algorithm.name === "RSA-OAEP") {
|
if (expected.algorithm.name === "RSA-OAEP") {
|
||||||
["d","p","q","dp","dq","qi","oth"].forEach(function(field){ delete jwkExpectedKey[field]; });
|
["d","p","q","dp","dq","qi","oth"].forEach(function(field){ delete jwkExpectedKey[field]; });
|
||||||
}
|
}
|
||||||
jwkExpectedKey.key_ops = ["encrypt"];
|
jwkExpectedKey.key_ops = ["encrypt"];
|
||||||
return subtle.importKey("jwk", jwkExpectedKey, expected.algorithm, true, ["encrypt"]);
|
var expectedEncryptKey = await subtle.importKey("jwk", jwkExpectedKey, expected.algorithm, true, ["encrypt"]);
|
||||||
}).then(function(expectedEncryptKey){
|
var encryptedData = await subtle.encrypt(cryptParams, expectedEncryptKey, new Uint8Array(32));
|
||||||
return subtle.encrypt(cryptParams, expectedEncryptKey, new Uint8Array(32));
|
var decryptedData = await subtle.decrypt(cryptParams, got, encryptedData);
|
||||||
}).then(function(encryptedData){
|
|
||||||
return subtle.decrypt(cryptParams, got, encryptedData);
|
|
||||||
}).then(function(decryptedData){
|
|
||||||
var result = new Uint8Array(decryptedData);
|
var result = new Uint8Array(decryptedData);
|
||||||
return !result.some(x => x);
|
return !result.some(x => x);
|
||||||
});
|
|
||||||
} else if (signParams) {
|
} else if (signParams) {
|
||||||
var verifyKey;
|
var verifyKey;
|
||||||
return subtle.exportKey("jwk",expected)
|
var jwkExpectedKey = await subtle.exportKey("jwk",expected);
|
||||||
.then(function(jwkExpectedKey){
|
|
||||||
if (expected.algorithm.name === "RSA-PSS" || expected.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
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]; });
|
["d","p","q","dp","dq","qi","oth"].forEach(function(field){ delete jwkExpectedKey[field]; });
|
||||||
}
|
}
|
||||||
@ -445,49 +457,40 @@
|
|||||||
delete jwkExpectedKey["d"];
|
delete jwkExpectedKey["d"];
|
||||||
}
|
}
|
||||||
jwkExpectedKey.key_ops = ["verify"];
|
jwkExpectedKey.key_ops = ["verify"];
|
||||||
return subtle.importKey("jwk", jwkExpectedKey, expected.algorithm, true, ["verify"]);
|
var expectedVerifyKey = await subtle.importKey("jwk", jwkExpectedKey, expected.algorithm, true, ["verify"]);
|
||||||
}).then(function(expectedVerifyKey){
|
|
||||||
verifyKey = expectedVerifyKey;
|
verifyKey = expectedVerifyKey;
|
||||||
return subtle.sign(signParams, got, new Uint8Array(32));
|
var signature = await subtle.sign(signParams, got, new Uint8Array(32));
|
||||||
}).then(function(signature){
|
var result = await subtle.verify(signParams, verifyKey, signature, new Uint8Array(32));
|
||||||
return subtle.verify(signParams, verifyKey, signature, new Uint8Array(32));
|
return result;
|
||||||
});
|
|
||||||
} else if (wrapParams) {
|
} else if (wrapParams) {
|
||||||
var aKeyToWrap, wrappedWithExpected;
|
var aKeyToWrap, wrappedWithExpected;
|
||||||
return subtle.importKey("raw", new Uint8Array(16), "AES-CBC", true, ["encrypt"])
|
var key = await subtle.importKey("raw",new Uint8Array(16), "AES-CBC", true, ["encrypt"])
|
||||||
.then(function(key){
|
|
||||||
aKeyToWrap = key;
|
aKeyToWrap = key;
|
||||||
return subtle.wrapKey("raw", aKeyToWrap, expected, wrapParams);
|
var wrapResult = await subtle.wrapKey("raw", aKeyToWrap, expected, wrapParams);
|
||||||
}).then(function(wrapResult){
|
|
||||||
wrappedWithExpected = Array.from((new Uint8Array(wrapResult)).values());
|
wrappedWithExpected = Array.from((new Uint8Array(wrapResult)).values());
|
||||||
return subtle.wrapKey("raw", aKeyToWrap, got, wrapParams);
|
wrapResult = await subtle.wrapKey("raw", aKeyToWrap, got, wrapParams);
|
||||||
}).then(function(wrapResult){
|
|
||||||
var wrappedWithGot = Array.from((new Uint8Array(wrapResult)).values());
|
var wrappedWithGot = Array.from((new Uint8Array(wrapResult)).values());
|
||||||
return wrappedWithGot.every((x,i) => x === wrappedWithExpected[i]);
|
return wrappedWithGot.every((x,i) => x === wrappedWithExpected[i]);
|
||||||
});
|
|
||||||
} else if (deriveParams) {
|
} else if (deriveParams) {
|
||||||
var expectedDerivedBits;
|
var expectedDerivedBits;
|
||||||
return subtle.generateKey(expected.algorithm, true, ['deriveBits']).then(({ publicKey }) => {
|
var key = await subtle.generateKey(expected.algorithm, true, ['deriveBits']);
|
||||||
deriveParams.public = publicKey;
|
deriveParams.public = key.publicKey;
|
||||||
return subtle.deriveBits(deriveParams, expected, 128)
|
var result = await subtle.deriveBits(deriveParams, expected, 128);
|
||||||
})
|
|
||||||
.then(function(result){
|
|
||||||
expectedDerivedBits = Array.from((new Uint8Array(result)).values());
|
expectedDerivedBits = Array.from((new Uint8Array(result)).values());
|
||||||
return subtle.deriveBits(deriveParams, got, 128);
|
result = await subtle.deriveBits(deriveParams, got, 128);
|
||||||
}).then(function(result){
|
|
||||||
var gotDerivedBits = Array.from((new Uint8Array(result)).values());
|
var gotDerivedBits = Array.from((new Uint8Array(result)).values());
|
||||||
return gotDerivedBits.every((x,i) => x === expectedDerivedBits[i]);
|
return gotDerivedBits.every((x,i) => x === expectedDerivedBits[i]);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raw AES encryption
|
// Raw AES encryption
|
||||||
function aes( k, p ) {
|
async function aes(k, p) {
|
||||||
return subtle.encrypt({name: "AES-CBC", iv: new Uint8Array(16) }, k, p).then(function(ciphertext){return ciphertext.slice(0,16);});
|
const ciphertext = await subtle.encrypt({ name: "AES-CBC", iv: new Uint8Array(16) }, k, p);
|
||||||
|
return ciphertext.slice(0, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AES Key Wrap
|
// AES Key Wrap
|
||||||
function aeskw(key, data) {
|
async function aeskw(key, data) {
|
||||||
if (data.byteLength % 8 !== 0) {
|
if (data.byteLength % 8 !== 0) {
|
||||||
throw new Error("AES Key Wrap data must be a multiple of 8 bytes in length");
|
throw new Error("AES Key Wrap data must be a multiple of 8 bytes in length");
|
||||||
}
|
}
|
||||||
@ -501,7 +504,7 @@
|
|||||||
R.push(new Uint8Array(data.slice(i,i+8)));
|
R.push(new Uint8Array(data.slice(i,i+8)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function aeskw_step(j, i, final, B) {
|
async function aeskw_step(j, i, final, B) {
|
||||||
A.set(new Uint8Array(B.slice(0,8)));
|
A.set(new Uint8Array(B.slice(0,8)));
|
||||||
Av.setUint32(4,Av.getUint32(4) ^ (n*j+i+1));
|
Av.setUint32(4,Av.getUint32(4) ^ (n*j+i+1));
|
||||||
R[i] = new Uint8Array(B.slice(8,16));
|
R[i] = new Uint8Array(B.slice(8,16));
|
||||||
@ -516,18 +519,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var p = new Promise(function(resolve){
|
A.set(R[0], 8);
|
||||||
A.set(R[0],8);
|
let B = await aes(key, A);
|
||||||
resolve(aes(key,A));
|
|
||||||
});
|
|
||||||
|
|
||||||
for(var j=0;j<6;++j) {
|
for(var j=0;j<6;++j) {
|
||||||
for(var i=0;i<n;++i) {
|
for(var i=0;i<n;++i) {
|
||||||
p = p.then(aeskw_step.bind(undefined, j, i,j===5 && i===(n-1)));
|
B = await aeskw_step(j, i, j === 5 && i === (n - 1), B);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return p;
|
return B;
|
||||||
}
|
}
|
||||||
|
|
||||||
function str2ab(str) { return Uint8Array.from( str.split(''), function(s){return s.charCodeAt(0)} ); }
|
function str2ab(str) { return Uint8Array.from( str.split(''), function(s){return s.charCodeAt(0)} ); }
|
||||||
|
114
test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey_vectors.js
vendored
Normal file
114
test/fixtures/wpt/WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey_vectors.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
test/fixtures/wpt/versions.json
vendored
2
test/fixtures/wpt/versions.json
vendored
@ -84,7 +84,7 @@
|
|||||||
"path": "wasm/webapi"
|
"path": "wasm/webapi"
|
||||||
},
|
},
|
||||||
"WebCryptoAPI": {
|
"WebCryptoAPI": {
|
||||||
"commit": "b81831169b8527a6c569a4ad92cf8a1baf4a7118",
|
"commit": "edd42c005cf8192fbae41ec061c14342e7bcac15",
|
||||||
"path": "WebCryptoAPI"
|
"path": "WebCryptoAPI"
|
||||||
},
|
},
|
||||||
"webidl/ecmascript-binding/es-exceptions": {
|
"webidl/ecmascript-binding/es-exceptions": {
|
||||||
|
@ -12,7 +12,7 @@ const { subtle } = globalThis.crypto;
|
|||||||
|
|
||||||
const keyData = {
|
const keyData = {
|
||||||
'Ed25519': {
|
'Ed25519': {
|
||||||
jwsAlg: 'EdDSA',
|
jwsAlg: 'Ed25519',
|
||||||
spki: Buffer.from(
|
spki: Buffer.from(
|
||||||
'302a300506032b6570032100a054b618c12b26c8d43595a5c38dd2b0140b944a' +
|
'302a300506032b6570032100a054b618c12b26c8d43595a5c38dd2b0140b944a' +
|
||||||
'151f75003278c2b6c58ec08f', 'hex'),
|
'151f75003278c2b6c58ec08f', 'hex'),
|
||||||
@ -27,7 +27,7 @@ const keyData = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Ed448': {
|
'Ed448': {
|
||||||
jwsAlg: 'EdDSA',
|
jwsAlg: 'Ed448',
|
||||||
spki: Buffer.from(
|
spki: Buffer.from(
|
||||||
'3043300506032b6571033a0008cc38160c85bca5656ac4924af7ea97a9161b20' +
|
'3043300506032b6571033a0008cc38160c85bca5656ac4924af7ea97a9161b20' +
|
||||||
'2528273dcb84afd2eeb99ac912a401b34ef15ef4d9486406a6eecc31e5909219' +
|
'2528273dcb84afd2eeb99ac912a401b34ef15ef4d9486406a6eecc31e5909219' +
|
||||||
@ -183,10 +183,7 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable)
|
|||||||
|
|
||||||
const jwk = keyData[name].jwk;
|
const jwk = keyData[name].jwk;
|
||||||
|
|
||||||
const [
|
const tests = [
|
||||||
publicKey,
|
|
||||||
privateKey,
|
|
||||||
] = await Promise.all([
|
|
||||||
subtle.importKey(
|
subtle.importKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
{
|
{
|
||||||
@ -221,7 +218,37 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable)
|
|||||||
{ name },
|
{ name },
|
||||||
extractable,
|
extractable,
|
||||||
privateUsages),
|
privateUsages),
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
// Test the deprecated "alg" value
|
||||||
|
if (keyData[name].jwsAlg?.startsWith('Ed')) {
|
||||||
|
tests.push(
|
||||||
|
subtle.importKey(
|
||||||
|
'jwk',
|
||||||
|
{
|
||||||
|
alg: 'EdDSA',
|
||||||
|
kty: jwk.kty,
|
||||||
|
crv: jwk.crv,
|
||||||
|
x: jwk.x,
|
||||||
|
},
|
||||||
|
{ name },
|
||||||
|
extractable, publicUsages),
|
||||||
|
subtle.importKey(
|
||||||
|
'jwk',
|
||||||
|
{
|
||||||
|
...jwk,
|
||||||
|
alg: 'EdDSA',
|
||||||
|
},
|
||||||
|
{ name },
|
||||||
|
extractable,
|
||||||
|
privateUsages),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [
|
||||||
|
publicKey,
|
||||||
|
privateKey,
|
||||||
|
] = await Promise.all(tests);
|
||||||
|
|
||||||
assert.strictEqual(publicKey.type, 'public');
|
assert.strictEqual(publicKey.type, 'public');
|
||||||
assert.strictEqual(privateKey.type, 'private');
|
assert.strictEqual(privateKey.type, 'private');
|
||||||
@ -259,8 +286,13 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable)
|
|||||||
assert.strictEqual(pvtJwk.crv, jwk.crv);
|
assert.strictEqual(pvtJwk.crv, jwk.crv);
|
||||||
assert.strictEqual(pvtJwk.d, jwk.d);
|
assert.strictEqual(pvtJwk.d, jwk.d);
|
||||||
|
|
||||||
|
if (jwk.crv.startsWith('Ed')) {
|
||||||
|
assert.strictEqual(pubJwk.alg, jwk.crv);
|
||||||
|
assert.strictEqual(pvtJwk.alg, jwk.crv);
|
||||||
|
} else {
|
||||||
assert.strictEqual(pubJwk.alg, undefined);
|
assert.strictEqual(pubJwk.alg, undefined);
|
||||||
assert.strictEqual(pvtJwk.alg, undefined);
|
assert.strictEqual(pvtJwk.alg, undefined);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
subtle.exportKey('jwk', publicKey), {
|
subtle.exportKey('jwk', publicKey), {
|
||||||
@ -284,22 +316,24 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable)
|
|||||||
{ message: 'Invalid JWK "use" Parameter' });
|
{ message: 'Invalid JWK "use" Parameter' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// The JWK alg member is ignored
|
|
||||||
// https://github.com/WICG/webcrypto-secure-curves/pull/24
|
|
||||||
if (name.startsWith('Ed')) {
|
if (name.startsWith('Ed')) {
|
||||||
await subtle.importKey(
|
await assert.rejects(
|
||||||
|
subtle.importKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
{ kty: jwk.kty, x: jwk.x, crv: jwk.crv, alg: 'foo' },
|
{ kty: jwk.kty, x: jwk.x, crv: jwk.crv, alg: 'foo' },
|
||||||
{ name },
|
{ name },
|
||||||
extractable,
|
extractable,
|
||||||
publicUsages);
|
publicUsages),
|
||||||
|
{ message: 'JWK "alg" does not match the requested algorithm' });
|
||||||
|
|
||||||
await subtle.importKey(
|
await assert.rejects(
|
||||||
|
subtle.importKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
{ ...jwk, alg: 'foo' },
|
{ ...jwk, alg: 'foo' },
|
||||||
{ name },
|
{ name },
|
||||||
extractable,
|
extractable,
|
||||||
privateUsages);
|
privateUsages),
|
||||||
|
{ message: 'JWK "alg" does not match the requested algorithm' });
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const crv of [undefined, name === 'Ed25519' ? 'Ed448' : 'Ed25519']) {
|
for (const crv of [undefined, name === 'Ed25519' ? 'Ed448' : 'Ed25519']) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user