reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

hash.cpp (7196B)


      1 /* ReaPack: Package manager for REAPER
      2  * Copyright (C) 2015-2025  Christian Fillion
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU Lesser General Public License as published by
      6  * the Free Software Foundation, either version 3 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  * GNU Lesser General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Lesser General Public License
     15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16  */
     17 
     18 #include "hash.hpp"
     19 
     20 #include <cstdio>
     21 #include <vector>
     22 
     23 #ifdef _WIN32
     24 #  include <map>
     25 #  include <windows.h>
     26 
     27 class CNGAlgorithmProvider;
     28 std::map<Hash::Algorithm, std::weak_ptr<CNGAlgorithmProvider>> s_algoCache;
     29 
     30 class CNGAlgorithmProvider {
     31 public:
     32   static std::shared_ptr<CNGAlgorithmProvider> get(const Hash::Algorithm algo)
     33   {
     34     auto it = s_algoCache.find(algo);
     35 
     36     if(it != s_algoCache.end() && !it->second.expired())
     37       return it->second.lock();
     38 
     39     wchar_t *algoName;
     40 
     41     switch(algo) {
     42     case Hash::SHA256:
     43       algoName = BCRYPT_SHA256_ALGORITHM;
     44       break;
     45     default:
     46       return nullptr;
     47     }
     48 
     49     auto provider = std::make_shared<CNGAlgorithmProvider>(algoName);
     50     s_algoCache[algo] = provider;
     51     return provider;
     52   }
     53 
     54   CNGAlgorithmProvider(const wchar_t *algoName)
     55   {
     56     BCryptOpenAlgorithmProvider(&m_algo, algoName, MS_PRIMITIVE_PROVIDER, 0);
     57   }
     58 
     59   ~CNGAlgorithmProvider()
     60   {
     61     BCryptCloseAlgorithmProvider(m_algo, 0);
     62   }
     63 
     64   operator BCRYPT_ALG_HANDLE() const
     65   {
     66     return m_algo;
     67   }
     68 
     69 private:
     70   BCRYPT_ALG_HANDLE m_algo;
     71 };
     72 
     73 class Hash::CNGContext : public Hash::Context {
     74 public:
     75   CNGContext(const std::shared_ptr<CNGAlgorithmProvider> &algo)
     76     : m_algo(algo), m_hash(), m_hashLength()
     77   {
     78     unsigned long bytesWritten;
     79     BCryptGetProperty(*m_algo, BCRYPT_HASH_LENGTH,
     80       reinterpret_cast<PUCHAR>(&m_hashLength), sizeof(m_hashLength),
     81       &bytesWritten, 0);
     82 
     83     BCryptCreateHash(*m_algo, &m_hash, nullptr, 0, nullptr, 0, 0);
     84   }
     85 
     86   ~CNGContext() override
     87   {
     88     BCryptDestroyHash(m_hash);
     89   }
     90 
     91   size_t hashSize() const override
     92   {
     93     return m_hashLength;
     94   }
     95 
     96   void addData(const char *data, const size_t len) override
     97   {
     98     BCryptHashData(m_hash,
     99       reinterpret_cast<unsigned char *>(const_cast<char *>(data)),
    100       static_cast<unsigned long>(len), 0);
    101   }
    102 
    103   void getHash(unsigned char *out)
    104   {
    105     BCryptFinishHash(m_hash, out, m_hashLength, 0);
    106   }
    107 
    108 private:
    109   std::shared_ptr<CNGAlgorithmProvider> m_algo;
    110   BCRYPT_HASH_HANDLE m_hash;
    111   unsigned long m_hashLength;
    112 };
    113 
    114 #elif __APPLE__
    115 #  define COMMON_DIGEST_FOR_OPENSSL
    116 #  include <CommonCrypto/CommonDigest.h>
    117 
    118 class Hash::SHA256Context : public Hash::Context {
    119 public:
    120   SHA256Context()
    121   {
    122     SHA256_Init(&m_context);
    123   }
    124 
    125   size_t hashSize() const override
    126   {
    127     return SHA256_DIGEST_LENGTH;
    128   }
    129 
    130   void addData(const char *data, const size_t len) override
    131   {
    132     SHA256_Update(&m_context, data, len);
    133   }
    134 
    135   void getHash(unsigned char *out) override
    136   {
    137     SHA256_Final(out, &m_context);
    138   }
    139 
    140 private:
    141   SHA256_CTX m_context;
    142 };
    143 
    144 #else
    145 #  include <openssl/evp.h>
    146 
    147 #  ifdef RUNTIME_OPENSSL
    148 #    include <dlfcn.h>
    149 
    150 static struct OpenSSL {
    151   OpenSSL()
    152     : m_loaded { true }
    153   {
    154     constexpr const char *names[] { "libcrypto.so.3", "libcrypto.so.1.1" };
    155 
    156     for(const char *name : names) {
    157       if((m_so = dlopen(name, RTLD_LAZY)))
    158         return;
    159     }
    160 
    161     m_loaded = false;
    162   }
    163 
    164   ~OpenSSL()
    165   {
    166     if(m_so)
    167       dlclose(m_so);
    168   }
    169 
    170   bool isLoaded() const { return m_loaded; }
    171 
    172   template<typename T> T get(const char *name)
    173   {
    174     const T func { m_so ? reinterpret_cast<T>(dlsym(m_so, name)) : nullptr };
    175     if(!func)
    176       m_loaded = false;
    177     return func;
    178   }
    179 
    180 private:
    181   void *m_so;
    182   bool m_loaded;
    183 } g_openssl;
    184 
    185 #    define IMPORT_OPENSSL(func) \
    186        static auto _##func { g_openssl.get<decltype(&func)>(#func) };
    187 
    188 IMPORT_OPENSSL(EVP_DigestFinal_ex);
    189 #    define EVP_DigestFinal_ex _EVP_DigestFinal_ex
    190 IMPORT_OPENSSL(EVP_DigestInit_ex);
    191 #    define EVP_DigestInit_ex _EVP_DigestInit_ex
    192 IMPORT_OPENSSL(EVP_DigestUpdate);
    193 #    define EVP_DigestUpdate _EVP_DigestUpdate
    194 IMPORT_OPENSSL(EVP_MD_CTX_new);
    195 #    define EVP_MD_CTX_new _EVP_MD_CTX_new
    196 IMPORT_OPENSSL(EVP_MD_CTX_free);
    197 #    define EVP_MD_CTX_free _EVP_MD_CTX_free
    198 #    ifdef EVP_MD_size // OpenSSL 3
    199 IMPORT_OPENSSL(EVP_MD_get_size);
    200 #      undef  EVP_MD_size
    201 #      define EVP_MD_size _EVP_MD_get_size
    202 #    else
    203 IMPORT_OPENSSL(EVP_MD_size);
    204 #      define EVP_MD_size _EVP_MD_size
    205 #    endif
    206 IMPORT_OPENSSL(EVP_sha256);
    207 #    define EVP_sha256 _EVP_sha256
    208 #  endif
    209 
    210 class Hash::EVPContext : public Hash::Context {
    211 public:
    212   static const EVP_MD *getAlgo(const Algorithm algo)
    213   {
    214 #ifdef RUNTIME_OPENSSL
    215     if(!g_openssl.isLoaded())
    216       return nullptr;
    217 #endif
    218 
    219     switch(algo) {
    220     case SHA256:
    221       return EVP_sha256();
    222     default:
    223       return nullptr;
    224     }
    225   }
    226 
    227   EVPContext(const EVP_MD *md)
    228     : m_md { md }
    229   {
    230     m_ctx = EVP_MD_CTX_new();
    231     EVP_DigestInit_ex(m_ctx, m_md, nullptr);
    232   }
    233 
    234   ~EVPContext()
    235   {
    236     EVP_MD_CTX_free(m_ctx);
    237   }
    238 
    239   size_t hashSize() const override
    240   {
    241     return EVP_MD_size(m_md);
    242   }
    243 
    244   void addData(const char *data, const size_t len) override
    245   {
    246     EVP_DigestUpdate(m_ctx, data, len);
    247   }
    248 
    249   void getHash(unsigned char *out) override
    250   {
    251     EVP_DigestFinal_ex(m_ctx, out, nullptr);
    252   }
    253 
    254 private:
    255   EVP_MD_CTX *m_ctx;
    256   const EVP_MD *m_md;
    257 };
    258 #endif
    259 
    260 Hash::Hash(const Algorithm algo)
    261   : m_algo(algo)
    262 {
    263 #ifdef _WIN32
    264   if(const auto &algoProvider = CNGAlgorithmProvider::get(algo))
    265     m_context = std::make_unique<CNGContext>(algoProvider);
    266 #elif __APPLE__
    267   switch(algo) {
    268   case SHA256:
    269     m_context = std::make_unique<SHA256Context>();
    270     break;
    271   }
    272 #else
    273   if(const auto &algoDesc = EVPContext::getAlgo(algo))
    274     m_context = std::make_unique<EVPContext>(algoDesc);
    275 #endif
    276 }
    277 
    278 void Hash::addData(const char *data, const size_t len)
    279 {
    280   if(m_context)
    281     m_context->addData(data, len);
    282 }
    283 
    284 const std::string &Hash::digest()
    285 {
    286   if(!m_context || !m_value.empty())
    287     return m_value;
    288 
    289   // Assuming m_algo and hashSize can fit in one byte. We'll need to implement
    290   // multihash's varint if we need larger values in the future.
    291   const size_t hashSize = m_context->hashSize();
    292 
    293   std::vector<unsigned char> multihash(2 + hashSize);
    294   multihash[0] = m_algo;
    295   multihash[1] = static_cast<unsigned char>(hashSize);
    296   m_context->getHash(&multihash[2]);
    297 
    298   m_value.resize(multihash.size() * 2);
    299 
    300   for(size_t i = 0; i < multihash.size(); ++i)
    301     sprintf(&m_value[i * 2], "%02x", multihash[i]);
    302 
    303   return m_value;
    304 }
    305 
    306 bool Hash::getAlgorithm(const std::string &hash, Algorithm *out)
    307 {
    308   unsigned int algo, size;
    309   if(sscanf(hash.c_str(), "%2x%2x", &algo, &size) != 2)
    310     return false;
    311 
    312   if(hash.size() != (size * 2) + 4)
    313     return false;
    314 
    315   switch(algo) {
    316   case SHA256:
    317     *out = static_cast<Algorithm>(algo);
    318     return true;
    319   default:
    320     return false;
    321   };
    322 }