diff --git a/.drone.jsonnet b/.drone.jsonnet index a2c30dcd..94778455 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -391,7 +391,15 @@ local windows_pipeline(name, image, environment, arch = "amd64") = "cppalliance/droneubuntu2404:1", { TOOLSET: 'clang', COMPILER: 'clang++-18', CXXSTD: '03,11,14,17,20,2b' }, "clang-18", - ["deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main"], + ["deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main"], + ), + + linux_pipeline( + "Linux 24.04 Clang 19", + "cppalliance/droneubuntu2404:1", + { TOOLSET: 'clang', COMPILER: 'clang++-19', CXXSTD: '03,11,14,17,20,2b' }, + "clang-19", + ["deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main"], ), macos_pipeline( diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 4487018f..b54d3ac4 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -86,6 +86,26 @@ jobs: - "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" source_keys: - "https://apt.llvm.org/llvm-snapshot.gpg.key" + - toolset: clang + compiler: clang++-18 + cxxstd: "14,17,20" + os: ubuntu-24.04 + install: + - clang-18 + sources: + - "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" + - toolset: clang + compiler: clang++-19 + cxxstd: "14,17,20" + os: ubuntu-24.04 + install: + - clang-19 + sources: + - "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main" + source_keys: + - "https://apt.llvm.org/llvm-snapshot.gpg.key" timeout-minutes: 60 diff --git a/doc/crypt.adoc b/doc/crypt.adoc index 093e4827..28eb78f4 100644 --- a/doc/crypt.adoc +++ b/doc/crypt.adoc @@ -21,6 +21,8 @@ include::crypt/api_reference.adoc[] include::crypt/md5.adoc[] +include::crypt/sha1.adoc[] + include::crypt/config.adoc[] include::crypt/reference.adoc[] diff --git a/doc/crypt/reference.adoc b/doc/crypt/reference.adoc index 87d49fc4..844bce8b 100644 --- a/doc/crypt/reference.adoc +++ b/doc/crypt/reference.adoc @@ -13,3 +13,5 @@ The following books, papers and blog posts serve as the basis for the algorithms :linkattrs: - Ronald L. Rivest, https://www.ietf.org/rfc/rfc1321.txt[RFC 1321: The MD5 Message-Digest Algorithm], 1992 + +- Donald E. Eastlake and Paul E. Jones, https://datatracker.ietf.org/doc/html/rfc3174[RFC 3174: US Secure Hash Algorithm 1 (SHA1)], 2001 diff --git a/doc/crypt/sha1.adoc b/doc/crypt/sha1.adoc new file mode 100644 index 00000000..0e55346b --- /dev/null +++ b/doc/crypt/sha1.adoc @@ -0,0 +1,115 @@ +//// +Copyright 2024 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#sha1] +:idprefix: sha1_ + += SHA1 + +This library supports SHA1 as described in https://datatracker.ietf.org/doc/html/rfc3174[RFC 3174]. +There is a wide range of acceptable inputs for the base sha1 function: + +== Hashing Functions + +[source, c++] +---- +namespace boost { +namespace crypt { + +uisng return_type = boost::crypt::array; + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char* str) noexcept -> return_type; + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char* str, size_t len) noexcept -> return_type; + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const unsigned char* str) noexcept -> return_type; + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const unsigned char* str, size_t len) noexcept -> return_type; + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char16_t* str) noexcept -> return_type; + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char16_t* str, size_t len) noexcept -> return_type; + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char32_t* str) noexcept -> return_type; + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char32_t* str, size_t len) noexcept -> return_type; + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const wchar_t* str) noexcept -> return_type; + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const wchar_t* str, size_t len) noexcept -> return_type; + +inline auto sha1(const std::string& str) noexcept -> return_type; + +inline auto sha1(const std::u16string& str) noexcept -> return_type; + +inline auto sha1(const std::u32string& str) noexcept -> return_type; + +inline auto sha1(const std::wstring& str) noexcept -> return_type; + +#ifdef BOOST_CRYPT_HAS_STRING_VIEW + +inline auto sha1(std::string_view str) noexcept -> return_type; + +inline auto sha1(std::u16string_view str) noexcept -> return_type; + +inline auto sha1(std::u32string_view str) noexcept -> return_type; + +inline auto sha1(std::wstring_view str) noexcept -> return_type; + +#endif // BOOST_CRYPT_HAS_STRING_VIEW + +} //namespace crypt +} //namespace boost +---- + +== File Hashing Functions + +We also have the ability to scan files and return the sha1 value: + +[source, c++] +---- +namespace boost { +namespace crypt { + +uisng return_type = boost::crypt::array; + +inline auto sha1_file(const char* filepath) noexcept -> return_type; + +inline auto sha1_file(const std::string& filepath) noexcept -> return_type; + +inline auto sha1_file(std::string_view filepath) noexcept -> return_type; + +} // namespace crypt +} // namespace boost +---- + +== Hashing Object + +[#sha1_hasher] +Lastly, there is also the ability to create a sha1 hashing object and feed it bytes as the user parses them. +This class does not use any dynamic memory allocation. + +[source, c++] +---- +namespace boost { +namespace crypt { + +class sha1_hasher +{ + init(); + + template + BOOST_CRYPT_GPU_ENABLED constexpr auto process_byte(ByteType byte) noexcept -> void; + + template + BOOST_CRYPT_GPU_ENABLED constexpr auto process_bytes(ForwardIter buffer, size_t byte_count) noexcept -> void; + + constexpr auto get_digest() noexcept -> boost::crypt::array; +}; + +} // namespace crypt +} // namespace boost +---- diff --git a/fuzzing/Jamfile b/fuzzing/Jamfile index 4739898a..56294a10 100644 --- a/fuzzing/Jamfile +++ b/fuzzing/Jamfile @@ -15,9 +15,7 @@ local all_fuzzers = [ regex.replace-list for local fuzzer in $(all_fuzzers) { - # These two fuzzers are the most complex ones. The rest are really - # simple, so less time is enough - local fuzz_time = 30 ; + local fuzz_time = 120 ; # Create the output corpus directories make /tmp/corpus/$(fuzzer) : : common.MkDir ; diff --git a/fuzzing/fuzz_sha1.cpp b/fuzzing/fuzz_sha1.cpp new file mode 100644 index 00000000..9c59c5bc --- /dev/null +++ b/fuzzing/fuzz_sha1.cpp @@ -0,0 +1,33 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) +{ + try + { + auto c_data = reinterpret_cast(data); + std::string c_data_str {c_data, size}; // Guarantee null termination since we can't pass the size argument + + boost::crypt::sha1(c_data_str); + boost::crypt::sha1(c_data, size); + boost::crypt::sha1(data, size); + + #ifdef BOOST_CRYPT_HAS_STRING_VIEW + std::string_view view {c_data_str}; + boost::crypt::sha1(view); + #endif + } + catch(...) + { + std::cerr << "Error with: " << data << std::endl; + std::terminate(); + } + + return 0; +} diff --git a/fuzzing/seedcorpus/fuzz_sha1/sha1.txt b/fuzzing/seedcorpus/fuzz_sha1/sha1.txt new file mode 100644 index 00000000..48e3b98c --- /dev/null +++ b/fuzzing/seedcorpus/fuzz_sha1/sha1.txt @@ -0,0 +1,18 @@ +"The quick brown fox jumps over the lazy dog" +"The quick brown fox jumps over the lazy dog." +"" +"aB3$x9Yz" +"12345" +"!@#$%^&*()" +"FuzzTest123" +" " +"Lorem ipsum dolor sit amet" +"a" +"9876543210" +"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +"ñÑáéíóúÁÉÍÓÚ" +"\n\r\t" +"0" +"ThisIsAVeryLongStringWithNoSpacesOrPunctuationToTestEdgeCases" +"" +"SELECT * FROM users;" diff --git a/include/boost/crypt/hash/hasher_state.hpp b/include/boost/crypt/hash/hasher_state.hpp new file mode 100644 index 00000000..066b79f8 --- /dev/null +++ b/include/boost/crypt/hash/hasher_state.hpp @@ -0,0 +1,24 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CRYPT_HASH_HASHER_STATE_HPP +#define BOOST_CRYPT_HASH_HASHER_STATE_HPP + +#include + +namespace boost { +namespace crypt { + +enum class hasher_state : boost::crypt::uint8_t +{ + success, // no issues + null, // nullptr as parameter + input_too_long, // input data too long (exceeded size_t) + state_error // added more input after get_digest without re-init +}; + +} // namespace crypt +} // namespace boost + +#endif // BOOST_CRYPT_HASH_HASHER_STATE_HPP diff --git a/include/boost/crypt/hash/sha1.hpp b/include/boost/crypt/hash/sha1.hpp new file mode 100644 index 00000000..c1118ab3 --- /dev/null +++ b/include/boost/crypt/hash/sha1.hpp @@ -0,0 +1,737 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// See: https://datatracker.ietf.org/doc/html/rfc3174 + +#ifndef BOOST_CRYPT_HASH_SHA1_HPP +#define BOOST_CRYPT_HASH_SHA1_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef BOOST_CRYPT_BUILD_MODULE +#include +#include +#include +#include +#endif + +namespace boost { +namespace crypt { + +class sha1_hasher +{ +private: + + boost::crypt::array intermediate_hash_ {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0}; + boost::crypt::array buffer_ {}; + + boost::crypt::size_t buffer_index_ {}; + boost::crypt::size_t low_ {}; + boost::crypt::size_t high_ {}; + + bool computed {}; + bool corrupted {}; + + constexpr auto sha1_process_message_block() -> void; + + template + BOOST_CRYPT_GPU_ENABLED constexpr auto sha1_update(ForwardIter data, boost::crypt::size_t size) noexcept -> hasher_state; + + BOOST_CRYPT_GPU_ENABLED constexpr auto pad_message() noexcept -> void; + +public: + + using return_type = boost::crypt::array; + + BOOST_CRYPT_GPU_ENABLED constexpr auto init() -> void; + + template + BOOST_CRYPT_GPU_ENABLED constexpr auto process_byte(ByteType byte) noexcept -> hasher_state; + + template ::value_type) == 1, bool> = true> + BOOST_CRYPT_GPU_ENABLED constexpr auto process_bytes(ForwardIter buffer, boost::crypt::size_t byte_count) noexcept -> hasher_state; + + template ::value_type) == 2, bool> = true> + BOOST_CRYPT_GPU_ENABLED constexpr auto process_bytes(ForwardIter buffer, boost::crypt::size_t byte_count) noexcept -> hasher_state; + + template ::value_type) == 4, bool> = true> + BOOST_CRYPT_GPU_ENABLED constexpr auto process_bytes(ForwardIter buffer, boost::crypt::size_t byte_count) noexcept -> hasher_state; + + + BOOST_CRYPT_GPU_ENABLED constexpr auto get_digest() noexcept -> return_type ; +}; + +namespace detail { + +constexpr auto round1(boost::crypt::uint32_t& A, + boost::crypt::uint32_t& B, + boost::crypt::uint32_t& C, + boost::crypt::uint32_t& D, + boost::crypt::uint32_t& E, + boost::crypt::uint32_t W) +{ + const auto temp {detail::rotl(A, 5U) + ((B & C) | ((~B) & D)) + E + W + 0x5A827999U}; + E = D; + D = C; + C = detail::rotl(B, 30U); + B = A; + A = temp; +} + +constexpr auto round2(boost::crypt::uint32_t& A, + boost::crypt::uint32_t& B, + boost::crypt::uint32_t& C, + boost::crypt::uint32_t& D, + boost::crypt::uint32_t& E, + boost::crypt::uint32_t W) +{ + const auto temp {detail::rotl(A, 5U) + (B ^ C ^ D) + E + W + 0x6ED9EBA1U}; + E = D; + D = C; + C = detail::rotl(B, 30U); + B = A; + A = temp; +} + +constexpr auto round3(boost::crypt::uint32_t& A, + boost::crypt::uint32_t& B, + boost::crypt::uint32_t& C, + boost::crypt::uint32_t& D, + boost::crypt::uint32_t& E, + boost::crypt::uint32_t W) +{ + const auto temp {detail::rotl(A, 5U) + ((B & C) | (B & D) | (C & D)) + E + W + 0x8F1BBCDCU}; + E = D; + D = C; + C = detail::rotl(B, 30U); + B = A; + A = temp; +} + +constexpr auto round4(boost::crypt::uint32_t& A, + boost::crypt::uint32_t& B, + boost::crypt::uint32_t& C, + boost::crypt::uint32_t& D, + boost::crypt::uint32_t& E, + boost::crypt::uint32_t W) +{ + const auto temp {detail::rotl(A, 5U) + (B ^ C ^ D) + E + W + 0xCA62C1D6U}; + E = D; + D = C; + C = detail::rotl(B, 30U); + B = A; + A = temp; +} + +} // Namespace detail + +// See definitions from the RFC on the rounds +constexpr auto sha1_hasher::sha1_process_message_block() -> void +{ + boost::crypt::array W {}; + + // Init the first 16 words of W + for (boost::crypt::size_t i {}; i < 16UL; ++i) + { + W[i] = (static_cast(buffer_[i * 4U]) << 24U) | + (static_cast(buffer_[i * 4U + 1U]) << 16U) | + (static_cast(buffer_[i * 4U + 2U]) << 8U) | + (static_cast(buffer_[i * 4U + 3U])); + + } + + for (boost::crypt::size_t i {16U}; i < W.size(); ++i) + { + W[i] = detail::rotl(W[i - 3U] ^ W[i - 8U] ^ W[i - 14] ^ W[i - 16], 1U); + } + + auto A {intermediate_hash_[0]}; + auto B {intermediate_hash_[1]}; + auto C {intermediate_hash_[2]}; + auto D {intermediate_hash_[3]}; + auto E {intermediate_hash_[4]}; + + // Round 1 + detail::round1(A, B, C, D, E, W[0]); + detail::round1(A, B, C, D, E, W[1]); + detail::round1(A, B, C, D, E, W[2]); + detail::round1(A, B, C, D, E, W[3]); + detail::round1(A, B, C, D, E, W[4]); + detail::round1(A, B, C, D, E, W[5]); + detail::round1(A, B, C, D, E, W[6]); + detail::round1(A, B, C, D, E, W[7]); + detail::round1(A, B, C, D, E, W[8]); + detail::round1(A, B, C, D, E, W[9]); + detail::round1(A, B, C, D, E, W[10]); + detail::round1(A, B, C, D, E, W[11]); + detail::round1(A, B, C, D, E, W[12]); + detail::round1(A, B, C, D, E, W[13]); + detail::round1(A, B, C, D, E, W[14]); + detail::round1(A, B, C, D, E, W[15]); + detail::round1(A, B, C, D, E, W[16]); + detail::round1(A, B, C, D, E, W[17]); + detail::round1(A, B, C, D, E, W[18]); + detail::round1(A, B, C, D, E, W[19]); + + // Round 2 + detail::round2(A, B, C, D, E, W[20]); + detail::round2(A, B, C, D, E, W[21]); + detail::round2(A, B, C, D, E, W[22]); + detail::round2(A, B, C, D, E, W[23]); + detail::round2(A, B, C, D, E, W[24]); + detail::round2(A, B, C, D, E, W[25]); + detail::round2(A, B, C, D, E, W[26]); + detail::round2(A, B, C, D, E, W[27]); + detail::round2(A, B, C, D, E, W[28]); + detail::round2(A, B, C, D, E, W[29]); + detail::round2(A, B, C, D, E, W[30]); + detail::round2(A, B, C, D, E, W[31]); + detail::round2(A, B, C, D, E, W[32]); + detail::round2(A, B, C, D, E, W[33]); + detail::round2(A, B, C, D, E, W[34]); + detail::round2(A, B, C, D, E, W[35]); + detail::round2(A, B, C, D, E, W[36]); + detail::round2(A, B, C, D, E, W[37]); + detail::round2(A, B, C, D, E, W[38]); + detail::round2(A, B, C, D, E, W[39]); + + // Round 3 + detail::round3(A, B, C, D, E, W[40]); + detail::round3(A, B, C, D, E, W[41]); + detail::round3(A, B, C, D, E, W[42]); + detail::round3(A, B, C, D, E, W[43]); + detail::round3(A, B, C, D, E, W[44]); + detail::round3(A, B, C, D, E, W[45]); + detail::round3(A, B, C, D, E, W[46]); + detail::round3(A, B, C, D, E, W[47]); + detail::round3(A, B, C, D, E, W[48]); + detail::round3(A, B, C, D, E, W[49]); + detail::round3(A, B, C, D, E, W[50]); + detail::round3(A, B, C, D, E, W[51]); + detail::round3(A, B, C, D, E, W[52]); + detail::round3(A, B, C, D, E, W[53]); + detail::round3(A, B, C, D, E, W[54]); + detail::round3(A, B, C, D, E, W[55]); + detail::round3(A, B, C, D, E, W[56]); + detail::round3(A, B, C, D, E, W[57]); + detail::round3(A, B, C, D, E, W[58]); + detail::round3(A, B, C, D, E, W[59]); + + // Round 4 + detail::round4(A, B, C, D, E, W[60]); + detail::round4(A, B, C, D, E, W[61]); + detail::round4(A, B, C, D, E, W[62]); + detail::round4(A, B, C, D, E, W[63]); + detail::round4(A, B, C, D, E, W[64]); + detail::round4(A, B, C, D, E, W[65]); + detail::round4(A, B, C, D, E, W[66]); + detail::round4(A, B, C, D, E, W[67]); + detail::round4(A, B, C, D, E, W[68]); + detail::round4(A, B, C, D, E, W[69]); + detail::round4(A, B, C, D, E, W[70]); + detail::round4(A, B, C, D, E, W[71]); + detail::round4(A, B, C, D, E, W[72]); + detail::round4(A, B, C, D, E, W[73]); + detail::round4(A, B, C, D, E, W[74]); + detail::round4(A, B, C, D, E, W[75]); + detail::round4(A, B, C, D, E, W[76]); + detail::round4(A, B, C, D, E, W[77]); + detail::round4(A, B, C, D, E, W[78]); + detail::round4(A, B, C, D, E, W[79]); + + intermediate_hash_[0] += A; + intermediate_hash_[1] += B; + intermediate_hash_[2] += C; + intermediate_hash_[3] += D; + intermediate_hash_[4] += E; + + buffer_index_ = 0U; +} + +// Like MD5, the message must be padded to an even 512 bits. +// The first bit of padding must be a 1 +// The last 64-bits should be the length of the message +// All bits in between should be 0s +constexpr auto sha1_hasher::pad_message() noexcept -> void +{ + constexpr boost::crypt::size_t message_length_start_index {56U}; + + // We don't have enough space for everything we need + if (buffer_index_ >= message_length_start_index) + { + buffer_[buffer_index_++] = static_cast(0x80); + while (buffer_index_ < buffer_.size()) + { + buffer_[buffer_index_++] = static_cast(0x00); + } + + sha1_process_message_block(); + + while (buffer_index_ < message_length_start_index) + { + buffer_[buffer_index_++] = static_cast(0x00); + } + } + else + { + buffer_[buffer_index_++] = static_cast(0x80); + while (buffer_index_ < message_length_start_index) + { + buffer_[buffer_index_++] = static_cast(0x00); + } + } + + // Add the message length to the end of the buffer + BOOST_CRYPT_ASSERT(buffer_index_ == message_length_start_index); + + buffer_[56U] = static_cast(high_ >> 24U); + buffer_[57U] = static_cast(high_ >> 16U); + buffer_[58U] = static_cast(high_ >> 8U); + buffer_[59U] = static_cast(high_); + buffer_[60U] = static_cast(low_ >> 24U); + buffer_[61U] = static_cast(low_ >> 16U); + buffer_[62U] = static_cast(low_ >> 8U); + buffer_[63U] = static_cast(low_); + + sha1_process_message_block(); +} + +template +constexpr auto sha1_hasher::sha1_update(ForwardIter data, boost::crypt::size_t size) noexcept -> hasher_state +{ + if (size == 0U) + { + return hasher_state::success; + } + if (computed) + { + corrupted = true; + } + if (corrupted) + { + return hasher_state::state_error; + } + + while (size-- && !corrupted) + { + buffer_[buffer_index_++] = static_cast(*data & 0xFF); + low_ += 8U; + + if (BOOST_CRYPT_UNLIKELY(low_ == 0)) + { + // Would indicate size_t rollover which should not happen on a single data stream + // LCOV_EXCL_START + ++high_; + if (high_ == 0) + { + corrupted = true; + return hasher_state::input_too_long; + } + // LCOV_EXCL_STOP + } + + if (buffer_index_ == buffer_.size()) + { + sha1_process_message_block(); + } + + ++data; + } + + return hasher_state::success; +} + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1_hasher::init() -> void +{ + intermediate_hash_[0] = 0x67452301; + intermediate_hash_[1] = 0xEFCDAB89; + intermediate_hash_[2] = 0x98BADCFE; + intermediate_hash_[3] = 0x10325476; + intermediate_hash_[4] = 0xC3D2E1F0; + + buffer_.fill(0); + buffer_index_ = 0UL; + low_ = 0UL; + high_ = 0UL; + computed = false; + corrupted = false; +} + +template +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1_hasher::process_byte(ByteType byte) noexcept -> hasher_state +{ + static_assert(boost::crypt::is_convertible_v, "Byte must be convertible to uint8_t"); + const auto value {static_cast(byte)}; + return sha1_update(&value, 1UL); +} + +template ::value_type) == 1, bool>> +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1_hasher::process_bytes(ForwardIter buffer, boost::crypt::size_t byte_count) noexcept -> hasher_state +{ + if (!utility::is_null(buffer)) + { + return sha1_update(buffer, byte_count); + } + else + { + return hasher_state::null; + } +} + +template ::value_type) == 2, bool>> +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1_hasher::process_bytes(ForwardIter buffer, boost::crypt::size_t byte_count) noexcept -> hasher_state +{ + #ifndef BOOST_CRYPT_HAS_CUDA + + if (!utility::is_null(buffer)) + { + const auto* char_ptr {reinterpret_cast(std::addressof(*buffer))}; + const auto* data {reinterpret_cast(char_ptr)}; + return sha1_update(data, byte_count * 2U); + } + else + { + return hasher_state::null; + } + + #else + + if (!utility::is_null(buffer)) + { + const auto* data {reinterpret_cast(buffer)}; + return sha1_update(data, byte_count * 2U); + } + else + { + return hasher_state::null; + } + + #endif +} + +template ::value_type) == 4, bool>> +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1_hasher::process_bytes(ForwardIter buffer, boost::crypt::size_t byte_count) noexcept -> hasher_state +{ + #ifndef BOOST_CRYPT_HAS_CUDA + + if (!utility::is_null(buffer)) + { + const auto* char_ptr {reinterpret_cast(std::addressof(*buffer))}; + const auto* data {reinterpret_cast(char_ptr)}; + return sha1_update(data, byte_count * 4U); + } + else + { + return hasher_state::null; + } + + #else + + if (!utility::is_null(buffer)) + { + const auto* data {reinterpret_cast(buffer)}; + return sha1_update(data, byte_count * 4U); + } + else + { + return hasher_state::null; + } + + #endif +} + +constexpr auto sha1_hasher::get_digest() noexcept -> sha1_hasher::return_type +{ + boost::crypt::array digest{}; + + if (corrupted) + { + // Return empty message on corruption + return digest; + } + if (!computed) + { + pad_message(); + + // Overwrite whatever is in the buffer in case it is sensitive + buffer_.fill(0); + low_ = 0U; + high_ = 0U; + computed = true; + } + + for (boost::crypt::size_t i {}; i < digest.size(); ++i) + { + digest[i] = static_cast(intermediate_hash_[i >> 2U] >> 8 * (3 - (i & 0x03))); + } + + return digest; +} + +namespace detail { + +template +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(T begin, T end) noexcept -> sha1_hasher::return_type +{ + if (end < begin) + { + return sha1_hasher::return_type {}; + } + else if (end == begin) + { + return sha1_hasher::return_type { + 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, + 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 + }; + } + + sha1_hasher hasher; + hasher.process_bytes(begin, static_cast(end - begin)); + auto result {hasher.get_digest()}; + + return result; +} + +} // namespace detail + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char* str) noexcept -> sha1_hasher::return_type +{ + if (str == nullptr) + { + return sha1_hasher::return_type{}; // LCOV_EXCL_LINE + } + + const auto message_len {utility::strlen(str)}; + return detail::sha1(str, str + message_len); +} + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char* str, boost::crypt::size_t len) noexcept -> sha1_hasher::return_type +{ + if (str == nullptr) + { + return sha1_hasher::return_type{}; // LCOV_EXCL_LINE + } + + return detail::sha1(str, str + len); +} + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const boost::crypt::uint8_t* str) noexcept -> sha1_hasher::return_type +{ + if (str == nullptr) + { + return sha1_hasher::return_type{}; // LCOV_EXCL_LINE + } + + const auto message_len {utility::strlen(str)}; + return detail::sha1(str, str + message_len); +} + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const boost::crypt::uint8_t* str, boost::crypt::size_t len) noexcept -> sha1_hasher::return_type +{ + if (str == nullptr) + { + return sha1_hasher::return_type{}; // LCOV_EXCL_LINE + } + + return detail::sha1(str, str + len); +} + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char16_t* str) noexcept -> sha1_hasher::return_type +{ + if (str == nullptr) + { + return sha1_hasher::return_type{}; // LCOV_EXCL_LINE + } + + const auto message_len {utility::strlen(str)}; + return detail::sha1(str, str + message_len); +} + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char16_t* str, boost::crypt::size_t len) noexcept -> sha1_hasher::return_type +{ + if (str == nullptr) + { + return sha1_hasher::return_type{}; // LCOV_EXCL_LINE + } + + return detail::sha1(str, str + len); +} + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char32_t* str) noexcept -> sha1_hasher::return_type +{ + if (str == nullptr) + { + return sha1_hasher::return_type{}; // LCOV_EXCL_LINE + } + + const auto message_len {utility::strlen(str)}; + return detail::sha1(str, str + message_len); +} + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const char32_t* str, boost::crypt::size_t len) noexcept -> sha1_hasher::return_type +{ + if (str == nullptr) + { + return sha1_hasher::return_type{}; // LCOV_EXCL_LINE + } + + return detail::sha1(str, str + len); +} + +// On some platforms wchar_t is 16 bits and others it's 32 +// Since we check sizeof() the underlying with SFINAE in the actual implementation this is handled transparently +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const wchar_t* str) noexcept -> sha1_hasher::return_type +{ + if (str == nullptr) + { + return sha1_hasher::return_type{}; // LCOV_EXCL_LINE + } + + const auto message_len {utility::strlen(str)}; + return detail::sha1(str, str + message_len); +} + +BOOST_CRYPT_GPU_ENABLED constexpr auto sha1(const wchar_t* str, boost::crypt::size_t len) noexcept -> sha1_hasher::return_type +{ + if (str == nullptr) + { + return sha1_hasher::return_type{}; // LCOV_EXCL_LINE + } + + return detail::sha1(str, str + len); +} + +// ----- String and String view aren't in the libcu++ STL so they so not have device markers ----- + +#ifndef BOOST_CRYPT_HAS_CUDA + +inline auto sha1(const std::string& str) noexcept -> sha1_hasher::return_type +{ + return detail::sha1(str.begin(), str.end()); +} + +inline auto sha1(const std::u16string& str) noexcept -> sha1_hasher::return_type +{ + return detail::sha1(str.begin(), str.end()); +} + +inline auto sha1(const std::u32string& str) noexcept -> sha1_hasher::return_type +{ + return detail::sha1(str.begin(), str.end()); +} + +inline auto sha1(const std::wstring& str) noexcept -> sha1_hasher::return_type +{ + return detail::sha1(str.begin(), str.end()); +} + +#ifdef BOOST_CRYPT_HAS_STRING_VIEW + +inline auto sha1(std::string_view str) -> sha1_hasher::return_type +{ + return detail::sha1(str.begin(), str.end()); +} + +inline auto sha1(std::u16string_view str) -> sha1_hasher::return_type +{ + return detail::sha1(str.begin(), str.end()); +} + +inline auto sha1(std::u32string_view str) -> sha1_hasher::return_type +{ + return detail::sha1(str.begin(), str.end()); +} + +inline auto sha1(std::wstring_view str) -> sha1_hasher::return_type +{ + return detail::sha1(str.begin(), str.end()); +} + +#endif // BOOST_CRYPT_HAS_STRING_VIEW + +// ---- CUDA also does not have the ability to consume files ----- + +namespace detail { + +template +auto sha1_file_impl(utility::file_reader& reader) noexcept -> sha1_hasher::return_type +{ + sha1_hasher hasher; + while (!reader.eof()) + { + const auto buffer_iter {reader.read_next_block()}; + const auto len {reader.get_bytes_read()}; + hasher.process_bytes(buffer_iter, len); + } + + return hasher.get_digest(); +} + +} // namespace detail + +inline auto sha1_file(const std::string& filepath) noexcept -> sha1_hasher::return_type +{ + try + { + utility::file_reader<64U> reader(filepath); + return detail::sha1_file_impl(reader); + } + catch (const std::runtime_error&) + { + return sha1_hasher::return_type{}; + } +} + +inline auto sha1_file(const char* filepath) noexcept -> sha1_hasher::return_type +{ + try + { + if (filepath == nullptr) + { + return sha1_hasher::return_type{}; + } + + utility::file_reader<64U> reader(filepath); + return detail::sha1_file_impl(reader); + } + catch (const std::runtime_error&) + { + return sha1_hasher::return_type{}; + } +} + +#ifdef BOOST_CRYPT_HAS_STRING_VIEW + +inline auto sha1_file(std::string_view filepath) noexcept -> sha1_hasher::return_type +{ + try + { + utility::file_reader<64U> reader(filepath); + return detail::sha1_file_impl(reader); + } + catch (const std::runtime_error&) + { + return sha1_hasher::return_type{}; + } +} + +#endif // BOOST_CRYPT_HAS_STRING_VIEW + +#endif // BOOST_CRYPT_HAS_CUDA + +} // namespace crypt +} // namepsace boost + +#endif // BOOST_CRYPT_HASH_SHA1_HPP diff --git a/include/boost/crypt/utility/array.hpp b/include/boost/crypt/utility/array.hpp index f13b1ddf..b6a628e8 100644 --- a/include/boost/crypt/utility/array.hpp +++ b/include/boost/crypt/utility/array.hpp @@ -35,10 +35,10 @@ class array // Iterators BOOST_CRYPT_GPU_ENABLED constexpr auto begin() noexcept -> iterator { return elements; } - BOOST_CRYPT_GPU_ENABLED constexpr auto begin() const noexcept -> iterator { return elements; } + BOOST_CRYPT_GPU_ENABLED constexpr auto begin() const noexcept -> const_iterator { return elements; } BOOST_CRYPT_GPU_ENABLED constexpr auto cbegin() const noexcept -> const_iterator { return elements; } BOOST_CRYPT_GPU_ENABLED constexpr auto end() noexcept -> iterator { return elements + N; } - BOOST_CRYPT_GPU_ENABLED constexpr auto end() const noexcept -> iterator { return elements + N; } + BOOST_CRYPT_GPU_ENABLED constexpr auto end() const noexcept -> const_iterator { return elements + N; } BOOST_CRYPT_GPU_ENABLED constexpr auto cend() const noexcept -> const_iterator { return elements + N; } // Sizing diff --git a/include/boost/crypt/utility/config.hpp b/include/boost/crypt/utility/config.hpp index 07e0f940..210d14da 100644 --- a/include/boost/crypt/utility/config.hpp +++ b/include/boost/crypt/utility/config.hpp @@ -58,7 +58,7 @@ #define BOOST_CRYPT_ASSERT_MSG(expr, msg) assert((expr)&&(msg)) // ----- Assertions ----- -// ----- Has CXX something ----- +// ----- Has something ----- // C++17 #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # if __has_include() @@ -68,7 +68,14 @@ # endif # endif #endif -// ----- Has CXX something ----- + +#if defined(__has_builtin) +#define BOOST_CRYPT_HAS_BUILTIN(x) __has_builtin(x) +#else +#define BOOST_CRYPT_HAS_BUILTIN(x) false +#endif + +// ----- Has something ----- // ----- Unreachable ----- #if defined(__GNUC__) || defined(__clang__) @@ -80,4 +87,16 @@ #endif // ----- Unreachable ----- +// ----- Unlikely ----- + +#if BOOST_CRYPT_HAS_BUILTIN(__builtin_expect) +# define BOOST_CRYPT_LIKELY(x) __builtin_expect(x, 1) +# define BOOST_CRYPT_UNLIKELY(x) __builtin_expect(x, 0) +#else +# define BOOST_CRYPT_LIKELY(x) x +# define BOOST_CRYPT_UNLIKELY(x) x +#endif + +// ----- Unlikely ----- + #endif //BOOST_CRYPT_DETAIL_CONFIG_HPP diff --git a/include/boost/crypt/utility/null.hpp b/include/boost/crypt/utility/null.hpp new file mode 100644 index 00000000..cca760d2 --- /dev/null +++ b/include/boost/crypt/utility/null.hpp @@ -0,0 +1,33 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// Since our algorithms take both pointers and iterators we need to check the nulls only for the pointers + +#ifndef BOOST_CRYPT_UTILITY_NULL_HPP +#define BOOST_CRYPT_UTILITY_NULL_HPP + +#include +#include + +namespace boost { +namespace crypt { +namespace utility { + +template , bool> = true> +BOOST_CRYPT_GPU_ENABLED constexpr auto is_null(ForwardIter iter) noexcept -> bool +{ + return iter == nullptr; +} + +template , bool> = true> +BOOST_CRYPT_GPU_ENABLED constexpr auto is_null(ForwardIter) noexcept -> bool +{ + return false; +} + +} // namespace utility +} // namespace crypt +} // namespace boost + +#endif // BOOST_CRYPT_UTILITY_NULL_HPP diff --git a/test/Jamfile b/test/Jamfile index 37a5fd33..a24b1306 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -45,3 +45,4 @@ project : requirements run quick.cpp ; run test_md5.cpp ; +run test_sha1.cpp ; diff --git a/test/generate_random_strings.hpp b/test/generate_random_strings.hpp index c85f0ff8..536c631f 100644 --- a/test/generate_random_strings.hpp +++ b/test/generate_random_strings.hpp @@ -10,6 +10,8 @@ #include #include +static std::mt19937_64 string_rng(42); + namespace boost { namespace crypt { @@ -22,12 +24,11 @@ inline void generate_random_string(char* str, std::size_t length) const std::size_t charset_size = sizeof(charset) - 1; - std::mt19937_64 rng(42); std::uniform_int_distribution dist(0, charset_size); for (std::size_t i = 0; i < length - 1; ++i) { - const auto index = dist(rng); + const auto index = dist(string_rng); str[i] = charset[index]; } @@ -42,12 +43,11 @@ inline void generate_random_string(char16_t* str, std::size_t length) const std::size_t charset_size = std::char_traits::length(charset); - std::mt19937_64 rng(42); std::uniform_int_distribution dist(0, charset_size - 1); for (std::size_t i = 0; i < length - 1; ++i) { - const auto index = dist(rng); + const auto index = dist(string_rng); str[i] = charset[index]; } @@ -62,12 +62,11 @@ inline void generate_random_string(char32_t* str, std::size_t length) const std::size_t charset_size = std::char_traits::length(charset); - std::mt19937_64 rng(42); std::uniform_int_distribution dist(0, charset_size - 1); for (std::size_t i = 0; i < length - 1; ++i) { - const auto index = dist(rng); + const auto index = dist(string_rng); str[i] = charset[index]; } @@ -82,12 +81,11 @@ inline void generate_random_string(wchar_t* str, std::size_t length) const std::size_t charset_size = std::char_traits::length(charset); - std::mt19937_64 rng(42); std::uniform_int_distribution dist(0, charset_size - 1); for (std::size_t i = 0; i < length - 1; ++i) { - const auto index = dist(rng); + const auto index = dist(string_rng); str[i] = charset[index]; } diff --git a/test/test_sha1.cpp b/test/test_sha1.cpp new file mode 100644 index 00000000..ea115abc --- /dev/null +++ b/test/test_sha1.cpp @@ -0,0 +1,484 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include "generate_random_strings.hpp" + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wold-style-cast" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +auto get_boost_uuid_result(const char* str, size_t length) +{ + unsigned char digest[20]; + boost::uuids::detail::sha1 hasher; + hasher.process_bytes(str, length); + hasher.get_digest(digest); + + std::array return_array {}; + for (std::size_t i {}; i < 20U; ++i) + { + return_array[i] = digest[i]; + } + + return return_array; +} + +constexpr std::array>, 7> test_values = +{ + // Start with the sample hashes from wiki + std::make_tuple("The quick brown fox jumps over the lazy dog", + std::array{0x2f, 0xd4, 0xe1, 0xc6, 0x7a, 0x2d, 0x28, 0xfc, 0xed, 0x84, + 0x9e, 0xe1, 0xbb, 0x76, 0xe7, 0x39, 0x1b, 0x93, 0xeb, 0x12}), + std::make_tuple("The quick brown fox jumps over the lazy cog", + std::array{0xde, 0x9f, 0x2c, 0x7f, 0xd2, 0x5e, 0x1b, 0x3a, 0xfa, 0xd3, + 0xe8, 0x5a, 0x0b, 0xd1, 0x7d, 0x9b, 0x10, 0x0d, 0xb4, 0xb3}), + std::make_tuple("", + std::array{0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, + 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09}), + // Now the ones from the RFC + std::make_tuple("abc", + std::array{0xA9, 0x99, 0x3E, 0x36, 0x47, 0x06, 0x81, 0x6A, 0xBA, 0x3E, + 0x25, 0x71, 0x78, 0x50, 0xC2, 0x6C, 0x9C, 0xD0, 0xD8, 0x9D}), + + std::make_tuple("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + std::array{0x84, 0x98, 0x3E, 0x44, 0x1C, 0x3B, 0xD2, 0x6E, 0xBA, 0xAE, + 0x4A, 0xA1, 0xF9, 0x51, 0x29, 0xE5, 0xE5, 0x46, 0x70, 0xF1}), + + std::make_tuple("a", + std::array{0x86, 0xf7, 0xe4, 0x37, 0xfa, 0xa5, 0xa7, 0xfc, 0xe1, 0x5d, + 0x1d, 0xdc, 0xb9, 0xea, 0xea, 0xea, 0x37, 0x76, 0x67, 0xb8}), + + std::make_tuple("0123456701234567012345670123456701234567012345670123456701234567", + std::array{0xe0, 0xc0, 0x94, 0xe8, 0x67, 0xef, 0x46, 0xc3, 0x50, 0xef, + 0x54, 0xa7, 0xf5, 0x9d, 0xd6, 0x0b, 0xed, 0x92, 0xae, 0x83}), +}; + +void basic_tests() +{ + for (const auto& test_value : test_values) + { + const auto message_result {boost::crypt::sha1(std::get<0>(test_value))}; + const auto valid_result {std::get<1>(test_value)}; + for (std::size_t i {}; i < message_result.size(); ++i) + { + if (!BOOST_TEST_EQ(message_result[i], valid_result[i])) + { + // LCOV_EXCL_START + std::cerr << "Failure with: " << std::get<0>(test_value) << '\n'; + break; + // LCOV_EXCL_STOP + } + } + } +} + +void string_test() +{ + for (const auto& test_value : test_values) + { + const std::string string_message {std::get<0>(test_value)}; + const auto message_result {boost::crypt::sha1(string_message)}; + const auto valid_result {std::get<1>(test_value)}; + for (std::size_t i {}; i < message_result.size(); ++i) + { + if (!BOOST_TEST_EQ(message_result[i], valid_result[i])) + { + // LCOV_EXCL_START + std::cerr << "Failure with: " << std::get<0>(test_value) << '\n'; + break; + // LCOV_EXCL_STOP + } + } + } +} + +void string_view_test() +{ + #ifdef BOOST_CRYPT_HAS_STRING_VIEW + for (const auto& test_value : test_values) + { + const std::string string_message {std::get<0>(test_value)}; + const std::string_view string_view_message {string_message}; + const auto message_result {boost::crypt::sha1(string_view_message)}; + const auto valid_result {std::get<1>(test_value)}; + for (std::size_t i {}; i < message_result.size(); ++i) + { + if (!BOOST_TEST_EQ(message_result[i], valid_result[i])) + { + // LCOV_EXCL_START + std::cerr << "Failure with: " << std::get<0>(test_value) << '\n'; + break; + // LCOV_EXCL_STOP + } + } + } + #endif +} + +void bad_input() +{ + const auto null_message {boost::crypt::sha1(static_cast(nullptr))}; + BOOST_TEST_EQ(null_message[0], 0x0); + BOOST_TEST_EQ(null_message[1], 0x0); + BOOST_TEST_EQ(null_message[2], 0x0); + BOOST_TEST_EQ(null_message[3], 0x0); + + const auto null_message_len {boost::crypt::sha1(static_cast(nullptr), 100)}; + BOOST_TEST_EQ(null_message_len[0], 0x0); + BOOST_TEST_EQ(null_message_len[1], 0x0); + BOOST_TEST_EQ(null_message_len[2], 0x0); + BOOST_TEST_EQ(null_message_len[3], 0x0); + + const auto unsigned_null_message {boost::crypt::sha1(static_cast(nullptr))}; + BOOST_TEST_EQ(unsigned_null_message[0], 0x0); + BOOST_TEST_EQ(unsigned_null_message[1], 0x0); + BOOST_TEST_EQ(unsigned_null_message[2], 0x0); + BOOST_TEST_EQ(unsigned_null_message[3], 0x0); + + const auto unsigned_null_message_len {boost::crypt::sha1(static_cast(nullptr), 100)}; + BOOST_TEST_EQ(unsigned_null_message_len[0], 0x0); + BOOST_TEST_EQ(unsigned_null_message_len[1], 0x0); + BOOST_TEST_EQ(unsigned_null_message_len[2], 0x0); + BOOST_TEST_EQ(unsigned_null_message_len[3], 0x0); + + std::string test_str {"Test string"}; + const auto reveresed_input {boost::crypt::detail::sha1(test_str.end(), test_str.begin())}; + BOOST_TEST_EQ(reveresed_input[0], 0x0); + BOOST_TEST_EQ(reveresed_input[1], 0x0); + BOOST_TEST_EQ(reveresed_input[2], 0x0); + BOOST_TEST_EQ(reveresed_input[3], 0x0); +} + +void test_class() +{ + boost::crypt::sha1_hasher hasher; + + for (const auto& test_value : test_values) + { + const auto msg {std::get<0>(test_value)}; + hasher.process_bytes(msg, std::strlen(msg)); + const auto message_result {hasher.get_digest()}; + + const auto valid_result {std::get<1>(test_value)}; + for (std::size_t i {}; i < message_result.size(); ++i) + { + if (!BOOST_TEST_EQ(message_result[i], valid_result[i])) + { + // LCOV_EXCL_START + std::cerr << "Failure with: " << std::get<0>(test_value) << '\n'; + break; + // LCOV_EXCL_STOP + } + } + + hasher.init(); + } +} + +template +void test_random_values() +{ + constexpr std::size_t max_str_len {65535U}; + std::mt19937_64 rng(42); + std::uniform_int_distribution str_len(1, max_str_len - 1); + + char* str {new char[max_str_len]}; + + for (std::size_t i {}; i < 1024; ++i) + { + std::memset(str, '\0', max_str_len); + const std::size_t current_str_len {str_len(rng)}; + boost::crypt::generate_random_string(str, current_str_len); + const auto uuid_res {get_boost_uuid_result(str, current_str_len)}; + + // boost::crypt::array is implicitly convertible to std::array + const std::array crypt_res = boost::crypt::sha1(str, current_str_len); + + for (std::size_t j {}; j < crypt_res.size(); ++j) + { + if (!BOOST_TEST_EQ(uuid_res[j], crypt_res[j])) + { + // LCOV_EXCL_START + std::cerr << "Failure with string: " << str << std::endl; + break; + // LCOV_EXCL_STOP + } + } + } + + delete[] str; +} + +template +void test_random_piecewise_values() +{ + constexpr std::size_t max_str_len {65535U}; + std::mt19937_64 rng(42); + std::uniform_int_distribution str_len(1, max_str_len - 1); + + char* str {new char[max_str_len]}; + char* str_2 {new char[max_str_len]}; + + for (std::size_t i {}; i < 1024; ++i) + { + boost::uuids::detail::sha1 boost_hasher; + boost::crypt::sha1_hasher sha1_hasher; + + std::memset(str, '\0', max_str_len); + std::memset(str_2, '\0', max_str_len); + + const std::size_t current_str_len {str_len(rng)}; + boost::crypt::generate_random_string(str, current_str_len); + boost::crypt::generate_random_string(str_2, current_str_len); + + boost_hasher.process_bytes(str, current_str_len); + boost_hasher.process_bytes(str_2, current_str_len); + boost_hasher.process_byte(52); // "4" + unsigned char digest[20]; + boost_hasher.get_digest(digest); + + std::array uuid_res {}; + for (std::size_t j {}; j < 20U; ++j) + { + uuid_res[j] = digest[j]; + } + + sha1_hasher.process_bytes(str, current_str_len); + sha1_hasher.process_bytes(str_2, current_str_len); + sha1_hasher.process_byte(52); // "4" + const auto crypt_res {sha1_hasher.get_digest()}; + + for (std::size_t j {}; j < crypt_res.size(); ++j) + { + if (!BOOST_TEST_EQ(uuid_res[j], crypt_res[j])) + { + // LCOV_EXCL_START + std::cerr << "Failure with string: " << str << std::endl; + break; + // LCOV_EXCL_STOP + } + } + } + + delete[] str; + delete[] str_2; +} + +template +void test_file(T filename, const std::array& res) +{ + const auto crypt_res {boost::crypt::sha1_file(filename)}; + + for (std::size_t j {}; j < crypt_res.size(); ++j) + { + if (!BOOST_TEST_EQ(res[j], crypt_res[j])) + { + // LCOV_EXCL_START + std::cerr << "Failure with file: " << filename << std::endl; + break; + // LCOV_EXCL_STOP + } + } +} + +template +void test_invalid_file(T filename) +{ + constexpr std::array res{}; + + const auto crypt_res {boost::crypt::sha1_file(filename)}; + + for (std::size_t j {}; j < crypt_res.size(); ++j) + { + if (!BOOST_TEST_EQ(res[j], crypt_res[j])) + { + // LCOV_EXCL_START + std::cerr << "Failure with file: " << filename << std::endl; + break; + // LCOV_EXCL_STOP + } + } +} + +void files_test() +{ + // Based off where we are testing from (test vs boost_root) we need to adjust our filepath + const char* filename; + const char* filename_2; + + // Boost-root + std::ifstream fd("libs/crypt/test/test_file_1.txt", std::ios::binary | std::ios::in); + filename = "libs/crypt/test/test_file_1.txt"; + filename_2 = "libs/crypt/test/test_file_2.txt"; + + // LCOV_EXCL_START + if (!fd.is_open()) + { + // Local test directory or IDE + std::ifstream fd2("test_file_1.txt", std::ios::binary | std::ios::in); + filename = "test_file_1.txt"; + filename_2 = "test_file_2.txt"; + + if (!fd2.is_open()) + { + // test/cover + std::ifstream fd3("../test_file_1.txt", std::ios::binary | std::ios::in); + filename = "../test_file_1.txt"; + filename_2 = "../test_file_2.txt"; + + if (!fd3.is_open()) + { + std::cerr << "Test not run due to file system issues" << std::endl; + return; + } + else + { + fd3.close(); + } + } + else + { + fd2.close(); + } + } + else + { + fd.close(); + } + // LCOV_EXCL_STOP + + // On macOS 15 + // sha1 test_file_1.txt + // SHA1 (test_file_1.txt) = 9c04cd6372077e9b11f70ca111c9807dc7137e4b + constexpr std::array res{0x9c, 0x04, 0xcd, 0x63, 0x72, 0x07, 0x7e, 0x9b, 0x11, 0xf7, + 0x0c, 0xa1, 0x11, 0xc9, 0x80, 0x7d, 0xc7, 0x13, 0x7e, 0x4b}; + + test_file(filename, res); + + const std::string str_filename {filename}; + test_file(str_filename, res); + + #ifdef BOOST_CRYPT_HAS_STRING_VIEW + const std::string_view str_view_filename {str_filename}; + test_file(str_view_filename, res); + #endif + + const auto invalid_filename = "broken.bin"; + test_invalid_file(invalid_filename); + + const std::string str_invalid_filename {invalid_filename}; + test_invalid_file(str_invalid_filename); + + #ifdef BOOST_CRYPT_HAS_STRING_VIEW + const std::string_view str_view_invalid_filename {str_invalid_filename}; + test_invalid_file(str_view_invalid_filename); + #endif + + // On macOS 15 + // sha1 test_file_2.txt + // SHA1 (test_file_2.txt) = 5d987ba69fde8b2500594799b47bd9255ac9cb65 + constexpr std::array res_2{0x5d, 0x98, 0x7b, 0xa6, 0x9f, 0xde, 0x8b, 0x25, 0x00, 0x59, + 0x47, 0x99, 0xb4, 0x7b, 0xd9, 0x25, 0x5a, 0xc9, 0xcb, 0x65}; + + test_file(filename_2, res_2); + + const char* test_null_file = nullptr; + test_invalid_file(test_null_file); +} + +void test_invalid_state() +{ + boost::crypt::sha1_hasher hasher; + auto current_state = hasher.process_bytes("test", 4); + BOOST_TEST(current_state == boost::crypt::hasher_state::success); + + hasher.get_digest(); + + const auto bad_state = hasher.process_bytes("test", 4); + BOOST_TEST(bad_state == boost::crypt::hasher_state::state_error); + + const auto digest = hasher.get_digest(); + + for (const auto& val : digest) + { + BOOST_TEST_EQ(val, static_cast(0)); + } + + hasher.init(); + + current_state = hasher.process_bytes("test", 4); + BOOST_TEST(current_state == boost::crypt::hasher_state::success); + const char* ptr = nullptr; + current_state = hasher.process_bytes(ptr, 4); + BOOST_TEST(current_state == boost::crypt::hasher_state::null); + + const char16_t* ptr16 = nullptr; + current_state = hasher.process_bytes(ptr16, 4); + BOOST_TEST(current_state == boost::crypt::hasher_state::null); + + const char32_t* ptr32 = nullptr; + current_state = hasher.process_bytes(ptr32, 4); + BOOST_TEST(current_state == boost::crypt::hasher_state::null); + + const wchar_t* wptr = nullptr; + current_state = hasher.process_bytes(wptr, 4); + BOOST_TEST(current_state == boost::crypt::hasher_state::null); +} + +int main() +{ + basic_tests(); + string_test(); + string_view_test(); + bad_input(); + test_class(); + + test_random_values(); + test_random_piecewise_values(); + + test_random_values(); + test_random_piecewise_values(); + + test_random_values(); + test_random_piecewise_values(); + + test_random_values(); + test_random_piecewise_values(); + + // The Windows file system returns a different result than on UNIX platforms + #if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) + files_test(); + #endif + + test_invalid_state(); + + return boost::report_errors(); +}