/src/pdns/pdns/dnsdistdist/credentials.cc
Line | Count | Source |
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 | | |
24 | | #include <cmath> |
25 | | #include <stdexcept> |
26 | | |
27 | | #ifdef HAVE_LIBSODIUM |
28 | | #include <sodium.h> |
29 | | #endif |
30 | | |
31 | | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
32 | | #include <openssl/evp.h> |
33 | | #include <openssl/kdf.h> |
34 | | #include <openssl/opensslv.h> |
35 | | #include <openssl/rand.h> |
36 | | #endif |
37 | | |
38 | | #include <fcntl.h> |
39 | | #include <sys/stat.h> |
40 | | #include <unistd.h> |
41 | | |
42 | | #include "base64.hh" |
43 | | #include "dns_random.hh" |
44 | | #include "credentials.hh" |
45 | | #include "misc.hh" |
46 | | |
47 | | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
48 | | static size_t const pwhash_max_size = 128U; /* maximum size of the output */ |
49 | | static size_t const pwhash_output_size = 32U; /* size of the hashed output (before base64 encoding) */ |
50 | | static unsigned int const pwhash_salt_size = 16U; /* size of the salt (before base64 encoding */ |
51 | | static uint64_t const pwhash_max_work_factor = 32768U; /* max N for interactive login purposes */ |
52 | | |
53 | | /* PHC string format, storing N as log2(N) as done by passlib. |
54 | | for now we only support one algo but we might have to change that later */ |
55 | | static std::string const pwhash_prefix = "$scrypt$"; |
56 | | static size_t const pwhash_prefix_size = pwhash_prefix.size(); |
57 | | #endif |
58 | | |
59 | | void SensitiveData::reallyClearContent(void* data, size_t size) noexcept |
60 | 0 | { |
61 | | #ifdef HAVE_LIBSODIUM |
62 | | sodium_memzero(data, size); |
63 | | #elif defined(HAVE_EXPLICIT_BZERO) |
64 | | explicit_bzero(data, size); |
65 | | #elif defined(HAVE_EXPLICIT_MEMSET) |
66 | | explicit_memset(data, 0, size); |
67 | | #elif defined(HAVE_GNUTLS_MEMSET) |
68 | | gnutls_memset(data, 0, size); |
69 | | #else |
70 | | /* shamelessly taken from Dovecot's src/lib/safe-memset.c */ |
71 | | if (size == 0) { |
72 | | return; |
73 | | } |
74 | | |
75 | | volatile unsigned int volatile_zero_idx = 0; |
76 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): sorry! |
77 | | volatile unsigned char* p = reinterpret_cast<volatile unsigned char*>(data); |
78 | | do { |
79 | | memset(data, 0, size); |
80 | | } while (p[volatile_zero_idx] != 0); |
81 | | #endif |
82 | 0 | } |
83 | | |
84 | | SensitiveData::SensitiveData(std::string&& data) : |
85 | 0 | d_data(std::move(data)) |
86 | 0 | { |
87 | | // linters are complaining that we are calling data() and capacity() on a moved-from object, |
88 | | // so clear the object first so they shut up |
89 | 0 | data.clear(); |
90 | | #ifdef HAVE_LIBSODIUM |
91 | | // let's be nice and try to zero out the SSO buffer, that cannot be moved |
92 | | reallyClearContent(data.data(), data.capacity()); |
93 | | #endif |
94 | | #ifdef HAVE_LIBSODIUM |
95 | | sodium_mlock(d_data.data(), d_data.size()); |
96 | | #endif |
97 | 0 | } |
98 | | |
99 | | SensitiveData& SensitiveData::operator=(SensitiveData&& rhs) noexcept |
100 | 0 | { |
101 | 0 | d_data = std::move(rhs.d_data); |
102 | 0 | rhs.clear(); |
103 | 0 | return *this; |
104 | 0 | } |
105 | | |
106 | | SensitiveData::SensitiveData(size_t bytes) |
107 | 0 | { |
108 | 0 | d_data.resize(bytes); |
109 | | #ifdef HAVE_LIBSODIUM |
110 | | sodium_mlock(d_data.data(), d_data.size()); |
111 | | #endif |
112 | 0 | } |
113 | | |
114 | | SensitiveData::~SensitiveData() |
115 | 0 | { |
116 | 0 | clear(); |
117 | 0 | } |
118 | | |
119 | | void SensitiveData::clear() |
120 | 0 | { |
121 | | #ifdef HAVE_LIBSODIUM |
122 | | // let's be nice and try to zero out the SSO buffer (be careful, sodium_munlock will zero out the current size |
123 | | // which might be zero if the object was moved) |
124 | | reallyClearContent(d_data.data(), d_data.capacity()); |
125 | | sodium_munlock(d_data.data(), d_data.size()); |
126 | | #endif |
127 | 0 | d_data.clear(); |
128 | 0 | } |
129 | | |
130 | | static std::string hashPasswordInternal([[maybe_unused]] const std::string& password, [[maybe_unused]] const std::string& salt, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize) |
131 | 0 | { |
132 | 0 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
133 | 0 | 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); |
134 | 0 | if (!pctx) { |
135 | 0 | throw std::runtime_error("Error getting a scrypt context to hash the supplied password"); |
136 | 0 | } |
137 | | |
138 | 0 | if (EVP_PKEY_derive_init(pctx.get()) <= 0) { |
139 | 0 | throw std::runtime_error("Error intializing the scrypt context to hash the supplied password"); |
140 | 0 | } |
141 | | |
142 | | // OpenSSL 3.0 changed the string arg to const unsigned char*, other versions use const char * |
143 | | // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) |
144 | | #if OPENSSL_VERSION_MAJOR >= 3 |
145 | | const auto* passwordData = reinterpret_cast<const char*>(password.data()); |
146 | | #else |
147 | 0 | const auto* passwordData = reinterpret_cast<const unsigned char*>(password.data()); |
148 | 0 | #endif |
149 | 0 | if (EVP_PKEY_CTX_set1_pbe_pass(pctx.get(), passwordData, password.size()) <= 0) { |
150 | 0 | throw std::runtime_error("Error adding the password to the scrypt context to hash the supplied password"); |
151 | 0 | } |
152 | | |
153 | 0 | if (EVP_PKEY_CTX_set1_scrypt_salt(pctx.get(), reinterpret_cast<const unsigned char*>(salt.data()), salt.size()) <= 0) { |
154 | 0 | throw std::runtime_error("Error adding the salt to the scrypt context to hash the supplied password"); |
155 | 0 | } |
156 | | // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) |
157 | | |
158 | 0 | if (EVP_PKEY_CTX_set_scrypt_N(pctx.get(), workFactor) <= 0) { |
159 | 0 | throw std::runtime_error("Error setting the work factor to the scrypt context to hash the supplied password"); |
160 | 0 | } |
161 | | |
162 | 0 | if (EVP_PKEY_CTX_set_scrypt_r(pctx.get(), blockSize) <= 0) { |
163 | 0 | throw std::runtime_error("Error setting the block size to the scrypt context to hash the supplied password"); |
164 | 0 | } |
165 | | |
166 | 0 | if (EVP_PKEY_CTX_set_scrypt_p(pctx.get(), parallelFactor) <= 0) { |
167 | 0 | throw std::runtime_error("Error setting the parallel factor to the scrypt context to hash the supplied password"); |
168 | 0 | } |
169 | | |
170 | 0 | std::string out; |
171 | 0 | out.resize(pwhash_output_size); |
172 | 0 | size_t outlen = out.size(); |
173 | | |
174 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
175 | 0 | if (EVP_PKEY_derive(pctx.get(), reinterpret_cast<unsigned char*>(out.data()), &outlen) <= 0 || outlen != pwhash_output_size) { |
176 | 0 | throw std::runtime_error("Error deriving the output from the scrypt context to hash the supplied password"); |
177 | 0 | } |
178 | | |
179 | 0 | return out; |
180 | | #else |
181 | | throw std::runtime_error("Hashing support is not available"); |
182 | | #endif |
183 | 0 | } |
184 | | |
185 | | static std::string generateRandomSalt() |
186 | 0 | { |
187 | 0 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
188 | | /* generate a random salt */ |
189 | 0 | std::string salt; |
190 | 0 | salt.resize(pwhash_salt_size); |
191 | | |
192 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
193 | 0 | if (RAND_bytes(reinterpret_cast<unsigned char*>(salt.data()), static_cast<int>(salt.size())) != 1) { |
194 | 0 | throw std::runtime_error("Error while generating a salt to hash the supplied password"); |
195 | 0 | } |
196 | | |
197 | 0 | return salt; |
198 | | #else |
199 | | throw std::runtime_error("Generating a salted password requires scrypt support in OpenSSL, and it is not available"); |
200 | | #endif |
201 | 0 | } |
202 | | |
203 | | std::string hashPassword([[maybe_unused]] const std::string& password, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize) |
204 | 0 | { |
205 | 0 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
206 | 0 | if (workFactor == 0) { |
207 | 0 | throw std::runtime_error("Invalid work factor of " + std::to_string(workFactor) + " passed to hashPassword()"); |
208 | 0 | } |
209 | | |
210 | 0 | std::string result; |
211 | 0 | result.reserve(pwhash_max_size); |
212 | |
|
213 | 0 | result.append(pwhash_prefix); |
214 | 0 | result.append("ln="); |
215 | 0 | result.append(std::to_string(static_cast<uint64_t>(std::log2(workFactor)))); |
216 | 0 | result.append(",p="); |
217 | 0 | result.append(std::to_string(parallelFactor)); |
218 | 0 | result.append(",r="); |
219 | 0 | result.append(std::to_string(blockSize)); |
220 | 0 | result.append("$"); |
221 | 0 | auto salt = generateRandomSalt(); |
222 | 0 | result.append(Base64Encode(salt)); |
223 | 0 | result.append("$"); |
224 | |
|
225 | 0 | auto out = hashPasswordInternal(password, salt, workFactor, parallelFactor, blockSize); |
226 | |
|
227 | 0 | result.append(Base64Encode(out)); |
228 | |
|
229 | 0 | return result; |
230 | | #else |
231 | | throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available"); |
232 | | #endif |
233 | 0 | } |
234 | | |
235 | | std::string hashPassword([[maybe_unused]] const std::string& password) |
236 | 0 | { |
237 | 0 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
238 | 0 | return hashPassword(password, CredentialsHolder::s_defaultWorkFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize); |
239 | | #else |
240 | | throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available"); |
241 | | #endif |
242 | 0 | } |
243 | | |
244 | | bool verifyPassword([[maybe_unused]] const std::string& binaryHash, [[maybe_unused]] const std::string& salt, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize, [[maybe_unused]] const std::string& binaryPassword) |
245 | 0 | { |
246 | 0 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
247 | 0 | auto expected = hashPasswordInternal(binaryPassword, salt, workFactor, parallelFactor, blockSize); |
248 | 0 | return constantTimeStringEquals(expected, binaryHash); |
249 | | #else |
250 | | throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available"); |
251 | | #endif |
252 | 0 | } |
253 | | |
254 | | /* parse a hashed password in PHC string format */ |
255 | | static void parseHashed([[maybe_unused]] const std::string& hash, [[maybe_unused]] std::string& salt, [[maybe_unused]] std::string& hashedPassword, [[maybe_unused]] uint64_t& workFactor, [[maybe_unused]] uint64_t& parallelFactor, [[maybe_unused]] uint64_t& blockSize) |
256 | 0 | { |
257 | 0 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
258 | 0 | auto parametersEnd = hash.find('$', pwhash_prefix.size()); |
259 | 0 | if (parametersEnd == std::string::npos || parametersEnd == hash.size()) { |
260 | 0 | throw std::runtime_error("Invalid hashed password format, no parameters"); |
261 | 0 | } |
262 | | |
263 | 0 | auto parametersStr = hash.substr(pwhash_prefix.size(), parametersEnd); |
264 | 0 | std::vector<std::string> parameters; |
265 | 0 | parameters.reserve(3); |
266 | 0 | stringtok(parameters, parametersStr, ","); |
267 | 0 | if (parameters.size() != 3) { |
268 | 0 | throw std::runtime_error("Invalid hashed password format, expecting 3 parameters, got " + std::to_string(parameters.size())); |
269 | 0 | } |
270 | | |
271 | 0 | if (!boost::starts_with(parameters.at(0), "ln=")) { |
272 | 0 | throw std::runtime_error("Invalid hashed password format, ln= parameter not found"); |
273 | 0 | } |
274 | | |
275 | 0 | if (!boost::starts_with(parameters.at(1), "p=")) { |
276 | 0 | throw std::runtime_error("Invalid hashed password format, p= parameter not found"); |
277 | 0 | } |
278 | | |
279 | 0 | if (!boost::starts_with(parameters.at(2), "r=")) { |
280 | 0 | throw std::runtime_error("Invalid hashed password format, r= parameter not found"); |
281 | 0 | } |
282 | | |
283 | 0 | auto saltPos = parametersEnd + 1; |
284 | 0 | auto saltEnd = hash.find('$', saltPos); |
285 | 0 | if (saltEnd == std::string::npos || saltEnd == hash.size()) { |
286 | 0 | throw std::runtime_error("Invalid hashed password format"); |
287 | 0 | } |
288 | | |
289 | 0 | try { |
290 | 0 | workFactor = pdns::checked_stoi<uint64_t>(parameters.at(0).substr(3)); |
291 | 0 | workFactor = static_cast<uint64_t>(1) << workFactor; |
292 | 0 | if (workFactor > pwhash_max_work_factor) { |
293 | 0 | 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)); |
294 | 0 | } |
295 | | |
296 | 0 | parallelFactor = pdns::checked_stoi<uint64_t>(parameters.at(1).substr(2)); |
297 | 0 | blockSize = pdns::checked_stoi<uint64_t>(parameters.at(2).substr(2)); |
298 | |
|
299 | 0 | auto b64Salt = hash.substr(saltPos, saltEnd - saltPos); |
300 | 0 | salt.reserve(pwhash_salt_size); |
301 | 0 | B64Decode(b64Salt, salt); |
302 | |
|
303 | 0 | if (salt.size() != pwhash_salt_size) { |
304 | 0 | throw std::runtime_error("Invalid salt in hashed password string"); |
305 | 0 | } |
306 | | |
307 | 0 | hashedPassword.reserve(pwhash_output_size); |
308 | 0 | B64Decode(hash.substr(saltEnd + 1), hashedPassword); |
309 | |
|
310 | 0 | if (hashedPassword.size() != pwhash_output_size) { |
311 | 0 | throw std::runtime_error("Invalid hash in hashed password string"); |
312 | 0 | } |
313 | 0 | } |
314 | 0 | catch (const std::exception& e) { |
315 | 0 | throw std::runtime_error("Invalid hashed password format, unable to parse parameters"); |
316 | 0 | } |
317 | 0 | #endif |
318 | 0 | } |
319 | | |
320 | | bool verifyPassword(const std::string& hash, [[maybe_unused]] const std::string& password) |
321 | 0 | { |
322 | 0 | if (!isPasswordHashed(hash)) { |
323 | 0 | return false; |
324 | 0 | } |
325 | | |
326 | 0 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
327 | 0 | std::string salt; |
328 | 0 | std::string hashedPassword; |
329 | 0 | uint64_t workFactor = 0; |
330 | 0 | uint64_t parallelFactor = 0; |
331 | 0 | uint64_t blockSize = 0; |
332 | 0 | parseHashed(hash, salt, hashedPassword, workFactor, parallelFactor, blockSize); |
333 | |
|
334 | 0 | auto expected = hashPasswordInternal(password, salt, workFactor, parallelFactor, blockSize); |
335 | |
|
336 | 0 | return constantTimeStringEquals(expected, hashedPassword); |
337 | | #else |
338 | | throw std::runtime_error("Verifying a hashed password requires scrypt support in OpenSSL, and it is not available"); |
339 | | #endif |
340 | 0 | } |
341 | | |
342 | | bool isPasswordHashed([[maybe_unused]] const std::string& password) |
343 | 0 | { |
344 | 0 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
345 | 0 | if (password.size() < pwhash_prefix_size || password.size() > pwhash_max_size) { |
346 | 0 | return false; |
347 | 0 | } |
348 | | |
349 | 0 | if (!boost::starts_with(password, pwhash_prefix)) { |
350 | 0 | return false; |
351 | 0 | } |
352 | | |
353 | 0 | auto parametersEnd = password.find('$', pwhash_prefix.size()); |
354 | 0 | if (parametersEnd == std::string::npos || parametersEnd == password.size()) { |
355 | 0 | return false; |
356 | 0 | } |
357 | | |
358 | 0 | size_t parametersSize = parametersEnd - pwhash_prefix.size(); |
359 | | /* ln=X,p=Y,r=Z */ |
360 | 0 | if (parametersSize < 12) { |
361 | 0 | return false; |
362 | 0 | } |
363 | | |
364 | 0 | auto saltEnd = password.find('$', parametersEnd + 1); |
365 | 0 | if (saltEnd == std::string::npos || saltEnd == password.size()) { |
366 | 0 | return false; |
367 | 0 | } |
368 | | |
369 | | /* the salt is base64 encoded so it has to be larger than that */ |
370 | 0 | if ((saltEnd - parametersEnd - 1) < pwhash_salt_size) { |
371 | 0 | return false; |
372 | 0 | } |
373 | | |
374 | | /* the hash base64 encoded so it has to be larger than that */ |
375 | 0 | if ((password.size() - saltEnd - 1) < pwhash_output_size) { |
376 | 0 | return false; |
377 | 0 | } |
378 | | |
379 | 0 | return true; |
380 | | #else |
381 | | return false; |
382 | | #endif |
383 | 0 | } |
384 | | |
385 | | /* if the password is in cleartext and hashing is available, |
386 | | the hashed form will be kept in memory */ |
387 | | CredentialsHolder::CredentialsHolder(std::string&& password, bool hashPlaintext) : |
388 | 0 | d_credentials(std::move(password)) |
389 | 0 | { |
390 | 0 | if (isHashingAvailable()) { |
391 | 0 | if (!isPasswordHashed(d_credentials.getString())) { |
392 | 0 | if (hashPlaintext) { |
393 | 0 | d_salt = generateRandomSalt(); |
394 | 0 | d_workFactor = s_defaultWorkFactor; |
395 | 0 | d_parallelFactor = s_defaultParallelFactor; |
396 | 0 | d_blockSize = s_defaultBlockSize; |
397 | 0 | d_credentials = SensitiveData(hashPasswordInternal(d_credentials.getString(), d_salt, d_workFactor, d_parallelFactor, d_blockSize)); |
398 | 0 | d_isHashed = true; |
399 | 0 | } |
400 | 0 | } |
401 | 0 | else { |
402 | 0 | d_wasHashed = true; |
403 | 0 | d_isHashed = true; |
404 | 0 | std::string hashedPassword; |
405 | 0 | parseHashed(d_credentials.getString(), d_salt, hashedPassword, d_workFactor, d_parallelFactor, d_blockSize); |
406 | 0 | d_credentials = SensitiveData(std::move(hashedPassword)); |
407 | 0 | } |
408 | 0 | } |
409 | |
|
410 | 0 | if (!d_isHashed) { |
411 | 0 | d_fallbackHashPerturb = dns_random_uint32(); |
412 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
413 | 0 | d_fallbackHash = burtle(reinterpret_cast<const unsigned char*>(d_credentials.getString().data()), d_credentials.getString().size(), d_fallbackHashPerturb); |
414 | 0 | } |
415 | 0 | } |
416 | | |
417 | | CredentialsHolder::~CredentialsHolder() |
418 | 0 | { |
419 | 0 | d_fallbackHashPerturb = 0; |
420 | 0 | d_fallbackHash = 0; |
421 | 0 | } |
422 | | |
423 | | bool CredentialsHolder::matches(const std::string& password) const |
424 | 0 | { |
425 | 0 | if (d_isHashed) { |
426 | 0 | return verifyPassword(d_credentials.getString(), d_salt, d_workFactor, d_parallelFactor, d_blockSize, password); |
427 | 0 | } |
428 | | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
429 | 0 | uint32_t fallback = burtle(reinterpret_cast<const unsigned char*>(password.data()), password.size(), d_fallbackHashPerturb); |
430 | 0 | if (fallback != d_fallbackHash) { |
431 | 0 | return false; |
432 | 0 | } |
433 | | |
434 | 0 | return constantTimeStringEquals(password, d_credentials.getString()); |
435 | 0 | } |
436 | | |
437 | | bool CredentialsHolder::isHashingAvailable() |
438 | 0 | { |
439 | 0 | #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT) |
440 | 0 | return true; |
441 | | #else |
442 | | return false; |
443 | | #endif |
444 | 0 | } |
445 | | |
446 | | #include <csignal> |
447 | | #include <termios.h> |
448 | | |
449 | | SensitiveData CredentialsHolder::readFromTerminal() |
450 | 0 | { |
451 | 0 | termios term{}; |
452 | 0 | termios oterm{}; |
453 | 0 | bool restoreTermSettings = false; |
454 | 0 | int termAction = TCSAFLUSH; |
455 | | #ifdef TCSASOFT |
456 | | termAction |= TCSASOFT; |
457 | | #endif |
458 | |
|
459 | 0 | FDWrapper input(open("/dev/tty", O_RDONLY)); |
460 | 0 | if (int(input) != -1) { |
461 | 0 | if (tcgetattr(input, &oterm) == 0) { |
462 | 0 | memcpy(&term, &oterm, sizeof(term)); |
463 | 0 | term.c_lflag &= ~(ECHO | ECHONL); |
464 | 0 | tcsetattr(input, termAction, &term); |
465 | 0 | restoreTermSettings = true; |
466 | 0 | } |
467 | 0 | } |
468 | 0 | else { |
469 | 0 | input = FDWrapper(dup(STDIN_FILENO)); |
470 | 0 | restoreTermSettings = false; |
471 | 0 | } |
472 | |
|
473 | 0 | FDWrapper output(open("/dev/tty", O_WRONLY)); |
474 | 0 | if (int(output) == -1) { |
475 | 0 | output = FDWrapper(dup(STDERR_FILENO)); |
476 | 0 | } |
477 | |
|
478 | 0 | struct std::map<int, struct sigaction> signals; |
479 | 0 | struct sigaction sigact // just sigaction does not work, it clashes with sigaction(2) |
480 | 0 | {}; |
481 | 0 | sigemptyset(&sigact.sa_mask); |
482 | 0 | sigact.sa_flags = 0; |
483 | 0 | sigact.sa_handler = [](int /* s */) {}; |
484 | 0 | sigaction(SIGALRM, &sigact, &signals[SIGALRM]); |
485 | 0 | sigaction(SIGHUP, &sigact, &signals[SIGHUP]); |
486 | 0 | sigaction(SIGINT, &sigact, &signals[SIGINT]); |
487 | 0 | sigaction(SIGPIPE, &sigact, &signals[SIGPIPE]); |
488 | 0 | sigaction(SIGQUIT, &sigact, &signals[SIGQUIT]); |
489 | 0 | sigaction(SIGTERM, &sigact, &signals[SIGTERM]); |
490 | 0 | sigaction(SIGTSTP, &sigact, &signals[SIGTSTP]); |
491 | 0 | sigaction(SIGTTIN, &sigact, &signals[SIGTTIN]); |
492 | 0 | sigaction(SIGTTOU, &sigact, &signals[SIGTTOU]); |
493 | |
|
494 | 0 | std::string buffer; |
495 | | /* let's allocate a huge buffer now to prevent reallocation, |
496 | | which would leave parts of the buffer around */ |
497 | 0 | buffer.reserve(512); |
498 | |
|
499 | 0 | for (;;) { |
500 | 0 | char character = '\0'; |
501 | 0 | auto got = read(input, &character, 1); |
502 | 0 | if (got == 1 && character != '\n' && character != '\r') { |
503 | 0 | buffer.push_back(character); |
504 | 0 | } |
505 | 0 | else { |
506 | 0 | break; |
507 | 0 | } |
508 | 0 | } |
509 | |
|
510 | 0 | if (restoreTermSettings) { |
511 | 0 | tcsetattr(input, termAction, &oterm); |
512 | 0 | } |
513 | |
|
514 | 0 | for (const auto& sig : signals) { |
515 | 0 | sigaction(sig.first, &sig.second, nullptr); |
516 | 0 | } |
517 | |
|
518 | 0 | return {std::move(buffer)}; |
519 | 0 | } |