From 80fe259dab77269e52db62a8c8d80f81f634b623 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Sun, 23 Feb 2025 12:10:27 +0100 Subject: [PATCH] LibCrypto: Refactor `HMAC` implementations with OpenSSL --- Libraries/LibCrypto/Authentication/HMAC.cpp | 69 +++++++++++++ Libraries/LibCrypto/Authentication/HMAC.h | 102 ++++--------------- Libraries/LibCrypto/CMakeLists.txt | 1 + Libraries/LibCrypto/OpenSSL.cpp | 2 + Libraries/LibCrypto/OpenSSLForward.h | 2 + Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 28 ++--- Tests/LibCrypto/TestHMAC.cpp | 72 +++++++------ 7 files changed, 153 insertions(+), 123 deletions(-) create mode 100644 Libraries/LibCrypto/Authentication/HMAC.cpp diff --git a/Libraries/LibCrypto/Authentication/HMAC.cpp b/Libraries/LibCrypto/Authentication/HMAC.cpp new file mode 100644 index 00000000000..3fbf470a1d8 --- /dev/null +++ b/Libraries/LibCrypto/Authentication/HMAC.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025, Altomani Gianluca + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include + +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(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(); + } +} + +} diff --git a/Libraries/LibCrypto/Authentication/HMAC.h b/Libraries/LibCrypto/Authentication/HMAC.h index 3409db84786..d6065cb095e 100644 --- a/Libraries/LibCrypto/Authentication/HMAC.h +++ b/Libraries/LibCrypto/Authentication/HMAC.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Ali Mohammad Pur + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,110 +9,51 @@ #include #include -#include -#include -#include -#include - -constexpr static auto IPAD = 0x36; -constexpr static auto OPAD = 0x5c; +#include +#include +#include namespace Crypto::Authentication { -template 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 - 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 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 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 }; }; } diff --git a/Libraries/LibCrypto/CMakeLists.txt b/Libraries/LibCrypto/CMakeLists.txt index 7f65b3111c9..257e1619c31 100644 --- a/Libraries/LibCrypto/CMakeLists.txt +++ b/Libraries/LibCrypto/CMakeLists.txt @@ -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 diff --git a/Libraries/LibCrypto/OpenSSL.cpp b/Libraries/LibCrypto/OpenSSL.cpp index dddf321992f..8cccd6294ba 100644 --- a/Libraries/LibCrypto/OpenSSL.cpp +++ b/Libraries/LibCrypto/OpenSSL.cpp @@ -47,6 +47,8 @@ ErrorOr openssl_bignum_to_unsigned_big_integer(OpenSSL_BN co ErrorOr 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: diff --git a/Libraries/LibCrypto/OpenSSLForward.h b/Libraries/LibCrypto/OpenSSLForward.h index 14167d54f94..129d1d36bed 100644 --- a/Libraries/LibCrypto/OpenSSLForward.h +++ b/Libraries/LibCrypto/OpenSSLForward.h @@ -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); diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 95330956d82..d0e3f629413 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -7781,21 +7781,21 @@ WebIDL::ExceptionOr> X448::import_key( static WebIDL::ExceptionOr hmac_calculate_message_digest(JS::Realm& realm, GC::Ptr hash, ReadonlyBytes key, ReadonlyBytes message) { - auto calculate_digest = [&]() -> ByteBuffer { - ::Crypto::Authentication::HMAC 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 hmac_hash_block_size(JS::Realm& realm, HashAlgorithmIdentifier hash) diff --git a/Tests/LibCrypto/TestHMAC.cpp b/Tests/LibCrypto/TestHMAC.cpp index ec655538671..a7b2b841583 100644 --- a/Tests/LibCrypto/TestHMAC.cpp +++ b/Tests/LibCrypto/TestHMAC.cpp @@ -1,52 +1,58 @@ /* - * Copyright (c) 2021, [your name here] <[your email here]> + * Copyright (c) 2021, the Ladybird developers. + * Copyright (c) 2025, Altomani Gianluca * * SPDX-License-Identifier: BSD-2-Clause */ #include -#include -#include -#include #include -#include + +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 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 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 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 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 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 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 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 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 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 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 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 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 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 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); }