Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Libraries/LibCrypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ set(SOURCES
Cipher/AES.cpp
Curves/EdwardsCurve.cpp
Curves/SECPxxxr1.cpp
Hash/Argon2.cpp
Hash/BLAKE2b.cpp
Hash/HKDF.cpp
Hash/MD5.cpp
Expand Down
70 changes: 70 additions & 0 deletions Libraries/LibCrypto/Hash/Argon2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2025, Miguel Sacristán Izcue <miguel_tete17@hotmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <LibCrypto/Hash/Argon2.h>

#include <AK/ByteBuffer.h>
#include <LibCrypto/OpenSSL.h>

#include <openssl/core_names.h>
#include <openssl/kdf.h>
#include <openssl/params.h>
#include <openssl/thread.h>

namespace Crypto::Hash {

Argon2::Argon2(char const* openssl_name)
: m_kdf(EVP_KDF_fetch(nullptr, openssl_name, nullptr))
{
}

ErrorOr<ByteBuffer> Argon2::derive_key(
ReadonlyBytes message,
ReadonlyBytes nonce,
u32 parallelism,
u32 memory,
u32 passes,
u32 version,
Optional<ByteBuffer> secret_value,
Optional<ByteBuffer> associated_data,
u32 tag_length) const
{

auto ctx = TRY(OpenSSL_KDF_CTX::wrap(EVP_KDF_CTX_new(m_kdf)));

OPENSSL_TRY(OSSL_set_max_threads(nullptr, parallelism));

OSSL_PARAM params[] = {
OSSL_PARAM_uint32(OSSL_KDF_PARAM_THREADS, &parallelism),
OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &parallelism),
OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memory),
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, const_cast<u8*>(nonce.data()), nonce.size()),
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_PASSWORD, const_cast<u8*>(message.data()), message.size()),
OSSL_PARAM_uint32(OSSL_KDF_PARAM_ARGON2_VERSION, &version),
OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, &passes),
OSSL_PARAM_uint32(OSSL_KDF_PARAM_SIZE, &tag_length),
OSSL_PARAM_END,
OSSL_PARAM_END,
OSSL_PARAM_END,
};

auto insertion_point = 8;

if (secret_value.has_value()) {
params[insertion_point++] = OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SECRET, secret_value->data(), secret_value->size());
}

if (associated_data.has_value()) {
params[insertion_point++] = OSSL_PARAM_octet_string(OSSL_KDF_PARAM_ARGON2_AD, associated_data->data(), associated_data->size());
}

auto buf = TRY(ByteBuffer::create_uninitialized(tag_length));
OPENSSL_TRY(EVP_KDF_derive(ctx.ptr(), buf.data(), tag_length, params));

return buf;
}

}
40 changes: 40 additions & 0 deletions Libraries/LibCrypto/Hash/Argon2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2025, Miguel Sacristán Izcue <miguel_tete17@hotmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <AK/Error.h>
#include <LibCrypto/OpenSSLForward.h>

namespace Crypto::Hash {

class Argon2 {
AK_MAKE_NONCOPYABLE(Argon2);

public:
Argon2(char const* openssl_name);

virtual ~Argon2()
{
EVP_KDF_free(m_kdf);
}

ErrorOr<ByteBuffer> derive_key(
ReadonlyBytes message,
ReadonlyBytes nonce,
u32 parallelism,
u32 memory,
u32 passes,
u32 version,
Optional<ByteBuffer> secret_value,
Optional<ByteBuffer> associated_data,
u32 tag_length) const;

private:
EVP_KDF* m_kdf;
};

}
2 changes: 2 additions & 0 deletions Libraries/LibCrypto/Hash/HKDF.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
namespace Crypto::Hash {

class HKDF {
AK_MAKE_NONCOPYABLE(HKDF);

public:
HKDF(HashKind hash_kind);

Expand Down
2 changes: 2 additions & 0 deletions Libraries/LibCrypto/Hash/PBKDF2.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
namespace Crypto::Hash {

class PBKDF2 {
AK_MAKE_NONCOPYABLE(PBKDF2);

public:
PBKDF2(HashKind hash_kind);

Expand Down
171 changes: 171 additions & 0 deletions Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <LibCrypto/Cipher/AES.h>
#include <LibCrypto/Curves/EdwardsCurve.h>
#include <LibCrypto/Curves/SECPxxxr1.h>
#include <LibCrypto/Hash/Argon2.h>
#include <LibCrypto/Hash/HKDF.h>
#include <LibCrypto/Hash/HashManager.h>
#include <LibCrypto/Hash/PBKDF2.h>
Expand Down Expand Up @@ -628,6 +629,56 @@ JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> Ed448Params::from_value(JS
return adopt_own<AlgorithmParams>(*new Ed448Params { maybe_context });
}

Argon2Params::~Argon2Params() = default;

JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> Argon2Params::from_value(JS::VM& vm, JS::Value value)
{
VERIFY(value.is_object());
auto& object = value.as_object();

if (!MUST(object.has_property("nonce"_utf16_fly_string))) {
return vm.throw_completion<JS::TypeError>(JS::ErrorType::MissingRequiredProperty, "nonce");
}
auto nonce_value = TRY(object.get("nonce"_utf16_fly_string));
if (!nonce_value.is_object() || !(is<JS::TypedArrayBase>(nonce_value.as_object()) || is<JS::ArrayBuffer>(nonce_value.as_object()) || is<JS::DataView>(nonce_value.as_object())))
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "BufferSource");
auto const nonce = TRY_OR_THROW_OOM(vm, WebIDL::get_buffer_source_copy(nonce_value.as_object()));

auto const extract_u32_value = [&](auto const& name) -> JS::ThrowCompletionOr<WebIDL::UnsignedLong> {
if (!MUST(object.has_property(name))) {
return vm.throw_completion<JS::TypeError>(JS::ErrorType::MissingRequiredProperty, name);
}
auto key_value = TRY(object.get(name));
return WebIDL::convert_to_int<WebIDL::UnsignedLong>(vm, key_value, WebIDL::EnforceRange::Yes, WebIDL::Clamp::No);
};

auto const parallelism = TRY(extract_u32_value("parallelism"_utf16_fly_string));
auto const memory = TRY(extract_u32_value("memory"_utf16_fly_string));
auto const passes = TRY(extract_u32_value("passes"_utf16_fly_string));

auto maybe_version = Optional<u8> {};
if (MUST(object.has_property("version"_utf16_fly_string))) {
auto version_value = TRY(object.get("version"_utf16_fly_string));
maybe_version = TRY(WebIDL::convert_to_int<WebIDL::Octet>(vm, version_value, WebIDL::EnforceRange::Yes, WebIDL::Clamp::No));
}

auto const extract_optional_buffer_source_value = [&](auto const& name) -> JS::ThrowCompletionOr<Optional<ByteBuffer>> {
auto maybe_buffer = Optional<ByteBuffer> {};
if (MUST(object.has_property(name))) {
auto key_value = TRY(object.get(name));
if (!key_value.is_object() || !(is<JS::TypedArrayBase>(key_value.as_object()) || is<JS::ArrayBuffer>(key_value.as_object()) || is<JS::DataView>(key_value.as_object())))
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "BufferSource");
maybe_buffer = TRY_OR_THROW_OOM(vm, WebIDL::get_buffer_source_copy(key_value.as_object()));
}
return maybe_buffer;
};

auto const secret_value = TRY(extract_optional_buffer_source_value("secretValue"_utf16_fly_string));
auto const associated_data = TRY(extract_optional_buffer_source_value("associatedData"_utf16_fly_string));

return adopt_own<AlgorithmParams>(*new Argon2Params { nonce, parallelism, memory, passes, maybe_version, secret_value, associated_data });
}

// https://w3c.github.io/webcrypto/#rsa-oaep-operations
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> RSAOAEP::encrypt(AlgorithmParams const& params, GC::Ref<CryptoKey> key, ByteBuffer const& plaintext)
{
Expand Down Expand Up @@ -8290,4 +8341,124 @@ WebIDL::ExceptionOr<JS::Value> HMAC::get_key_length(AlgorithmParams const& param
return JS::Value(length);
}

// https://wicg.github.io/webcrypto-modern-algos/#argon2-operations-import-key
WebIDL::ExceptionOr<GC::Ref<CryptoKey>> Argon2::import_key(AlgorithmParams const& params, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector<Bindings::KeyUsage> const& usages)
{
// 1. Let keyData be the key data to be imported.
// 2. If format is not "raw-secret", throw a NotSupportedError
if (format != Bindings::KeyFormat::RawSecret)
return WebIDL::NotSupportedError::create(m_realm, "Invalid key format"_utf16);

// 3. If usages contains a value that is not "deriveKey" or "deriveBits", then throw a SyntaxError.
for (auto const usage : usages) {
if (!(usage == Bindings::KeyUsage::Derivekey || usage == Bindings::KeyUsage::Derivebits))
return WebIDL::SyntaxError::create(m_realm, "Invalid key usage"_utf16);
}

// 4. If extractable is not false, then throw a SyntaxError.
if (extractable) {
return WebIDL::SyntaxError::create(m_realm, "Key cannot be extractable"_utf16);
}

// 5. Let key be a new CryptoKey representing keyData.
auto key = CryptoKey::create(m_realm, key_data);

// 6. Set the [[type]] internal slot of key to "secret".
key->set_type(Bindings::KeyType::Secret);

// 7. Set the [[extractable]] internal slot of key to false.
key->set_extractable(false);

// 8. Let algorithm be a new KeyAlgorithm object.
auto algorithm = KeyAlgorithm::create(m_realm);

// 9. Set the name attribute of algorithm to the name member of normalizedAlgorithm.
algorithm->set_name(params.name);

// 10. Set the [[algorithm]] internal slot of key to algorithm.
key->set_algorithm(algorithm);

// 11. Return key.
return key;
}

// https://wicg.github.io/webcrypto-modern-algos/#argon2-operations-derive-bits
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> Argon2::derive_bits(AlgorithmParams const& params, GC::Ref<CryptoKey> key, Optional<u32> length)
{
auto const& normalized_algorithm = static_cast<Argon2Params const&>(params);
// 1. If length is null, or is less than 32 (4*8), then throw an OperationError.
if (!length.has_value() || length.value() < 32u)
return WebIDL::OperationError::create(m_realm, "Length must be equal or greater than 32"_utf16);

// 2. If the version member of normalizedAlgorithm is present and is not 19 (0x13), then throw an OperationError.
if (normalized_algorithm.version.has_value() && normalized_algorithm.version != 19)
return WebIDL::OperationError::create(m_realm, "Invalid algorithm version"_utf16);

// 3. If the parallelism member of normalizedAlgorithm is zero, or greater than 16777215 (2^24-1), then throw an OperationError.
if (normalized_algorithm.parallelism == 0 || normalized_algorithm.parallelism > (1 << 24) - 1)
return WebIDL::OperationError::create(m_realm, "Invalid parallelism"_utf16);

// 4. If the memory member of normalizedAlgorithm is less than 8 times the parallelism member of normalizedAlgorithm, then throw an OperationError.
if (normalized_algorithm.memory < 8 * normalized_algorithm.parallelism)
return WebIDL::OperationError::create(m_realm, "Memory is too low for the parallelism level"_utf16);

// 5. If the passes member of normalizedAlgorithm is zero, then throw an OperationError.
if (normalized_algorithm.passes == 0)
return WebIDL::OperationError::create(m_realm, "Invalid passes"_utf16);

auto const algorithm = [&]() {
// 6 => If the name member of normalizedAlgorithm is a case-sensitive string match for "Argon2d":
// Let type be 0.
if (normalized_algorithm.name.equals_ignoring_ascii_case("Argon2d"_string))
return ::Crypto::Hash::Argon2("ARGON2d");
// => If the name member of normalizedAlgorithm is a case-sensitive string match for "Argon2i":
// Let type be 1.
if (normalized_algorithm.name.equals_ignoring_ascii_case("Argon2i"_string))
return ::Crypto::Hash::Argon2("ARGON2i");
// => If the name member of normalizedAlgorithm is a case-sensitive string match for "Argon2id":
// Let type be 2.
if (normalized_algorithm.name.equals_ignoring_ascii_case("Argon2id"_string))
return ::Crypto::Hash::Argon2("ARGON2id");

VERIFY_NOT_REACHED();
}();

// 7. Let secretValue be the secretValue member of normalizedAlgorithm, if present.
auto const& secret_value = normalized_algorithm.secret_value;

// 8. Let associatedData be the associatedData member of normalizedAlgorithm, if present.
auto const& associated_data = normalized_algorithm.associated_data;

// 9. Let result be the result of performing the Argon2 function defined in Section 3 of [RFC9106] using the
// password represented by [[handle]] internal slot of key as the message, P, the nonce attribute of
// normalizedAlgorithm as the nonce, S, the value of the parallelism attribute of normalizedAlgorithm as the
// degree of parallelism, p, the value of the memory attribute of normalizedAlgorithm as the memory size, m, the
// value of the passes attribute of normalizedAlgorithm as the number of passes, t, 0x13 as the version number,
// v, secretValue (if present) as the secret value, K, associatedData (if present) as the associated data, X, type
// as the type, y, and length divided by 8 as the tag length, T.
VERIFY(key->handle().has<ByteBuffer>());
auto const maybe_result = algorithm.derive_key(
key->handle().get<ByteBuffer>(),
normalized_algorithm.nonce,
normalized_algorithm.parallelism,
normalized_algorithm.memory,
normalized_algorithm.passes,
0x13,
secret_value,
associated_data,
length.value() / 8);

// 10. If the key derivation operation fails, then throw an OperationError.
if (maybe_result.is_error())
return WebIDL::OperationError::create(m_realm, Utf16String::formatted("Hashing function failed: {}", maybe_result.error()));

return JS::ArrayBuffer::create(m_realm, maybe_result.value());
}

// https://wicg.github.io/webcrypto-modern-algos/#argon2-operations-get-key-length
WebIDL::ExceptionOr<JS::Value> Argon2::get_key_length(AlgorithmParams const&)
{
return JS::js_null();
}

}
41 changes: 41 additions & 0 deletions Libraries/LibWeb/Crypto/CryptoAlgorithms.h
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,21 @@ class HMAC : public AlgorithmMethods {
}
};

class Argon2 : public AlgorithmMethods {
public:
virtual WebIDL::ExceptionOr<GC::Ref<CryptoKey>> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector<Bindings::KeyUsage> const&) override;
virtual WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> derive_bits(AlgorithmParams const&, GC::Ref<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 Argon2(realm)); }

private:
explicit Argon2(JS::Realm& realm)
: AlgorithmMethods(realm)
{
}
};

struct EcdhKeyDeriveParams : public AlgorithmParams {
virtual ~EcdhKeyDeriveParams() override;

Expand Down Expand Up @@ -725,6 +740,32 @@ struct Ed448Params : public AlgorithmParams {
static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
};

// https://wicg.github.io/webcrypto-modern-algos/#argon2-params
struct Argon2Params : public AlgorithmParams {
virtual ~Argon2Params() override;

Argon2Params(ByteBuffer nonce, u32 parallelism, u32 memory, u32 passes, Optional<u8> version, Optional<ByteBuffer> secret_value, Optional<ByteBuffer> associated_data)
: nonce(move(nonce))
, parallelism(parallelism)
, memory(memory)
, passes(passes)
, version(version)
, secret_value(secret_value)
, associated_data(associated_data)
{
}

ByteBuffer nonce;
u32 parallelism;
u32 memory;
u32 passes;
Optional<u8> version;
Optional<ByteBuffer> secret_value;
Optional<ByteBuffer> associated_data;

static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
};

ErrorOr<String> base64_url_uint_encode(::Crypto::UnsignedBigInteger);
WebIDL::ExceptionOr<ByteBuffer> base64_url_bytes_decode(JS::Realm&, String const& base64_url_string);
WebIDL::ExceptionOr<::Crypto::UnsignedBigInteger> base64_url_uint_decode(JS::Realm&, String const& base64_url_string);
Expand Down
7 changes: 7 additions & 0 deletions Libraries/LibWeb/Crypto/SubtleCrypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,13 @@ SupportedAlgorithmsMap const& supported_algorithms()
define_an_algorithm<ED448>("importKey"_string, "Ed448"_string);
define_an_algorithm<ED448>("exportKey"_string, "Ed448"_string);

// https://wicg.github.io/webcrypto-modern-algos/#argon2-registration
for (auto const& algorithm : { "Argon2d"_string, "Argon2i"_string, "Argon2id"_string }) {
define_an_algorithm<Argon2>("importKey"_string, algorithm);
define_an_algorithm<Argon2, Argon2Params>("deriveBits"_string, algorithm);
define_an_algorithm<Argon2>("get key length"_string, algorithm);
}

return internal_object;
}

Expand Down
Loading
Loading