diff --git a/Meta/Lagom/Fuzzers/FuzzBLAKE2b.cpp b/Meta/Lagom/Fuzzers/FuzzBLAKE2b.cpp new file mode 100644 index 00000000000..0963ad294b4 --- /dev/null +++ b/Meta/Lagom/Fuzzers/FuzzBLAKE2b.cpp @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2023, the SerenityOS developers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) +{ + Crypto::Hash::BLAKE2b::hash(data, size); + return 0; +} diff --git a/Meta/Lagom/Fuzzers/fuzzers.cmake b/Meta/Lagom/Fuzzers/fuzzers.cmake index 28d15aceaac..c2aaaf91612 100644 --- a/Meta/Lagom/Fuzzers/fuzzers.cmake +++ b/Meta/Lagom/Fuzzers/fuzzers.cmake @@ -1,5 +1,6 @@ set(FUZZER_TARGETS ASN1 + BLAKE2b BMPLoader Brotli CyrillicDecoder @@ -70,6 +71,7 @@ if (TARGET LibWeb) endif() set(FUZZER_DEPENDENCIES_ASN1 LibCrypto LibTLS) +set(FUZZER_DEPENDENCIES_BLAKE2b LibCrypto) set(FUZZER_DEPENDENCIES_BMPLoader LibGfx) set(FUZZER_DEPENDENCIES_Brotli LibCompress) set(FUZZER_DEPENDENCIES_CSSParser LibWeb) diff --git a/Tests/LibCrypto/TestHash.cpp b/Tests/LibCrypto/TestHash.cpp index 25e14cf540a..b06f1796020 100644 --- a/Tests/LibCrypto/TestHash.cpp +++ b/Tests/LibCrypto/TestHash.cpp @@ -6,12 +6,69 @@ #include #include +#include #include #include #include #include #include +TEST_CASE(test_BLAKE2b_name) +{ + Crypto::Hash::BLAKE2b blake2b; + EXPECT_EQ(blake2b.class_name(), "BLAKE2b"sv); +} + +TEST_CASE(test_BLAKE2b_hash_string) +{ + u8 result[] { + 0x9d, 0xaa, 0x2e, 0x57, 0xc4, 0x94, 0xb6, 0xfd, 0x61, 0x6e, 0x39, 0x0b, 0x71, 0xf4, 0x19, 0x03, 0x41, 0x5c, 0x5c, 0x61, 0x7e, 0x30, 0x0a, 0xf0, 0x0b, 0x3e, 0x9c, 0x77, 0x23, 0x1f, 0x11, 0x4d, 0x83, 0x9d, 0xd6, 0xe0, 0x4a, 0x92, 0x19, 0xae, 0xec, 0xc9, 0x13, 0x57, 0xc6, 0xf1, 0x06, 0x92, 0xb9, 0xf9, 0x97, 0x3e, 0xfd, 0xb3, 0x6f, 0xc8, 0xe1, 0x94, 0xad, 0x8e, 0x33, 0xc2, 0x66, 0x3f + }; + auto digest = Crypto::Hash::BLAKE2b::hash("Well hello friends"sv); + EXPECT(memcmp(result, digest.data, Crypto::Hash::BLAKE2b::digest_size()) == 0); +} + +TEST_CASE(test_BLAKE2b_hash_empty_string) +{ + u8 result[] { + 0x78, 0x6a, 0x02, 0xf7, 0x42, 0x01, 0x59, 0x03, 0xc6, 0xc6, 0xfd, 0x85, 0x25, 0x52, 0xd2, 0x72, 0x91, 0x2f, 0x47, 0x40, 0xe1, 0x58, 0x47, 0x61, 0x8a, 0x86, 0xe2, 0x17, 0xf7, 0x1f, 0x54, 0x19, 0xd2, 0x5e, 0x10, 0x31, 0xaf, 0xee, 0x58, 0x53, 0x13, 0x89, 0x64, 0x44, 0x93, 0x4e, 0xb0, 0x4b, 0x90, 0x3a, 0x68, 0x5b, 0x14, 0x48, 0xb7, 0x55, 0xd5, 0x6f, 0x70, 0x1a, 0xfe, 0x9b, 0xe2, 0xce + }; + auto digest = Crypto::Hash::BLAKE2b::hash(""sv); + EXPECT(memcmp(result, digest.data, Crypto::Hash::BLAKE2b::digest_size()) == 0); +} + +TEST_CASE(test_BLAKE2b_consecutive_multiple_updates) +{ + u8 result[] { + 0x9d, 0xaa, 0x2e, 0x57, 0xc4, 0x94, 0xb6, 0xfd, 0x61, 0x6e, 0x39, 0x0b, 0x71, 0xf4, 0x19, 0x03, 0x41, 0x5c, 0x5c, 0x61, 0x7e, 0x30, 0x0a, 0xf0, 0x0b, 0x3e, 0x9c, 0x77, 0x23, 0x1f, 0x11, 0x4d, 0x83, 0x9d, 0xd6, 0xe0, 0x4a, 0x92, 0x19, 0xae, 0xec, 0xc9, 0x13, 0x57, 0xc6, 0xf1, 0x06, 0x92, 0xb9, 0xf9, 0x97, 0x3e, 0xfd, 0xb3, 0x6f, 0xc8, 0xe1, 0x94, 0xad, 0x8e, 0x33, 0xc2, 0x66, 0x3f + }; + Crypto::Hash::BLAKE2b blake2b; + + blake2b.update("Well"sv); + blake2b.update(" hello "sv); + blake2b.update("friends"sv); + auto digest = blake2b.digest(); + + EXPECT(memcmp(result, digest.data, Crypto::Hash::BLAKE2b::digest_size()) == 0); +} + +TEST_CASE(test_BLAKE2b_consecutive_updates_reuse) +{ + Crypto::Hash::BLAKE2b blake2b; + + blake2b.update("Well"sv); + blake2b.update(" hello "sv); + blake2b.update("friends"sv); + auto digest0 = blake2b.digest(); + + blake2b.update("Well"sv); + blake2b.update(" hello "sv); + blake2b.update("friends"sv); + auto digest1 = blake2b.digest(); + + EXPECT(memcmp(digest0.data, digest1.data, Crypto::Hash::BLAKE2b::digest_size()) == 0); +} + TEST_CASE(test_MD5_name) { Crypto::Hash::MD5 md5; diff --git a/Userland/Libraries/LibCrypto/CMakeLists.txt b/Userland/Libraries/LibCrypto/CMakeLists.txt index 290bfff83f8..184239bcc1b 100644 --- a/Userland/Libraries/LibCrypto/CMakeLists.txt +++ b/Userland/Libraries/LibCrypto/CMakeLists.txt @@ -25,6 +25,7 @@ set(SOURCES Curves/SECP256r1.cpp Curves/X25519.cpp Curves/X448.cpp + Hash/BLAKE2b.cpp Hash/MD5.cpp Hash/SHA1.cpp Hash/SHA2.cpp diff --git a/Userland/Libraries/LibCrypto/Hash/BLAKE2b.cpp b/Userland/Libraries/LibCrypto/Hash/BLAKE2b.cpp new file mode 100644 index 00000000000..d4dc3e47c79 --- /dev/null +++ b/Userland/Libraries/LibCrypto/Hash/BLAKE2b.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023, the SerenityOS developers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Crypto::Hash { +constexpr static auto ROTRIGHT(u64 a, size_t b) { return (a >> b) | (a << (64 - b)); } + +void BLAKE2b::update(u8 const* in, size_t inlen) +{ + if (inlen > 0) { + size_t left = m_internal_state.buffer_length; + size_t fill = BLAKE2bConstants::blockbytes - left; + if (inlen > fill) { + m_internal_state.buffer_length = 0; + // Fill the buffer. + __builtin_memcpy(m_internal_state.buffer + left, in, fill); + + increment_counter_by(BLAKE2bConstants::blockbytes); + transform(m_internal_state.buffer); + in += fill; + inlen -= fill; + while (inlen > BLAKE2bConstants::blockbytes) { + increment_counter_by(BLAKE2bConstants::blockbytes); + transform(in); + in += BLAKE2bConstants::blockbytes; + inlen -= BLAKE2bConstants::blockbytes; + } + } + __builtin_memcpy(m_internal_state.buffer + m_internal_state.buffer_length, in, inlen); + m_internal_state.buffer_length += inlen; + } +} + +BLAKE2b::DigestType BLAKE2b::peek() +{ + DigestType digest; + increment_counter_by(m_internal_state.buffer_length); + + // Set this as the last block + m_internal_state.is_at_last_block = UINT64_MAX; + + // Pad the buffer with zeros + __builtin_memset(m_internal_state.buffer + m_internal_state.buffer_length, 0, BLAKE2bConstants::blockbytes - m_internal_state.buffer_length); + transform(m_internal_state.buffer); + + for (size_t i = 0; i < 8; ++i) + __builtin_memcpy(&digest.data[0] + sizeof(m_internal_state.hash_state[i]) * i, &m_internal_state.hash_state[i], sizeof(m_internal_state.hash_state[i])); + + return digest; +} + +BLAKE2b::DigestType BLAKE2b::digest() +{ + auto digest = peek(); + reset(); + return digest; +} + +void BLAKE2b::increment_counter_by(const u64 amount) +{ + m_internal_state.message_byte_offset[0] += amount; + m_internal_state.message_byte_offset[1] += (m_internal_state.message_byte_offset[0] < amount); +} + +void BLAKE2b::mix(u64* work_array, u64 a, u64 b, u64 c, u64 d, u64 x, u64 y) +{ + constexpr auto rotation_constant_1 = 32; + constexpr auto rotation_constant_2 = 24; + constexpr auto rotation_constant_3 = 16; + constexpr auto rotation_constant_4 = 63; + + work_array[a] = work_array[a] + work_array[b] + x; + work_array[d] = ROTRIGHT(work_array[d] ^ work_array[a], rotation_constant_1); + work_array[c] = work_array[c] + work_array[d]; + work_array[b] = ROTRIGHT(work_array[b] ^ work_array[c], rotation_constant_2); + work_array[a] = work_array[a] + work_array[b] + y; + work_array[d] = ROTRIGHT(work_array[d] ^ work_array[a], rotation_constant_3); + work_array[c] = work_array[c] + work_array[d]; + work_array[b] = ROTRIGHT(work_array[b] ^ work_array[c], rotation_constant_4); +} + +void BLAKE2b::transform(u8 const* block) +{ + u64 m[16]; + u64 v[16]; + + for (size_t i = 0; i < 16; ++i) + m[i] = ByteReader::load64(block + i * sizeof(m[i])); + + for (size_t i = 0; i < 8; ++i) + v[i] = m_internal_state.hash_state[i]; + + v[8] = SHA512Constants::InitializationHashes[0]; + v[9] = SHA512Constants::InitializationHashes[1]; + v[10] = SHA512Constants::InitializationHashes[2]; + v[11] = SHA512Constants::InitializationHashes[3]; + v[12] = SHA512Constants::InitializationHashes[4] ^ m_internal_state.message_byte_offset[0]; + v[13] = SHA512Constants::InitializationHashes[5] ^ m_internal_state.message_byte_offset[1]; + v[14] = SHA512Constants::InitializationHashes[6] ^ m_internal_state.is_at_last_block; + v[15] = SHA512Constants::InitializationHashes[7]; + + for (size_t i = 0; i < 12; ++i) { + u64 sigma_selection[16]; + for (size_t j = 0; j < 16; ++j) + sigma_selection[j] = BLAKE2bSigma[i % 10][j]; + mix(v, 0, 4, 8, 12, m[sigma_selection[0]], m[sigma_selection[1]]); + mix(v, 1, 5, 9, 13, m[sigma_selection[2]], m[sigma_selection[3]]); + mix(v, 2, 6, 10, 14, m[sigma_selection[4]], m[sigma_selection[5]]); + mix(v, 3, 7, 11, 15, m[sigma_selection[6]], m[sigma_selection[7]]); + + mix(v, 0, 5, 10, 15, m[sigma_selection[8]], m[sigma_selection[9]]); + mix(v, 1, 6, 11, 12, m[sigma_selection[10]], m[sigma_selection[11]]); + mix(v, 2, 7, 8, 13, m[sigma_selection[12]], m[sigma_selection[13]]); + mix(v, 3, 4, 9, 14, m[sigma_selection[14]], m[sigma_selection[15]]); + } + + for (size_t i = 0; i < 8; ++i) + m_internal_state.hash_state[i] = m_internal_state.hash_state[i] ^ v[i] ^ v[i + 8]; +} + +} diff --git a/Userland/Libraries/LibCrypto/Hash/BLAKE2b.h b/Userland/Libraries/LibCrypto/Hash/BLAKE2b.h new file mode 100644 index 00000000000..463bbe233b4 --- /dev/null +++ b/Userland/Libraries/LibCrypto/Hash/BLAKE2b.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023, the SerenityOS developers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +#ifndef KERNEL +# include +#endif + +namespace Crypto::Hash { + +namespace BLAKE2bConstants { +static constexpr auto blockbytes { 128 }; +static constexpr auto hash_length { 64 }; +}; + +class BLAKE2b final : public HashFunction<1024, 512> { +public: + using HashFunction::update; + + BLAKE2b() + { + reset(); + } + + virtual void update(u8 const*, size_t) override; + virtual DigestType digest() override; + virtual DigestType peek() override; + + static DigestType hash(u8 const* data, size_t length) + { + BLAKE2b blake2b; + blake2b.update(data, length); + return blake2b.digest(); + } + + static DigestType hash(ByteBuffer const& buffer) { return hash(buffer.data(), buffer.size()); } + static DigestType hash(StringView buffer) { return hash((u8 const*)buffer.characters_without_null_termination(), buffer.length()); } + +#ifndef KERNEL + virtual DeprecatedString class_name() const override + { + return "BLAKE2b"; + } +#endif + + virtual void reset() override + { + m_internal_state = {}; + // BLAKE2b uses the same initialization vector as SHA512. + for (size_t i = 0; i < 8; ++i) + m_internal_state.hash_state[i] = SHA512Constants::InitializationHashes[i]; + m_internal_state.hash_state[0] ^= 0x01010000 ^ (0 << 8) ^ BLAKE2bConstants::hash_length; + } + +private: + static constexpr u8 BLAKE2bSigma[12][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } + }; + + struct BLAKE2bState { + u64 hash_state[8] {}; + u64 message_byte_offset[2] {}; + u64 is_at_last_block { 0 }; + u8 buffer[BLAKE2bConstants::blockbytes] = {}; + size_t buffer_length { 0 }; + }; + + BLAKE2bState m_internal_state {}; + + void mix(u64* work_vector, u64 a, u64 b, u64 c, u64 d, u64 x, u64 y); + void increment_counter_by(const u64 amount); + void transform(u8 const*); +}; + +}; diff --git a/Userland/Libraries/LibCrypto/Hash/HashManager.h b/Userland/Libraries/LibCrypto/Hash/HashManager.h index 053d49332f6..bf27dda1bc4 100644 --- a/Userland/Libraries/LibCrypto/Hash/HashManager.h +++ b/Userland/Libraries/LibCrypto/Hash/HashManager.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -19,11 +20,12 @@ namespace Crypto::Hash { enum class HashKind { Unknown, None, + BLAKE2b, + MD5, SHA1, SHA256, SHA384, SHA512, - MD5, }; struct MultiHashDigestVariant { @@ -132,6 +134,9 @@ public: m_kind = kind; switch (kind) { + case HashKind::BLAKE2b: + m_algorithm = BLAKE2b(); + break; case HashKind::MD5: m_algorithm = MD5(); break; @@ -211,7 +216,7 @@ public: } private: - using AlgorithmVariant = Variant; + using AlgorithmVariant = Variant; AlgorithmVariant m_algorithm {}; HashKind m_kind { HashKind::None }; ByteBuffer m_pre_init_buffer; diff --git a/Userland/Utilities/test-fuzz.cpp b/Userland/Utilities/test-fuzz.cpp index 14323dd1daa..da39231db40 100644 --- a/Userland/Utilities/test-fuzz.cpp +++ b/Userland/Utilities/test-fuzz.cpp @@ -13,6 +13,7 @@ // TODO: Look into generating this from the authoritative list of fuzzing targets in fuzzer.cmake. #define ENUMERATE_TARGETS(T) \ T(ASN1) \ + T(BLAKE2b) \ T(BMPLoader) \ T(Brotli) \ T(CSSParser) \