LibWeb: Implement wrapKey and unwrapKey methods

This implements the last WebCryptoAPI methods `wrapKey` and `unwrapKey`.
Most of the functionality is already there because they rely on
`encrypt` and `decrypt`. The only test failures are for `AES-GCM` which
is not implemented yet.
This commit is contained in:
devgianlu 2024-12-14 12:23:52 +01:00 committed by Andreas Kling
parent c1a65f3d53
commit 584cbcf3ef
Notes: github-actions[bot] 2024-12-16 10:35:53 +00:00
8 changed files with 1221 additions and 2 deletions

View file

@ -337,6 +337,16 @@ public:
return WebIDL::NotSupportedError::create(m_realm, "getKeyLength is not supported"_string);
}
virtual WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> wrap_key(AlgorithmParams const&, GC::Ref<CryptoKey>, ByteBuffer const&)
{
return WebIDL::NotSupportedError::create(m_realm, "wrapKey is not supported"_string);
}
virtual WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> unwrap_key(AlgorithmParams const&, GC::Ref<CryptoKey>, ByteBuffer const&)
{
return WebIDL::NotSupportedError::create(m_realm, "unwwrapKey is not supported"_string);
}
static NonnullOwnPtr<AlgorithmMethods> create(JS::Realm& realm) { return adopt_own(*new AlgorithmMethods(realm)); }
protected:

View file

@ -6,9 +6,11 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteBuffer.h>
#include <AK/QuickSort.h>
#include <LibCrypto/Hash/HashManager.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/JSONObject.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/SubtleCryptoPrototype.h>
@ -744,6 +746,293 @@ JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> SubtleCrypto::derive_key(Algorit
return promise;
}
// https://w3c.github.io/webcrypto/#SubtleCrypto-method-wrapKey
JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> SubtleCrypto::wrap_key(Bindings::KeyFormat format, GC::Ref<CryptoKey> key, GC::Ref<CryptoKey> wrapping_key, AlgorithmIdentifier algorithm)
{
auto& realm = this->realm();
// 1. Let format, key, wrappingKey and algorithm be the format, key, wrappingKey and wrapAlgorithm parameters passed to the wrapKey() method, respectively.
StringView operation;
auto normalized_algorithm_or_error = [&]() -> WebIDL::ExceptionOr<NormalizedAlgorithmAndParameter> {
// 2. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "wrapKey".
auto normalized_algorithm_wrap_key_or_error = normalize_an_algorithm(realm, algorithm, "wrapKey"_string);
// 3. If an error occurred, let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "encrypt".
// 4. If an error occurred, return a Promise rejected with normalizedAlgorithm.
if (normalized_algorithm_wrap_key_or_error.is_error()) {
auto normalized_algorithm_encrypt_or_error = normalize_an_algorithm(realm, algorithm, "encrypt"_string);
if (normalized_algorithm_encrypt_or_error.is_error())
return normalized_algorithm_encrypt_or_error.release_error();
operation = "encrypt"sv;
return normalized_algorithm_encrypt_or_error.release_value();
} else {
operation = "wrapKey"sv;
return normalized_algorithm_wrap_key_or_error.release_value();
}
}();
if (normalized_algorithm_or_error.is_error())
return WebIDL::create_rejected_promise_from_exception(realm, normalized_algorithm_or_error.release_error());
auto normalized_algorithm = normalized_algorithm_or_error.release_value();
// 5. Let promise be a new Promise.
auto promise = WebIDL::create_promise(realm);
// 6. Return promise and perform the remaining steps in parallel.
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, normalized_algorithm = move(normalized_algorithm), promise, wrapping_key = move(wrapping_key), key = move(key), format, operation]() mutable -> void {
HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
// 7. If the following steps or referenced procedures say to throw an error, reject promise with the returned error and then terminate the algorithm.
// 8. If the name member of normalizedAlgorithm is not equal to the name attribute
// of the [[algorithm]] internal slot of wrappingKey then throw an InvalidAccessError.
if (normalized_algorithm.parameter->name != wrapping_key->algorithm_name()) {
WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Algorithm mismatch"_string));
return;
}
// 9. If the [[usages]] internal slot of wrappingKey does not contain an entry that is "wrapKey", then throw an InvalidAccessError.
if (!wrapping_key->internal_usages().contains_slow(Bindings::KeyUsage::Wrapkey)) {
WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Key does not support wrapping keys"_string));
return;
}
// 10. If the algorithm identified by the [[algorithm]] internal slot of key does not support the export key operation, then throw a NotSupportedError.
// Note: Handled by the base AlgorithmMethods implementation
// 11. If the [[extractable]] internal slot of key is false, then throw an InvalidAccessError.
if (!key->extractable()) {
WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Key is not extractable"_string));
return;
}
// 12. Let key be the result of performing the export key operation specified the [[algorithm]] internal slot of key using key and format.
// NOTE: The spec does not mention we need to normalize this, but it's the only way we have to get to export_key.
auto& key_algorithm = verify_cast<KeyAlgorithm>(*key->algorithm());
auto normalized_key_algorithm = normalize_an_algorithm(realm, key_algorithm.name(), "exportKey"_string);
if (normalized_key_algorithm.is_error()) {
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), normalized_key_algorithm.release_error()).release_value().value());
return;
}
auto key_data_or_error = normalized_key_algorithm.release_value().methods->export_key(format, key);
if (key_data_or_error.is_error()) {
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), key_data_or_error.release_error()).release_value().value());
return;
}
auto key_data = key_data_or_error.release_value();
ByteBuffer bytes;
// 13. If format is equal to the strings "raw", "pkcs8", or "spki":
if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::Pkcs8 || format == Bindings::KeyFormat::Spki) {
// Set bytes be set to key.
bytes = verify_cast<JS::ArrayBuffer>(*key_data).buffer();
}
// If format is equal to the string "jwk":
else if (format == Bindings::KeyFormat::Jwk) {
// 1. Convert key to an ECMAScript Object, as specified in [WEBIDL],
// performing the conversion in the context of a new global object.
// 2. Let json be the result of representing key as a UTF-16 string conforming to the JSON grammar;
// for example, by executing the JSON.stringify algorithm specified in [ECMA-262] in the context of a new global object.
auto maybe_json = JS::JSONObject::stringify_impl(realm.vm(), key_data, JS::Value {}, JS::Value {});
if (maybe_json.is_error()) {
WebIDL::reject_promise(realm, promise, maybe_json.release_error().release_value().value());
return;
}
// 3. Let bytes be the result of UTF-8 encoding json.
bytes = maybe_json.release_value()->to_byte_buffer();
} else {
VERIFY_NOT_REACHED();
}
JS::Value result;
// 14. If normalizedAlgorithm supports the wrap key operation:
if (operation == "wrapKey") {
// Let result be the result of performing the wrap key operation specified by normalizedAlgorithm
// using algorithm, wrappingKey as key and bytes as plaintext.
auto result_or_error = normalized_algorithm.methods->wrap_key(*normalized_algorithm.parameter, wrapping_key, bytes);
if (result_or_error.is_error()) {
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value());
return;
}
result = result_or_error.release_value();
}
// Otherwise, if normalizedAlgorithm supports the encrypt operation:
else if (operation == "encrypt") {
// Let result be the result of performing the encrypt operation specified by normalizedAlgorithm
// using algorithm, wrappingKey as key and bytes as plaintext.
auto result_or_error = normalized_algorithm.methods->encrypt(*normalized_algorithm.parameter, wrapping_key, bytes);
if (result_or_error.is_error()) {
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value());
return;
}
result = result_or_error.release_value();
}
// Otherwise:
else {
// throw a NotSupportedError.
WebIDL::reject_promise(realm, promise, WebIDL::NotSupportedError::create(realm, "Algorithm does not support wrapping"_string));
return;
}
// 15. Resolve promise with result.
WebIDL::resolve_promise(realm, promise, result);
}));
return promise;
}
// https://w3c.github.io/webcrypto/#SubtleCrypto-method-unwrapKey
JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> SubtleCrypto::unwrap_key(Bindings::KeyFormat format, KeyDataType wrapped_key, GC::Ref<CryptoKey> unwrapping_key, AlgorithmIdentifier algorithm, AlgorithmIdentifier unwrapped_key_algorithm, bool extractable, Vector<Bindings::KeyUsage> key_usages)
{
auto& realm = this->realm();
// 1. Let format, unwrappingKey, algorithm, unwrappedKeyAlgorithm, extractable and usages, be the format, unwrappingKey, unwrapAlgorithm,
// unwrappedKeyAlgorithm, extractable and keyUsages parameters passed to the unwrapKey() method, respectively.
// 2. Let wrappedKey be the result of getting a copy of the bytes held by the wrappedKey parameter passed to the unwrapKey() method.
auto real_wrapped_key = MUST(WebIDL::get_buffer_source_copy(*wrapped_key.get<GC::Root<WebIDL::BufferSource>>()->raw_object()));
StringView operation;
auto normalized_algorithm_or_error = [&]() -> WebIDL::ExceptionOr<NormalizedAlgorithmAndParameter> {
// 3. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "unwrapKey".
auto normalized_algorithm_unwrap_key_or_error = normalize_an_algorithm(realm, algorithm, "unwrapKey"_string);
// 4. If an error occurred, let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "decrypt".
// 5. If an error occurred, return a Promise rejected with normalizedAlgorithm.
if (normalized_algorithm_unwrap_key_or_error.is_error()) {
auto normalized_algorithm_decrypt_or_error = normalize_an_algorithm(realm, algorithm, "decrypt"_string);
if (normalized_algorithm_decrypt_or_error.is_error())
return normalized_algorithm_decrypt_or_error.release_error();
operation = "decrypt"sv;
return normalized_algorithm_decrypt_or_error.release_value();
} else {
operation = "unwrapKey"sv;
return normalized_algorithm_unwrap_key_or_error.release_value();
}
}();
if (normalized_algorithm_or_error.is_error())
return WebIDL::create_rejected_promise_from_exception(realm, normalized_algorithm_or_error.release_error());
auto normalized_algorithm = normalized_algorithm_or_error.release_value();
// 6. Let normalizedKeyAlgorithm be the result of normalizing an algorithm, with alg set to unwrappedKeyAlgorithm and op set to "importKey".
auto normalized_key_algorithm_or_error = normalize_an_algorithm(realm, unwrapped_key_algorithm, "importKey"_string);
if (normalized_key_algorithm_or_error.is_error()) {
// 7. If an error occurred, return a Promise rejected with normalizedKeyAlgorithm.
return WebIDL::create_rejected_promise_from_exception(realm, normalized_key_algorithm_or_error.release_error());
}
auto normalized_key_algorithm = normalized_key_algorithm_or_error.release_value();
// 8. Let promise be a new Promise.
auto promise = WebIDL::create_promise(realm);
// 9. Return promise and perform the remaining steps in parallel.
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, normalized_algorithm = move(normalized_algorithm), promise, unwrapping_key = unwrapping_key, real_wrapped_key = move(real_wrapped_key), operation, format, extractable, key_usages = move(key_usages), normalized_key_algorithm = move(normalized_key_algorithm)]() mutable -> void {
HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
// 10. If the following steps or referenced procedures say to throw an error, reject promise with the returned error and then terminate the algorithm.
// 11. If the name member of normalizedAlgorithm is not equal to the name attribute of the [[algorithm]] internal slot
// of unwrappingKey then throw an InvalidAccessError.
if (normalized_algorithm.parameter->name != unwrapping_key->algorithm_name()) {
WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Algorithm mismatch"_string));
return;
}
// 12. If the [[usages]] internal slot of unwrappingKey does not contain an entry that is "unwrapKey", then throw an InvalidAccessError.
if (!unwrapping_key->internal_usages().contains_slow(Bindings::KeyUsage::Unwrapkey)) {
WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Key does not support unwrapping keys"_string));
return;
}
auto key_or_error = [&]() -> WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> {
// 13. If normalizedAlgorithm supports an unwrap key operation:
if (operation == "unwrapKey") {
// Let key be the result of performing the unwrap key operation specified by normalizedAlgorithm
// using algorithm, unwrappingKey as key and wrappedKey as ciphertext.
return normalized_algorithm.methods->unwrap_key(*normalized_algorithm.parameter, unwrapping_key, real_wrapped_key);
}
// Otherwise, if normalizedAlgorithm supports a decrypt operation:
else if (operation == "decrypt") {
// Let key be the result of performing the decrypt operation specified by normalizedAlgorithm
// using algorithm, unwrappingKey as key and wrappedKey as ciphertext.
return normalized_algorithm.methods->decrypt(*normalized_algorithm.parameter, unwrapping_key, real_wrapped_key);
}
// Otherwise:
else {
// throw a NotSupportedError.
return WebIDL::NotSupportedError::create(realm, "Algorithm does not support wrapping"_string);
}
}();
if (key_or_error.is_error()) {
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), key_or_error.release_error()).release_value().value());
return;
}
auto key = key_or_error.release_value();
Variant<ByteBuffer, Bindings::JsonWebKey, Empty> bytes;
// 14. If format is equal to the strings "raw", "pkcs8", or "spki":
if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::Pkcs8 || format == Bindings::KeyFormat::Spki) {
// Set bytes be set to key.
bytes = key->buffer();
}
// If format is equal to the string "jwk":
else if (format == Bindings::KeyFormat::Jwk) {
// Let bytes be the result of executing the parse a JWK algorithm, with key as the data to be parsed.
auto maybe_parsed = Bindings::JsonWebKey::parse(realm, key->buffer());
if (maybe_parsed.is_error()) {
WebIDL::reject_promise(realm, promise, maybe_parsed.release_error().release_value().value());
return;
}
bytes = maybe_parsed.release_value();
} else {
VERIFY_NOT_REACHED();
}
// 15. Let result be the result of performing the import key operation specified by normalizedKeyAlgorithm
// using unwrappedKeyAlgorithm as algorithm, format, usages and extractable and with bytes as keyData.
auto result_or_error = normalized_key_algorithm.methods->import_key(*normalized_key_algorithm.parameter, format, bytes.downcast<CryptoKey::InternalKeyData>(), extractable, key_usages);
if (result_or_error.is_error()) {
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value());
return;
}
auto result = result_or_error.release_value();
// 16. If the [[type]] internal slot of result is "secret" or "private" and usages is empty, then throw a SyntaxError.
if ((result->type() == Bindings::KeyType::Secret || result->type() == Bindings::KeyType::Private) && key_usages.is_empty()) {
WebIDL::reject_promise(realm, promise, WebIDL::SyntaxError::create(realm, "Usages must not be empty"_string));
return;
}
// 17. Set the [[extractable]] internal slot of result to extractable.
result->set_extractable(extractable);
// 18. Set the [[usages]] internal slot of result to the normalized value of usages.
normalize_key_usages(key_usages);
result->set_usages(key_usages);
// 19. Resolve promise with result.
WebIDL::resolve_promise(realm, promise, result);
}));
return promise;
}
SupportedAlgorithmsMap& supported_algorithms_internal()
{
static SupportedAlgorithmsMap s_supported_algorithms;

View file

@ -40,6 +40,9 @@ public:
JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> import_key(Bindings::KeyFormat format, KeyDataType key_data, AlgorithmIdentifier algorithm, bool extractable, Vector<Bindings::KeyUsage> key_usages);
JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> export_key(Bindings::KeyFormat format, GC::Ref<CryptoKey> key);
JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> wrap_key(Bindings::KeyFormat format, GC::Ref<CryptoKey> key, GC::Ref<CryptoKey> wrapping_key, AlgorithmIdentifier wrap_algorithm);
JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> unwrap_key(Bindings::KeyFormat format, KeyDataType wrapped_key, GC::Ref<CryptoKey> unwrapping_key, AlgorithmIdentifier unwrap_algorithm, AlgorithmIdentifier unwrapped_key_algorithm, bool extractable, Vector<Bindings::KeyUsage> key_usages);
private:
explicit SubtleCrypto(JS::Realm&);
virtual void initialize(JS::Realm&) override;

View file

@ -58,6 +58,6 @@ interface SubtleCrypto {
Promise<CryptoKey> importKey(KeyFormat format, (BufferSource or JsonWebKey) keyData, AlgorithmIdentifier algorithm, boolean extractable, sequence<KeyUsage> keyUsages);
Promise<any> exportKey(KeyFormat format, CryptoKey key);
[FIXME] Promise<any> wrapKey(KeyFormat format, CryptoKey key, CryptoKey wrappingKey, AlgorithmIdentifier wrapAlgorithm);
[FIXME] Promise<CryptoKey> unwrapKey(KeyFormat format, BufferSource wrappedKey, CryptoKey unwrappingKey, AlgorithmIdentifier unwrapAlgorithm, AlgorithmIdentifier unwrappedKeyAlgorithm, boolean extractable, sequence<KeyUsage> keyUsages);
Promise<any> wrapKey(KeyFormat format, CryptoKey key, CryptoKey wrappingKey, AlgorithmIdentifier wrapAlgorithm);
Promise<CryptoKey> unwrapKey(KeyFormat format, BufferSource wrappedKey, CryptoKey unwrappingKey, AlgorithmIdentifier unwrapAlgorithm, AlgorithmIdentifier unwrappedKeyAlgorithm, boolean extractable, sequence<KeyUsage> keyUsages);
};

View file

@ -0,0 +1,250 @@
Harness status: OK
Found 244 tests
173 Pass
71 Fail
Pass setup
Pass Can wrap and unwrap RSA-OAEP public key keys using spki and RSA-OAEP
Pass Can wrap and unwrap RSA-OAEP public key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap ECDSA public key keys using spki and RSA-OAEP
Pass Can wrap and unwrap ECDSA public key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap ECDSA private key keys using pkcs8 and RSA-OAEP
Pass Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and RSA-OAEP
Pass Can wrap and unwrap ECDSA private key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and RSA-OAEP
Pass Can unwrap ECDSA private key non-extractable keys using jwk and RSA-OAEP
Pass Can wrap and unwrap ECDH public key keys using spki and RSA-OAEP
Pass Can wrap and unwrap ECDH public key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap ECDH private key keys using pkcs8 and RSA-OAEP
Pass Can wrap and unwrap ECDH private key keys as non-extractable using pkcs8 and RSA-OAEP
Pass Can wrap and unwrap ECDH private key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap ECDH private key keys as non-extractable using jwk and RSA-OAEP
Pass Can unwrap ECDH private key non-extractable keys using jwk and RSA-OAEP
Pass Can wrap and unwrap Ed25519 public key keys using spki and RSA-OAEP
Pass Can wrap and unwrap Ed25519 public key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap Ed25519 private key keys using pkcs8 and RSA-OAEP
Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and RSA-OAEP
Pass Can wrap and unwrap Ed25519 private key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using jwk and RSA-OAEP
Pass Can unwrap Ed25519 private key non-extractable keys using jwk and RSA-OAEP
Pass Can wrap and unwrap X25519 public key keys using spki and RSA-OAEP
Pass Can wrap and unwrap X25519 public key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap X25519 private key keys using pkcs8 and RSA-OAEP
Pass Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and RSA-OAEP
Pass Can wrap and unwrap X25519 private key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap X25519 private key keys as non-extractable using jwk and RSA-OAEP
Pass Can unwrap X25519 private key non-extractable keys using jwk and RSA-OAEP
Pass Can wrap and unwrap X448 public key keys using spki and RSA-OAEP
Pass Can wrap and unwrap X448 public key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap X448 private key keys using pkcs8 and RSA-OAEP
Pass Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and RSA-OAEP
Pass Can wrap and unwrap X448 private key keys using jwk and RSA-OAEP
Pass Can wrap and unwrap X448 private key keys as non-extractable using jwk and RSA-OAEP
Pass Can unwrap X448 private key non-extractable keys using jwk and RSA-OAEP
Pass Can wrap and unwrap AES-CTR keys using raw and RSA-OAEP
Pass Can wrap and unwrap AES-CTR keys as non-extractable using raw and RSA-OAEP
Pass Can wrap and unwrap AES-CTR keys using jwk and RSA-OAEP
Pass Can wrap and unwrap AES-CTR keys as non-extractable using jwk and RSA-OAEP
Pass Can unwrap AES-CTR non-extractable keys using jwk and RSA-OAEP
Pass Can wrap and unwrap AES-CBC keys using raw and RSA-OAEP
Pass Can wrap and unwrap AES-CBC keys as non-extractable using raw and RSA-OAEP
Pass Can wrap and unwrap AES-CBC keys using jwk and RSA-OAEP
Pass Can wrap and unwrap AES-CBC keys as non-extractable using jwk and RSA-OAEP
Pass Can unwrap AES-CBC non-extractable keys using jwk and RSA-OAEP
Pass Can wrap and unwrap AES-GCM keys using raw and RSA-OAEP
Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and RSA-OAEP
Pass Can wrap and unwrap AES-GCM keys using jwk and RSA-OAEP
Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and RSA-OAEP
Fail Can unwrap AES-GCM non-extractable keys using jwk and RSA-OAEP
Pass Can wrap and unwrap HMAC keys using raw and RSA-OAEP
Pass Can wrap and unwrap HMAC keys as non-extractable using raw and RSA-OAEP
Pass Can wrap and unwrap HMAC keys using jwk and RSA-OAEP
Pass Can wrap and unwrap HMAC keys as non-extractable using jwk and RSA-OAEP
Pass Can unwrap HMAC non-extractable keys using jwk and RSA-OAEP
Pass Can wrap and unwrap RSA-OAEP public key keys using spki and AES-CTR
Pass Can wrap and unwrap RSA-OAEP public key keys using jwk and AES-CTR
Pass Can wrap and unwrap RSA-OAEP private key keys using pkcs8 and AES-CTR
Pass Can wrap and unwrap RSA-OAEP private key keys as non-extractable using pkcs8 and AES-CTR
Pass Can wrap and unwrap RSA-OAEP private key keys using jwk and AES-CTR
Pass Can wrap and unwrap RSA-OAEP private key keys as non-extractable using jwk and AES-CTR
Pass Can unwrap RSA-OAEP private key non-extractable keys using jwk and AES-CTR
Pass Can wrap and unwrap ECDSA public key keys using spki and AES-CTR
Pass Can wrap and unwrap ECDSA public key keys using jwk and AES-CTR
Pass Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-CTR
Pass Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and AES-CTR
Pass Can wrap and unwrap ECDSA private key keys using jwk and AES-CTR
Pass Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and AES-CTR
Pass Can unwrap ECDSA private key non-extractable keys using jwk and AES-CTR
Pass Can wrap and unwrap ECDH public key keys using spki and AES-CTR
Pass Can wrap and unwrap ECDH public key keys using jwk and AES-CTR
Pass Can wrap and unwrap ECDH private key keys using pkcs8 and AES-CTR
Pass Can wrap and unwrap ECDH private key keys as non-extractable using pkcs8 and AES-CTR
Pass Can wrap and unwrap ECDH private key keys using jwk and AES-CTR
Pass Can wrap and unwrap ECDH private key keys as non-extractable using jwk and AES-CTR
Pass Can unwrap ECDH private key non-extractable keys using jwk and AES-CTR
Pass Can wrap and unwrap Ed25519 public key keys using spki and AES-CTR
Pass Can wrap and unwrap Ed25519 public key keys using jwk and AES-CTR
Pass Can wrap and unwrap Ed25519 private key keys using pkcs8 and AES-CTR
Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and AES-CTR
Pass Can wrap and unwrap Ed25519 private key keys using jwk and AES-CTR
Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using jwk and AES-CTR
Pass Can unwrap Ed25519 private key non-extractable keys using jwk and AES-CTR
Pass Can wrap and unwrap X25519 public key keys using spki and AES-CTR
Pass Can wrap and unwrap X25519 public key keys using jwk and AES-CTR
Pass Can wrap and unwrap X25519 private key keys using pkcs8 and AES-CTR
Pass Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and AES-CTR
Pass Can wrap and unwrap X25519 private key keys using jwk and AES-CTR
Pass Can wrap and unwrap X25519 private key keys as non-extractable using jwk and AES-CTR
Pass Can unwrap X25519 private key non-extractable keys using jwk and AES-CTR
Pass Can wrap and unwrap X448 public key keys using spki and AES-CTR
Pass Can wrap and unwrap X448 public key keys using jwk and AES-CTR
Pass Can wrap and unwrap X448 private key keys using pkcs8 and AES-CTR
Pass Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and AES-CTR
Pass Can wrap and unwrap X448 private key keys using jwk and AES-CTR
Pass Can wrap and unwrap X448 private key keys as non-extractable using jwk and AES-CTR
Pass Can unwrap X448 private key non-extractable keys using jwk and AES-CTR
Pass Can wrap and unwrap AES-CTR keys using raw and AES-CTR
Pass Can wrap and unwrap AES-CTR keys as non-extractable using raw and AES-CTR
Pass Can wrap and unwrap AES-CTR keys using jwk and AES-CTR
Pass Can wrap and unwrap AES-CTR keys as non-extractable using jwk and AES-CTR
Pass Can unwrap AES-CTR non-extractable keys using jwk and AES-CTR
Pass Can wrap and unwrap AES-CBC keys using raw and AES-CTR
Pass Can wrap and unwrap AES-CBC keys as non-extractable using raw and AES-CTR
Pass Can wrap and unwrap AES-CBC keys using jwk and AES-CTR
Pass Can wrap and unwrap AES-CBC keys as non-extractable using jwk and AES-CTR
Pass Can unwrap AES-CBC non-extractable keys using jwk and AES-CTR
Pass Can wrap and unwrap AES-GCM keys using raw and AES-CTR
Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CTR
Pass Can wrap and unwrap AES-GCM keys using jwk and AES-CTR
Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CTR
Fail Can unwrap AES-GCM non-extractable keys using jwk and AES-CTR
Pass Can wrap and unwrap HMAC keys using raw and AES-CTR
Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-CTR
Pass Can wrap and unwrap HMAC keys using jwk and AES-CTR
Pass Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-CTR
Pass Can unwrap HMAC non-extractable keys using jwk and AES-CTR
Pass Can wrap and unwrap RSA-OAEP public key keys using spki and AES-CBC
Pass Can wrap and unwrap RSA-OAEP public key keys using jwk and AES-CBC
Pass Can wrap and unwrap RSA-OAEP private key keys using pkcs8 and AES-CBC
Pass Can wrap and unwrap RSA-OAEP private key keys as non-extractable using pkcs8 and AES-CBC
Pass Can wrap and unwrap RSA-OAEP private key keys using jwk and AES-CBC
Pass Can wrap and unwrap RSA-OAEP private key keys as non-extractable using jwk and AES-CBC
Pass Can unwrap RSA-OAEP private key non-extractable keys using jwk and AES-CBC
Pass Can wrap and unwrap ECDSA public key keys using spki and AES-CBC
Pass Can wrap and unwrap ECDSA public key keys using jwk and AES-CBC
Pass Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-CBC
Pass Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and AES-CBC
Pass Can wrap and unwrap ECDSA private key keys using jwk and AES-CBC
Pass Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and AES-CBC
Pass Can unwrap ECDSA private key non-extractable keys using jwk and AES-CBC
Pass Can wrap and unwrap ECDH public key keys using spki and AES-CBC
Pass Can wrap and unwrap ECDH public key keys using jwk and AES-CBC
Pass Can wrap and unwrap ECDH private key keys using pkcs8 and AES-CBC
Pass Can wrap and unwrap ECDH private key keys as non-extractable using pkcs8 and AES-CBC
Pass Can wrap and unwrap ECDH private key keys using jwk and AES-CBC
Pass Can wrap and unwrap ECDH private key keys as non-extractable using jwk and AES-CBC
Pass Can unwrap ECDH private key non-extractable keys using jwk and AES-CBC
Pass Can wrap and unwrap Ed25519 public key keys using spki and AES-CBC
Pass Can wrap and unwrap Ed25519 public key keys using jwk and AES-CBC
Pass Can wrap and unwrap Ed25519 private key keys using pkcs8 and AES-CBC
Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and AES-CBC
Pass Can wrap and unwrap Ed25519 private key keys using jwk and AES-CBC
Pass Can wrap and unwrap Ed25519 private key keys as non-extractable using jwk and AES-CBC
Pass Can unwrap Ed25519 private key non-extractable keys using jwk and AES-CBC
Pass Can wrap and unwrap X25519 public key keys using spki and AES-CBC
Pass Can wrap and unwrap X25519 public key keys using jwk and AES-CBC
Pass Can wrap and unwrap X25519 private key keys using pkcs8 and AES-CBC
Pass Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and AES-CBC
Pass Can wrap and unwrap X25519 private key keys using jwk and AES-CBC
Pass Can wrap and unwrap X25519 private key keys as non-extractable using jwk and AES-CBC
Pass Can unwrap X25519 private key non-extractable keys using jwk and AES-CBC
Pass Can wrap and unwrap X448 public key keys using spki and AES-CBC
Pass Can wrap and unwrap X448 public key keys using jwk and AES-CBC
Pass Can wrap and unwrap X448 private key keys using pkcs8 and AES-CBC
Pass Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and AES-CBC
Pass Can wrap and unwrap X448 private key keys using jwk and AES-CBC
Pass Can wrap and unwrap X448 private key keys as non-extractable using jwk and AES-CBC
Pass Can unwrap X448 private key non-extractable keys using jwk and AES-CBC
Pass Can wrap and unwrap AES-CTR keys using raw and AES-CBC
Pass Can wrap and unwrap AES-CTR keys as non-extractable using raw and AES-CBC
Pass Can wrap and unwrap AES-CTR keys using jwk and AES-CBC
Pass Can wrap and unwrap AES-CTR keys as non-extractable using jwk and AES-CBC
Pass Can unwrap AES-CTR non-extractable keys using jwk and AES-CBC
Pass Can wrap and unwrap AES-CBC keys using raw and AES-CBC
Pass Can wrap and unwrap AES-CBC keys as non-extractable using raw and AES-CBC
Pass Can wrap and unwrap AES-CBC keys using jwk and AES-CBC
Pass Can wrap and unwrap AES-CBC keys as non-extractable using jwk and AES-CBC
Pass Can unwrap AES-CBC non-extractable keys using jwk and AES-CBC
Pass Can wrap and unwrap AES-GCM keys using raw and AES-CBC
Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-CBC
Pass Can wrap and unwrap AES-GCM keys using jwk and AES-CBC
Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-CBC
Fail Can unwrap AES-GCM non-extractable keys using jwk and AES-CBC
Pass Can wrap and unwrap HMAC keys using raw and AES-CBC
Pass Can wrap and unwrap HMAC keys as non-extractable using raw and AES-CBC
Pass Can wrap and unwrap HMAC keys using jwk and AES-CBC
Pass Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-CBC
Pass Can unwrap HMAC non-extractable keys using jwk and AES-CBC
Fail Can wrap and unwrap RSA-OAEP public key keys using spki and AES-GCM
Fail Can wrap and unwrap RSA-OAEP public key keys using jwk and AES-GCM
Fail Can wrap and unwrap RSA-OAEP private key keys using pkcs8 and AES-GCM
Fail Can wrap and unwrap RSA-OAEP private key keys as non-extractable using pkcs8 and AES-GCM
Fail Can wrap and unwrap RSA-OAEP private key keys using jwk and AES-GCM
Fail Can wrap and unwrap RSA-OAEP private key keys as non-extractable using jwk and AES-GCM
Fail Can unwrap RSA-OAEP private key non-extractable keys using jwk and AES-GCM
Fail Can wrap and unwrap ECDSA public key keys using spki and AES-GCM
Fail Can wrap and unwrap ECDSA public key keys using jwk and AES-GCM
Fail Can wrap and unwrap ECDSA private key keys using pkcs8 and AES-GCM
Fail Can wrap and unwrap ECDSA private key keys as non-extractable using pkcs8 and AES-GCM
Fail Can wrap and unwrap ECDSA private key keys using jwk and AES-GCM
Fail Can wrap and unwrap ECDSA private key keys as non-extractable using jwk and AES-GCM
Fail Can unwrap ECDSA private key non-extractable keys using jwk and AES-GCM
Fail Can wrap and unwrap ECDH public key keys using spki and AES-GCM
Fail Can wrap and unwrap ECDH public key keys using jwk and AES-GCM
Fail Can wrap and unwrap ECDH private key keys using pkcs8 and AES-GCM
Fail Can wrap and unwrap ECDH private key keys as non-extractable using pkcs8 and AES-GCM
Fail Can wrap and unwrap ECDH private key keys using jwk and AES-GCM
Fail Can wrap and unwrap ECDH private key keys as non-extractable using jwk and AES-GCM
Fail Can unwrap ECDH private key non-extractable keys using jwk and AES-GCM
Fail Can wrap and unwrap Ed25519 public key keys using spki and AES-GCM
Fail Can wrap and unwrap Ed25519 public key keys using jwk and AES-GCM
Fail Can wrap and unwrap Ed25519 private key keys using pkcs8 and AES-GCM
Fail Can wrap and unwrap Ed25519 private key keys as non-extractable using pkcs8 and AES-GCM
Fail Can wrap and unwrap Ed25519 private key keys using jwk and AES-GCM
Fail Can wrap and unwrap Ed25519 private key keys as non-extractable using jwk and AES-GCM
Fail Can unwrap Ed25519 private key non-extractable keys using jwk and AES-GCM
Fail Can wrap and unwrap X25519 public key keys using spki and AES-GCM
Fail Can wrap and unwrap X25519 public key keys using jwk and AES-GCM
Fail Can wrap and unwrap X25519 private key keys using pkcs8 and AES-GCM
Fail Can wrap and unwrap X25519 private key keys as non-extractable using pkcs8 and AES-GCM
Fail Can wrap and unwrap X25519 private key keys using jwk and AES-GCM
Fail Can wrap and unwrap X25519 private key keys as non-extractable using jwk and AES-GCM
Fail Can unwrap X25519 private key non-extractable keys using jwk and AES-GCM
Fail Can wrap and unwrap X448 public key keys using spki and AES-GCM
Fail Can wrap and unwrap X448 public key keys using jwk and AES-GCM
Fail Can wrap and unwrap X448 private key keys using pkcs8 and AES-GCM
Fail Can wrap and unwrap X448 private key keys as non-extractable using pkcs8 and AES-GCM
Fail Can wrap and unwrap X448 private key keys using jwk and AES-GCM
Fail Can wrap and unwrap X448 private key keys as non-extractable using jwk and AES-GCM
Fail Can unwrap X448 private key non-extractable keys using jwk and AES-GCM
Fail Can wrap and unwrap AES-CTR keys using raw and AES-GCM
Fail Can wrap and unwrap AES-CTR keys as non-extractable using raw and AES-GCM
Fail Can wrap and unwrap AES-CTR keys using jwk and AES-GCM
Fail Can wrap and unwrap AES-CTR keys as non-extractable using jwk and AES-GCM
Fail Can unwrap AES-CTR non-extractable keys using jwk and AES-GCM
Fail Can wrap and unwrap AES-CBC keys using raw and AES-GCM
Fail Can wrap and unwrap AES-CBC keys as non-extractable using raw and AES-GCM
Fail Can wrap and unwrap AES-CBC keys using jwk and AES-GCM
Fail Can wrap and unwrap AES-CBC keys as non-extractable using jwk and AES-GCM
Fail Can unwrap AES-CBC non-extractable keys using jwk and AES-GCM
Fail Can wrap and unwrap AES-GCM keys using raw and AES-GCM
Fail Can wrap and unwrap AES-GCM keys as non-extractable using raw and AES-GCM
Fail Can wrap and unwrap AES-GCM keys using jwk and AES-GCM
Fail Can wrap and unwrap AES-GCM keys as non-extractable using jwk and AES-GCM
Fail Can unwrap AES-GCM non-extractable keys using jwk and AES-GCM
Fail Can wrap and unwrap HMAC keys using raw and AES-GCM
Fail Can wrap and unwrap HMAC keys as non-extractable using raw and AES-GCM
Fail Can wrap and unwrap HMAC keys using jwk and AES-GCM
Fail Can wrap and unwrap HMAC keys as non-extractable using jwk and AES-GCM
Fail Can unwrap HMAC non-extractable keys using jwk and AES-GCM

View file

@ -0,0 +1,17 @@
<!doctype html>
<meta charset=utf-8>
<title>WebCryptoAPI: wrapKey() and unwrapKey()</title>
<meta name="timeout" content="long">
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../util/helpers.js"></script>
<script src="wrapKey_unwrapKey_vectors.js"></script>
<div id=log></div>
<script src="../../WebCryptoAPI/wrapKey_unwrapKey/wrapKey_unwrapKey.https.any.js"></script>

View file

@ -0,0 +1,536 @@
// META: title=WebCryptoAPI: wrapKey() and unwrapKey()
// META: timeout=long
// META: script=../util/helpers.js
// META: script=wrapKey_unwrapKey_vectors.js
// Tests for wrapKey and unwrapKey round tripping
var subtle = self.crypto.subtle;
var wrappers = {}; // Things we wrap (and upwrap) keys with
var keys = {}; // Things to wrap and unwrap
// There are five algorithms that can be used for wrapKey/unwrapKey.
// Generate one key with typical parameters for each kind.
//
// Note: we don't need cryptographically strong parameters for things
// like IV - just any legal value will do.
var wrappingKeysParameters = [
{
name: "RSA-OAEP",
importParameters: {name: "RSA-OAEP", hash: "SHA-256"},
wrapParameters: {name: "RSA-OAEP", label: new Uint8Array(8)}
},
{
name: "AES-CTR",
importParameters: {name: "AES-CTR", length: 128},
wrapParameters: {name: "AES-CTR", counter: new Uint8Array(16), length: 64}
},
{
name: "AES-CBC",
importParameters: {name: "AES-CBC", length: 128},
wrapParameters: {name: "AES-CBC", iv: new Uint8Array(16)}
},
{
name: "AES-GCM",
importParameters: {name: "AES-GCM", length: 128},
wrapParameters: {name: "AES-GCM", iv: new Uint8Array(16), additionalData: new Uint8Array(16), tagLength: 128}
},
{
name: "AES-KW",
importParameters: {name: "AES-KW", length: 128},
wrapParameters: {name: "AES-KW"}
}
];
var keysToWrapParameters = [
{algorithm: {name: "RSASSA-PKCS1-v1_5", hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
{algorithm: {name: "RSA-PSS", hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
{algorithm: {name: "RSA-OAEP", hash: "SHA-256"}, privateUsages: ["decrypt"], publicUsages: ["encrypt"]},
{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"]},
{algorithm: {name: "AES-KW", length: 128}, usages: ["wrapKey", "unwrapKey"]},
{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.
var promises = [];
wrappingKeysParameters.forEach(function(params) {
if (params.name === "RSA-OAEP") { // we have a key pair, not just a key
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 {
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);
}
async function importAndExport(format, keyData, algorithm, keyUsages, keyType) {
var importedKey;
try {
importedKey = await subtle.importKey(format, keyData, algorithm, true, keyUsages);
keys[algorithm.name][format] = { name: algorithm.name + " " + keyType, algorithm: algorithm, usages: keyUsages, key: importedKey, originalExport: {} };
} catch (err) {
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, ""));
}
});
// Using allSettled to skip unsupported test cases.
return Promise.allSettled(promises);
}
// Can we successfully "round-trip" (wrap, then unwrap, a key)?
function testWrapping(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, true, toWrap.usages);
assert_goodCryptoKey(unwrappedResult, toWrap.algorithm, true, toWrap.usages, toWrap.key.type);
var roundTripExport = await subtle.exportKey(fmt, unwrappedResult);
assert_true(equalExport(toWrap.originalExport[fmt], roundTripExport), "Post-wrap export matches original export");
} catch (err) {
if (err instanceof AssertionError) {
throw 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);
}
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);
var result = await equalKeys(toWrap.key, unwrappedResult);
assert_true(result, "Unwrapped key matches original");
} catch (err) {
if (err instanceof AssertionError) {
throw err;
}
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);
}
function testWrappingNonExtractableAsExtractable(wrapper, toWrap) {
promise_test(async() => {
var wrappedKey;
try {
var wrappedResult = await wrapAsNonExtractableJwk(toWrap.key,wrapper);
wrappedKey = wrappedResult;
var unwrappedResult = await subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages);
assert_false(unwrappedResult.extractable, "Unwrapped key is non-extractable");
var result = await equalKeys(toWrap.key,unwrappedResult);
assert_true(result, "Unwrapped key matches original");
} catch (err) {
if (err instanceof AssertionError) {
throw err;
}
assert_unreached("Round trip for non-extractable key threw an error - " + err.name + ': "' + err.message + '"');
};
try {
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");
} catch (err) {
if (err instanceof AssertionError) {
throw 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);
}
// Implement key wrapping by hand to wrap a key as non-extractable JWK
async function wrapAsNonExtractableJwk(key, wrapper) {
var wrappingKey = wrapper.wrappingKey,
encryptKey;
var jwkWrappingKey = await subtle.exportKey("jwk",wrappingKey);
// Update the key generation parameters to work as key import parameters
var params = Object.create(wrapper.parameters.importParameters);
if(params.name === "AES-KW") {
params.name = "AES-CBC";
jwkWrappingKey.alg = "A"+params.length+"CBC";
} else if (params.name === "RSA-OAEP") {
params.modulusLength = undefined;
params.publicExponent = undefined;
}
jwkWrappingKey.key_ops = ["encrypt"];
var importedWrappingKey = await subtle.importKey("jwk", jwkWrappingKey, params, true, ["encrypt"]);
encryptKey = importedWrappingKey;
var exportedKey = await subtle.exportKey("jwk",key);
exportedKey.ext = false;
var jwk = JSON.stringify(exportedKey)
var result;
if (wrappingKey.algorithm.name === "AES-KW") {
result = await aeskw(encryptKey, str2ab(jwk.slice(0,-1) + " ".repeat(jwk.length%8 ? 8-jwk.length%8 : 0) + "}"));
} else {
result = await subtle.encrypt(wrapper.parameters.wrapParameters,encryptKey,str2ab(jwk));
}
return result;
}
// RSA-OAEP can only wrap relatively small payloads. AES-KW can only
// wrap payloads a multiple of 8 bytes long.
function wrappingIsPossible(exportedKey, algorithmName) {
if ("byteLength" in exportedKey && algorithmName === "AES-KW") {
return exportedKey.byteLength % 8 === 0;
}
if ("byteLength" in exportedKey && algorithmName === "RSA-OAEP") {
// RSA-OAEP can only encrypt payloads with lengths shorter
// than modulusLength - 2*hashLength - 1 bytes long. For
// a 4096 bit modulus and SHA-256, that comes to
// 4096/8 - 2*(256/8) - 1 = 512 - 2*32 - 1 = 447 bytes.
return exportedKey.byteLength <= 446;
}
if ("kty" in exportedKey && algorithmName === "AES-KW") {
return JSON.stringify(exportedKey).length % 8 == 0;
}
if ("kty" in exportedKey && algorithmName === "RSA-OAEP") {
return JSON.stringify(exportedKey).length <= 478;
}
return true;
}
// Helper methods follow:
// Are two exported keys equal
function equalExport(originalExport, roundTripExport) {
if ("byteLength" in originalExport) {
return equalBuffers(originalExport, roundTripExport);
} else {
return equalJwk(originalExport, roundTripExport);
}
}
// Are two array buffers the same?
function equalBuffers(a, b) {
if (a.byteLength !== b.byteLength) {
return false;
}
var aBytes = new Uint8Array(a);
var bBytes = new Uint8Array(b);
for (var i=0; i<a.byteLength; i++) {
if (aBytes[i] !== bBytes[i]) {
return false;
}
}
return true;
}
// Are two Jwk objects "the same"? That is, does the object returned include
// matching values for each property that was expected? It's okay if the
// returned object has extra methods; they aren't checked.
function equalJwk(expected, got) {
var fields = Object.keys(expected);
var fieldName;
for(var i=0; i<fields.length; i++) {
fieldName = fields[i];
if (!(fieldName in got)) {
return false;
}
if (objectToString(expected[fieldName]) !== objectToString(got[fieldName])) {
return false;
}
}
return true;
}
// Character representation of any object we may use as a parameter.
function objectToString(obj) {
var keyValuePairs = [];
if (Array.isArray(obj)) {
return "[" + obj.map(function(elem){return objectToString(elem);}).join(", ") + "]";
} else if (typeof obj === "object") {
Object.keys(obj).sort().forEach(function(keyName) {
keyValuePairs.push(keyName + ": " + objectToString(obj[keyName]));
});
return "{" + keyValuePairs.join(", ") + "}";
} else if (typeof obj === "undefined") {
return "undefined";
} else {
return obj.toString();
}
var keyValuePairs = [];
Object.keys(obj).sort().forEach(function(keyName) {
var value = obj[keyName];
if (typeof value === "object") {
value = objectToString(value);
} else if (typeof value === "array") {
value = "[" + value.map(function(elem){return objectToString(elem);}).join(", ") + "]";
} else {
value = value.toString();
}
keyValuePairs.push(keyName + ": " + value);
});
return "{" + keyValuePairs.join(", ") + "}";
}
// Can we compare key values by using them
function canCompareNonExtractableKeys(key){
if (key.usages.indexOf("decrypt") !== -1) {
return true;
}
if (key.usages.indexOf("sign") !== -1) {
return true;
}
if (key.usages.indexOf("wrapKey") !== -1) {
return true;
}
if (key.usages.indexOf("deriveBits") !== -1) {
return true;
}
return false;
}
// Compare two keys by using them (works for non-extractable keys)
async function equalKeys(expected, got){
if ( expected.algorithm.name !== got.algorithm.name ) {
return false;
}
var cryptParams, signParams, wrapParams, deriveParams;
switch(expected.algorithm.name){
case "AES-CTR" :
cryptParams = {name: "AES-CTR", counter: new Uint8Array(16), length: 64};
break;
case "AES-CBC" :
cryptParams = {name: "AES-CBC", iv: new Uint8Array(16) };
break;
case "AES-GCM" :
cryptParams = {name: "AES-GCM", iv: new Uint8Array(16) };
break;
case "RSA-OAEP" :
cryptParams = {name: "RSA-OAEP", label: new Uint8Array(8) };
break;
case "RSASSA-PKCS1-v1_5" :
signParams = {name: "RSASSA-PKCS1-v1_5"};
break;
case "RSA-PSS" :
signParams = {name: "RSA-PSS", saltLength: 32 };
break;
case "ECDSA" :
signParams = {name: "ECDSA", hash: "SHA-256"};
break;
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;
case "AES-KW" :
wrapParams = {name: "AES-KW"};
break;
case "ECDH" :
deriveParams = {name: "ECDH"};
break;
default:
throw new Error("Unsupported algorithm for key comparison");
}
if (cryptParams) {
var jwkExpectedKey = await subtle.exportKey("jwk", expected);
if (expected.algorithm.name === "RSA-OAEP") {
["d","p","q","dp","dq","qi","oth"].forEach(function(field){ delete jwkExpectedKey[field]; });
}
jwkExpectedKey.key_ops = ["encrypt"];
var expectedEncryptKey = await subtle.importKey("jwk", jwkExpectedKey, expected.algorithm, true, ["encrypt"]);
var encryptedData = await subtle.encrypt(cryptParams, expectedEncryptKey, new Uint8Array(32));
var decryptedData = await subtle.decrypt(cryptParams, got, encryptedData);
var result = new Uint8Array(decryptedData);
return !result.some(x => x);
} else if (signParams) {
var verifyKey;
var jwkExpectedKey = await subtle.exportKey("jwk",expected);
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.startsWith("Ed")) {
delete jwkExpectedKey["d"];
}
jwkExpectedKey.key_ops = ["verify"];
var expectedVerifyKey = await subtle.importKey("jwk", jwkExpectedKey, expected.algorithm, true, ["verify"]);
verifyKey = expectedVerifyKey;
var signature = await subtle.sign(signParams, got, new Uint8Array(32));
var result = await subtle.verify(signParams, verifyKey, signature, new Uint8Array(32));
return result;
} else if (wrapParams) {
var aKeyToWrap, wrappedWithExpected;
var key = await subtle.importKey("raw",new Uint8Array(16), "AES-CBC", true, ["encrypt"])
aKeyToWrap = key;
var wrapResult = await subtle.wrapKey("raw", aKeyToWrap, expected, wrapParams);
wrappedWithExpected = Array.from((new Uint8Array(wrapResult)).values());
wrapResult = await subtle.wrapKey("raw", aKeyToWrap, got, wrapParams);
var wrappedWithGot = Array.from((new Uint8Array(wrapResult)).values());
return wrappedWithGot.every((x,i) => x === wrappedWithExpected[i]);
} else if (deriveParams) {
var expectedDerivedBits;
var key = await subtle.generateKey(expected.algorithm, true, ['deriveBits']);
deriveParams.public = key.publicKey;
var result = await subtle.deriveBits(deriveParams, expected, 128);
expectedDerivedBits = Array.from((new Uint8Array(result)).values());
result = await subtle.deriveBits(deriveParams, got, 128);
var gotDerivedBits = Array.from((new Uint8Array(result)).values());
return gotDerivedBits.every((x,i) => x === expectedDerivedBits[i]);
}
}
// Raw AES encryption
async function aes(k, p) {
const ciphertext = await subtle.encrypt({ name: "AES-CBC", iv: new Uint8Array(16) }, k, p);
return ciphertext.slice(0, 16);
}
// AES Key Wrap
async function aeskw(key, data) {
if (data.byteLength % 8 !== 0) {
throw new Error("AES Key Wrap data must be a multiple of 8 bytes in length");
}
var A = Uint8Array.from([0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0, 0, 0, 0, 0, 0, 0, 0]),
Av = new DataView(A.buffer),
R = [],
n = data.byteLength / 8;
for(var i = 0; i<data.byteLength; i+=8) {
R.push(new Uint8Array(data.slice(i,i+8)));
}
async function aeskw_step(j, i, final, B) {
A.set(new Uint8Array(B.slice(0,8)));
Av.setUint32(4,Av.getUint32(4) ^ (n*j+i+1));
R[i] = new Uint8Array(B.slice(8,16));
if (final) {
R.unshift(A.slice(0,8));
var result = new Uint8Array(R.length * 8);
R.forEach(function(Ri,i){ result.set(Ri, i*8); });
return result;
} else {
A.set(R[(i+1)%n],8);
return aes(key,A);
}
}
A.set(R[0], 8);
let B = await aes(key, A);
for(var j=0;j<6;++j) {
for(var i=0;i<n;++i) {
B = await aeskw_step(j, i, j === 5 && i === (n - 1), B);
}
}
return B;
}
function str2ab(str) { return Uint8Array.from( str.split(''), function(s){return s.charCodeAt(0)} ); }
function ab2str(ab) { return String.fromCharCode.apply(null, new Uint8Array(ab)); }

File diff suppressed because one or more lines are too long