/* vim:set ts=2 sw=2 sts=2 et: */ /** * \author Marcus Holland-Moritz (github@mhxnet.de) * \copyright Copyright (c) Marcus Holland-Moritz * * This file is part of dwarfs. * * dwarfs is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * dwarfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with dwarfs. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace dwarfs { namespace { std::unordered_set supported_algorithms{ "xxh3-64", "xxh3-128", }; std::string make_hexdigest(checksum::impl& cs) { std::array tmp; auto dig_size = cs.digest_size(); assert(dig_size <= tmp.size()); if (!cs.finalize(tmp.data())) { throw std::runtime_error("failed to finalize digest"); } std::string result; result.resize(dig_size * 2); boost::algorithm::hex_lower(tmp.begin(), tmp.begin() + dig_size, result.begin()); return result; } class checksum_evp : public checksum::impl { public: explicit checksum_evp(::EVP_MD const* evp) : context_{::EVP_MD_CTX_new(), &::EVP_MD_CTX_free} , dig_size_(::EVP_MD_size(evp)) { DWARFS_CHECK(::EVP_DigestInit(context_.get(), evp), "EVP_DigestInit() failed"); } void update(void const* data, size_t size) override { assert(context_); DWARFS_CHECK(::EVP_DigestUpdate(context_.get(), data, size), "EVP_DigestUpdate() failed"); } bool finalize(void* digest) override { if (!context_) { return false; } unsigned int dig_size = 0; bool rv = ::EVP_DigestFinal_ex( context_.get(), reinterpret_cast(digest), &dig_size); context_.reset(); if (rv) { DWARFS_CHECK( dig_size_ == dig_size, fmt::format("digest size mismatch: {0} != {1}", dig_size_, dig_size)); } return rv; } std::string hexdigest() override { return make_hexdigest(*this); } static std::vector available_algorithms() { std::vector available; ::EVP_MD_do_all( [](const ::EVP_MD*, const char* from, const char* to, void* vec) { if (!to) { reinterpret_cast*>(vec)->emplace_back( from); } }, &available); available.erase(std::remove_if(available.begin(), available.end(), std::not_fn(is_available)), available.end()); return available; } static bool is_available(std::string const& algo) { if (auto md = ::EVP_get_digestbyname(algo.c_str())) { ::EVP_MD_CTX* cx = ::EVP_MD_CTX_new(); bool success = ::EVP_DigestInit(cx, md); ::EVP_MD_CTX_free(cx); return success; } return false; } size_t digest_size() override { return dig_size_; } private: std::unique_ptr<::EVP_MD_CTX, decltype(&::EVP_MD_CTX_free)> context_; size_t const dig_size_; }; struct xxh3_64_policy { using result_type = XXH64_hash_t; static constexpr auto reset = XXH3_64bits_reset; static constexpr auto update = XXH3_64bits_update; static constexpr auto digest = XXH3_64bits_digest; }; struct xxh3_128_policy { using result_type = XXH128_hash_t; static constexpr auto reset = XXH3_128bits_reset; static constexpr auto update = XXH3_128bits_update; static constexpr auto digest = XXH3_128bits_digest; }; template class checksum_xxh3 : public checksum::impl { public: checksum_xxh3() : state_{XXH3_createState(), &XXH3_freeState} { DWARFS_CHECK(Policy::reset(state_.get()) == XXH_OK, "XXH3 reset failed"); } void update(void const* data, size_t size) override { assert(state_); auto err = Policy::update(state_.get(), data, size); DWARFS_CHECK(err == XXH_OK, fmt::format("XXH3 update failed: {}", static_cast(err))); } bool finalize(void* digest) override { if (!state_) { return false; } auto hash = Policy::digest(state_.get()); state_.reset(); ::memcpy(digest, &hash, sizeof(hash)); return true; } std::string hexdigest() override { return make_hexdigest(*this); } size_t digest_size() override { static_assert( sizeof(typename Policy::result_type) == sizeof(typename decltype(std::function{Policy::digest})::result_type)); return sizeof(typename Policy::result_type); } private: std::unique_ptr state_; }; using checksum_xxh3_64 = checksum_xxh3; using checksum_xxh3_128 = checksum_xxh3; template bool verify_impl(T&& alg, void const* data, size_t size, const void* digest, size_t digest_size) { std::array tmp; checksum cs(std::forward(alg)); DWARFS_CHECK(digest_size == cs.digest_size(), "digest size mismatch"); cs.update(data, size); return cs.finalize(tmp.data()) && ::memcmp(digest, tmp.data(), digest_size) == 0; } } // namespace bool checksum::is_available(std::string const& algo) { return supported_algorithms.count(algo) or checksum_evp::is_available(algo); } std::vector checksum::available_algorithms() { auto available_evp = checksum_evp::available_algorithms(); std::vector available; available.insert(available.end(), supported_algorithms.begin(), supported_algorithms.end()); available.insert(available.end(), available_evp.begin(), available_evp.end()); std::sort(available.begin(), available.end()); return available; } bool checksum::verify(algorithm alg, void const* data, size_t size, const void* digest, size_t digest_size) { return verify_impl(alg, data, size, digest, digest_size); } bool checksum::verify(std::string const& alg, void const* data, size_t size, const void* digest, size_t digest_size) { return verify_impl(alg, data, size, digest, digest_size); } checksum::checksum(algorithm alg) { switch (alg) { case algorithm::SHA2_512_256: impl_ = std::make_unique(::EVP_sha512_256()); break; case algorithm::XXH3_64: impl_ = std::make_unique(); break; case algorithm::XXH3_128: impl_ = std::make_unique(); break; default: DWARFS_CHECK(false, "unknown algorithm"); break; } } checksum::checksum(std::string const& alg) { if (alg == "xxh3-64") { impl_ = std::make_unique(); } else if (alg == "xxh3-128") { impl_ = std::make_unique(); } else if (auto md = ::EVP_get_digestbyname(alg.c_str())) { impl_ = std::make_unique(md); } else { DWARFS_CHECK(false, "unknown algorithm"); } } std::ostream& operator<<(std::ostream& os, checksum::algorithm alg) { switch (alg) { case checksum::algorithm::SHA2_512_256: os << "SHA2_512_256"; break; case checksum::algorithm::XXH3_64: os << "XXH3_64"; break; case checksum::algorithm::XXH3_128: os << "XXH3_128"; break; default: os << ""; break; } return os; } } // namespace dwarfs