From 725d4d5da4e8eb92dedcebe72574ba599666e72e Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Sun, 6 Apr 2025 05:40:58 +0100 Subject: Add the hash_gnutls module. This provides the SHA family using GnuTLS. In the future we can add any new algorithms that appear. --- .github/workflows/ci-alpine.yml | 2 +- .github/workflows/ci-linux.yml | 2 +- .github/workflows/ci-macos.yml | 2 +- .gitignore | 1 + configure | 1 + docs/conf/modules.example.conf | 20 ++++ modules/extra/hash_gnutls.cpp | 256 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 modules/extra/hash_gnutls.cpp diff --git a/.github/workflows/ci-alpine.yml b/.github/workflows/ci-alpine.yml index 814c0ddb5..8c61c7601 100644 --- a/.github/workflows/ci-alpine.yml +++ b/.github/workflows/ci-alpine.yml @@ -40,7 +40,7 @@ jobs: - name: Run configure run: | - ./configure --enable-extras "geo_maxmind hash_argon2 ldap log_syslog mysql pgsql regex_pcre2 regex_posix regex_re2 rehashsignal sqlite3 ssl_gnutls ssl_openssl" + ./configure --enable-extras "geo_maxmind hash_argon2 hash_gnutls ldap log_syslog mysql pgsql regex_pcre2 regex_posix regex_re2 rehashsignal sqlite3 ssl_gnutls ssl_openssl" ./configure --development --disable-auto-extras --disable-ownership --socketengine ${{ matrix.socketengine }} - name: Build core diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index 03fbf04e3..7fa6d0ff0 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -38,7 +38,7 @@ jobs: - name: Run configure run: | - ./configure --enable-extras "geo_maxmind hash_argon2 ldap log_syslog mysql pgsql regex_pcre2 regex_posix regex_re2 rehashsignal sqlite3 ssl_gnutls ssl_openssl" + ./configure --enable-extras "geo_maxmind hash_argon2 hash_gnutls ldap log_syslog mysql pgsql regex_pcre2 regex_posix regex_re2 rehashsignal sqlite3 ssl_gnutls ssl_openssl" ./configure --development --disable-auto-extras --socketengine ${{ matrix.socketengine }} - name: Build core diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 0a76ae203..400aa5df5 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -44,7 +44,7 @@ jobs: - name: Run configure run: | - ./configure --enable-extras "geo_maxmind hash_argon2 ldap log_syslog mysql pgsql regex_pcre2 regex_posix regex_re2 rehashsignal sqlite3 ssl_gnutls ssl_openssl" + ./configure --enable-extras "geo_maxmind hash_argon2 hash_gnutls ldap log_syslog mysql pgsql regex_pcre2 regex_posix regex_re2 rehashsignal sqlite3 ssl_gnutls ssl_openssl" ./configure --development --disable-auto-extras --socketengine ${{ matrix.socketengine }} - name: Build core diff --git a/.gitignore b/.gitignore index 53f09f34d..03326f5e1 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ /modules/geo_maxmind.cpp /modules/hash_argon2.cpp +/modules/hash_gnutls.cpp /modules/ldap.cpp /modules/log_syslog.cpp /modules/mysql.cpp diff --git a/configure b/configure index 5d238161b..eb617ca5c 100755 --- a/configure +++ b/configure @@ -390,6 +390,7 @@ if (prompt_bool $interactive, $question, 0) { my %modules = ( 'geo_maxmind' => 'pkg-config --exists libmaxminddb', 'hash_argon2' => 'pkg-config --exists libargon2', + 'hash_gnutls' => 'pkg-config --exists "gnutls >= 3.6.0"', 'mysql' => 'mysql_config --version', 'pgsql' => 'pg_config --version', 'ldap' => "pkg-config --exists lber && pkg-config --exists ldap", diff --git a/docs/conf/modules.example.conf b/docs/conf/modules.example.conf index 15efe5ac3..b55432f4f 100644 --- a/docs/conf/modules.example.conf +++ b/docs/conf/modules.example.conf @@ -1148,6 +1148,26 @@ # more secure but will use more server resources. Defaults to 10. # +#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# +# GnuTLS hashing module: Provides support for hashing server passwords +# using the SHA-1, SHA-2, SHA-3, HMAC-SHA-1, HMAC-SHA-2, and HMAC-SHA-3 +# algorithms (sha1, sha224, sha256, sha384, sha512, sha3-224, sha3-256, +# sha3-384, sha3-512, hmac-sha1, hmac-sha224, hmac-sha256, hmac-sha384, +# hmac-sha512, hmac-sha3-224, hmac-sha3-256, hmac-sha3-384, and +# hmac-sha3-512). This is an alternative to the hash_sha1 and hash_sha2 +# modules and can not be loaded at the same time as them. +# +# This module depends on a third-party library (GnuTLS) and may need to +# be manually enabled at build time. If you are building from source +# you can do this by installing this dependency and running: +# +# ./configure --enable-extras hash_gnutls +# make install +# +# Users of binary packages should consult the documentation for their +# package to find out whether this module is available. +# + #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # PBKDF2 hashing module: Provides support for hashing server passwords # using the PBKDF2 algorithm. This does not provide any hash algorithms diff --git a/modules/extra/hash_gnutls.cpp b/modules/extra/hash_gnutls.cpp new file mode 100644 index 000000000..e2650e74e --- /dev/null +++ b/modules/extra/hash_gnutls.cpp @@ -0,0 +1,256 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2025 Sadie Powell + * + * 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 . + */ + + +/// $CompilerFlags: find_compiler_flags("gnutls") +/// $LinkerFlags: find_linker_flags("gnutls") + +/// $PackageInfo: require_system("arch") gnutls pkgconf +/// $PackageInfo: require_system("darwin") gnutls pkg-config +/// $PackageInfo: require_system("debian~") libgnutls28-dev pkg-config +/// $PackageInfo: require_system("rhel~") gnutls-devel pkgconfig + + +#include +#include + +#include "inspircd.h" +#include "modules/hash.h" + +namespace +{ + template + std::string GetAlgoName(Enum algo) + { + std::string name = gnutls_mac_get_name(static_cast(algo)); + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + return name; + } +} + + +class GnuTLSHMACContext final + : public Hash::Context +{ +private: + gnutls_mac_algorithm_t algorithm; + gnutls_hmac_hd_t context; + std::vector salt; + +public: + GnuTLSHMACContext(gnutls_mac_algorithm_t algo) + : algorithm(algo) + , salt(gnutls_hmac_get_len(algo)) + { + ServerInstance->GenRandom(salt.data(), salt.size()); + gnutls_hmac_init(&context, algorithm, salt.data(), salt.size()); + } + + ~GnuTLSHMACContext() + { + gnutls_hmac_deinit(context, nullptr); + } + + + void Update(const unsigned char *data, size_t len) override + { + gnutls_hmac(context, data, len); + } + + std::string Finalize() override + { + std::vector digest(gnutls_hmac_get_len(algorithm)); + gnutls_hmac_output(context, digest.data()); + return FMT::format("{}${}", Base64::Encode(salt.data(), salt.size()), Base64::Encode(digest.data(), digest.size())); + } +}; + + +class GnuTLSHMACProvider final + : public Hash::Provider +{ +private: + gnutls_mac_algorithm_t algorithm; + +public: + GnuTLSHMACProvider(Module* mod, gnutls_mac_algorithm_t algo) + : Hash::Provider(mod, FMT::format("hmac-{}", GetAlgoName(algo))) + , algorithm(algo) + { + } + + bool Compare(const std::string& hash, const std::string& plain) override + { + auto sep = hash.find('$'); + if (sep == std::string::npos) + return false; // Malformed hash. + + auto rawkey = Base64::Decode(hash.substr(0, sep)); + auto rawhash = Base64::Decode(hash.substr(sep + 1)); + + std::vector expectedbuf(gnutls_hmac_get_len(algorithm)); + if (gnutls_hmac_fast(algorithm, rawkey.data(), rawkey.length(), plain.data(), plain.length(), expectedbuf.data()) < 0) + return false; // Hashing error. + + const std::string expected(reinterpret_cast(expectedbuf.data()), expectedbuf.size()); + return InspIRCd::TimingSafeCompare(rawhash, expected); + } + + std::unique_ptr CreateContext() override + { + return std::make_unique(algorithm); + } + + std::string ToPrintable(const std::string &hash) override + { + // We have no way to make this printable without the creating context + // so we always return the printed form. + return hash; + } +}; + +class GnuTLSContext final + : public Hash::Context +{ +private: + gnutls_digest_algorithm_t algorithm; + gnutls_hash_hd_t context; + +public: + GnuTLSContext(gnutls_digest_algorithm_t algo) + : algorithm(algo) + { + gnutls_hash_init(&context, algorithm); + } + + ~GnuTLSContext() + { + gnutls_hash_deinit(context, nullptr); + } + + void Update(const unsigned char *data, size_t len) override + { + gnutls_hash(context, data, len); + } + + std::string Finalize() override + { + std::vector digest(gnutls_hash_get_len(algorithm)); + gnutls_hash_output(context, digest.data()); + return std::string(reinterpret_cast(digest.data()), digest.size()); + } +}; + +class GnuTLSProvider final + : public Hash::Provider +{ +private: + gnutls_digest_algorithm_t algorithm; + GnuTLSHMACProvider hmacalgo; + +public: + GnuTLSProvider(Module* mod, gnutls_digest_algorithm_t algo, size_t bs) + : Hash::Provider(mod, GetAlgoName(algo), gnutls_hash_get_len(algo), bs) + , algorithm(algo) + , hmacalgo(mod, static_cast(algo)) + { + } + + std::unique_ptr CreateContext() override + { + return std::make_unique(algorithm); + } +}; + +class ModuleHashGnuTLS final + : public Module +{ +private: + GnuTLSProvider sha1algo; + GnuTLSProvider sha224algo; + GnuTLSProvider sha256algo; + GnuTLSProvider sha384algo; + GnuTLSProvider sha512algo; + GnuTLSProvider sha3224algo; + GnuTLSProvider sha3256algo; + GnuTLSProvider sha3384algo; + GnuTLSProvider sha3512algo; + +public: + ModuleHashGnuTLS() + : Module(VF_VENDOR, "Allows other modules to generate hashes using GnuTLS.") + , sha1algo(this, GNUTLS_DIG_SHA1, 64) + , sha224algo(this, GNUTLS_DIG_SHA224, 64) + , sha256algo(this, GNUTLS_DIG_SHA256, 64) + , sha384algo(this, GNUTLS_DIG_SHA384, 128) + , sha512algo(this, GNUTLS_DIG_SHA512, 128) + , sha3224algo(this, GNUTLS_DIG_SHA3_224, 144) + , sha3256algo(this, GNUTLS_DIG_SHA3_256, 136) + , sha3384algo(this, GNUTLS_DIG_SHA3_384, 104) + , sha3512algo(this, GNUTLS_DIG_SHA3_512, 72) + { + gnutls_global_init(); + } + + ~ModuleHashGnuTLS() + { + gnutls_global_deinit(); + } + + void init() override + { + sha1algo.Check({ + { "da39a3ee5e6b4b0d3255bfef95601890afd80709", "" }, + { "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", "The quick brown fox jumps over the lazy dog" }, + }); + sha224algo.Check({ + { "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", "" }, + { "730e109bd7a8a32b1cb9d9a09aa2325d2430587ddbc0c38bad911525", "The quick brown fox jumps over the lazy dog" }, + }); + sha256algo.Check({ + { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "" }, + { "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", "The quick brown fox jumps over the lazy dog" }, + }); + sha384algo.Check({ + { "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", "" }, + { "ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1", "The quick brown fox jumps over the lazy dog" }, + }); + sha512algo.Check({ + { "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", "" }, + { "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6", "The quick brown fox jumps over the lazy dog" }, + }); + sha3224algo.Check({ + { "6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7", "" }, + { "d15dadceaa4d5d7bb3b48f446421d542e08ad8887305e28d58335795", "The quick brown fox jumps over the lazy dog" }, + }); + sha3256algo.Check({ + { "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", "" }, + { "69070dda01975c8c120c3aada1b282394e7f032fa9cf32f4cb2259a0897dfc04", "The quick brown fox jumps over the lazy dog" }, + }); + sha3384algo.Check({ + { "0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004", "" }, + { "7063465e08a93bce31cd89d2e3ca8f602498696e253592ed26f07bf7e703cf328581e1471a7ba7ab119b1a9ebdf8be41", "The quick brown fox jumps over the lazy dog" }, + }); + sha3512algo.Check({ + { "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", "" }, + { "01dedd5de4ef14642445ba5f5b97c15e47b9ad931326e4b0727cd94cefc44fff23f07bf543139939b49128caf436dc1bdee54fcb24023a08d9403f9b4bf0d450", "The quick brown fox jumps over the lazy dog" }, + }); + } +}; + +MODULE_INIT(ModuleHashGnuTLS) -- cgit v1.3.1-10-gc9f91