]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/credentials.cc
clang-tidy: modernize deprecated header invarious places
[thirdparty/pdns.git] / pdns / credentials.cc
CommitLineData
cfe95ada
RG
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22#include "config.h"
23
8a6030b6 24#include <cmath>
cfe95ada
RG
25#include <stdexcept>
26
27#ifdef HAVE_LIBSODIUM
28#include <sodium.h>
29#endif
30
2f32819a 31#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
8a6030b6
RG
32#include <openssl/evp.h>
33#include <openssl/kdf.h>
8535f666 34#include <openssl/opensslv.h>
8a6030b6
RG
35#include <openssl/rand.h>
36#endif
37
061bc5f0
RG
38#include <fcntl.h>
39#include <sys/stat.h>
40#include <unistd.h>
41
8a6030b6 42#include "base64.hh"
cfe95ada 43#include "credentials.hh"
b2504b29 44#include "misc.hh"
cfe95ada 45
2f32819a 46#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
8a6030b6
RG
47static size_t const pwhash_max_size = 128U; /* maximum size of the output */
48static size_t const pwhash_output_size = 32U; /* size of the hashed output (before base64 encoding) */
49static unsigned int const pwhash_salt_size = 16U; /* size of the salt (before base64 encoding */
31ec1c9f 50static uint64_t const pwhash_max_work_factor = 32768U; /* max N for interactive login purposes */
8a6030b6
RG
51
52/* PHC string format, storing N as log2(N) as done by passlib.
53 for now we only support one algo but we might have to change that later */
54static std::string const pwhash_prefix = "$scrypt$";
55static size_t const pwhash_prefix_size = pwhash_prefix.size();
56#endif
57
58uint64_t const CredentialsHolder::s_defaultWorkFactor{1024U}; /* N */
59uint64_t const CredentialsHolder::s_defaultParallelFactor{1U}; /* p */
60uint64_t const CredentialsHolder::s_defaultBlockSize{8U}; /* r */
61
31ec1c9f
RG
62SensitiveData::SensitiveData(std::string&& data) :
63 d_data(std::move(data))
8a6030b6
RG
64{
65#ifdef HAVE_LIBSODIUM
66 sodium_mlock(d_data.data(), d_data.size());
67#endif
68}
69
0bc984f9
RG
70SensitiveData& SensitiveData::operator=(SensitiveData&& rhs)
71{
72 d_data = std::move(rhs.d_data);
73 return *this;
74}
75
8a6030b6
RG
76SensitiveData::SensitiveData(size_t bytes)
77{
78 d_data.resize(bytes);
79#ifdef HAVE_LIBSODIUM
80 sodium_mlock(d_data.data(), d_data.size());
81#endif
82}
83
84SensitiveData::~SensitiveData()
85{
86 clear();
87}
88
89void SensitiveData::clear()
90{
91#ifdef HAVE_LIBSODIUM
92 sodium_munlock(d_data.data(), d_data.size());
93#endif
94 d_data.clear();
95}
96
8a6030b6
RG
97static std::string hashPasswordInternal(const std::string& password, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize)
98{
2f32819a 99#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
31ec1c9f 100 auto pctx = std::unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX*)>(EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, nullptr), EVP_PKEY_CTX_free);
8a6030b6
RG
101 if (!pctx) {
102 throw std::runtime_error("Error getting a scrypt context to hash the supplied password");
103 }
104
105 if (EVP_PKEY_derive_init(pctx.get()) <= 0) {
106 throw std::runtime_error("Error intializing the scrypt context to hash the supplied password");
107 }
108
8535f666
RG
109 // OpenSSL 3.0 changed the string arg to const unsigned char*, other versions use const char *
110#if OPENSSL_VERSION_MAJOR >= 3
111 auto passwordData = reinterpret_cast<const char*>(password.data());
112#else
113 auto passwordData = reinterpret_cast<const unsigned char*>(password.data());
114#endif
115 if (EVP_PKEY_CTX_set1_pbe_pass(pctx.get(), passwordData, password.size()) <= 0) {
8a6030b6 116 throw std::runtime_error("Error adding the password to the scrypt context to hash the supplied password");
31ec1c9f 117 }
8a6030b6 118
810cb195 119 if (EVP_PKEY_CTX_set1_scrypt_salt(pctx.get(), reinterpret_cast<const unsigned char*>(salt.data()), salt.size()) <= 0) {
8a6030b6
RG
120 throw std::runtime_error("Error adding the salt to the scrypt context to hash the supplied password");
121 }
122
123 if (EVP_PKEY_CTX_set_scrypt_N(pctx.get(), workFactor) <= 0) {
124 throw std::runtime_error("Error setting the work factor to the scrypt context to hash the supplied password");
125 }
126
127 if (EVP_PKEY_CTX_set_scrypt_r(pctx.get(), blockSize) <= 0) {
128 throw std::runtime_error("Error setting the block size to the scrypt context to hash the supplied password");
129 }
130
131 if (EVP_PKEY_CTX_set_scrypt_p(pctx.get(), parallelFactor) <= 0) {
132 throw std::runtime_error("Error setting the parallel factor to the scrypt context to hash the supplied password");
133 }
134
135 std::string out;
136 out.resize(pwhash_output_size);
137 size_t outlen = out.size();
138
139 if (EVP_PKEY_derive(pctx.get(), reinterpret_cast<unsigned char*>(out.data()), &outlen) <= 0 || outlen != pwhash_output_size) {
140 throw std::runtime_error("Error deriving the output from the scrypt context to hash the supplied password");
141 }
142
143 return out;
b59e6712
RG
144#else
145 throw std::runtime_error("Hashing support is not available");
8a6030b6 146#endif
b59e6712 147}
8a6030b6
RG
148
149static std::string generateRandomSalt()
150{
2f32819a 151#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
8a6030b6
RG
152 /* generate a random salt */
153 std::string salt;
154 salt.resize(pwhash_salt_size);
155
156 if (RAND_bytes(reinterpret_cast<unsigned char*>(salt.data()), salt.size()) != 1) {
157 throw std::runtime_error("Error while generating a salt to hash the supplied password");
158 }
159
160 return salt;
161#else
162 throw std::runtime_error("Generating a salted password requires scrypt support in OpenSSL, and it is not available");
163#endif
164}
165
4ec3ff03 166std::string hashPassword(const std::string& password, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize)
cfe95ada 167{
2f32819a
RG
168#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
169 if (workFactor == 0) {
170 throw std::runtime_error("Invalid work factor of " + std::to_string(workFactor) + " passed to hashPassword()");
171 }
172
cfe95ada 173 std::string result;
8a6030b6 174 result.reserve(pwhash_max_size);
cfe95ada 175
8a6030b6
RG
176 result.append(pwhash_prefix);
177 result.append("ln=");
4ec3ff03 178 result.append(std::to_string(static_cast<uint64_t>(std::log2(workFactor))));
8a6030b6 179 result.append(",p=");
4ec3ff03 180 result.append(std::to_string(parallelFactor));
8a6030b6 181 result.append(",r=");
4ec3ff03 182 result.append(std::to_string(blockSize));
8a6030b6
RG
183 result.append("$");
184 auto salt = generateRandomSalt();
185 result.append(Base64Encode(salt));
186 result.append("$");
187
4ec3ff03 188 auto out = hashPasswordInternal(password, salt, workFactor, parallelFactor, blockSize);
8a6030b6
RG
189
190 result.append(Base64Encode(out));
cfe95ada
RG
191
192 return result;
193#else
8a6030b6
RG
194 throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available");
195#endif
196}
197
4ec3ff03
RG
198std::string hashPassword(const std::string& password)
199{
2f32819a 200#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
4ec3ff03
RG
201 return hashPassword(password, CredentialsHolder::s_defaultWorkFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize);
202#else
203 throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available");
204#endif
205}
206
8a6030b6
RG
207bool verifyPassword(const std::string& binaryHash, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize, const std::string& binaryPassword)
208{
2f32819a 209#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
8a6030b6
RG
210 auto expected = hashPasswordInternal(binaryPassword, salt, workFactor, parallelFactor, blockSize);
211 return constantTimeStringEquals(expected, binaryHash);
212#else
213 throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available");
214#endif
215}
216
217/* parse a hashed password in PHC string format */
218static void parseHashed(const std::string& hash, std::string& salt, std::string& hashedPassword, uint64_t& workFactor, uint64_t& parallelFactor, uint64_t& blockSize)
219{
2f32819a 220#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
8a6030b6
RG
221 auto parametersEnd = hash.find('$', pwhash_prefix.size());
222 if (parametersEnd == std::string::npos || parametersEnd == hash.size()) {
8a6030b6
RG
223 throw std::runtime_error("Invalid hashed password format, no parameters");
224 }
225
226 auto parametersStr = hash.substr(pwhash_prefix.size(), parametersEnd);
227 std::vector<std::string> parameters;
228 parameters.reserve(3);
229 stringtok(parameters, parametersStr, ",");
230 if (parameters.size() != 3) {
231 throw std::runtime_error("Invalid hashed password format, expecting 3 parameters, got " + std::to_string(parameters.size()));
232 }
233
234 if (!boost::starts_with(parameters.at(0), "ln=")) {
235 throw std::runtime_error("Invalid hashed password format, ln= parameter not found");
236 }
237
238 if (!boost::starts_with(parameters.at(1), "p=")) {
239 throw std::runtime_error("Invalid hashed password format, p= parameter not found");
240 }
241
242 if (!boost::starts_with(parameters.at(2), "r=")) {
243 throw std::runtime_error("Invalid hashed password format, r= parameter not found");
244 }
245
246 auto saltPos = parametersEnd + 1;
247 auto saltEnd = hash.find('$', saltPos);
248 if (saltEnd == std::string::npos || saltEnd == hash.size()) {
249 throw std::runtime_error("Invalid hashed password format");
250 }
251
252 try {
a0383aad 253 workFactor = pdns::checked_stoi<uint64_t>(parameters.at(0).substr(3));
0ef92c6c 254 workFactor = static_cast<uint64_t>(1) << workFactor;
8a6030b6
RG
255 if (workFactor > pwhash_max_work_factor) {
256 throw std::runtime_error("Invalid work factor of " + std::to_string(workFactor) + " in hashed password string, maximum is " + std::to_string(pwhash_max_work_factor));
257 }
258
a0383aad
FM
259 parallelFactor = pdns::checked_stoi<uint64_t>(parameters.at(1).substr(2));
260 blockSize = pdns::checked_stoi<uint64_t>(parameters.at(2).substr(2));
8a6030b6
RG
261
262 auto b64Salt = hash.substr(saltPos, saltEnd - saltPos);
263 salt.reserve(pwhash_salt_size);
264 B64Decode(b64Salt, salt);
265
266 if (salt.size() != pwhash_salt_size) {
267 throw std::runtime_error("Invalid salt in hashed password string");
268 }
269
270 hashedPassword.reserve(pwhash_output_size);
271 B64Decode(hash.substr(saltEnd + 1), hashedPassword);
272
273 if (hashedPassword.size() != pwhash_output_size) {
274 throw std::runtime_error("Invalid hash in hashed password string");
275 }
276 }
277 catch (const std::exception& e) {
278 throw std::runtime_error("Invalid hashed password format, unable to parse parameters");
279 }
cfe95ada
RG
280#endif
281}
282
283bool verifyPassword(const std::string& hash, const std::string& password)
284{
8a6030b6
RG
285 if (!isPasswordHashed(hash)) {
286 return false;
cfe95ada
RG
287 }
288
2f32819a 289#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
8a6030b6
RG
290 std::string salt;
291 std::string hashedPassword;
292 uint64_t workFactor = 0;
293 uint64_t parallelFactor = 0;
294 uint64_t blockSize = 0;
295 parseHashed(hash, salt, hashedPassword, workFactor, parallelFactor, blockSize);
296
297 auto expected = hashPasswordInternal(password, salt, workFactor, parallelFactor, blockSize);
298
299 return constantTimeStringEquals(expected, hashedPassword);
cfe95ada 300#else
8a6030b6 301 throw std::runtime_error("Verifying a hashed password requires scrypt support in OpenSSL, and it is not available");
cfe95ada
RG
302#endif
303}
304
305bool isPasswordHashed(const std::string& password)
306{
2f32819a 307#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
8a6030b6
RG
308 if (password.size() < pwhash_prefix_size || password.size() > pwhash_max_size) {
309 return false;
310 }
311
312 if (!boost::starts_with(password, pwhash_prefix)) {
cfe95ada
RG
313 return false;
314 }
315
8a6030b6
RG
316 auto parametersEnd = password.find('$', pwhash_prefix.size());
317 if (parametersEnd == std::string::npos || parametersEnd == password.size()) {
318 return false;
319 }
cfe95ada 320
4ec3ff03
RG
321 size_t parametersSize = parametersEnd - pwhash_prefix.size();
322 /* ln=X,p=Y,r=Z */
323 if (parametersSize < 12) {
324 return false;
325 }
326
8a6030b6
RG
327 auto saltEnd = password.find('$', parametersEnd + 1);
328 if (saltEnd == std::string::npos || saltEnd == password.size()) {
cfe95ada
RG
329 return false;
330 }
8a6030b6
RG
331
332 /* the salt is base64 encoded so it has to be larger than that */
333 if ((saltEnd - parametersEnd - 1) < pwhash_salt_size) {
334 return false;
335 }
336
337 /* the hash base64 encoded so it has to be larger than that */
31ec1c9f 338 if ((password.size() - saltEnd - 1) < pwhash_output_size) {
8a6030b6
RG
339 return false;
340 }
341
cfe95ada
RG
342 return true;
343#else
344 return false;
345#endif
346}
347
348/* if the password is in cleartext and hashing is available,
349 the hashed form will be kept in memory */
31ec1c9f
RG
350CredentialsHolder::CredentialsHolder(std::string&& password, bool hashPlaintext) :
351 d_credentials(std::move(password))
cfe95ada 352{
cfe95ada 353 if (isHashingAvailable()) {
8a6030b6 354 if (!isPasswordHashed(d_credentials.getString())) {
64c4f83c 355 if (hashPlaintext) {
8a6030b6
RG
356 d_salt = generateRandomSalt();
357 d_workFactor = s_defaultWorkFactor;
358 d_parallelFactor = s_defaultParallelFactor;
359 d_blockSize = s_defaultBlockSize;
360 d_credentials = SensitiveData(hashPasswordInternal(d_credentials.getString(), d_salt, d_workFactor, d_parallelFactor, d_blockSize));
64c4f83c
RG
361 d_isHashed = true;
362 }
cfe95ada
RG
363 }
364 else {
32e8669a 365 d_wasHashed = true;
64c4f83c 366 d_isHashed = true;
8a6030b6
RG
367 std::string hashedPassword;
368 parseHashed(d_credentials.getString(), d_salt, hashedPassword, d_workFactor, d_parallelFactor, d_blockSize);
369 d_credentials = SensitiveData(std::move(hashedPassword));
cfe95ada
RG
370 }
371 }
64c4f83c
RG
372
373 if (!d_isHashed) {
b2504b29 374 d_fallbackHashPerturb = random();
8a6030b6 375 d_fallbackHash = burtle(reinterpret_cast<const unsigned char*>(d_credentials.getString().data()), d_credentials.getString().size(), d_fallbackHashPerturb);
cfe95ada 376 }
cfe95ada
RG
377}
378
379CredentialsHolder::~CredentialsHolder()
380{
b2504b29
RG
381 d_fallbackHashPerturb = 0;
382 d_fallbackHash = 0;
cfe95ada
RG
383}
384
385bool CredentialsHolder::matches(const std::string& password) const
386{
64c4f83c 387 if (d_isHashed) {
8a6030b6 388 return verifyPassword(d_credentials.getString(), d_salt, d_workFactor, d_parallelFactor, d_blockSize, password);
cfe95ada
RG
389 }
390 else {
b2504b29
RG
391 uint32_t fallback = burtle(reinterpret_cast<const unsigned char*>(password.data()), password.size(), d_fallbackHashPerturb);
392 if (fallback != d_fallbackHash) {
393 return false;
394 }
395
8a6030b6 396 return constantTimeStringEquals(password, d_credentials.getString());
cfe95ada
RG
397 }
398}
399
400bool CredentialsHolder::isHashingAvailable()
401{
2f32819a 402#if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
cfe95ada
RG
403 return true;
404#else
405 return false;
406#endif
407}
061bc5f0 408
dc6aa7f5 409#include <csignal>
061bc5f0
RG
410#include <termios.h>
411
8a6030b6 412SensitiveData CredentialsHolder::readFromTerminal()
061bc5f0
RG
413{
414 struct termios term;
415 struct termios oterm;
061bc5f0
RG
416 bool restoreTermSettings = false;
417 int termAction = TCSAFLUSH;
418#ifdef TCSASOFT
9437be08 419 termAction |= TCSASOFT;
061bc5f0
RG
420#endif
421
422 FDWrapper input(open("/dev/tty", O_RDONLY));
423 if (int(input) != -1) {
424 if (tcgetattr(input, &oterm) == 0) {
425 memcpy(&term, &oterm, sizeof(term));
426 term.c_lflag &= ~(ECHO | ECHONL);
427 tcsetattr(input, termAction, &term);
428 restoreTermSettings = true;
429 }
430 }
431 else {
432 input = FDWrapper(dup(STDIN_FILENO));
d78e47e3 433 restoreTermSettings = false;
061bc5f0 434 }
d78e47e3 435
061bc5f0
RG
436 FDWrapper output(open("/dev/tty", O_WRONLY));
437 if (int(output) == -1) {
438 output = FDWrapper(dup(STDERR_FILENO));
439 }
440
441 struct std::map<int, struct sigaction> signals;
442 struct sigaction sa;
443 sigemptyset(&sa.sa_mask);
444 sa.sa_flags = 0;
31ec1c9f 445 sa.sa_handler = [](int s) {};
061bc5f0
RG
446 sigaction(SIGALRM, &sa, &signals[SIGALRM]);
447 sigaction(SIGHUP, &sa, &signals[SIGHUP]);
448 sigaction(SIGINT, &sa, &signals[SIGINT]);
449 sigaction(SIGPIPE, &sa, &signals[SIGPIPE]);
450 sigaction(SIGQUIT, &sa, &signals[SIGQUIT]);
451 sigaction(SIGTERM, &sa, &signals[SIGTERM]);
452 sigaction(SIGTSTP, &sa, &signals[SIGTSTP]);
453 sigaction(SIGTTIN, &sa, &signals[SIGTTIN]);
454 sigaction(SIGTTOU, &sa, &signals[SIGTTOU]);
455
456 std::string buffer;
457 /* let's allocate a huge buffer now to prevent reallocation,
458 which would leave parts of the buffer around */
459 buffer.reserve(512);
460
461 for (;;) {
462 char ch = '\0';
463 auto got = read(input, &ch, 1);
464 if (got == 1 && ch != '\n' && ch != '\r') {
465 buffer.push_back(ch);
466 }
467 else {
468 break;
469 }
470 }
471
061bc5f0
RG
472 if (restoreTermSettings) {
473 tcsetattr(input, termAction, &oterm);
474 }
475
476 for (const auto& sig : signals) {
477 sigaction(sig.first, &sig.second, nullptr);
478 }
479
8a6030b6 480 return SensitiveData(std::move(buffer));
061bc5f0 481}