summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorSadie Powell <sadie@witchery.services>2024-03-10 17:48:45 +0000
committerSadie Powell <sadie@witchery.services>2024-03-10 20:46:03 +0000
commitfb9c8950ed0fea4c981e2ea7301f5f969296a116 (patch)
treed379409e7a713006455e40edac8ef51127502b12 /modules
parenta849a81ac3005aae933cb5a673b873fbd6125c2a (diff)
Refactor the enc_bcrypt module and expose an encryption provider.
Diffstat (limited to 'modules')
-rw-r--r--modules/encryption/enc_bcrypt.cpp176
-rw-r--r--modules/extra/enc_argon2.cpp1
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
{