]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Allow hashing with a custom work factor
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 21 Jun 2021 15:53:17 +0000 (17:53 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 16 Sep 2021 12:12:27 +0000 (14:12 +0200)
pdns/credentials.cc
pdns/credentials.hh
pdns/dnsdist-console.cc
pdns/dnsdist-lua.cc
pdns/dnsdistdist/docs/reference/config.rst
pdns/test-credentials_cc.cc

index ddb23439f79043742fee989c548cda11333682e2..c83f007e03dbb13740da74663c067aeb0ad629ab 100644 (file)
@@ -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<uint64_t>(std::log2(CredentialsHolder::s_defaultWorkFactor))));
+  result.append(std::to_string(static_cast<uint64_t>(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<<hash<<endl;
     throw std::runtime_error("Invalid hashed password format, no parameters");
   }
 
@@ -290,6 +298,12 @@ bool isPasswordHashed(const std::string& password)
     return false;
   }
 
+  size_t parametersSize = parametersEnd - pwhash_prefix.size();
+  /* ln=X,p=Y,r=Z */
+  if (parametersSize < 12) {
+    return false;
+  }
+
   auto saltEnd = password.find('$', parametersEnd + 1);
   if (saltEnd == std::string::npos || saltEnd == password.size()) {
     return false;
index dd0c801e6a51c92500a0d0823dd401019e7f0d36..83b55264caff698ad52b4b28867a2a3f82e8d65f 100644 (file)
@@ -45,6 +45,7 @@ private:
 };
 
 std::string hashPassword(const std::string& password);
+std::string hashPassword(const std::string& password, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize);
 bool verifyPassword(const std::string& hash, const std::string& password);
 bool verifyPassword(const std::string& binaryHash, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize, const std::string& binaryPassword);
 bool isPasswordHashed(const std::string& password);
index 4e8be9822b1c0db4f0cf07b5712a52d77df5faa2..72e97189138edd9c8d242d63cbe9c7b6f6a7d6f6 100644 (file)
@@ -472,7 +472,7 @@ const std::vector<ConsoleKeyword> 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'"},
index b09f4ba076019d0ce93e754ba729f960a835ff11..bd646ab0ed049344721035c5f310b496926c4254 100644 (file)
@@ -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<uint64_t> workFactor) {
+    if (workFactor) {
+      return hashPassword(password, *workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize);
+    }
     return hashPassword(password);
   });
 
index 296c2397774506c940aaa6621e2b3ce75e1adea3..b6c07bbd2d16d35ae065e7ca7946abe08f5f7936 100644 (file)
@@ -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]]]])
 
index 48b27c8b847283f1e3257921c1209c956975e800..89bd6e4a227657e0bd3c585c70455cfe1e2684ff 100644 (file)
@@ -2,6 +2,7 @@
 #define BOOST_TEST_DYN_LINK
 #define BOOST_TEST_NO_MAIN
 
+#include <boost/algorithm/string.hpp>
 #include <boost/test/unit_test.hpp>
 
 #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