mirror of
https://github.com/anope/anope.git
synced 2026-07-01 12:46:39 +02:00
Refactor the enc_bcrypt module and expose an encryption provider.
This commit is contained in:
@@ -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
|
||||
{
|
||||
unsigned int rounds;
|
||||
#include "module.h"
|
||||
#include "modules/encryption.h"
|
||||
|
||||
Anope::string Salt()
|
||||
class BCryptContext final
|
||||
: public Encryption::Context
|
||||
{
|
||||
private:
|
||||
Anope::string buffer;
|
||||
|
||||
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;
|
||||
|
||||
return (ret == hash);
|
||||
buffer.append(reinterpret_cast<const char *>(data), len);
|
||||
}
|
||||
|
||||
public:
|
||||
EBCRYPT(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR),
|
||||
rounds(10)
|
||||
Anope::string Finalize() override
|
||||
{
|
||||
// Test a pre-calculated hash
|
||||
bool test = Compare("Test!", "$2a$10$x9AQFAQScY0v9KF2suqkEOepsHFrG.CXHbIXI.1F28SfSUb56A/7K");
|
||||
auto salt = GenerateSalt();
|
||||
if (!salt.empty())
|
||||
return {};
|
||||
return Hash(this->buffer, salt);
|
||||
}
|
||||
};
|
||||
|
||||
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!");
|
||||
unsigned long BCryptContext::rounds = 10;
|
||||
|
||||
class BCryptProvider final
|
||||
: public Encryption::Provider
|
||||
{
|
||||
public:
|
||||
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
|
||||
{
|
||||
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)
|
||||
auto rounds = block->Get<unsigned long>("rounds", "10");
|
||||
if (rounds < 10 || rounds > 32)
|
||||
{
|
||||
rounds = 10;
|
||||
Log(this) << "Rounds can't be 0! Setting ignored.";
|
||||
}
|
||||
else if (rounds < 10)
|
||||
{
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user