mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-27 21:42:53 +00:00
LibWeb: Implement and test SubtleCrypto interface for HKDF operations
This fixes several hundred if not thousands of WPT tests: https://wpt.live/WebCryptoAPI/derive_bits_keys/hkdf.https.any.html?1-1000
This commit is contained in:
parent
6072ae5bae
commit
f670c68ded
Notes:
github-actions[bot]
2024-10-23 18:21:54 +00:00
Author: https://github.com/BenWiederhake
Commit: f670c68ded
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1877
Reviewed-by: https://github.com/ADKaster ✅
Reviewed-by: https://github.com/alimpfard ✅
Reviewed-by: https://github.com/stelar7
3 changed files with 167 additions and 0 deletions
|
@ -10,6 +10,7 @@
|
||||||
#include <LibCrypto/Authentication/HMAC.h>
|
#include <LibCrypto/Authentication/HMAC.h>
|
||||||
#include <LibCrypto/Curves/Ed25519.h>
|
#include <LibCrypto/Curves/Ed25519.h>
|
||||||
#include <LibCrypto/Curves/SECPxxxr1.h>
|
#include <LibCrypto/Curves/SECPxxxr1.h>
|
||||||
|
#include <LibCrypto/Hash/HKDF.h>
|
||||||
#include <LibCrypto/Hash/HashManager.h>
|
#include <LibCrypto/Hash/HashManager.h>
|
||||||
#include <LibCrypto/Hash/PBKDF2.h>
|
#include <LibCrypto/Hash/PBKDF2.h>
|
||||||
#include <LibCrypto/Hash/SHA1.h>
|
#include <LibCrypto/Hash/SHA1.h>
|
||||||
|
@ -226,6 +227,31 @@ JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> AlgorithmParams::from_valu
|
||||||
return adopt_own(*new AlgorithmParams { name_string });
|
return adopt_own(*new AlgorithmParams { name_string });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HKDFParams::~HKDFParams() = default;
|
||||||
|
|
||||||
|
JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> HKDFParams::from_value(JS::VM& vm, JS::Value value)
|
||||||
|
{
|
||||||
|
auto& object = value.as_object();
|
||||||
|
|
||||||
|
auto name_value = TRY(object.get("name"));
|
||||||
|
auto name = TRY(name_value.to_string(vm));
|
||||||
|
|
||||||
|
auto hash_value = TRY(object.get("hash"));
|
||||||
|
auto hash = TRY(hash_value.to_string(vm));
|
||||||
|
|
||||||
|
auto salt_value = TRY(object.get("salt"));
|
||||||
|
if (!salt_value.is_object() || !(is<JS::TypedArrayBase>(salt_value.as_object()) || is<JS::ArrayBuffer>(salt_value.as_object()) || is<JS::DataView>(salt_value.as_object())))
|
||||||
|
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "BufferSource");
|
||||||
|
auto salt = TRY_OR_THROW_OOM(vm, WebIDL::get_buffer_source_copy(salt_value.as_object()));
|
||||||
|
|
||||||
|
auto info_value = TRY(object.get("info"));
|
||||||
|
if (!info_value.is_object() || !(is<JS::TypedArrayBase>(info_value.as_object()) || is<JS::ArrayBuffer>(info_value.as_object()) || is<JS::DataView>(info_value.as_object())))
|
||||||
|
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "BufferSource");
|
||||||
|
auto info = TRY_OR_THROW_OOM(vm, WebIDL::get_buffer_source_copy(info_value.as_object()));
|
||||||
|
|
||||||
|
return adopt_own<AlgorithmParams>(*new HKDFParams { name, hash, salt, info });
|
||||||
|
}
|
||||||
|
|
||||||
PBKDF2Params::~PBKDF2Params() = default;
|
PBKDF2Params::~PBKDF2Params() = default;
|
||||||
|
|
||||||
JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> PBKDF2Params::from_value(JS::VM& vm, JS::Value value)
|
JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> PBKDF2Params::from_value(JS::VM& vm, JS::Value value)
|
||||||
|
@ -939,6 +965,52 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Object>> RSAOAEP::export_key(Bindings::
|
||||||
return JS::NonnullGCPtr { *result };
|
return JS::NonnullGCPtr { *result };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/webcrypto/#hkdf-operations
|
||||||
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> HKDF::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector<Bindings::KeyUsage> const& key_usages)
|
||||||
|
{
|
||||||
|
// 1. Let keyData be the key data to be imported.
|
||||||
|
|
||||||
|
// 2. If format is "raw":
|
||||||
|
// (… see below …)
|
||||||
|
// Otherwise:
|
||||||
|
// throw a NotSupportedError.
|
||||||
|
if (format != Bindings::KeyFormat::Raw) {
|
||||||
|
return WebIDL::NotSupportedError::create(m_realm, "Only raw format is supported"_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. If usages contains a value that is not "deriveKey" or "deriveBits", then throw a SyntaxError.
|
||||||
|
for (auto& usage : key_usages) {
|
||||||
|
if (usage != Bindings::KeyUsage::Derivekey && usage != Bindings::KeyUsage::Derivebits) {
|
||||||
|
return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. If extractable is not false, then throw a SyntaxError.
|
||||||
|
if (extractable)
|
||||||
|
return WebIDL::SyntaxError::create(m_realm, "extractable must be false"_string);
|
||||||
|
|
||||||
|
// 3. Let key be a new CryptoKey representing the key data provided in keyData.
|
||||||
|
auto key = CryptoKey::create(m_realm, move(key_data));
|
||||||
|
|
||||||
|
// 4. Set the [[type]] internal slot of key to "secret".
|
||||||
|
key->set_type(Bindings::KeyType::Secret);
|
||||||
|
|
||||||
|
// 5. Set the [[extractable]] internal slot of key to false.
|
||||||
|
key->set_extractable(false);
|
||||||
|
|
||||||
|
// 6. Let algorithm be a new KeyAlgorithm object.
|
||||||
|
auto algorithm = KeyAlgorithm::create(m_realm);
|
||||||
|
|
||||||
|
// 7. Set the name attribute of algorithm to "HKDF".
|
||||||
|
algorithm->set_name("HKDF"_string);
|
||||||
|
|
||||||
|
// 8. Set the [[algorithm]] internal slot of key to algorithm.
|
||||||
|
key->set_algorithm(algorithm);
|
||||||
|
|
||||||
|
// 9. Return key.
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> PBKDF2::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector<Bindings::KeyUsage> const& key_usages)
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> PBKDF2::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector<Bindings::KeyUsage> const& key_usages)
|
||||||
{
|
{
|
||||||
// 1. If format is not "raw", throw a NotSupportedError
|
// 1. If format is not "raw", throw a NotSupportedError
|
||||||
|
@ -1371,6 +1443,63 @@ WebIDL::ExceptionOr<JS::Value> ED25519::verify([[maybe_unused]] AlgorithmParams
|
||||||
return JS::Value(result);
|
return JS::Value(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/webcrypto/#hkdf-operations
|
||||||
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> HKDF::derive_bits(AlgorithmParams const& params, JS::NonnullGCPtr<CryptoKey> key, Optional<u32> length_optional)
|
||||||
|
{
|
||||||
|
auto& realm = *m_realm;
|
||||||
|
auto const& normalized_algorithm = static_cast<HKDFParams const&>(params);
|
||||||
|
|
||||||
|
// 1. If length is null or zero, or is not a multiple of 8, then throw an OperationError.
|
||||||
|
auto length = length_optional.value_or(0);
|
||||||
|
|
||||||
|
if (length == 0 || length % 8 != 0)
|
||||||
|
return WebIDL::OperationError::create(realm, "Length must be greater than 0 and divisible by 8"_string);
|
||||||
|
|
||||||
|
// 2. Let extractKey be a key equal to n zero bits where n is the size of the output of the hash function described by the hash member of normalizedAlgorithm.
|
||||||
|
// (However, this variable is never directly used, and therefore pointless.)
|
||||||
|
|
||||||
|
// 3. Let keyDerivationKey be the secret represented by [[handle]] internal slot of key as the message.
|
||||||
|
auto key_derivation_key = key->handle().get<ByteBuffer>();
|
||||||
|
|
||||||
|
// 4. Let result be the result of performing the HKDF extract and then the HKDF expand step described in Section 2 of [RFC5869] using:
|
||||||
|
// * the hash member of normalizedAlgorithm as Hash,
|
||||||
|
// * keyDerivationKey as the input keying material, IKM,
|
||||||
|
// * the contents of the salt member of normalizedAlgorithm as salt,
|
||||||
|
// * the contents of the info member of normalizedAlgorithm as info,
|
||||||
|
// * length divided by 8 as the value of L,
|
||||||
|
// FIXME: salt null versus salt empty?!
|
||||||
|
auto const& hash_algorithm = TRY(normalized_algorithm.hash.visit(
|
||||||
|
[](String const& name) -> JS::ThrowCompletionOr<String> { return name; },
|
||||||
|
[&](JS::Handle<JS::Object> const& obj) -> JS::ThrowCompletionOr<String> {
|
||||||
|
auto name_property = TRY(obj->get("name"));
|
||||||
|
return name_property.to_string(m_realm->vm()); }));
|
||||||
|
ErrorOr<ByteBuffer> result = Error::from_string_literal("noop error");
|
||||||
|
if (hash_algorithm.equals_ignoring_ascii_case("SHA-1"sv)) {
|
||||||
|
result = ::Crypto::Hash::HKDF<::Crypto::Hash::SHA1>::derive_key(Optional<ReadonlyBytes>(normalized_algorithm.salt), key_derivation_key, normalized_algorithm.info, length / 8);
|
||||||
|
} else if (hash_algorithm.equals_ignoring_ascii_case("SHA-256"sv)) {
|
||||||
|
result = ::Crypto::Hash::HKDF<::Crypto::Hash::SHA256>::derive_key(Optional<ReadonlyBytes>(normalized_algorithm.salt), key_derivation_key, normalized_algorithm.info, length / 8);
|
||||||
|
} else if (hash_algorithm.equals_ignoring_ascii_case("SHA-384"sv)) {
|
||||||
|
result = ::Crypto::Hash::HKDF<::Crypto::Hash::SHA384>::derive_key(Optional<ReadonlyBytes>(normalized_algorithm.salt), key_derivation_key, normalized_algorithm.info, length / 8);
|
||||||
|
} else if (hash_algorithm.equals_ignoring_ascii_case("SHA-512"sv)) {
|
||||||
|
result = ::Crypto::Hash::HKDF<::Crypto::Hash::SHA512>::derive_key(Optional<ReadonlyBytes>(normalized_algorithm.salt), key_derivation_key, normalized_algorithm.info, length / 8);
|
||||||
|
} else {
|
||||||
|
return WebIDL::NotSupportedError::create(m_realm, MUST(String::formatted("Invalid hash function '{}'", hash_algorithm)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. If the key derivation operation fails, then throw an OperationError.
|
||||||
|
if (result.is_error())
|
||||||
|
return WebIDL::OperationError::create(realm, "Failed to derive key"_string);
|
||||||
|
|
||||||
|
// 6. Return result
|
||||||
|
return JS::ArrayBuffer::create(realm, result.release_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
WebIDL::ExceptionOr<JS::Value> HKDF::get_key_length(AlgorithmParams const&)
|
||||||
|
{
|
||||||
|
// 1. Return null.
|
||||||
|
return JS::js_null();
|
||||||
|
}
|
||||||
|
|
||||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> PBKDF2::derive_bits(AlgorithmParams const& params, JS::NonnullGCPtr<CryptoKey> key, Optional<u32> length_optional)
|
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> PBKDF2::derive_bits(AlgorithmParams const& params, JS::NonnullGCPtr<CryptoKey> key, Optional<u32> length_optional)
|
||||||
{
|
{
|
||||||
auto& realm = *m_realm;
|
auto& realm = *m_realm;
|
||||||
|
|
|
@ -37,6 +37,24 @@ struct AlgorithmParams {
|
||||||
static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
|
static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// https://w3c.github.io/webcrypto/#hkdf-params
|
||||||
|
struct HKDFParams : public AlgorithmParams {
|
||||||
|
virtual ~HKDFParams() override;
|
||||||
|
HKDFParams(String name, HashAlgorithmIdentifier hash, ByteBuffer salt, ByteBuffer info)
|
||||||
|
: AlgorithmParams(move(name))
|
||||||
|
, hash(move(hash))
|
||||||
|
, salt(move(salt))
|
||||||
|
, info(move(info))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
HashAlgorithmIdentifier hash;
|
||||||
|
ByteBuffer salt;
|
||||||
|
ByteBuffer info;
|
||||||
|
|
||||||
|
static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
|
||||||
|
};
|
||||||
|
|
||||||
// https://w3c.github.io/webcrypto/#pbkdf2-params
|
// https://w3c.github.io/webcrypto/#pbkdf2-params
|
||||||
struct PBKDF2Params : public AlgorithmParams {
|
struct PBKDF2Params : public AlgorithmParams {
|
||||||
virtual ~PBKDF2Params() override;
|
virtual ~PBKDF2Params() override;
|
||||||
|
@ -232,6 +250,21 @@ private:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class HKDF : public AlgorithmMethods {
|
||||||
|
public:
|
||||||
|
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector<Bindings::KeyUsage> const&) override;
|
||||||
|
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> derive_bits(AlgorithmParams const&, JS::NonnullGCPtr<CryptoKey>, Optional<u32>) override;
|
||||||
|
virtual WebIDL::ExceptionOr<JS::Value> get_key_length(AlgorithmParams const&) override;
|
||||||
|
|
||||||
|
static NonnullOwnPtr<AlgorithmMethods> create(JS::Realm& realm) { return adopt_own(*new HKDF(realm)); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit HKDF(JS::Realm& realm)
|
||||||
|
: AlgorithmMethods(realm)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class PBKDF2 : public AlgorithmMethods {
|
class PBKDF2 : public AlgorithmMethods {
|
||||||
public:
|
public:
|
||||||
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector<Bindings::KeyUsage> const&) override;
|
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector<Bindings::KeyUsage> const&) override;
|
||||||
|
|
|
@ -768,6 +768,11 @@ SupportedAlgorithmsMap supported_algorithms()
|
||||||
define_an_algorithm<SHA>("digest"_string, "SHA-384"_string);
|
define_an_algorithm<SHA>("digest"_string, "SHA-384"_string);
|
||||||
define_an_algorithm<SHA>("digest"_string, "SHA-512"_string);
|
define_an_algorithm<SHA>("digest"_string, "SHA-512"_string);
|
||||||
|
|
||||||
|
// https://w3c.github.io/webcrypto/#hkdf
|
||||||
|
define_an_algorithm<HKDF>("importKey"_string, "HKDF"_string);
|
||||||
|
define_an_algorithm<HKDF, HKDFParams>("deriveBits"_string, "HKDF"_string);
|
||||||
|
define_an_algorithm<HKDF>("get key length"_string, "HKDF"_string);
|
||||||
|
|
||||||
// https://w3c.github.io/webcrypto/#pbkdf2
|
// https://w3c.github.io/webcrypto/#pbkdf2
|
||||||
define_an_algorithm<PBKDF2>("importKey"_string, "PBKDF2"_string);
|
define_an_algorithm<PBKDF2>("importKey"_string, "PBKDF2"_string);
|
||||||
define_an_algorithm<PBKDF2, PBKDF2Params>("deriveBits"_string, "PBKDF2"_string);
|
define_an_algorithm<PBKDF2, PBKDF2Params>("deriveBits"_string, "PBKDF2"_string);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue