aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Sadie Powell2025-04-06 05:40:58 +0100
committerGravatar Sadie Powell2025-04-06 12:05:31 +0100
commit725d4d5da4e8eb92dedcebe72574ba599666e72e (patch)
treef48f827838a0f510428e4d8b792a406c8eede54c
parentRename newhash to hash now its the only option. (diff)
Add the hash_gnutls module.
This provides the SHA family using GnuTLS. In the future we can add any new algorithms that appear.
-rw-r--r--.github/workflows/ci-alpine.yml2
-rw-r--r--.github/workflows/ci-linux.yml2
-rw-r--r--.github/workflows/ci-macos.yml2
-rw-r--r--.gitignore1
-rwxr-xr-xconfigure1
-rw-r--r--docs/conf/modules.example.conf20
-rw-r--r--modules/extra/hash_gnutls.cpp256
7 files changed, 281 insertions, 3 deletions
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
@@ -1149,6 +1149,26 @@
#<bcrypt rounds="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.
+#<module name="hash_gnutls">
+
+#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
# PBKDF2 hashing module: Provides support for hashing server passwords
# using the PBKDF2 algorithm. This does not provide any hash algorithms
# directly but augments other digest-based hashing modules with PBKDF2
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 <sadie@witchery.services>
+ *
+ * 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("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 <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#include "inspircd.h"
+#include "modules/hash.h"
+
+namespace
+{
+ template<typename Enum>
+ std::string GetAlgoName(Enum algo)
+ {
+ std::string name = gnutls_mac_get_name(static_cast<gnutls_mac_algorithm_t>(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<char> 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<unsigned char> 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<unsigned char> 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<const char *>(expectedbuf.data()), expectedbuf.size());
+ return InspIRCd::TimingSafeCompare(rawhash, expected);
+ }
+
+ std::unique_ptr<Hash::Context> CreateContext() override
+ {
+ return std::make_unique<GnuTLSHMACContext>(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<unsigned char> digest(gnutls_hash_get_len(algorithm));
+ gnutls_hash_output(context, digest.data());
+ return std::string(reinterpret_cast<const char *>(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<gnutls_mac_algorithm_t>(algo))
+ {
+ }
+
+ std::unique_ptr<Hash::Context> CreateContext() override
+ {
+ return std::make_unique<GnuTLSContext>(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)