1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
|
/*
* 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/>.
*/
#pragma once
#include "stringutils.h"
#include "utility/string.h"
namespace Hash
{
class Context;
class Provider;
class ProviderRef;
class HMACContext;
class HMACProvider;
/** Compares a password to a hashed password.
* @param password The hashed password.
* @param algorithm If non-empty then the algorithm the password is hashed with.
* @param value The value to check to see if the password is valid.
* @return True if the password is correct, otherwise, false.
*/
inline bool CheckPassword(const std::string& password, const std::string& algorithm, const std::string& value);
/** Generates a hash-based message authentication code.
* @param prov The hash algorithm to hash with.
* @param key The secret key.
* @param data The data to hash.
*/
inline std::string HMAC(Hash::Provider* prov, const std::string& key, const std::string& data);
}
/** Base class for hash contexts. */
class Hash::Context
{
public:
virtual ~Context() = default;
/** Updates the hash context with the specified data.
* @param str The data to update the context with.
*/
inline void Update(const std::string& str)
{
Update(reinterpret_cast<const unsigned char *>(str.c_str()), str.length());
}
/** Updates the hash context with the specified data.
* @param data The data to update the context with.
* @param len The length of the data.
*/
virtual void Update(const unsigned char *data, size_t len) = 0;
/** Finalises the hash context and returns the digest. */
virtual std::string Finalize() = 0;
};
/** Provider of hash contexts. */
class Hash::Provider
: public DataProvider
{
public:
/** The byte size of the block cipher. */
const size_t block_size;
/** The byte size of the resulting digest. */
const size_t digest_size;
/** Creates a provider of hash contexts.
* @param mod The module that created this provider.
* @param algorithm The name of the hash algorithm.
* @param ds The byte size of the resulting digest or 0 if it is variable length.
* @param bs The byte size of the block cipher or 0 if not a block cipher.
*/
Provider(Module* mod, const std::string& algorithm, size_t ds = 0, size_t bs = 0)
: DataProvider(mod, "hash/" + algorithm)
, block_size(bs)
, digest_size(ds)
{
}
virtual ~Provider() = default;
/** Checks whether a plain text value matches a hash created by this provider
* @param hash The hashed value to compare against.
* @param plain The plain text password to compare.
*/
virtual bool Compare(const std::string& hash, const std::string& plain)
{
return !hash.empty() && InspIRCd::TimingSafeCompare(hash, ToPrintable(Hash(plain)));
}
/** Called on initialising a hash provider to check it works properly.
* @param checks A map of known ciphertexts to plaintexts.
*/
inline void Check(const std::map<std::string, std::string>& checks)
{
for (const auto& [hash, plain] : checks)
{
if (!Compare(hash, plain))
throw ModuleException(creator, "BUG: unable to generate {} hashes safely! Please report this!", GetAlgorithm());
}
ServerInstance->Logs.Debug("HASH", "The {} hash provider appears to be working correctly.", GetAlgorithm());
}
/** Creates a new hash context. */
virtual std::unique_ptr<Context> CreateContext() = 0;
/** Retrieves the name of the hash algorithm. */
const char* GetAlgorithm() const
{
return name.c_str() + 5;
}
/** Quickly hashes the specified values and returns the digest. */
template<typename... Args>
std::string Hash(Args&& ...args)
{
auto context = CreateContext();
context->Update(std::forward<Args>(args)...);
return context->Finalize();
}
/** Determines whether this hash algorithm is a key derivation function. */
auto IsKDF() const { return block_size == 0; }
/** Determines whether this hash provider is safe for password hashing. */
virtual bool IsPasswordSafe() const { return true; }
/** Converts a hash to its printable form. */
virtual std::string ToPrintable(const std::string& hash)
{
return Hex::Encode(hash);
}
};
/** Holds a dynamic reference to a hash provider. */
class Hash::ProviderRef final
: public dynamic_reference_nocheck<Hash::Provider>
{
public:
/** Holds a dynamic reference to a hash algorithm.
* @param mod The module that created this reference.
* @param algorithm The name of the hash algorithm.
*/
ProviderRef(Module* mod, const std::string& algorithm)
: dynamic_reference_nocheck<Hash::Provider>(mod, "hash/" + algorithm)
{
}
/** Retrieves the name of the referenced hash algorithm. */
const char* GetAlgorithm() const
{
if (GetProvider().empty())
return nullptr;
return GetProvider().c_str() + 5;
}
};
/** Provides a hash context for HMAC generation. */
class Hash::HMACContext final
: public Hash::Context
{
private:
/** The data which has been written to this context. */
std::string buffer;
/** The underlying hash provider. */
Hash::ProviderRef provider;
/** Generates a salt for the HMAC. */
std::string GenerateSalt()
{
if (!provider || provider->IsKDF())
return {};
std::vector<char> salt(provider->digest_size);
ServerInstance->GenRandom(salt.data(), salt.size());
return std::string(salt.data(), salt.size());
}
public:
/** Creates HMAC hash context.
* @param prov The underlying hash provider.
*/
HMACContext(const Hash::ProviderRef& prov)
: provider(prov)
{
}
/** @copydoc Hash::HMACContext::Update */
void Update(const unsigned char *data, size_t len) override
{
buffer.append(reinterpret_cast<const char *>(data), len);
}
/** @copydoc Hash::HMACContext::Finalize */
std::string Finalize() override
{
if (!provider)
return {}; // No underlying hash (should never happen).
auto salt = this->GenerateSalt();
if (salt.empty())
return {};
auto hash = Hash::HMAC(*provider, salt, buffer);
if (hash.empty())
return {};
this->buffer.clear();
return FMT::format("{}${}", Base64::Encode(salt), Base64::Encode(hash));
}
};
/** Provides a hash provider for HMAC generation. */
class Hash::HMACProvider final
: public Hash::Provider
{
private:
/** The underlying hash provider. */
Hash::ProviderRef provider;
public:
/** Creates a provider of HMAC hash contexts.
* @param mod The module that created this provider.
* @param algorithm The name of the hash algorithm.
*/
HMACProvider(Module* mod, const std::string& algorithm)
: Hash::Provider(mod, FMT::format("hmac-{}", algorithm))
, provider(mod, algorithm)
{
}
/** @copydoc Hash::Provider::Compare */
bool Compare(const std::string& hash, const std::string& plain) override
{
if (!provider)
return false; // No underlying hash (should never happen).
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));
auto expected = Hash::HMAC(*provider, rawkey, plain);
return !expected.empty() && InspIRCd::TimingSafeCompare(rawhash, expected);
}
/** @copydoc Hash::Provider::CreateContext */
std::unique_ptr<Context> CreateContext() override
{
return std::make_unique<HMACContext>(provider);
}
/** @copydoc Hash::Provider::ToPrintable */
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;
}
};
inline bool Hash::CheckPassword(const std::string& password, const std::string& algorithm, const std::string& value)
{
auto* hash = ServerInstance->Modules.FindDataService<Hash::Provider>("hash/" + algorithm);
if (hash)
return hash->Compare(password, value);
// The hash algorithm wasn't provided by any modules. If its plain
// text then we can check it internally.
if (algorithm.empty() || insp::equalsci(algorithm, "plaintext"))
return InspIRCd::TimingSafeCompare(password, value);
ServerInstance->Logs.Debug("HASH", "Unable to check password hashed with an unknown algorithm: {}", algorithm);
return false;
}
inline std::string Hash::HMAC(Hash::Provider* prov, const std::string& key, const std::string& data)
{
if (!prov || prov->IsKDF())
return {};
auto keybuf = key.length() > prov->block_size ? prov->Hash(key) : key;
keybuf.resize(prov->block_size);
std::string hmac1;
std::string hmac2;
for (size_t i = 0; i < prov->block_size; ++i)
{
hmac1.push_back(static_cast<char>(keybuf[i] ^ 0x5C));
hmac2.push_back(static_cast<char>(keybuf[i] ^ 0x36));
}
hmac2.append(data);
hmac1.append(prov->Hash(hmac2));
return prov->Hash(hmac1);
}
|