From d3728fb71278e411749664172c92dbfb157bbf09 Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 5 Sep 2025 23:37:27 +0300 Subject: [PATCH] feat(pbkdf2): enforce iteration limit Add configurable MAX_PBKDF2_ITERATIONS and reject too-high iteration counts. Test limit boundaries. --- include/hmac_cpp/hmac_utils.hpp | 6 ++++++ src/hmac_utils.cpp | 5 ++++- test_all.cpp | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/include/hmac_cpp/hmac_utils.hpp b/include/hmac_cpp/hmac_utils.hpp index 1539904..87a64ed 100644 --- a/include/hmac_cpp/hmac_utils.hpp +++ b/include/hmac_cpp/hmac_utils.hpp @@ -6,8 +6,14 @@ #include #include +#ifndef HMAC_CPP_MAX_PBKDF2_ITERATIONS +#define HMAC_CPP_MAX_PBKDF2_ITERATIONS 1000000u +#endif + namespace hmac_cpp { + static constexpr uint32_t MAX_PBKDF2_ITERATIONS = HMAC_CPP_MAX_PBKDF2_ITERATIONS; + /// \brief Compares two byte arrays in constant time /// \param a Pointer to first array /// \param a_len Length of the first array diff --git a/src/hmac_utils.cpp b/src/hmac_utils.cpp index b864e0a..23acc7d 100644 --- a/src/hmac_utils.cpp +++ b/src/hmac_utils.cpp @@ -40,6 +40,8 @@ namespace hmac_cpp { throw std::invalid_argument("Null pointer with non-zero length"); if (iterations < 1) throw std::invalid_argument("PBKDF2: iterations must be >= 1"); + if (iterations > MAX_PBKDF2_ITERATIONS) + throw std::invalid_argument("PBKDF2: iterations too large"); if (dk_len == 0) throw std::invalid_argument("PBKDF2: dk_len must be positive"); if (salt_len < 16) @@ -115,7 +117,8 @@ namespace hmac_cpp { (salt_len > 0 && salt_ptr == nullptr) || out_ptr == nullptr) return false; - if (iterations < 1 || dk_len == 0 || salt_len < 16) + if (iterations < 1 || dk_len == 0 || salt_len < 16 || + iterations > MAX_PBKDF2_ITERATIONS) return false; const size_t hlen = hmac_hash::SHA256::DIGEST_SIZE; diff --git a/test_all.cpp b/test_all.cpp index 1da1be3..b604b00 100644 --- a/test_all.cpp +++ b/test_all.cpp @@ -237,6 +237,13 @@ TEST(PBKDF2Validation, TooLargeDkLenThrows) { EXPECT_THROW(hmac::pbkdf2("password", salt, 1, too_large, hmac::Pbkdf2Hash::Sha1), std::invalid_argument); } +TEST(PBKDF2Validation, IterationsLimit) { + std::string salt(16, 'a'); + uint32_t limit = hmac::MAX_PBKDF2_ITERATIONS; + EXPECT_NO_THROW(hmac::pbkdf2("password", salt, limit - 1, 32, hmac::Pbkdf2Hash::Sha256)); + EXPECT_THROW(hmac::pbkdf2("password", salt, limit + 1, 32, hmac::Pbkdf2Hash::Sha256), std::invalid_argument); +} + TEST(PBKDF2Test, SHA256WithValidSalt) { auto salt = from_hex("000102030405060708090a0b0c0d0e0f"); std::string salt_str(salt.begin(), salt.end()); @@ -256,6 +263,14 @@ TEST(PBKDF2BufferApiTest, SHA256ArrayOutput) { EXPECT_TRUE(std::equal(out.begin(), out.end(), ref.begin())); } +TEST(PBKDF2BufferApiTest, IterationsLimit) { + std::string salt(16, 'a'); + std::array out{}; + uint32_t limit = hmac::MAX_PBKDF2_ITERATIONS; + EXPECT_TRUE(hmac::pbkdf2_hmac_sha256(std::string("password"), salt, limit - 1, out)); + EXPECT_FALSE(hmac::pbkdf2_hmac_sha256(std::string("password"), salt, limit + 1, out)); +} + // SHA512 vector from BoringSSL pbkdf_test.cc TEST(PBKDF2Test, BoringSSL_SHA512) { auto dk = hmac::pbkdf2("passwordPASSWORDpassword",