diff options
Diffstat (limited to 'modules/encryption/enc_sha2.cpp')
-rw-r--r-- | modules/encryption/enc_sha2.cpp | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/modules/encryption/enc_sha2.cpp b/modules/encryption/enc_sha2.cpp new file mode 100644 index 000000000..11c0e76f6 --- /dev/null +++ b/modules/encryption/enc_sha2.cpp @@ -0,0 +1,215 @@ +/* Module for providing SHA-2 hashing + * + * (C) 2003-2025 Anope Team + * Contact us at team@anope.org + * + * This program is free but copyrighted software; see the file COPYING for + * details. + * + */ + +#include <climits> +#include <random> + +#include "sha2/sha2.c" + +#include "module.h" +#include "modules/encryption.h" + +template <typename SHAContext, + void (* SHAInit)(SHAContext *), + void (* SHAUpdate)(SHAContext *, const unsigned char *, unsigned int), + void (* SHAFinal)(SHAContext *, unsigned char *)> +class SHA2Context final + : public Encryption::Context +{ +private: + SHAContext context; + const size_t digest_size; + +public: + SHA2Context(size_t ds) + : digest_size(ds) + { + SHAInit(&context); + } + + void Update(const unsigned char *data, size_t len) override + { + SHAUpdate(&context, data, len); + } + + Anope::string Finalize() override + { + std::vector<unsigned char> digest(digest_size); + SHAFinal(&context, digest.data()); + return Anope::string(reinterpret_cast<const char *>(digest.data()), digest.size()); + } +}; + +template <typename SHAContext, + void (* SHAInit)(SHAContext *), + void (* SHAUpdate)(SHAContext *, const unsigned char *, unsigned int), + void (* SHAFinal)(SHAContext *, unsigned char *)> +class SHA2Provider final + : public Encryption::Provider +{ +public: + SHA2Provider(Module *creator, const Anope::string &algorithm, size_t bs, size_t ds) + : Encryption::Provider(creator, algorithm, bs, ds) + { + } + + std::unique_ptr<Encryption::Context> CreateContext() override + { + return std::make_unique<SHA2Context<SHAContext, SHAInit, SHAUpdate, SHAFinal>>(this->digest_size); + } +}; + +class ESHA2 final + : public Module +{ +private: + Encryption::Provider *defaultprovider = nullptr; + SHA2Provider<sha224_ctx, sha224_init, sha224_update, sha224_final> sha224provider; + SHA2Provider<sha256_ctx, sha256_init, sha256_update, sha256_final> sha256provider; + SHA2Provider<sha384_ctx, sha384_init, sha384_update, sha384_final> sha384provider; + SHA2Provider<sha512_ctx, sha512_init, sha512_update, sha512_final> sha512provider; + + Anope::string GenerateKey(size_t keylen) + { + static std::random_device device; + static std::mt19937 engine(device()); + static std::uniform_int_distribution<int> dist(CHAR_MIN, CHAR_MAX); + Anope::string keybuf(keylen, ' '); + for (size_t i = 0; i < keylen; ++i) + keybuf[i] = static_cast<char>(dist(engine)); + return keybuf; + } + + Encryption::Provider *GetAlgorithm(const Anope::string &algorithm) + { + if (algorithm == "sha224") + return &sha224provider; + if (algorithm == "sha256") + return &sha256provider; + if (algorithm == "sha384") + return &sha384provider; + if (algorithm == "sha512") + return &sha512provider; + return nullptr; + } + +public: + ESHA2(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, ENCRYPTION | VENDOR) + , sha224provider(this, "sha224", SHA224_BLOCK_SIZE, SHA224_DIGEST_SIZE) + , sha256provider(this, "sha256", SHA256_BLOCK_SIZE, SHA256_DIGEST_SIZE) + , sha384provider(this, "sha384", SHA384_BLOCK_SIZE, SHA384_DIGEST_SIZE) + , sha512provider(this, "sha512", SHA512_BLOCK_SIZE, SHA512_DIGEST_SIZE) + { + sha224provider.Check({ + { "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", "" }, + { "730e109bd7a8a32b1cb9d9a09aa2325d2430587ddbc0c38bad911525", "The quick brown fox jumps over the lazy dog" }, + }); + sha256provider.Check({ + { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "" }, + { "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", "The quick brown fox jumps over the lazy dog" }, + }); + sha384provider.Check({ + { "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", "" }, + { "ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1", "The quick brown fox jumps over the lazy dog" }, + }); + sha512provider.Check({ + { "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", "" }, + { "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6", "The quick brown fox jumps over the lazy dog" }, + }); + } + + void OnReload(Configuration::Conf *conf) override + { + this->defaultprovider = GetAlgorithm(Config->GetModule(this)->Get<const Anope::string>("algorithm", "sha256")); + } + + EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override + { + if (!defaultprovider) + return EVENT_CONTINUE; + + auto key = GenerateKey(defaultprovider->digest_size); + auto hmac = defaultprovider->HMAC(key, src); + auto enc = "hmac-" + defaultprovider->name + ":" + Anope::Hex(hmac) + ":" + Anope::Hex(key); + Log(LOG_DEBUG_2) << "(enc_sha2) hashed password from [" << src << "] to [" << enc << "]"; + dest = enc; + return EVENT_ALLOW; + } + + void OnCheckAuthentication(User *, IdentifyRequest *req) override + { + const auto *na = NickAlias::Find(req->GetAccount()); + if (!na) + return; + + NickCore *nc = na->nc; + auto apos = nc->pass.find(':'); + if (apos == Anope::string::npos) + return; + + Anope::string hash_method(nc->pass.begin(), nc->pass.begin() + apos); + bool is_hmac = !hash_method.compare(0, 5, "hmac-", 5); + if (!is_hmac && hash_method.compare(0, 4, "raw-", 4)) + return; // Not a SHA-2 password. + + auto *provider = GetAlgorithm(hash_method.substr(is_hmac ? 5 : 4)); + if (!provider) + return; // Not a hash for this module. + + auto valid = false; + if (is_hmac) + { + auto bpos = nc->pass.find(':', apos + 1); + if (bpos == Anope::string::npos) + return; // No HMAC key. + + // If you are using PHP you can validate passwords like this: + // + // <?php + // function check_pass($user_pass, $anope_pass) { + // [$algo, $hash, $key] = explode(':', $anope_pass); + // $hash_algo = substr($algo, 5); + // $unhex_key = hex2bin($key); + // $user_hash = hash_hmac($hash_algo, $user_pass, $unhex_key); + // return hash_equals($hash, $user_hash); + // } + // + // $anope_pass = "hmac-sha256:5f7f039818f6e10be84dab0f49610a387d3818b2c883eacdc1778c66a7cecf3b:1a924a3b05a11d7bf3c752d820391dbe01fb7e5d83f7400765de987c3fbb0ec3"; + // var_dump(check_pass("test12345", $anope_pass)); // valid + // var_dump(check_pass("test123456789", $anope_pass)); // invalid + + Anope::string pass_hex(nc->pass.begin() + apos + 1, nc->pass.begin() + bpos); + Anope::string key_hex(nc->pass.begin() + bpos + 1, nc->pass.end()); + Anope::string key; + Anope::Unhex(key_hex, key); + + auto enc = Anope::Hex(provider->HMAC(key, req->GetPassword())); + valid = pass_hex.equals_cs(enc); + } + else + { + Anope::string pass_hex(nc->pass.begin() + apos + 1, nc->pass.end()); + valid = provider->Compare(pass_hex, req->GetPassword()); + } + + if (valid) + { + // If we are NOT the first encryption module, the password is a raw + // hash, or the algorithm is different we want to re-encrypt the + // password with the primary encryption method. + if (ModuleManager::FindFirstOf(ENCRYPTION) != this || !is_hmac || provider != defaultprovider) + Anope::Encrypt(req->GetPassword(), nc->pass); + req->Success(this); + } + } +}; + +MODULE_INIT(ESHA2) |