diff options
author | Sadie Powell <sadie@witchery.services> | 2024-03-10 17:48:45 +0000 |
---|---|---|
committer | Sadie Powell <sadie@witchery.services> | 2024-03-10 20:46:03 +0000 |
commit | fb9c8950ed0fea4c981e2ea7301f5f969296a116 (patch) | |
tree | d379409e7a713006455e40edac8ef51127502b12 /modules | |
parent | a849a81ac3005aae933cb5a673b873fbd6125c2a (diff) |
Refactor the enc_bcrypt module and expose an encryption provider.
Diffstat (limited to 'modules')
-rw-r--r-- | modules/encryption/enc_bcrypt.cpp | 176 | ||||
-rw-r--r-- | modules/extra/enc_argon2.cpp | 1 |
2 files changed, 113 insertions, 64 deletions
diff --git a/modules/encryption/enc_bcrypt.cpp b/modules/encryption/enc_bcrypt.cpp index cc255813d..7fd3864ac 100644 --- a/modules/encryption/enc_bcrypt.cpp +++ b/modules/encryption/enc_bcrypt.cpp @@ -8,100 +8,158 @@ * */ -#include "module.h" -#include "modules/encryption.h" + +#include <climits> +#include <random> #include "bcrypt/crypt_blowfish.c" -class EBCRYPT final - : public Module +#include "module.h" +#include "modules/encryption.h" + +class BCryptContext final + : public Encryption::Context { - unsigned int rounds; +private: + Anope::string buffer; - Anope::string Salt() + Anope::string GenerateSalt() { + static std::random_device device; + static std::mt19937 engine(device()); + static std::uniform_int_distribution<int> dist(CHAR_MIN, CHAR_MAX); char entropy[16]; - for (auto &chr : entropy) - chr = static_cast<char>(Anope::RandomNumber() % 0xFF); + for (size_t i = 0; i < sizeof(entropy); ++i) + entropy[i] = static_cast<char>(dist(engine)); char salt[32]; if (!_crypt_gensalt_blowfish_rn("$2a$", rounds, entropy, sizeof(entropy), salt, sizeof(salt))) - return ""; + { + Log(LOG_DEBUG) << "Unable to generate a salt for Bcrypt: " << strerror(errno); + return {}; + } return salt; } - static Anope::string Generate(const Anope::string &data, const Anope::string &salt) +public: + static unsigned long rounds; + + static Anope::string Hash(const Anope::string &data, const Anope::string &salt) { char hash[64]; - _crypt_blowfish_rn(data.c_str(), salt.c_str(), hash, sizeof(hash)); + if (!_crypt_blowfish_rn(data.c_str(), salt.c_str(), hash, sizeof(hash))) + { + Log(LOG_DEBUG) << "Unable to generate a hash for Bcrypt: " << strerror(errno); + return {}; + } return hash; } - bool Compare(const Anope::string &string, const Anope::string &hash) + void Update(const unsigned char *data, size_t len) override { - Anope::string ret = Generate(string, hash); - if (ret.empty()) - return false; + buffer.append(reinterpret_cast<const char *>(data), len); + } - return (ret == hash); + Anope::string Finalize() override + { + auto salt = GenerateSalt(); + if (!salt.empty()) + return {}; + return Hash(this->buffer, salt); } +}; +unsigned long BCryptContext::rounds = 10; + +class BCryptProvider final + : public Encryption::Provider +{ public: - EBCRYPT(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR), - rounds(10) + BCryptProvider(Module *creator) + : Encryption::Provider(creator, "bcrypt", 0, 0) + { + } + + bool Compare(const Anope::string &hash, const Anope::string &plain) override + { + auto newhash = BCryptContext::Hash(plain, hash); + return !newhash.empty() && hash.equals_cs(newhash); + } + + std::unique_ptr<Encryption::Context> CreateContext() override { - // Test a pre-calculated hash - bool test = Compare("Test!", "$2a$10$x9AQFAQScY0v9KF2suqkEOepsHFrG.CXHbIXI.1F28SfSUb56A/7K"); - - Anope::string salt; - Anope::string hash; - // Make sure it's working - if (!test || (salt = Salt()).empty() || (hash = Generate("Test!", salt)).empty() || !Compare("Test!", hash)) - throw ModuleException("BCrypt could not load!"); + return std::make_unique<BCryptContext>(); + } + + Anope::string ToPrintable(const Anope::string &hash) override + { + // The crypt_blowfish library does not expose a raw form. + return hash; + } +}; + + +class EBCrypt final + : public Module +{ +private: + BCryptProvider bcryptprovider; + +public: + EBCrypt(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, ENCRYPTION | VENDOR) + , bcryptprovider(this) + { + bcryptprovider.Check({ + { "$2a$10$c9lUAuJmTYXEfNuLOiyIp.lZTMM.Rw5qsSAyZhvGT9EC3JevkUuOu", "" }, + { "$2a$10$YV4jDSGs0ZtQbpL6IHtNO.lt5Q.uzghIohCcnERQVBGyw7QJMfyhe", "The quick brown fox jumps over the lazy dog" }, + }); } EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override { - dest = "bcrypt:" + Generate(src, Salt()); + dest = "bcrypt:" + bcryptprovider.Encrypt(src); Log(LOG_DEBUG_2) << "(enc_bcrypt) hashed password from [" << src << "] to [" << dest << "]"; return EVENT_ALLOW; } void OnCheckAuthentication(User *, IdentifyRequest *req) override { - const NickAlias *na = NickAlias::Find(req->GetAccount()); - if (na == NULL) + const auto *na = NickAlias::Find(req->GetAccount()); + if (!na) return; - NickCore *nc = na->nc; - size_t pos = nc->pass.find(':'); + NickCore *nc = na->nc; + auto pos = nc->pass.find(':'); if (pos == Anope::string::npos) return; + Anope::string hash_method(nc->pass.begin(), nc->pass.begin() + pos); - if (hash_method != "bcrypt") + if (!hash_method.equals_cs("bcrypt")) return; - if (Compare(req->GetPassword(), nc->pass.substr(7))) + Anope::string hash_value(nc->pass.begin() + pos + 1, nc->pass.end()); + if (bcryptprovider.Compare(hash_value, req->GetPassword())) { - /* if we are NOT the first module in the list, - * we want to re-encrypt the pass with the new encryption - */ - - unsigned int hashrounds = 0; + unsigned long rounds = 0; try { - size_t roundspos = nc->pass.find('$', 11); - if (roundspos == Anope::string::npos) - throw ConvertException("Could not find hashrounds"); + // Try to extract the rounds count to cher + pos = hash_value.find('$', 4); + if (pos == Anope::string::npos) + throw ConvertException("Malformed BCrypt hash?!"); - hashrounds = convertTo<unsigned int>(nc->pass.substr(11, roundspos - 11)); + rounds = convertTo<unsigned long>(hash_value.substr(4, pos - 4)); } catch (const ConvertException &) { - Log(this) << "Could not get the round size of a hash. This is probably a bug. Hash: " << nc->pass; + Log(LOG_DEBUG) << "Unable to determine the rounds of a bcrypt hash: " << hash_value; } - if (ModuleManager::FindFirstOf(ENCRYPTION) != this || (hashrounds && hashrounds != rounds)) + // If we are NOT the first encryption module or the Bcrypt rounds + // are different we want to re-encrypt the password with the primary + // encryption method. + if (ModuleManager::FindFirstOf(ENCRYPTION) != this || (rounds && rounds != BCryptContext::rounds)) Anope::Encrypt(req->GetPassword(), nc->pass); req->Success(this); } @@ -109,28 +167,20 @@ public: void OnReload(Configuration::Conf *conf) override { - Configuration::Block *block = conf->GetModule(this); - rounds = block->Get<unsigned int>("rounds", "10"); + auto *block = conf->GetModule(this); - if (rounds == 0) - { - rounds = 10; - Log(this) << "Rounds can't be 0! Setting ignored."; - } - else if (rounds < 10) + auto rounds = block->Get<unsigned long>("rounds", "10"); + if (rounds < 10 || rounds > 32) { - Log(this) << "10 to 12 rounds is recommended."; - } - else if (rounds >= 32) - { - rounds = 10; - Log(this) << "The maximum number of rounds supported is 31. Ignoring setting and using 10."; - } - else if (rounds >= 14) - { - Log(this) << "Are you sure you want to use " << stringify(rounds) << " in your bcrypt settings? This is very CPU intensive! Recommended rounds is 10-12."; + Log(this) << "Bcrypt rounds MUST be between 10 and 32 inclusive; using 10 instead of " << rounds << '.'; + BCryptContext::rounds = 10; + return; } + + if (rounds > 14) + Log(this) << "Bcrypt rounds higher than 14 are very CPU intensive; are you sure you want to use " << rounds << '?'; + BCryptContext::rounds = rounds; } }; -MODULE_INIT(EBCRYPT) +MODULE_INIT(EBCrypt) diff --git a/modules/extra/enc_argon2.cpp b/modules/extra/enc_argon2.cpp index 266a80d30..f45a942c4 100644 --- a/modules/extra/enc_argon2.cpp +++ b/modules/extra/enc_argon2.cpp @@ -113,7 +113,6 @@ public: } }; - class EArgon2 final : public Module { |