summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSadie Powell <sadie@witchery.services>2024-03-10 14:50:56 +0000
committerSadie Powell <sadie@witchery.services>2024-03-10 15:09:38 +0000
commit9a8cac060d60aee638e368e96afb46967d6d8190 (patch)
tree32365ea7ebb3e255907e352fcf867877ab9cd6a9
parent0353338436141cd27cebd776cbc74e568e801ffe (diff)
Add support for encrypting passwords with the Argon2 algorithm.
Closes #369.
-rw-r--r--.github/workflows/ci-alpine.yml3
-rw-r--r--.github/workflows/ci-linux.yml1
-rw-r--r--.gitignore1
-rw-r--r--data/anope.example.conf36
-rw-r--r--include/modules/encryption.h6
-rw-r--r--modules/CMakeLists.txt1
-rw-r--r--modules/database/db_atheme.cpp11
-rw-r--r--modules/extra/enc_argon2.cpp193
-rw-r--r--src/win32/conanfile.txt2
9 files changed, 252 insertions, 2 deletions
diff --git a/.github/workflows/ci-alpine.yml b/.github/workflows/ci-alpine.yml
index a910e45ec..6a0466e44 100644
--- a/.github/workflows/ci-alpine.yml
+++ b/.github/workflows/ci-alpine.yml
@@ -20,6 +20,7 @@ jobs:
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
apk update
apk add \
+ argon2-dev \
clang \
cmake \
g++ \
@@ -36,7 +37,7 @@ jobs:
- name: Enable extras
run: |
- for MODULE in ldap mysql regex_pcre2 regex_posix regex_tre sqlite ssl_gnutls ssl_openssl
+ for MODULE in enc_argon2 ldap mysql regex_pcre2 regex_posix regex_tre sqlite ssl_gnutls ssl_openssl
do
ln -s $PWD/modules/extra/$MODULE.cpp $PWD/modules
done
diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml
index 3782e921c..d102be965 100644
--- a/.github/workflows/ci-linux.yml
+++ b/.github/workflows/ci-linux.yml
@@ -20,6 +20,7 @@ jobs:
g++ \
gettext \
git \
+ libargon2-dev \
libgnutls28-dev \
libldap2-dev \
libmysqlclient-dev \
diff --git a/.gitignore b/.gitignore
index a2f8a7bbb..cf096b4d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
build/
config.cache
include/sysconf.h
+modules/enc_argon2.cpp
modules/ldap.cpp
modules/mysql.cpp
modules/regex_pcre2.cpp
diff --git a/data/anope.example.conf b/data/anope.example.conf
index 3bc249d24..45ebef058 100644
--- a/data/anope.example.conf
+++ b/data/anope.example.conf
@@ -1255,6 +1255,42 @@ module
}
/*
+ * [EXTRA] enc_argon2
+ *
+ * Provides support for encrypting passwords using the Argon2 algorithm. See
+ * https://en.wikipedia.org/wiki/Argon2 for more information.
+ */
+#module
+{
+ name = "enc_argon2"
+
+ /** The sub-algorithm to use. Can be set to argon2d for Argon2d, argon2i for
+ * Argon2i, or argon2id for Argon2id. Defaults to argon2id.
+ */
+ #algorithm = "argon2id"
+
+ /** The memory hardness in kibibytes of the Argon2 algorithm. Defaults to
+ * 128 mebibytes.
+ */
+ #memory_cost = 121072
+
+ /** The time hardness (iterations) of the Argon2 algorithm. Defaults to 3.
+ */
+ #time_cost = 3
+
+ /** The amount of parallel threads to use when encrypting passwords.
+ * Defaults to 1.
+ */
+ #parallelism = 1
+
+ /** The length in bytes of an Argon2 hash. Defaults to 32. */
+ #hash_length = 32
+
+ /** The length in bytes of an Argon2 salt. Defaults to 32. */
+ #salt_length = 32
+}
+
+/*
* enc_bcrypt
*
* Provides support for encrypting passwords using the Bcrypt algorithm. See
diff --git a/include/modules/encryption.h b/include/modules/encryption.h
index 0eec23ec3..71d35044a 100644
--- a/include/modules/encryption.h
+++ b/include/modules/encryption.h
@@ -63,6 +63,12 @@ namespace Encryption
virtual ~Provider() = default;
+ /** Checks whether a plain text value matches a hash created by this provider. */
+ virtual bool Compare(const Anope::string &hash, const Anope::string &plain)
+ {
+ return hash.equals_cs(plain);
+ }
+
/** Creates a new encryption context. */
virtual std::unique_ptr<Context> CreateContext() = 0;
diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt
index 8485886e2..c6a98df7f 100644
--- a/modules/CMakeLists.txt
+++ b/modules/CMakeLists.txt
@@ -19,6 +19,7 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../conanbuildinfo.cmake")
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/extra/${NAME}.cpp" DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}")
endfunction()
+ enable_extra("enc_argon2" "ARGON2")
enable_extra("mysql" "LIBMYSQLCLIENT")
enable_extra("regex_pcre2" "PCRE2")
enable_extra("sqlite" "SQLITE3")
diff --git a/modules/database/db_atheme.cpp b/modules/database/db_atheme.cpp
index d3877b625..5871605a8 100644
--- a/modules/database/db_atheme.cpp
+++ b/modules/database/db_atheme.cpp
@@ -416,7 +416,7 @@ private:
// some of them currently.
//
// anope-enc-sha256 Converted to enc_sha256
- // argon2 NO
+ // argon2 Converted to enc_argon2
// base64 Converted to the first encryption algorithm
// bcrypt Converted to enc_bcrypt
// crypt3-des NO
@@ -440,6 +440,15 @@ private:
nc->pass = "sha256:" + Anope::Hex(pass) + ":" + Anope::Hex(iv);
}
+ else if (pass.compare(0, 9, "$argon2d$", 9) == 0)
+ nc->pass = "argon2d:" + pass;
+
+ else if (pass.compare(0, 9, "$argon2i$", 9) == 0)
+ nc->pass = "argon2i:" + pass;
+
+ else if (pass.compare(0, 10, "$argon2id$", 10) == 0)
+ nc->pass = "argon2id:" + pass;
+
else if (pass.compare(0, 8, "$base64$", 8) == 0)
{
Anope::string rawpass;
diff --git a/modules/extra/enc_argon2.cpp b/modules/extra/enc_argon2.cpp
new file mode 100644
index 000000000..6454411eb
--- /dev/null
+++ b/modules/extra/enc_argon2.cpp
@@ -0,0 +1,193 @@
+/* Module for providing Argon2 hashing
+ *
+ * (C) 2003-2024 Anope Team
+ * Contact us at team@anope.org
+ *
+ * This program is free but copyrighted software; see the file COPYING for
+ * details.
+ *
+ */
+
+/* RequiredLibraries: argon2 */
+/* RequiredWindowsLibraries: argon2 */
+
+#include <climits>
+#include <random>
+
+#include <argon2.h>
+
+#include "module.h"
+#include "modules/encryption.h"
+
+class Argon2Context final
+ : public Encryption::Context
+{
+private:
+ Anope::string buffer;
+ argon2_type type;
+
+ Anope::string GenerateSalt()
+ {
+ static std::random_device device;
+ static std::mt19937 engine(device());
+ static std::uniform_int_distribution<int> dist(CHAR_MIN, CHAR_MAX);
+ Anope::string saltbuf(this->salt_length, ' ');
+ for (size_t i = 0; i < this->salt_length; ++i)
+ saltbuf[i] = static_cast<char>(dist(engine));
+ return saltbuf;
+ }
+
+public:
+ static uint32_t memory_cost;
+ static uint32_t time_cost;
+ static uint32_t parallelism;
+ static uint32_t hash_length;
+ static uint32_t salt_length;
+
+ Argon2Context(argon2_type at)
+ : type(at)
+ {
+ }
+
+ void Update(const unsigned char *data, size_t len) override
+ {
+ buffer.append(reinterpret_cast<const char *>(data), len);
+ }
+
+ Anope::string Finalize() override
+ {
+ auto salt = GenerateSalt();
+
+ // Calculate the size of and allocate the output buffer.
+ auto length = argon2_encodedlen(this->time_cost, this->memory_cost, this->parallelism,
+ this->salt_length, this->hash_length, this->type);
+
+ std::vector<char> digest(length);
+ auto result = argon2_hash(this->time_cost, this->memory_cost, this->parallelism,
+ buffer.c_str(), buffer.length(), salt.c_str(), salt.length(), nullptr,
+ this->hash_length, digest.data(), digest.size(), this->type,
+ ARGON2_VERSION_NUMBER);
+
+ if (result == ARGON2_OK)
+ return Anope::string(digest.data(), digest.size());
+
+ Log(LOG_DEBUG_2) << "Argon2 error: " << argon2_error_message(result);
+ return {};
+ }
+};
+
+uint32_t Argon2Context::memory_cost;
+uint32_t Argon2Context::time_cost;
+uint32_t Argon2Context::parallelism;
+uint32_t Argon2Context::hash_length;
+uint32_t Argon2Context::salt_length;
+
+class Argon2Provider final
+ : public Encryption::Provider
+{
+private:
+ argon2_type type;
+
+public:
+ Argon2Provider(Module *creator, argon2_type at)
+ : Encryption::Provider(creator, argon2_type2string(at, 0), 0, 0)
+ , type(at)
+ {
+ }
+
+ bool Compare(const Anope::string &hash, const Anope::string &plain) override
+ {
+ return argon2_verify(hash.c_str(), plain.c_str(), plain.length(), this->type) == ARGON2_OK;
+ }
+
+ std::unique_ptr<Encryption::Context> CreateContext() override
+ {
+ return std::make_unique<Argon2Context>(this->type);
+ }
+};
+
+
+class EArgon2 final
+ : public Module
+{
+private:
+ Encryption::Provider *defaultprovider = nullptr;
+ Argon2Provider argon2dprovider;
+ Argon2Provider argon2iprovider;
+ Argon2Provider argon2idprovider;
+
+ Encryption::Provider *GetAlgorithm(const Anope::string &algorithm)
+ {
+ if (algorithm == "argon2d")
+ return &argon2dprovider;
+ if (algorithm == "argon2i")
+ return &argon2iprovider;
+ if (algorithm == "argon2id")
+ return &argon2idprovider;
+ return nullptr;
+ }
+
+public:
+ EArgon2(const Anope::string &modname, const Anope::string &creator)
+ : Module(modname, creator, ENCRYPTION | VENDOR)
+ , argon2dprovider(this, Argon2_d)
+ , argon2iprovider(this, Argon2_i)
+ , argon2idprovider(this, Argon2_id)
+
+ {
+ }
+
+ void OnReload(Configuration::Conf *conf) override
+ {
+ const auto *block = Config->GetModule(this);
+ this->defaultprovider = GetAlgorithm(block->Get<const Anope::string>("algorithm", "argon2id"));
+ Argon2Context::memory_cost = block->Get<uint32_t>("memory_cost", "131072");
+ Argon2Context::time_cost = block->Get<uint32_t>("time_cost", "3");
+ Argon2Context::parallelism = block->Get<uint32_t>("parallelism", "1");
+ Argon2Context::hash_length = block->Get<uint32_t>("hash_length", "32");
+ Argon2Context::salt_length = block->Get<uint32_t>("salt_length", "32");
+ }
+
+ EventReturn OnEncrypt(const Anope::string &src, Anope::string &dest) override
+ {
+ if (!defaultprovider)
+ return EVENT_CONTINUE;
+
+ auto hash = defaultprovider->Encrypt(src);
+ auto enc = defaultprovider->name + ":" + hash;
+ Log(LOG_DEBUG_2) << "(enc_argon2) 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 pos = nc->pass.find(':');
+ if (pos == Anope::string::npos)
+ return;
+
+ Anope::string hash_method(nc->pass.begin(), nc->pass.begin() + pos);
+ auto provider = GetAlgorithm(hash_method);
+ if (!provider)
+ return; // Not a hash for this module.
+
+ Anope::string hash_value(nc->pass.begin() + pos + 1, nc->pass.end());
+ if (provider->Compare(hash_value, req->GetPassword()))
+ {
+ // If we are NOT the first encryption module or the algorithm is
+ // different we want to re-encrypt the password with the primary
+ // encryption method.
+ if (ModuleManager::FindFirstOf(ENCRYPTION) != this || provider != defaultprovider)
+ Anope::Encrypt(req->GetPassword(), nc->pass);
+ req->Success(this);
+ }
+ }
+};
+
+MODULE_INIT(EArgon2)
diff --git a/src/win32/conanfile.txt b/src/win32/conanfile.txt
index ac72eee2a..d0ef69167 100644
--- a/src/win32/conanfile.txt
+++ b/src/win32/conanfile.txt
@@ -1,4 +1,5 @@
[requires]
+argon2/20190702
libmysqlclient/8.1.0
openssl/3.2.1
pcre2/10.42
@@ -7,6 +8,7 @@ gettext/0.21
libgettext/0.22
[options]
+argon2/*:shared=True
libmysqlclient/*:shared=True
openssl/*:shared=True
pcre2/*:shared=True