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 }