/* * InspIRCd -- Internet Relay Chat Daemon * * Copyright (C) 2021 Dominic Hamon * Copyright (C) 2018-2023 Sadie Powell * Copyright (C) 2018 linuxdaemon * Copyright (C) 2014, 2020 Daniel Vassdal * Copyright (C) 2014, 2016 Attila Molnar * * 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 . */ #include #include "inspircd.h" #include "modules/hash.h" // Format: // Iterations:B64(Hash):B64(Salt) // E.g. // 10200:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB class PBKDF2Hash final { public: unsigned long iterations; size_t length; std::string salt; std::string hash; PBKDF2Hash(unsigned long itr, size_t dkl, const std::string& slt, const std::string& hsh = "") : iterations(itr) , length(dkl) , salt(slt) , hash(hsh) { } PBKDF2Hash(const std::string& data) { irc::sepstream ss(data, ':'); std::string tok; ss.GetToken(tok); this->iterations = ConvToNum(tok); ss.GetToken(tok); this->hash = Base64::Decode(tok); ss.GetToken(tok); this->salt = Base64::Decode(tok); this->length = this->hash.length(); } std::string ToString() const { if (!IsValid()) return ""; return ConvToStr(this->iterations) + ":" + Base64::Encode(this->hash) + ":" + Base64::Encode(this->salt); } bool IsValid() const { return this->iterations && this->length && !this->salt.empty() && !this->hash.empty(); } }; class PBKDF2Provider final : public HashProvider { public: HashProvider* provider; unsigned long iterations; size_t dkey_length; std::string PBKDF2(const std::string& pass, const std::string& salt, unsigned long itr = 0, size_t dkl = 0) const { size_t blocks = std::ceil((double)dkl / provider->out_size); std::string output; std::string tmphash; std::string salt_block = salt; for (size_t block = 1; block <= blocks; block++) { char salt_data[4]; for (size_t i = 0; i < sizeof(salt_data); i++) salt_data[i] = block >> (24 - i * 8) & 0x0F; salt_block.erase(salt.length()); salt_block.append(salt_data, sizeof(salt_data)); std::string blockdata = provider->hmac(pass, salt_block); std::string lasthash = blockdata; for (size_t iter = 1; iter < itr; iter++) { tmphash = provider->hmac(pass, lasthash); for (size_t i = 0; i < provider->out_size; i++) blockdata[i] ^= tmphash[i]; lasthash.swap(tmphash); } output += blockdata; } output.erase(dkl); return output; } std::string GenerateRaw(const std::string& data) override { PBKDF2Hash hs(this->iterations, this->dkey_length, ServerInstance->GenRandomStr(dkey_length, false)); hs.hash = PBKDF2(data, hs.salt, this->iterations, this->dkey_length); return hs.ToString(); } bool Compare(const std::string& input, const std::string& hash) override { PBKDF2Hash hs(hash); if (!hs.IsValid()) return false; std::string cmp = PBKDF2(input, hs.salt, hs.iterations, hs.length); return InspIRCd::TimingSafeCompare(cmp, hs.hash); } std::string ToPrintable(const std::string& raw) override { return raw; } PBKDF2Provider(Module* mod, HashProvider* hp) : HashProvider(mod, "pbkdf2-hmac-" + hp->name.substr(hp->name.find('/') + 1)) , provider(hp) { DisableAutoRegister(); } }; struct ProviderConfig final { unsigned long dkey_length; unsigned long iterations; }; typedef std::map ProviderConfigMap; class ModulePBKDF2 final : public Module { std::vector providers; ProviderConfig globalconfig; ProviderConfigMap providerconfigs; ProviderConfig GetConfigForProvider(const std::string& name) const { ProviderConfigMap::const_iterator it = providerconfigs.find(name); if (it == providerconfigs.end()) return globalconfig; return it->second; } void ConfigureProviders() { for (auto* pi : providers) { ProviderConfig config = GetConfigForProvider(pi->name); pi->iterations = config.iterations; pi->dkey_length = config.dkey_length; } } void ReadConfig(ConfigStatus& status) override { // First set the common values const auto& tag = ServerInstance->Config->ConfValue("pbkdf2"); ProviderConfig newglobal; newglobal.iterations = tag->getNum("iterations", 12288, 1); newglobal.dkey_length = tag->getNum("length", 32, 1, 1024); // Then the specific values ProviderConfigMap newconfigs; for (const auto& [_, ptag] : ServerInstance->Config->ConfTags("pbkdf2prov")) { std::string hash_name = "hash/" + ptag->getString("hash"); ProviderConfig& config = newconfigs[hash_name]; config.iterations = ptag->getNum("iterations", newglobal.iterations, 1); config.dkey_length = ptag->getNum("length", newglobal.dkey_length, 1, 1024); } // Config is valid, apply it providerconfigs.swap(newconfigs); std::swap(globalconfig, newglobal); ConfigureProviders(); } public: ModulePBKDF2() : Module(VF_VENDOR, "Allows other modules to generate PBKDF2 hashes.") { } ~ModulePBKDF2() override { stdalgo::delete_all(providers); } void init() override { // Let ourself know about any existing services. for (const auto& [_, service] : ServerInstance->Modules.DataProviders) OnServiceAdd(*service); } void OnServiceAdd(ServiceProvider& provider) override { // Check if it's a hash provider if (provider.name.compare(0, 5, "hash/")) return; HashProvider* hp = static_cast(&provider); if (hp->IsKDF()) return; auto* prov = new PBKDF2Provider(this, hp); providers.push_back(prov); ServerInstance->Modules.AddService(*prov); ConfigureProviders(); } void OnServiceDel(ServiceProvider& prov) override { for (std::vector::iterator i = providers.begin(); i != providers.end(); ++i) { PBKDF2Provider* item = *i; if (item->provider != &prov) continue; ServerInstance->Modules.DelService(*item); delete item; providers.erase(i); break; } } }; MODULE_INIT(ModulePBKDF2)