From: Remi Gacogne Date: Mon, 21 Jun 2021 15:53:17 +0000 (+0200) Subject: dnsdist: Allow hashing with a custom work factor X-Git-Tag: dnsdist-1.7.0-alpha1~12^2~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4ec3ff03d170bc5bce244f3f58c3adaccf3b8baa;p=thirdparty%2Fpdns.git dnsdist: Allow hashing with a custom work factor --- diff --git a/pdns/credentials.cc b/pdns/credentials.cc index ddb23439f7..c83f007e03 100644 --- a/pdns/credentials.cc +++ b/pdns/credentials.cc @@ -147,7 +147,7 @@ static std::string generateRandomSalt() #endif } -std::string hashPassword(const std::string& password) +std::string hashPassword(const std::string& password, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize) { #ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT std::string result; @@ -155,17 +155,17 @@ std::string hashPassword(const std::string& password) result.append(pwhash_prefix); result.append("ln="); - result.append(std::to_string(static_cast(std::log2(CredentialsHolder::s_defaultWorkFactor)))); + result.append(std::to_string(static_cast(std::log2(workFactor)))); result.append(",p="); - result.append(std::to_string(CredentialsHolder::s_defaultParallelFactor)); + result.append(std::to_string(parallelFactor)); result.append(",r="); - result.append(std::to_string(CredentialsHolder::s_defaultBlockSize)); + result.append(std::to_string(blockSize)); result.append("$"); auto salt = generateRandomSalt(); result.append(Base64Encode(salt)); result.append("$"); - auto out = hashPasswordInternal(password, salt, CredentialsHolder::s_defaultWorkFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize); + auto out = hashPasswordInternal(password, salt, workFactor, parallelFactor, blockSize); result.append(Base64Encode(out)); @@ -175,6 +175,15 @@ std::string hashPassword(const std::string& password) #endif } +std::string hashPassword(const std::string& password) +{ +#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT + return hashPassword(password, CredentialsHolder::s_defaultWorkFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize); +#else + throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available"); +#endif +} + bool verifyPassword(const std::string& binaryHash, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize, const std::string& binaryPassword) { #ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT @@ -191,7 +200,6 @@ static void parseHashed(const std::string& hash, std::string& salt, std::string& #ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT auto parametersEnd = hash.find('$', pwhash_prefix.size()); if (parametersEnd == std::string::npos || parametersEnd == hash.size()) { - cerr< g_consoleKeywords{ { "getTLSFrontend", true, "n", "returns the TLS frontend with index n" }, { "getTLSFrontendCount", true, "", "returns the number of DoT listeners" }, { "grepq", true, "Netmask|DNS Name|100ms|{\"::1\", \"powerdns.com\", \"100ms\"} [, n]", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" }, - { "hashPassword", true, "password", "Returns a hashed and salted version of the supplied password, usable with 'setWebserverConfig()'"}, + { "hashPassword", true, "password [, workFactor]", "Returns a hashed and salted version of the supplied password, usable with 'setWebserverConfig()'"}, { "HTTPHeaderRule", true, "name, regex", "matches DoH queries with a HTTP header 'name' whose content matches the regular expression 'regex'"}, { "HTTPPathRegexRule", true, "regex", "matches DoH queries whose HTTP path matches 'regex'"}, { "HTTPPathRule", true, "path", "matches DoH queries whose HTTP path is an exact match to 'path'"}, diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index b09f4ba076..bd646ab0ed 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -1036,7 +1036,10 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } }); - luaCtx.writeFunction("hashPassword", [](const std::string& password) { + luaCtx.writeFunction("hashPassword", [](const std::string& password, boost::optional workFactor) { + if (workFactor) { + return hashPassword(password, *workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize); + } return hashPassword(password); }); diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index 296c239777..b6c07bbd2d 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -290,13 +290,14 @@ Control Socket, Console and Webserver Webserver configuration ~~~~~~~~~~~~~~~~~~~~~~~ -.. function:: hashPassword(password) +.. function:: hashPassword(password [, workFactor]) .. versionadded:: 1.7.0 Hash the supplied password using a random salt, and returns a string that can be used with :func:`setWebserverConfig`. :param string - password: The password to hash + :param int - workFactor: The work factor to use for the hash function (currently scrypt), as a power of two. Default is 1024. .. function:: webserver(listen_address [, password[, apikey[, custom_headers[, acl]]]]) diff --git a/pdns/test-credentials_cc.cc b/pdns/test-credentials_cc.cc index 48b27c8b84..89bd6e4a22 100644 --- a/pdns/test-credentials_cc.cc +++ b/pdns/test-credentials_cc.cc @@ -2,6 +2,7 @@ #define BOOST_TEST_DYN_LINK #define BOOST_TEST_NO_MAIN +#include #include #include "config.h" @@ -28,6 +29,57 @@ BOOST_AUTO_TEST_CASE(test_CredentialsUtils) BOOST_CHECK(isPasswordHashed(hashed)); BOOST_CHECK(isPasswordHashed(sampleHash)); BOOST_CHECK(!isPasswordHashed(plaintext)); + + { + // hash password with custom parameters + auto customParams = hashPassword(plaintext, 512, 2, 16); + // check that the output is OK + BOOST_CHECK(boost::starts_with(customParams, "$scrypt$ln=9,p=2,r=16$")); + // check that we can verify the password + BOOST_CHECK(verifyPassword(customParams, plaintext)); + } + + // empty + BOOST_CHECK(!isPasswordHashed("")); + // missing leading $ + BOOST_CHECK(!isPasswordHashed("scrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=")); + // unknown algo + BOOST_CHECK(!isPasswordHashed("$tcrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=")); + // missing parameters + BOOST_CHECK(!isPasswordHashed("$scrypt$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=")); + // empty parameters + BOOST_CHECK(!isPasswordHashed("$scrypt$$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=")); + // missing r + BOOST_CHECK(!isPasswordHashed("$scrypt$ln=10,p=1$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=")); + // salt is too short + BOOST_CHECK(!isPasswordHashed("$scrypt$ln=10,p=1,r=8$dGVzdA==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=")); + // hash is too short + BOOST_CHECK(!isPasswordHashed("$scrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$c2hvcnQ=")); + // missing salt + BOOST_CHECK(!isPasswordHashed("$scrypt$ln=10,p=1,r=8$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=")); + // missing $ between the salt and hash + BOOST_CHECK(!isPasswordHashed("$scrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=")); + // no hash + BOOST_CHECK(!isPasswordHashed("$scrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$")); + // hash is too long + BOOST_CHECK(!isPasswordHashed("$scrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$dGhpcyBpcyBhIHZlcnkgbG9uZyBoYXNoLCBtdWNoIG11Y2ggbG9uZ2VyIHRoYW4gdGhlIG9uZXMgd2UgYXJlIGdlbmVyYXRpbmc=")); + + // empty r + BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=10,p=1,r=$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error); + // too many parameters + BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=10,p=1,r=8,t=1$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error); + // invalid ln + BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=A,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error); + // invalid p + BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=10,p=p,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error); + // work factor is too large + BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=16,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error); + // salt is too long + BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=10,p=1,r=8$dGhpcyBpcyBhIHZlcnkgbG9uZyBzYWx0$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error); + // invalid b64 salt + BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=", plaintext), std::runtime_error); + // invalid b64 hash + BOOST_CHECK_THROW(verifyPassword("$scrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJd", plaintext), std::runtime_error); } #endif