]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/credentials.cc
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
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>
43 #include "dns_random.hh"
44 #include "credentials.hh"
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 */
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 ();
59 uint64_t const CredentialsHolder :: s_defaultWorkFactor
{ 1024U }; /* N */
60 uint64_t const CredentialsHolder :: s_defaultParallelFactor
{ 1U }; /* p */
61 uint64_t const CredentialsHolder :: s_defaultBlockSize
{ 8U }; /* r */
63 SensitiveData :: SensitiveData ( std :: string
&& data
) :
64 d_data ( std :: move ( data
))
68 sodium_mlock ( d_data
. data (), d_data
. size ());
72 SensitiveData
& SensitiveData :: operator =( SensitiveData
&& rhs
) noexcept
74 d_data
= std :: move ( rhs
. d_data
);
79 SensitiveData :: SensitiveData ( size_t bytes
)
83 sodium_mlock ( d_data
. data (), d_data
. size ());
87 SensitiveData ::~ SensitiveData ()
92 void SensitiveData :: clear ()
95 sodium_munlock ( d_data
. data (), d_data
. size ());
100 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
)
102 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
103 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
);
105 throw std :: runtime_error ( "Error getting a scrypt context to hash the supplied password" );
108 if ( EVP_PKEY_derive_init ( pctx
. get ()) <= 0 ) {
109 throw std :: runtime_error ( "Error intializing the scrypt context to hash the supplied password" );
112 // OpenSSL 3.0 changed the string arg to const unsigned char*, other versions use const char *
113 #if OPENSSL_VERSION_MAJOR >= 3
114 auto passwordData
= reinterpret_cast < const char *>( password
. data ());
116 auto passwordData
= reinterpret_cast < const unsigned char *>( password
. data ());
118 if ( EVP_PKEY_CTX_set1_pbe_pass ( pctx
. get (), passwordData
, password
. size ()) <= 0 ) {
119 throw std :: runtime_error ( "Error adding the password to the scrypt context to hash the supplied password" );
122 if ( EVP_PKEY_CTX_set1_scrypt_salt ( pctx
. get (), reinterpret_cast < const unsigned char *>( salt
. data ()), salt
. size ()) <= 0 ) {
123 throw std :: runtime_error ( "Error adding the salt to the scrypt context to hash the supplied password" );
126 if ( EVP_PKEY_CTX_set_scrypt_N ( pctx
. get (), workFactor
) <= 0 ) {
127 throw std :: runtime_error ( "Error setting the work factor to the scrypt context to hash the supplied password" );
130 if ( EVP_PKEY_CTX_set_scrypt_r ( pctx
. get (), blockSize
) <= 0 ) {
131 throw std :: runtime_error ( "Error setting the block size to the scrypt context to hash the supplied password" );
134 if ( EVP_PKEY_CTX_set_scrypt_p ( pctx
. get (), parallelFactor
) <= 0 ) {
135 throw std :: runtime_error ( "Error setting the parallel factor to the scrypt context to hash the supplied password" );
139 out
. resize ( pwhash_output_size
);
140 size_t outlen
= out
. size ();
142 if ( EVP_PKEY_derive ( pctx
. get (), reinterpret_cast < unsigned char *>( out
. data ()), & outlen
) <= 0 || outlen
!= pwhash_output_size
) {
143 throw std :: runtime_error ( "Error deriving the output from the scrypt context to hash the supplied password" );
148 throw std :: runtime_error ( "Hashing support is not available" );
152 static std :: string
generateRandomSalt ()
154 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
155 /* generate a random salt */
157 salt
. resize ( pwhash_salt_size
);
159 if ( RAND_bytes ( reinterpret_cast < unsigned char *>( salt
. data ()), salt
. size ()) != 1 ) {
160 throw std :: runtime_error ( "Error while generating a salt to hash the supplied password" );
165 throw std :: runtime_error ( "Generating a salted password requires scrypt support in OpenSSL, and it is not available" );
169 std :: string
hashPassword ([[ maybe_unused
]] const std :: string
& password
, [[ maybe_unused
]] uint64_t workFactor
, [[ maybe_unused
]] uint64_t parallelFactor
, [[ maybe_unused
]] uint64_t blockSize
)
171 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
172 if ( workFactor
== 0 ) {
173 throw std :: runtime_error ( "Invalid work factor of " + std :: to_string ( workFactor
) + " passed to hashPassword()" );
177 result
. reserve ( pwhash_max_size
);
179 result
. append ( pwhash_prefix
);
180 result
. append ( "ln=" );
181 result
. append ( std :: to_string ( static_cast < uint64_t >( std :: log2 ( workFactor
))));
182 result
. append ( ",p=" );
183 result
. append ( std :: to_string ( parallelFactor
));
184 result
. append ( ",r=" );
185 result
. append ( std :: to_string ( blockSize
));
187 auto salt
= generateRandomSalt ();
188 result
. append ( Base64Encode ( salt
));
191 auto out
= hashPasswordInternal ( password
, salt
, workFactor
, parallelFactor
, blockSize
);
193 result
. append ( Base64Encode ( out
));
197 throw std :: runtime_error ( "Hashing a password requires scrypt support in OpenSSL, and it is not available" );
201 std :: string
hashPassword ([[ maybe_unused
]] const std :: string
& password
)
203 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
204 return hashPassword ( password
, CredentialsHolder :: s_defaultWorkFactor
, CredentialsHolder :: s_defaultParallelFactor
, CredentialsHolder :: s_defaultBlockSize
);
206 throw std :: runtime_error ( "Hashing a password requires scrypt support in OpenSSL, and it is not available" );
210 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
)
212 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
213 auto expected
= hashPasswordInternal ( binaryPassword
, salt
, workFactor
, parallelFactor
, blockSize
);
214 return constantTimeStringEquals ( expected
, binaryHash
);
216 throw std :: runtime_error ( "Hashing a password requires scrypt support in OpenSSL, and it is not available" );
220 /* parse a hashed password in PHC string format */
221 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
)
223 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
224 auto parametersEnd
= hash
. find ( '$' , pwhash_prefix
. size ());
225 if ( parametersEnd
== std :: string :: npos
|| parametersEnd
== hash
. size ()) {
226 throw std :: runtime_error ( "Invalid hashed password format, no parameters" );
229 auto parametersStr
= hash
. substr ( pwhash_prefix
. size (), parametersEnd
);
230 std :: vector
< std :: string
> parameters
;
231 parameters
. reserve ( 3 );
232 stringtok ( parameters
, parametersStr
, "," );
233 if ( parameters
. size () != 3 ) {
234 throw std :: runtime_error ( "Invalid hashed password format, expecting 3 parameters, got " + std :: to_string ( parameters
. size ()));
237 if (! boost :: starts_with ( parameters
. at ( 0 ), "ln=" )) {
238 throw std :: runtime_error ( "Invalid hashed password format, ln= parameter not found" );
241 if (! boost :: starts_with ( parameters
. at ( 1 ), "p=" )) {
242 throw std :: runtime_error ( "Invalid hashed password format, p= parameter not found" );
245 if (! boost :: starts_with ( parameters
. at ( 2 ), "r=" )) {
246 throw std :: runtime_error ( "Invalid hashed password format, r= parameter not found" );
249 auto saltPos
= parametersEnd
+ 1 ;
250 auto saltEnd
= hash
. find ( '$' , saltPos
);
251 if ( saltEnd
== std :: string :: npos
|| saltEnd
== hash
. size ()) {
252 throw std :: runtime_error ( "Invalid hashed password format" );
256 workFactor
= pdns :: checked_stoi
< uint64_t >( parameters
. at ( 0 ). substr ( 3 ));
257 workFactor
= static_cast < uint64_t >( 1 ) << workFactor
;
258 if ( workFactor
> pwhash_max_work_factor
) {
259 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
));
262 parallelFactor
= pdns :: checked_stoi
< uint64_t >( parameters
. at ( 1 ). substr ( 2 ));
263 blockSize
= pdns :: checked_stoi
< uint64_t >( parameters
. at ( 2 ). substr ( 2 ));
265 auto b64Salt
= hash
. substr ( saltPos
, saltEnd
- saltPos
);
266 salt
. reserve ( pwhash_salt_size
);
267 B64Decode ( b64Salt
, salt
);
269 if ( salt
. size () != pwhash_salt_size
) {
270 throw std :: runtime_error ( "Invalid salt in hashed password string" );
273 hashedPassword
. reserve ( pwhash_output_size
);
274 B64Decode ( hash
. substr ( saltEnd
+ 1 ), hashedPassword
);
276 if ( hashedPassword
. size () != pwhash_output_size
) {
277 throw std :: runtime_error ( "Invalid hash in hashed password string" );
280 catch ( const std :: exception
& e
) {
281 throw std :: runtime_error ( "Invalid hashed password format, unable to parse parameters" );
286 bool verifyPassword ( const std :: string
& hash
, [[ maybe_unused
]] const std :: string
& password
)
288 if (! isPasswordHashed ( hash
)) {
292 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
294 std :: string hashedPassword
;
295 uint64_t workFactor
= 0 ;
296 uint64_t parallelFactor
= 0 ;
297 uint64_t blockSize
= 0 ;
298 parseHashed ( hash
, salt
, hashedPassword
, workFactor
, parallelFactor
, blockSize
);
300 auto expected
= hashPasswordInternal ( password
, salt
, workFactor
, parallelFactor
, blockSize
);
302 return constantTimeStringEquals ( expected
, hashedPassword
);
304 throw std :: runtime_error ( "Verifying a hashed password requires scrypt support in OpenSSL, and it is not available" );
308 bool isPasswordHashed ([[ maybe_unused
]] const std :: string
& password
)
310 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
311 if ( password
. size () < pwhash_prefix_size
|| password
. size () > pwhash_max_size
) {
315 if (! boost :: starts_with ( password
, pwhash_prefix
)) {
319 auto parametersEnd
= password
. find ( '$' , pwhash_prefix
. size ());
320 if ( parametersEnd
== std :: string :: npos
|| parametersEnd
== password
. size ()) {
324 size_t parametersSize
= parametersEnd
- pwhash_prefix
. size ();
326 if ( parametersSize
< 12 ) {
330 auto saltEnd
= password
. find ( '$' , parametersEnd
+ 1 );
331 if ( saltEnd
== std :: string :: npos
|| saltEnd
== password
. size ()) {
335 /* the salt is base64 encoded so it has to be larger than that */
336 if (( saltEnd
- parametersEnd
- 1 ) < pwhash_salt_size
) {
340 /* the hash base64 encoded so it has to be larger than that */
341 if (( password
. size () - saltEnd
- 1 ) < pwhash_output_size
) {
351 /* if the password is in cleartext and hashing is available,
352 the hashed form will be kept in memory */
353 CredentialsHolder :: CredentialsHolder ( std :: string
&& password
, bool hashPlaintext
) :
354 d_credentials ( std :: move ( password
))
356 if ( isHashingAvailable ()) {
357 if (! isPasswordHashed ( d_credentials
. getString ())) {
359 d_salt
= generateRandomSalt ();
360 d_workFactor
= s_defaultWorkFactor
;
361 d_parallelFactor
= s_defaultParallelFactor
;
362 d_blockSize
= s_defaultBlockSize
;
363 d_credentials
= SensitiveData ( hashPasswordInternal ( d_credentials
. getString (), d_salt
, d_workFactor
, d_parallelFactor
, d_blockSize
));
370 std :: string hashedPassword
;
371 parseHashed ( d_credentials
. getString (), d_salt
, hashedPassword
, d_workFactor
, d_parallelFactor
, d_blockSize
);
372 d_credentials
= SensitiveData ( std :: move ( hashedPassword
));
377 d_fallbackHashPerturb
= dns_random_uint32 ();
378 d_fallbackHash
= burtle ( reinterpret_cast < const unsigned char *>( d_credentials
. getString (). data ()), d_credentials
. getString (). size (), d_fallbackHashPerturb
);
382 CredentialsHolder ::~ CredentialsHolder ()
384 d_fallbackHashPerturb
= 0 ;
388 bool CredentialsHolder :: matches ( const std :: string
& password
) const
391 return verifyPassword ( d_credentials
. getString (), d_salt
, d_workFactor
, d_parallelFactor
, d_blockSize
, password
);
394 uint32_t fallback
= burtle ( reinterpret_cast < const unsigned char *>( password
. data ()), password
. size (), d_fallbackHashPerturb
);
395 if ( fallback
!= d_fallbackHash
) {
399 return constantTimeStringEquals ( password
, d_credentials
. getString ());
403 bool CredentialsHolder :: isHashingAvailable ()
405 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
415 SensitiveData
CredentialsHolder :: readFromTerminal ()
418 struct termios oterm
;
419 bool restoreTermSettings
= false ;
420 int termAction
= TCSAFLUSH
;
422 termAction
|= TCSASOFT
;
425 FDWrapper
input ( open ( "/dev/tty" , O_RDONLY
));
426 if ( int ( input
) != - 1 ) {
427 if ( tcgetattr ( input
, & oterm
) == 0 ) {
428 memcpy (& term
, & oterm
, sizeof ( term
));
429 term
. c_lflag
&= ~( ECHO
| ECHONL
);
430 tcsetattr ( input
, termAction
, & term
);
431 restoreTermSettings
= true ;
435 input
= FDWrapper ( dup ( STDIN_FILENO
));
436 restoreTermSettings
= false ;
439 FDWrapper
output ( open ( "/dev/tty" , O_WRONLY
));
440 if ( int ( output
) == - 1 ) {
441 output
= FDWrapper ( dup ( STDERR_FILENO
));
444 struct std :: map
< int , struct sigaction
> signals
;
446 sigemptyset (& sa
. sa_mask
);
448 sa
. sa_handler
= []( int /* s */ ) {};
449 sigaction ( SIGALRM
, & sa
, & signals
[ SIGALRM
]);
450 sigaction ( SIGHUP
, & sa
, & signals
[ SIGHUP
]);
451 sigaction ( SIGINT
, & sa
, & signals
[ SIGINT
]);
452 sigaction ( SIGPIPE
, & sa
, & signals
[ SIGPIPE
]);
453 sigaction ( SIGQUIT
, & sa
, & signals
[ SIGQUIT
]);
454 sigaction ( SIGTERM
, & sa
, & signals
[ SIGTERM
]);
455 sigaction ( SIGTSTP
, & sa
, & signals
[ SIGTSTP
]);
456 sigaction ( SIGTTIN
, & sa
, & signals
[ SIGTTIN
]);
457 sigaction ( SIGTTOU
, & sa
, & signals
[ SIGTTOU
]);
460 /* let's allocate a huge buffer now to prevent reallocation,
461 which would leave parts of the buffer around */
466 auto got
= read ( input
, & ch
, 1 );
467 if ( got
== 1 && ch
!= ' \n ' && ch
!= ' \r ' ) {
468 buffer
. push_back ( ch
);
475 if ( restoreTermSettings
) {
476 tcsetattr ( input
, termAction
, & oterm
);
479 for ( const auto & sig
: signals
) {
480 sigaction ( sig
. first
, & sig
. second
, nullptr );
483 return SensitiveData ( std :: move ( buffer
));