mirror of
https://github.com/inspircd/inspircd.git
synced 2025-03-09 18:49:03 -04:00
Add a new cloak system that cloaks using HMAC-SHA256.
Closes #1107. The core of the cloaking algorithm this uses is heavily inspired by the Plexus4 cloak_hmac_sha256 module written by Adam. I've made a few changes to the rest of it to work more like the old InspIRCd cloaking system as well as adding the option for configuring the case of the character table used for cloaking. Co-authored-by: Adam <Adam@anope.org> Co-authored-year: 2017
This commit is contained in:
parent
32258f5b11
commit
b680306816
@ -566,6 +566,41 @@
|
||||
# prefix="net-"
|
||||
# ignorecase="no">
|
||||
|
||||
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
|
||||
# HMAC-SHA256 cloak module: Adds the "hmac-sha256" cloak method. This
|
||||
# is the recommended cloak method for new networks.
|
||||
#
|
||||
# IMPORTANT: If you are using this module you should also load the sha2
|
||||
# module. Failure to do so will result in users not being cloaked.
|
||||
#<module name="cloak_hmac_sha256">
|
||||
#
|
||||
#-#-#-#-#-#-#-#-#- HMAC-SHA256 CLOAK CONFIGURATION -#-#-#-#-#-#-#-#-#-#
|
||||
# To use the cloak_sha256 module you must define a <cloak> tag. This #
|
||||
# tag can have the following fields. #
|
||||
# #
|
||||
# key - The secret key to use when hashing hostnames. This #
|
||||
# MUST be at least 30 characters long. #
|
||||
# #
|
||||
# prefix - A freeform value to prefix cloaks with. This must not #
|
||||
# contain spaces. #
|
||||
# #
|
||||
# suffix - A freeform value to suffix IPv4/IPv6 cloaks with. This #
|
||||
# must not contain spaces. #
|
||||
# hostparts - The maximum number of hostname labels that should be #
|
||||
# visible on the end of a host. Defaults to 3. #
|
||||
# #
|
||||
# IMPORTANT: Changing these details will break all of your existing #
|
||||
# bans. If you do not want this to happen you can define multiple #
|
||||
# cloak tags. The first will be used for hostnames and the rest will #
|
||||
# be used for checking if a user is banned in a channel. #
|
||||
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
|
||||
#
|
||||
#<cloak mode="hmac-sha256"
|
||||
# key="changeme"
|
||||
# prefix=""
|
||||
# suffix=""
|
||||
# hostparts="3">
|
||||
|
||||
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
|
||||
# Codepage module: Allows using a custom 8-bit codepage for nicknames
|
||||
# and case mapping.
|
||||
|
@ -54,7 +54,7 @@ namespace Cloak
|
||||
}
|
||||
|
||||
/** Base class for cloak engines. */
|
||||
class CoreExport Cloak::Engine
|
||||
class Cloak::Engine
|
||||
: public DataProvider
|
||||
{
|
||||
protected:
|
||||
|
250
src/modules/m_cloak_sha256.cpp
Normal file
250
src/modules/m_cloak_sha256.cpp
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* InspIRCd -- Internet Relay Chat Daemon
|
||||
*
|
||||
* Copyright (C) 2023 Sadie Powell <sadie@witchery.services>
|
||||
* Copyright (C) 2017 Adam <Adam@anope.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/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "inspircd.h"
|
||||
#include "modules/cloak.h"
|
||||
#include "modules/hash.h"
|
||||
|
||||
class SHA256Method final
|
||||
: public Cloak::Method
|
||||
{
|
||||
private:
|
||||
// The base32 table used for upper-case cloaks.
|
||||
static constexpr unsigned char base32upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
// The base32 table used for lower-case cloaks.
|
||||
static constexpr unsigned char base32lower[] = "abcdefghijklmnopqrstuvwxyz234567";
|
||||
|
||||
// The number of bytes of the hash to use when downsampling.
|
||||
static constexpr size_t segmentlen = 8;
|
||||
|
||||
// The number of parts of the hostname shown.
|
||||
unsigned long hostparts;
|
||||
|
||||
// The secret used for generating cloaks.
|
||||
std::string key;
|
||||
|
||||
// The prefix for cloaks (e.g. MyNet).
|
||||
std::string prefix;
|
||||
|
||||
// Dynamic reference to the sha256 implementation.
|
||||
dynamic_reference_nocheck<HashProvider> sha256;
|
||||
|
||||
// The base32 table used when encoding.
|
||||
const unsigned char* table;
|
||||
|
||||
// The suffix for IP cloaks (e.g. IP).
|
||||
std::string suffix;
|
||||
|
||||
std::string CloakAddress(const irc::sockets::sockaddrs& sa)
|
||||
{
|
||||
switch (sa.family())
|
||||
{
|
||||
case AF_INET:
|
||||
return CloakIPv4(sa.in4.sin_addr.s_addr);
|
||||
case AF_INET6:
|
||||
return CloakIPv6(sa.in6.sin6_addr.s6_addr);
|
||||
case AF_UNIX:
|
||||
return CloakHost(sa.un.sun_path, '/');
|
||||
}
|
||||
|
||||
// Should never be reached.
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string CloakIPv4(unsigned long address)
|
||||
{
|
||||
// IPv4 addresses are cloaked in the form ALPHA.BETA.GAMMA
|
||||
//
|
||||
// ALPHA is unique for a.b.c.d
|
||||
// BETA is unique for a.b.c
|
||||
// GAMMA is unique for a.b
|
||||
unsigned int a = (unsigned int)(address) & 0xFF;
|
||||
unsigned int b = (unsigned int)(address >> 8) & 0xFF;
|
||||
unsigned int c = (unsigned int)(address >> 16) & 0xFF;
|
||||
unsigned int d = (unsigned int)(address >> 24) & 0xFF;
|
||||
|
||||
const std::string alpha = Hash(InspIRCd::Format("%u.%u.%u.%u", a, b, c, d));
|
||||
const std::string beta = Hash(InspIRCd::Format("%u.%u.%u", a, b, c));
|
||||
const std::string gamma = Hash(InspIRCd::Format("%u.%u", a, b));
|
||||
|
||||
return Wrap(InspIRCd::Format("%s.%s.%s", alpha.c_str(), beta.c_str(), gamma.c_str()), ".");
|
||||
}
|
||||
|
||||
std::string CloakIPv6(const unsigned char* address)
|
||||
{
|
||||
// IPv6 addresses are cloaked in the form ALPHA.BETA.GAMMA.IP
|
||||
//
|
||||
// ALPHA is unique for a:b:c:d:e:f:g:h
|
||||
// BETA is unique for a:b:c:d:e:f:g
|
||||
// GAMMA is unique for a:b:c:d
|
||||
const uint16_t* address16 = reinterpret_cast<const uint16_t*>(address);
|
||||
unsigned int a = ntohs(address16[0]);
|
||||
unsigned int b = ntohs(address16[1]);
|
||||
unsigned int c = ntohs(address16[2]);
|
||||
unsigned int d = ntohs(address16[3]);
|
||||
unsigned int e = ntohs(address16[4]);
|
||||
unsigned int f = ntohs(address16[5]);
|
||||
unsigned int g = ntohs(address16[6]);
|
||||
unsigned int h = ntohs(address16[7]);
|
||||
|
||||
const std::string alpha = Hash(InspIRCd::Format("%x:%x:%x:%x:%x:%x:%x:%x", a, b, c, d, e, f, g, h));
|
||||
const std::string beta = Hash(InspIRCd::Format("%x:%x:%x:%x:%x:%x:%x", a, b, c, d, e, f, g));
|
||||
const std::string gamma = Hash(InspIRCd::Format("%x:%x:%x:%x", a, b, c, d));
|
||||
|
||||
return Wrap(InspIRCd::Format("%s:%s:%s", alpha.c_str(), beta.c_str(), gamma.c_str()), ":");
|
||||
}
|
||||
|
||||
std::string CloakHost(const std::string& host, char separator)
|
||||
{
|
||||
// Convert the host to lowercase to avoid ban evasion.
|
||||
std::string lowerhost(host.length(), '\0');
|
||||
std::transform(host.begin(), host.end(), lowerhost.begin(), ::tolower);
|
||||
|
||||
std::string cloak;
|
||||
cloak.append(prefix);
|
||||
cloak.append(Hash(lowerhost));
|
||||
|
||||
const std::string visiblepart = Cloak::VisiblePart(host, hostparts, separator);
|
||||
if (!visiblepart.empty())
|
||||
{
|
||||
cloak.push_back(separator);
|
||||
cloak.append(visiblepart);
|
||||
}
|
||||
|
||||
return cloak;
|
||||
}
|
||||
|
||||
std::string Hash(const std::string& str)
|
||||
{
|
||||
std::string out;
|
||||
for (const auto chr : sha256->hmac(key, str).substr(0, segmentlen))
|
||||
out.push_back(table[chr & 0x1F]);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string Wrap(const std::string& cloak, const char* separator)
|
||||
{
|
||||
std::string fullcloak;
|
||||
if (!prefix.empty())
|
||||
fullcloak.append(prefix).append(separator);
|
||||
fullcloak.append(cloak);
|
||||
if (!suffix.empty())
|
||||
fullcloak.append(separator).append(suffix);
|
||||
return fullcloak;
|
||||
}
|
||||
|
||||
public:
|
||||
SHA256Method(const Cloak::Engine* engine, const std::shared_ptr<ConfigTag>& tag, const std::string& k) ATTR_NOT_NULL(2)
|
||||
: Cloak::Method(engine)
|
||||
, hostparts(tag->getUInt("hostparts", 3, 1, UINT_MAX))
|
||||
, key(k)
|
||||
, prefix(tag->getString("prefix"))
|
||||
, sha256(engine->creator, "hash/sha256")
|
||||
, suffix(tag->getString("suffix", "ip"))
|
||||
{
|
||||
table = tag->getEnum("case", base32lower, {
|
||||
{ "upper", base32upper },
|
||||
{ "lower", base32lower }
|
||||
});
|
||||
}
|
||||
|
||||
std::string Generate(LocalUser* user) override ATTR_NOT_NULL(2)
|
||||
{
|
||||
if (!sha256)
|
||||
return {};
|
||||
|
||||
irc::sockets::sockaddrs sa(false);
|
||||
if (sa.from(user->GetRealHost()) && sa.addr() == user->client_sa.addr())
|
||||
return CloakAddress(user->client_sa);
|
||||
return CloakHost(user->GetRealHost(), '.');
|
||||
}
|
||||
|
||||
std::string Generate(const std::string& hostip) override
|
||||
{
|
||||
if (!sha256)
|
||||
return {};
|
||||
|
||||
irc::sockets::sockaddrs sa(false);
|
||||
return sa.from(hostip) ? CloakAddress(sa) : CloakHost(hostip, '.');
|
||||
}
|
||||
|
||||
void GetLinkData(Module::LinkData& data, std::string& compatdata) override
|
||||
{
|
||||
// The value we use for cloaks when the sha2 module is missing.
|
||||
const std::string broken = "missing-sha2-module";
|
||||
|
||||
// IMPORTANT: link data is sent over unauthenticated server links so we
|
||||
// can't directly send the key here. Instead we use dummy cloaks that
|
||||
// allow verification of or less the same thing.
|
||||
irc::sockets::sockaddrs sa;
|
||||
if (sa.from_ip("123.123.123.123"))
|
||||
data["cloak-v4"] = sha256 ? CloakAddress(sa) : broken;
|
||||
|
||||
if (sa.from_ip("dead:beef:cafe::"))
|
||||
data["cloak-v6"] = sha256 ? CloakAddress(sa) : broken;
|
||||
|
||||
data["cloak-host"] = sha256 ? CloakHost("cloak.inspircd.org", '.') : broken;
|
||||
data["cloak-unix"] = sha256 ? CloakHost("/inspircd/cloak.sock", '/') : broken;
|
||||
data["host-parts"] = ConvToStr(hostparts);
|
||||
data["prefix"] = prefix;
|
||||
data["suffix"] = suffix;
|
||||
}
|
||||
};
|
||||
|
||||
class SHA256Engine final
|
||||
: public Cloak::Engine
|
||||
{
|
||||
private:
|
||||
// The minimum length of a cloak key.
|
||||
static constexpr size_t minkeylen = 30;
|
||||
|
||||
public:
|
||||
SHA256Engine(Module* Creator)
|
||||
: Cloak::Engine(Creator, "hmac-sha256")
|
||||
{
|
||||
}
|
||||
|
||||
Cloak::MethodPtr Create(const std::shared_ptr<ConfigTag>& tag, bool primary) override
|
||||
{
|
||||
// Ensure that we have the <cloak:key> parameter.
|
||||
const std::string key = tag->getString("key");
|
||||
if (key.length() < minkeylen)
|
||||
throw ModuleException(creator, "Your cloak key should be at least " + ConvToStr(minkeylen) + " characters long, at " + tag->source.str());
|
||||
|
||||
return std::make_shared<SHA256Method>(this, tag, key);
|
||||
}
|
||||
};
|
||||
|
||||
class ModuleCloakSHA256 final
|
||||
: public Module
|
||||
{
|
||||
private:
|
||||
SHA256Engine cloakengine;
|
||||
|
||||
public:
|
||||
ModuleCloakSHA256()
|
||||
: Module(VF_VENDOR, "Provides the hmac-sha256 cloak engine.")
|
||||
, cloakengine(this)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
MODULE_INIT(ModuleCloakSHA256)
|
Loading…
x
Reference in New Issue
Block a user