1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

LibCrypto: Refactor HMAC implementations with OpenSSL

This commit is contained in:
devgianlu 2025-02-23 12:10:27 +01:00 committed by Ali Mohammad Pur
parent c5d0af54d0
commit 80fe259dab
Notes: github-actions[bot] 2025-03-02 14:12:54 +00:00
7 changed files with 153 additions and 123 deletions

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCrypto/Authentication/HMAC.h>
#include <openssl/core_names.h>
#include <openssl/evp.h>
namespace Crypto::Authentication {
HMAC::HMAC(Hash::HashKind hash_kind, ReadonlyBytes key)
: m_hash_kind(hash_kind)
, m_key(key)
, m_mac(EVP_MAC_fetch(nullptr, "HMAC", nullptr))
{
reset();
}
HMAC::~HMAC()
{
EVP_MAC_free(m_mac);
EVP_MAC_CTX_free(m_ctx);
}
size_t HMAC::digest_size() const
{
return EVP_MAC_CTX_get_mac_size(m_ctx);
}
void HMAC::update(u8 const* message, size_t length)
{
if (EVP_MAC_update(m_ctx, message, length) != 1) {
VERIFY_NOT_REACHED();
}
}
ByteBuffer HMAC::digest()
{
auto buf = MUST(ByteBuffer::create_uninitialized(digest_size()));
auto size = digest_size();
if (EVP_MAC_final(m_ctx, buf.data(), &size, size) != 1) {
VERIFY_NOT_REACHED();
}
return MUST(buf.slice(0, size));
}
void HMAC::reset()
{
EVP_MAC_CTX_free(m_ctx);
m_ctx = EVP_MAC_CTX_new(m_mac);
auto hash_name = MUST(hash_kind_to_openssl_digest_name(m_hash_kind));
OSSL_PARAM params[] = {
OSSL_PARAM_utf8_string(OSSL_MAC_PARAM_DIGEST, const_cast<char*>(hash_name.characters_without_null_termination()), hash_name.length()),
OSSL_PARAM_END
};
if (EVP_MAC_init(m_ctx, m_key.data(), m_key.size(), params) != 1) {
VERIFY_NOT_REACHED();
}
}
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,110 +9,51 @@
#include <AK/ByteBuffer.h>
#include <AK/ByteString.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include <AK/Vector.h>
constexpr static auto IPAD = 0x36;
constexpr static auto OPAD = 0x5c;
#include <LibCrypto/Hash/HashManager.h>
#include <LibCrypto/OpenSSL.h>
#include <LibCrypto/OpenSSLForward.h>
namespace Crypto::Authentication {
template<typename HashT>
class HMAC {
public:
using HashType = HashT;
using TagType = typename HashType::DigestType;
explicit HMAC(Hash::HashKind hash, ReadonlyBytes key);
~HMAC();
size_t digest_size() const { return m_inner_hasher->digest_size(); }
size_t digest_size() const;
template<typename KeyBufferType, typename... Args>
HMAC(KeyBufferType key, Args... args)
: m_inner_hasher(move(HashT::create(args...)))
, m_outer_hasher(move(HashT::create(args...)))
{
derive_key(key);
reset();
}
void update(u8 const* message, size_t length);
void update(ReadonlyBytes span) { return update(span.data(), span.size()); }
void update(StringView string) { return update((u8 const*)string.characters_without_null_termination(), string.length()); }
TagType process(u8 const* message, size_t length)
ByteBuffer process(u8 const* message, size_t length)
{
reset();
update(message, length);
return digest();
}
ByteBuffer process(ReadonlyBytes span) { return process(span.data(), span.size()); }
ByteBuffer process(StringView string) { return process((u8 const*)string.characters_without_null_termination(), string.length()); }
void update(u8 const* message, size_t length)
{
m_inner_hasher->update(message, length);
}
ByteBuffer digest();
TagType process(ReadonlyBytes span) { return process(span.data(), span.size()); }
TagType process(StringView string) { return process((u8 const*)string.characters_without_null_termination(), string.length()); }
void update(ReadonlyBytes span) { return update(span.data(), span.size()); }
void update(StringView string) { return update((u8 const*)string.characters_without_null_termination(), string.length()); }
TagType digest()
{
m_outer_hasher->update(m_inner_hasher->digest().immutable_data(), m_inner_hasher->digest_size());
auto result = m_outer_hasher->digest();
reset();
return result;
}
void reset()
{
m_inner_hasher->reset();
m_outer_hasher->reset();
m_inner_hasher->update(m_key_data, m_inner_hasher->block_size());
m_outer_hasher->update(m_key_data + m_inner_hasher->block_size(), m_outer_hasher->block_size());
}
void reset();
ByteString class_name() const
{
auto hash_name = MUST(hash_kind_to_openssl_digest_name(m_hash_kind));
StringBuilder builder;
builder.append("HMAC-"sv);
builder.append(m_inner_hasher->class_name());
builder.append(hash_name);
return builder.to_byte_string();
}
private:
void derive_key(u8 const* key, size_t length)
{
auto block_size = m_inner_hasher->block_size();
// Note: The block size of all the current hash functions is 512 bits.
Vector<u8, 64> v_key;
v_key.resize(block_size);
auto key_buffer = v_key.span();
// m_key_data is zero'd, so copying the data in
// the first few bytes leaves the rest zero, which
// is exactly what we want (zero padding)
if (length > block_size) {
m_inner_hasher->update(key, length);
auto digest = m_inner_hasher->digest();
// FIXME: should we check if the hash function creates more data than its block size?
key_buffer.overwrite(0, digest.immutable_data(), m_inner_hasher->digest_size());
} else if (length > 0) {
key_buffer.overwrite(0, key, length);
}
// fill out the inner and outer padded keys
auto* i_key = m_key_data;
auto* o_key = m_key_data + block_size;
for (size_t i = 0; i < block_size; ++i) {
auto key_byte = key_buffer[i];
i_key[i] = key_byte ^ IPAD;
o_key[i] = key_byte ^ OPAD;
}
}
void derive_key(ReadonlyBytes key) { derive_key(key.data(), key.size()); }
void derive_key(StringView key) { derive_key(key.bytes()); }
NonnullOwnPtr<HashType> m_inner_hasher, m_outer_hasher;
u8 m_key_data[2048];
Hash::HashKind m_hash_kind;
ReadonlyBytes m_key;
EVP_MAC* m_mac { nullptr };
EVP_MAC_CTX* m_ctx { nullptr };
};
}

View file

@ -6,6 +6,7 @@ set(SOURCES
ASN1/DER.cpp
ASN1/PEM.cpp
Authentication/GHash.cpp
Authentication/HMAC.cpp
BigFraction/BigFraction.cpp
BigInt/Algorithms/BitwiseOperations.cpp
BigInt/Algorithms/Division.cpp

View file

@ -47,6 +47,8 @@ ErrorOr<UnsignedBigInteger> openssl_bignum_to_unsigned_big_integer(OpenSSL_BN co
ErrorOr<StringView> hash_kind_to_openssl_digest_name(Hash::HashKind hash)
{
switch (hash) {
case Hash::HashKind::MD5:
return "MD5"sv;
case Hash::HashKind::SHA1:
return "SHA1"sv;
case Hash::HashKind::SHA256:

View file

@ -15,6 +15,8 @@ typedef struct evp_pkey_st EVP_PKEY;
typedef struct evp_pkey_ctx_st EVP_PKEY_CTX;
typedef struct evp_kdf_st EVP_KDF;
typedef struct evp_kdf_ctx_st EVP_KDF_CTX;
typedef struct evp_mac_st EVP_MAC;
typedef struct evp_mac_ctx_st EVP_MAC_CTX;
void ERR_print_errors_cb(int (*cb)(char const* str, size_t len, void* u), void* u);

View file

@ -7781,21 +7781,21 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> X448::import_key(
static WebIDL::ExceptionOr<ByteBuffer> hmac_calculate_message_digest(JS::Realm& realm, GC::Ptr<KeyAlgorithm> hash, ReadonlyBytes key, ReadonlyBytes message)
{
auto calculate_digest = [&]<typename T>() -> ByteBuffer {
::Crypto::Authentication::HMAC<T> hmac(key);
auto digest = hmac.process(message);
return MUST(ByteBuffer::copy(digest.bytes()));
};
auto hash_name = hash->name();
if (hash_name == "SHA-1")
return calculate_digest.operator()<::Crypto::Hash::SHA1>();
if (hash_name == "SHA-256")
return calculate_digest.operator()<::Crypto::Hash::SHA256>();
if (hash_name == "SHA-384")
return calculate_digest.operator()<::Crypto::Hash::SHA384>();
if (hash_name == "SHA-512")
return calculate_digest.operator()<::Crypto::Hash::SHA512>();
return WebIDL::NotSupportedError::create(realm, "Invalid algorithm"_string);
auto hash_kind = TRY([&] -> WebIDL::ExceptionOr<::Crypto::Hash::HashKind> {
if (hash_name == "SHA-1")
return ::Crypto::Hash::HashKind::SHA1;
if (hash_name == "SHA-256")
return ::Crypto::Hash::HashKind::SHA256;
if (hash_name == "SHA-384")
return ::Crypto::Hash::HashKind::SHA384;
if (hash_name == "SHA-512")
return ::Crypto::Hash::HashKind::SHA512;
return WebIDL::NotSupportedError::create(realm, MUST(String::formatted("Invalid hash function '{}'", hash_name)));
}());
::Crypto::Authentication::HMAC hmac(hash_kind, key);
return hmac.process(message);
}
static WebIDL::ExceptionOr<WebIDL::UnsignedLong> hmac_hash_block_size(JS::Realm& realm, HashAlgorithmIdentifier hash)

View file

@ -1,52 +1,58 @@
/*
* Copyright (c) 2021, [your name here] <[your email here]>
* Copyright (c) 2021, the Ladybird developers.
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCrypto/Authentication/HMAC.h>
#include <LibCrypto/Hash/MD5.h>
#include <LibCrypto/Hash/SHA1.h>
#include <LibCrypto/Hash/SHA2.h>
#include <LibTest/TestCase.h>
#include <cstring>
static ByteBuffer operator""_b(char const* string, size_t length)
{
return MUST(ByteBuffer::copy(string, length));
}
TEST_CASE(test_hmac_md5_name)
{
Crypto::Authentication::HMAC<Crypto::Hash::MD5> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::MD5, key);
EXPECT_EQ(hmac.class_name(), "HMAC-MD5"sv);
}
TEST_CASE(test_hmac_md5_process)
{
Crypto::Authentication::HMAC<Crypto::Hash::MD5> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::MD5, key);
u8 result[] {
0x3b, 0x5b, 0xde, 0x30, 0x3a, 0x54, 0x7b, 0xbb, 0x09, 0xfe, 0x78, 0x89, 0xbc, 0x9f, 0x22, 0xa3
};
auto mac = hmac.process("Some bogus data"sv);
EXPECT(memcmp(result, mac.data, hmac.digest_size()) == 0);
EXPECT(memcmp(result, mac.data(), hmac.digest_size()) == 0);
}
TEST_CASE(test_hmac_md5_process_reuse)
{
Crypto::Authentication::HMAC<Crypto::Hash::MD5> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::MD5, key);
auto mac_0 = hmac.process("Some bogus data"sv);
auto mac_1 = hmac.process("Some bogus data"sv);
EXPECT(memcmp(mac_0.data, mac_1.data, hmac.digest_size()) == 0);
EXPECT(memcmp(mac_0.data(), mac_1.data(), hmac.digest_size()) == 0);
}
TEST_CASE(test_hmac_sha1_name)
{
Crypto::Authentication::HMAC<Crypto::Hash::SHA1> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA1, key);
EXPECT_EQ(hmac.class_name(), "HMAC-SHA1"sv);
}
TEST_CASE(test_hmac_sha1_process)
{
u8 key[] { 0xc8, 0x52, 0xe5, 0x4a, 0x2c, 0x03, 0x2b, 0xc9, 0x63, 0xd3, 0xc2, 0x79, 0x0f, 0x76, 0x43, 0xef, 0x36, 0xc3, 0x7a, 0xca };
Crypto::Authentication::HMAC<Crypto::Hash::SHA1> hmac(ReadonlyBytes { key, sizeof(key) });
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA1, ReadonlyBytes { key, sizeof(key) });
u8 result[] {
0x2c, 0x57, 0x32, 0x61, 0x3b, 0xa7, 0x84, 0x87, 0x0e, 0x4f, 0x42, 0x07, 0x2f, 0xf0, 0xe7, 0x41, 0xd7, 0x15, 0xf4, 0x56
};
@ -54,13 +60,13 @@ TEST_CASE(test_hmac_sha1_process)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x03, 0x03, 0x00, 0x10, 0x14, 0x00, 0x00, 0x0c, 0xa1, 0x91, 0x1a, 0x20, 0x59, 0xb5, 0x45, 0xa9, 0xb4, 0xad, 0x75, 0x3e
};
auto mac = hmac.process(value, 29);
EXPECT(memcmp(result, mac.data, hmac.digest_size()) == 0);
EXPECT(memcmp(result, mac.data(), hmac.digest_size()) == 0);
}
TEST_CASE(test_hmac_sha1_process_reuse)
{
u8 key[] { 0xc8, 0x52, 0xe5, 0x4a, 0x2c, 0x03, 0x2b, 0xc9, 0x63, 0xd3, 0xc2, 0x79, 0x0f, 0x76, 0x43, 0xef, 0x36, 0xc3, 0x7a, 0xca };
Crypto::Authentication::HMAC<Crypto::Hash::SHA1> hmac(ReadonlyBytes { key, sizeof(key) });
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA1, ReadonlyBytes { key, sizeof(key) });
u8 result[] {
0x2c, 0x57, 0x32, 0x61, 0x3b, 0xa7, 0x84, 0x87, 0x0e, 0x4f, 0x42, 0x07, 0x2f, 0xf0, 0xe7, 0x41, 0xd7, 0x15, 0xf4, 0x56
};
@ -71,77 +77,85 @@ TEST_CASE(test_hmac_sha1_process_reuse)
hmac.update(value + 8, 5);
hmac.update(value + 13, 16);
auto mac = hmac.digest();
EXPECT(memcmp(result, mac.data, hmac.digest_size()) == 0);
EXPECT(memcmp(result, mac.data(), hmac.digest_size()) == 0);
}
TEST_CASE(test_hmac_sha256_name)
{
Crypto::Authentication::HMAC<Crypto::Hash::SHA256> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA256, key);
EXPECT_EQ(hmac.class_name(), "HMAC-SHA256"sv);
}
TEST_CASE(test_hmac_sha256_process)
{
Crypto::Authentication::HMAC<Crypto::Hash::SHA256> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA256, key);
u8 result[] {
0x1a, 0xf2, 0x20, 0x62, 0xde, 0x3b, 0x84, 0x65, 0xc1, 0x25, 0x23, 0x99, 0x76, 0x15, 0x1b, 0xec, 0x15, 0x21, 0x82, 0x1f, 0x23, 0xca, 0x11, 0x66, 0xdd, 0x8c, 0x6e, 0xf1, 0x81, 0x3b, 0x7f, 0x1b
};
auto mac = hmac.process("Some bogus data"sv);
EXPECT(memcmp(result, mac.data, hmac.digest_size()) == 0);
EXPECT(memcmp(result, mac.data(), hmac.digest_size()) == 0);
}
TEST_CASE(test_hmac_sha256_reuse)
{
Crypto::Authentication::HMAC<Crypto::Hash::SHA256> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA256, key);
auto mac_0 = hmac.process("Some bogus data"sv);
auto mac_1 = hmac.process("Some bogus data"sv);
EXPECT(memcmp(mac_0.data, mac_1.data, hmac.digest_size()) == 0);
EXPECT(memcmp(mac_0.data(), mac_1.data(), hmac.digest_size()) == 0);
}
TEST_CASE(test_hmac_sha256_data_is_same_size_as_block)
{
Crypto::Authentication::HMAC<Crypto::Hash::SHA256> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA256, key);
u8 result[] = {
0x1d, 0x90, 0xce, 0x68, 0x45, 0x0b, 0xba, 0xd6, 0xbe, 0x1c, 0xb2, 0x3a, 0xea, 0x7f, 0xac, 0x4b, 0x68, 0x08, 0xa4, 0x77, 0x81, 0x2a, 0xad, 0x5d, 0x05, 0xe2, 0x15, 0xe8, 0xf4, 0xcb, 0x06, 0xaf
};
auto mac = hmac.process("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"sv);
EXPECT(memcmp(result, mac.data, hmac.digest_size()) == 0);
EXPECT(memcmp(result, mac.data(), hmac.digest_size()) == 0);
}
TEST_CASE(test_hmac_sha256_data_is_bigger_size_as_block)
{
Crypto::Authentication::HMAC<Crypto::Hash::SHA256> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA256, key);
u8 result[] = {
0x9b, 0xa3, 0x9e, 0xf3, 0xb4, 0x30, 0x5f, 0x6f, 0x67, 0xd0, 0xa8, 0xb0, 0xf0, 0xcb, 0x12, 0xf5, 0x85, 0xe2, 0x19, 0xba, 0x0c, 0x8b, 0xe5, 0x43, 0xf0, 0x93, 0x39, 0xa8, 0xa3, 0x07, 0xf1, 0x95
};
auto mac = hmac.process("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"sv);
EXPECT(memcmp(result, mac.data, hmac.digest_size()) == 0);
EXPECT(memcmp(result, mac.data(), hmac.digest_size()) == 0);
}
TEST_CASE(test_hmac_sha512_name)
{
Crypto::Authentication::HMAC<Crypto::Hash::SHA512> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA512, key);
EXPECT_EQ(hmac.class_name(), "HMAC-SHA512");
}
TEST_CASE(test_hmac_sha512_process)
{
Crypto::Authentication::HMAC<Crypto::Hash::SHA512> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA512, key);
u8 result[] {
0xeb, 0xa8, 0x34, 0x11, 0xfd, 0x5b, 0x46, 0x5b, 0xef, 0xbb, 0x67, 0x5e, 0x7d, 0xc2, 0x7c, 0x2c, 0x6b, 0xe1, 0xcf, 0xe6, 0xc7, 0xe4, 0x7d, 0xeb, 0xca, 0x97, 0xb7, 0x4c, 0xd3, 0x4d, 0x6f, 0x08, 0x9f, 0x0d, 0x3a, 0xf1, 0xcb, 0x00, 0x79, 0x78, 0x2f, 0x05, 0x8e, 0xeb, 0x94, 0x48, 0x0d, 0x50, 0x64, 0x3b, 0xca, 0x70, 0xe2, 0x69, 0x38, 0x4f, 0xe4, 0xb0, 0x49, 0x0f, 0xc5, 0x4c, 0x7a, 0xa7
};
auto mac = hmac.process("Some bogus data"sv);
EXPECT(memcmp(result, mac.data, hmac.digest_size()) == 0);
EXPECT(memcmp(result, mac.data(), hmac.digest_size()) == 0);
}
TEST_CASE(test_hmac_sha512_reuse)
{
Crypto::Authentication::HMAC<Crypto::Hash::SHA512> hmac("Well Hello Friends"sv);
auto key = "Well Hello Friends"_b;
Crypto::Authentication::HMAC hmac(Crypto::Hash::HashKind::SHA512, key);
auto mac_0 = hmac.process("Some bogus data"sv);
auto mac_1 = hmac.process("Some bogus data"sv);
EXPECT(memcmp(mac_0.data, mac_1.data, hmac.digest_size()) == 0);
EXPECT(memcmp(mac_0.data(), mac_1.data(), hmac.digest_size()) == 0);
}