diff options
author | ShutterQuick <shutter@canternet.org> | 2020-10-10 19:25:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-10 18:25:24 +0100 |
commit | 219f8e62623ff0c3002be764c1dbf7201d0293db (patch) | |
tree | 0c466767e867f8d1e645afef1e971d6610a42385 | |
parent | 5f71fbdbffeecff18914fbe523e795a1d7680b47 (diff) |
Implement support for Argon2 password hashing.
Resolves #1540.
-rw-r--r-- | .github/workflows/ci-linux.yml | 4 | ||||
-rw-r--r-- | .github/workflows/ci-macos.yml | 4 | ||||
-rw-r--r-- | .gitignore | 1 | ||||
-rwxr-xr-x | configure | 1 | ||||
-rw-r--r-- | docs/conf/modules.conf.example | 24 | ||||
-rw-r--r-- | src/modules/extra/m_argon2.cpp | 214 |
6 files changed, 244 insertions, 4 deletions
diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index 0d7de4708..fa0d065e3 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -7,14 +7,14 @@ jobs: runs-on: ubuntu-16.04 env: CXXFLAGS: -std=${{ matrix.standard }} - TEST_BUILD_MODULES: geo_maxmind ldap mysql pgsql regex_pcre regex_posix regex_re2 regex_stdlib regex_tre sqlite3 ssl_gnutls ssl_mbedtls ssl_openssl sslrehashsignal + TEST_BUILD_MODULES: argon2 geo_maxmind ldap mysql pgsql regex_pcre regex_posix regex_re2 regex_stdlib regex_tre sqlite3 ssl_gnutls ssl_mbedtls ssl_openssl sslrehashsignal steps: - uses: actions/checkout@v2 - name: Install dependencies run: | sudo apt-get update --assume-yes sudo apt-get install --assume-yes --no-install-recommends clang g++ git make libc++-dev libc++abi-dev pkg-config - sudo apt-get install --assume-yes --no-install-recommends libgnutls28-dev libldap2-dev libmaxminddb-dev libmbedtls-dev libmysqlclient-dev libpcre3-dev libpq-dev libre2-dev libsqlite3-dev libssl-dev libtre-dev + sudo apt-get install --assume-yes --no-install-recommends libargon2-0-dev libgnutls28-dev libldap2-dev libmaxminddb-dev libmbedtls-dev libmysqlclient-dev libpcre3-dev libpq-dev libre2-dev libsqlite3-dev libssl-dev libtre-dev - name: Run test-build run: ./tools/test-build ${{ matrix.compiler }} strategy: diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index db155781a..21296fc32 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -9,13 +9,13 @@ jobs: CXXFLAGS: -std=${{ matrix.standard }} -I/usr/local/opt/openssl@1.1/include -Wno-error=deprecated-declarations LDFLAGS: -L/usr/local/opt/openssl@1.1/lib PKG_CONFIG_PATH: /usr/local/opt/openssl@1.1/lib/pkgconfig:/usr/local/opt/sqlite/lib/pkgconfig - TEST_BUILD_MODULES: geo_maxmind ldap mysql pgsql regex_pcre regex_posix regex_re2 regex_stdlib regex_tre sqlite3 ssl_gnutls ssl_mbedtls ssl_openssl sslrehashsignal + TEST_BUILD_MODULES: argon2 geo_maxmind ldap mysql pgsql regex_pcre regex_posix regex_re2 regex_stdlib regex_tre sqlite3 ssl_gnutls ssl_mbedtls ssl_openssl sslrehashsignal steps: - uses: actions/checkout@v2 - name: Install dependencies run: | brew update - for PACKAGE in pkg-config gnutls libmaxminddb libpq mbedtls mysql-client openssl@1.1 pcre re2 sqlite tre; + for PACKAGE in pkg-config argon2 gnutls libmaxminddb libpq mbedtls mysql-client openssl@1.1 pcre re2 sqlite tre; do brew install $PACKAGE || brew upgrade $PACKAGE done diff --git a/.gitignore b/.gitignore index 0bf719c7c..d5c7d4de8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ /include/config.h +/src/modules/m_argon2.cpp /src/modules/m_geo_maxmind.cpp /src/modules/m_ldap.cpp /src/modules/m_mysql.cpp @@ -363,6 +363,7 @@ if (prompt_bool $interactive, $question, 0) { # system './modulemanager', 'enable', '--auto'; my %modules = ( # Missing: m_ldap, m_regex_stdlib, m_ssl_mbedtls + 'm_argon2.cpp' => 'pkg-config --exists libargon2', 'm_geo_maxmind.cpp' => 'pkg-config --exists libmaxminddb', 'm_mysql.cpp' => 'mysql_config --version', 'm_pgsql.cpp' => 'pg_config --version', diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index 3255360b9..2f74634d7 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -187,6 +187,30 @@ # uppercase="ABCDEFGHIJKLMNOPQRSTUVWXYZ"> #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# +# Argon2 module: Allows other modules to generate Argon2 hashes, +# usually for cryptographic uses and security. +# This module makes the algorithms argon2i, argon2d and argon2id +# available for use. +# Note that this module is extra, and must be enabled explicitly +# to build. It depends on libargon2. +#<module name="argon2"> +# +# memory: Memory hardness, in KiB. E.g. 131072 KiB = 128 MiB. +# iterations: Time hardness in iterations. (def. 3) +# lanes: How many parallel chains can be run. (def. 1) +# threads: Maximum amount of threads each invokation can spawn. (def. 1) +# length: Output length in bytes. (def. 32) +# saltlength: Salt length in bytes. (def. 16) +# version: Algorithm version, 10 or 13. (def. 13) +# The parameters can be customized as follows: +#<argon2 iterations="3" memory="131074" length="32" saltlength="16"> +# Defines the parameters that are common for all the variants (i/d/id). +# Can be overriden on individual basis, e.g. +#<argon2i iterations="4"> +#<argon2d memory="131074" +#<argon2id iterations="5" memory="262144" length="64" saltlength="32"> + +#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # Auditorium module: Adds channel mode +u which makes everyone else # except you in the channel invisible, used for large meetings etc. #<module name="auditorium"> diff --git a/src/modules/extra/m_argon2.cpp b/src/modules/extra/m_argon2.cpp new file mode 100644 index 000000000..8a4b2f38c --- /dev/null +++ b/src/modules/extra/m_argon2.cpp @@ -0,0 +1,214 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2020 Daniel Vassdal <shutter@canternet.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/// $CompilerFlags: find_compiler_flags("libargon2" "") + +/// $LinkerFlags: find_linker_flags("libargon2" "-llibargon2") + +/// $PackageInfo: require_system("arch") argon2 pkgconf +/// $PackageInfo: require_system("darwin") argon2 pkg-config +/// $PackageInfo: require_system("debian" "9.0") libargon2-0 pkg-config +/// $PackageInfo: require_system("ubuntu" "18.04") libargon2-0-dev pkg-config + + +#include "inspircd.h" +#include "modules/hash.h" + +#ifdef __GNUC__ +# pragma GCC diagnostic push +#endif + +// Fix warnings about the use of `long long` on C++03 +#if defined __clang__ +# pragma clang diagnostic ignored "-Wc++11-long-long" +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wlong-long" +#endif + +#include <argon2.h> + +class ProviderConfig +{ + private: + static Argon2_version SanitizeArgon2Version(unsigned long version) + { + // Note, 10 is 0x10, and 13 is 0x13. Refering to it as + // dec 10 or 13 in the config file, for the name to + // match better. + switch (version) + { + case 10: + return ARGON2_VERSION_10; + case 13: + return ARGON2_VERSION_13; + } + + ServerInstance->Logs->Log("MODULE", LOG_DEFAULT, "Unknown Argon2 version (%lu) specified; assuming 13", + version); + return ARGON2_VERSION_13; + } + + public: + uint32_t iterations; + uint32_t lanes; + uint32_t memory; + uint32_t outlen; + uint32_t saltlen; + uint32_t threads; + Argon2_version version; + + ProviderConfig() + { + // Nothing interesting happens here. + } + + ProviderConfig(const std::string& tagname, ProviderConfig* def) + { + ConfigTag* tag = ServerInstance->Config->ConfValue(tagname); + + uint32_t def_iterations = def ? def->iterations : 3; + this->iterations = tag->getUInt("iterations", def_iterations, 1); + + uint32_t def_lanes = def ? def->lanes : 1; + this->lanes = tag->getUInt("lanes", def_lanes, ARGON2_MIN_LANES, ARGON2_MAX_LANES); + + uint32_t def_memory = def ? def->memory : 131072; // 128 MiB + this->memory = tag->getUInt("memory", def_memory, ARGON2_MIN_MEMORY, ARGON2_MAX_MEMORY); + + uint32_t def_outlen = def ? def->outlen : 32; + this->outlen = tag->getUInt("length", def_outlen, ARGON2_MIN_OUTLEN, ARGON2_MAX_OUTLEN); + + uint32_t def_saltlen = def ? def->saltlen : 16; + this->saltlen = tag->getUInt("saltlength", def_saltlen, ARGON2_MIN_SALT_LENGTH, ARGON2_MAX_SALT_LENGTH); + + uint32_t def_threads = def ? def->threads : 1; + this->threads = tag->getUInt("threads", def_threads, ARGON2_MIN_THREADS, ARGON2_MAX_THREADS); + + uint32_t def_version = def ? def->version : 13; + this->version = SanitizeArgon2Version(tag->getUInt("version", def_version)); + } +}; + +class HashArgon2 : public HashProvider +{ + private: + const Argon2_type argon2Type; + + public: + ProviderConfig config; + + bool Compare(const std::string& input, const std::string& hash) CXX11_OVERRIDE + { + int result = argon2_verify( + hash.c_str(), + input.c_str(), + input.length(), + argon2Type); + + return result == ARGON2_OK; + } + + std::string GenerateRaw(const std::string& data) CXX11_OVERRIDE + { + const std::string salt = ServerInstance->GenRandomStr(config.saltlen, false); + + size_t encodedLen = argon2_encodedlen( + config.iterations, + config.memory, + config.lanes, + config.saltlen, + config.outlen, + argon2Type); + + std::vector<char> raw_data(config.outlen); + std::vector<char> encoded_data(encodedLen + 1); + + int argonResult = argon2_hash( + config.iterations, + config.memory, + config.threads, + data.c_str(), + data.length(), + salt.c_str(), + salt.length(), + &raw_data[0], + raw_data.size(), + &encoded_data[0], + encoded_data.size(), + argon2Type, + config.version); + + if (argonResult != ARGON2_OK) + throw ModuleException("Argon2 hashing failed!: " + std::string(argon2_error_message(argonResult))); + + // This isn't the raw version, but we don't have + // the facilities to juggle around the extra state required + // to do anything useful with them if we don't encode them. + // So we pretend this is the raw version, and instead make + // ToPrintable return its input. + return std::string(&encoded_data[0], encoded_data.size()); + } + + std::string ToPrintable(const std::string& raw) CXX11_OVERRIDE + { + return raw; + } + + HashArgon2(Module* parent, const std::string& hashName, Argon2_type type) + : HashProvider(parent, hashName) + , argon2Type(type) + + { + } +}; + +class ModuleArgon2 : public Module +{ + private: + HashArgon2 argon2i; + HashArgon2 argon2d; + HashArgon2 argon2id; + + public: + ModuleArgon2() + : argon2i(this, "argon2i", Argon2_i) + , argon2d(this, "argon2d", Argon2_d) + , argon2id(this, "argon2id", Argon2_id) + { + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Allows other modules to generate Argon2 hashes.", VF_VENDOR); + } + + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE + { + ProviderConfig defaultConfig("argon2", NULL); + argon2i.config = ProviderConfig("argon2i", &defaultConfig); + argon2d.config = ProviderConfig("argon2d", &defaultConfig); + argon2id.config = ProviderConfig("argon2id", &defaultConfig); + } +}; + +MODULE_INIT(ModuleArgon2) + +// This needs to be down here because of warnings from macros. +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif |