diff options
author | Adam <adam@sigterm.info> | 2014-02-16 14:10:25 -0500 |
---|---|---|
committer | Adam <adam@sigterm.info> | 2014-02-16 14:10:25 -0500 |
commit | cffe86490047bae2fcba2e5bd8085c1854562645 (patch) | |
tree | 4bd259ccb26455bf0cfc7dfb4f7bb052d3ac05a9 | |
parent | 50d7bf710ec0501b7309892646a31da065aaae5a (diff) | |
parent | bc1575318bc40e00722049ae8055d290240b8886 (diff) |
Merge pull request #50 from ShutterQuick/bcrypt
Add enc_bcrypt
-rw-r--r-- | data/modules.example.conf | 19 | ||||
-rw-r--r-- | modules/extra/enc_bcrypt.cpp | 121 |
2 files changed, 140 insertions, 0 deletions
diff --git a/data/modules.example.conf b/data/modules.example.conf index df2077b4d..e58ebea88 100644 --- a/data/modules.example.conf +++ b/data/modules.example.conf @@ -10,6 +10,23 @@ */ /* + * enc_bcrypt [EXTRA] + * + * Adds the possibility to store passwords using bcrypt. + * This follows the same rules as the other encryption modules, + * eg. if you want it to be your primary encryption method it must + * be loaded before any other encryption modules. + * See the Encryption section of services.conf for more information. + * + * Requires libxcrypt. + */ +#module +{ + name = "enc_bcrypt" + rounds = "10" +} + +/* * help * * Provides the command generic/help. @@ -438,6 +455,7 @@ module { name = "help" } * * Add the DH-AES mechanism to SASL. * Requires m_sasl to be loaded. + * Requires openssl. */ #module { name = "m_sasl_dh-aes" } @@ -446,6 +464,7 @@ module { name = "help" } * * Add the DH-BLOWFISH mechanism to SASL. * Requires m_sasl to be loaded. + * Requires openssl. */ #module { name = "m_sasl_dh-blowfish" } diff --git a/modules/extra/enc_bcrypt.cpp b/modules/extra/enc_bcrypt.cpp new file mode 100644 index 000000000..67f2e8bef --- /dev/null +++ b/modules/extra/enc_bcrypt.cpp @@ -0,0 +1,121 @@ +/* RequiredLibraries: xcrypt */ + +#include "module.h" +#include "encryption.h" +#include <xcrypt.h> + +class EBCRYPT : public Module +{ + unsigned int rounds; + + Anope::string Salt() + { + char entropy[16]; + for (unsigned int i = 0; i < sizeof(entropy); i++) + entropy[i] = static_cast<char>(rand() % 0xFF); + + char salt[32]; + if (!crypt_gensalt_rn("$2a$", rounds, entropy, sizeof(entropy), salt, sizeof(salt))) + return ""; + return salt; + } + + Anope::string Generate(const Anope::string& data, const Anope::string& salt) + { + char hash[64]; + crypt_rn(data.c_str(), salt.c_str(), hash, sizeof(hash)); + return hash; + } + + bool Compare(const Anope::string& string, const Anope::string& hash) + { + Anope::string ret = Generate(string, hash); + if (ret.empty()) + return false; + + return (ret == hash); + } + + public: + EBCRYPT(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, ENCRYPTION | VENDOR | EXTRA), + rounds(10) + { + // Should obviously never happen, but if xcrypt somehow was compiled without support for $2a$ I guess it might + if (Salt() == "") + throw ModuleException("Could not salt! BCrypt will not be available!"); + } + + EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) anope_override + { + dest = "bcrypt:" + Generate(src, Salt()); + Log(LOG_DEBUG_2) << "(enc_bcrypt) hashed password from [" << src << "] to [" << dest << "]"; + return EVENT_ALLOW; + } + + void OnCheckAuthentication(User *, IdentifyRequest *req) anope_override + { + const NickAlias *na = NickAlias::Find(req->GetAccount()); + if (na == NULL) + return; + NickCore *nc = na->nc; + + size_t 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") + return; + + if (Compare(req->GetPassword(), nc->pass.substr(7))) + { + /* 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; + try + { + size_t roundspos = nc->pass.find('$', 11); + if (roundspos == Anope::string::npos) + throw ConvertException("Could not find hashrounds"); + + hashrounds = convertTo<unsigned int>(nc->pass.substr(11, roundspos - 11)); + } + catch (const ConvertException &) + { + Log(this) << "Could not get the round size of a hash. This is probably a bug. Hash: " << nc->pass; + } + + if (ModuleManager::FindFirstOf(ENCRYPTION) != this || (hashrounds && hashrounds != rounds)) + Anope::Encrypt(req->GetPassword(), nc->pass); + req->Success(this); + } + } + + void OnReload(Configuration::Conf *conf) anope_override + { + Configuration::Block *block = conf->GetModule(this); + rounds = block->Get<unsigned int>("rounds", "10"); + + if (rounds == 0) + { + 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."; + } + } +}; + +MODULE_INIT(EBCRYPT) |