diff options
-rw-r--r-- | src/lib/rng/chacha_rng/chacha_rng.cpp | 32 | ||||
-rw-r--r-- | src/lib/rng/chacha_rng/chacha_rng.h | 2 | ||||
-rw-r--r-- | src/lib/rng/hmac_drbg/hmac_drbg.cpp | 2 | ||||
-rw-r--r-- | src/lib/rng/hmac_drbg/hmac_drbg.h | 3 | ||||
-rw-r--r-- | src/lib/rng/stateful_rng/stateful_rng.cpp | 6 | ||||
-rw-r--r-- | src/lib/rng/stateful_rng/stateful_rng.h | 24 | ||||
-rw-r--r-- | src/tests/data/rng/hmac_drbg.vec (renamed from src/tests/data/hmac_drbg.vec) | 0 | ||||
-rw-r--r-- | src/tests/test_rng.cpp | 684 | ||||
-rw-r--r-- | src/tests/test_rng.h | 41 | ||||
-rw-r--r-- | src/tests/test_rng_kat.cpp | 116 |
10 files changed, 518 insertions, 392 deletions
diff --git a/src/lib/rng/chacha_rng/chacha_rng.cpp b/src/lib/rng/chacha_rng/chacha_rng.cpp index 86c71f9fe..ad8ee9ba8 100644 --- a/src/lib/rng/chacha_rng/chacha_rng.cpp +++ b/src/lib/rng/chacha_rng/chacha_rng.cpp @@ -68,28 +68,34 @@ void ChaCha_RNG::randomize(uint8_t output[], size_t output_len) void ChaCha_RNG::randomize_with_input(uint8_t output[], size_t output_len, const uint8_t input[], size_t input_len) { - add_entropy(input, input_len); reseed_check(); + if(input_len > 0) + { + update(input, input_len); + } + clear_mem(output, output_len); m_chacha->cipher1(output, output_len); } -void ChaCha_RNG::add_entropy(const uint8_t input[], size_t input_len) +void ChaCha_RNG::update(const uint8_t input[], size_t input_len) { - if(input_len > 0) - { - m_hmac->update(input, input_len); - m_chacha->set_key(m_hmac->final()); + m_hmac->update(input, input_len); + m_chacha->set_key(m_hmac->final()); + + secure_vector<uint8_t> mac_key(m_hmac->output_length()); + m_chacha->cipher1(mac_key.data(), mac_key.size()); + m_hmac->set_key(mac_key); + } - secure_vector<uint8_t> mac_key(m_hmac->output_length()); - m_chacha->cipher1(mac_key.data(), mac_key.size()); - m_hmac->set_key(mac_key); +void ChaCha_RNG::add_entropy(const uint8_t input[], size_t input_len) + { + update(input, input_len); - if(8*input_len >= security_level()) - { - m_reseed_counter = 1; - } + if(8*input_len >= security_level()) + { + reset_reseed_counter(); } } diff --git a/src/lib/rng/chacha_rng/chacha_rng.h b/src/lib/rng/chacha_rng/chacha_rng.h index b6a763f62..7deaa2d89 100644 --- a/src/lib/rng/chacha_rng/chacha_rng.h +++ b/src/lib/rng/chacha_rng/chacha_rng.h @@ -115,6 +115,8 @@ class BOTAN_DLL ChaCha_RNG final : public Stateful_RNG size_t security_level() const override; + size_t max_number_of_bytes_per_request() const override { return 0; } + private: void update(const uint8_t input[], size_t input_len); diff --git a/src/lib/rng/hmac_drbg/hmac_drbg.cpp b/src/lib/rng/hmac_drbg/hmac_drbg.cpp index 4f19b5256..a01b761d9 100644 --- a/src/lib/rng/hmac_drbg/hmac_drbg.cpp +++ b/src/lib/rng/hmac_drbg/hmac_drbg.cpp @@ -161,7 +161,7 @@ void HMAC_DRBG::add_entropy(const uint8_t input[], size_t input_len) if(8*input_len >= security_level()) { - m_reseed_counter = 1; + reset_reseed_counter(); } } diff --git a/src/lib/rng/hmac_drbg/hmac_drbg.h b/src/lib/rng/hmac_drbg/hmac_drbg.h index 1c95cb304..4d3faa082 100644 --- a/src/lib/rng/hmac_drbg/hmac_drbg.h +++ b/src/lib/rng/hmac_drbg/hmac_drbg.h @@ -143,6 +143,9 @@ class BOTAN_DLL HMAC_DRBG final : public Stateful_RNG size_t security_level() const override; + size_t max_number_of_bytes_per_request() const override + { return m_max_number_of_bytes_per_request; } + private: void update(const uint8_t input[], size_t input_len); diff --git a/src/lib/rng/stateful_rng/stateful_rng.cpp b/src/lib/rng/stateful_rng/stateful_rng.cpp index df33a2f54..dec791793 100644 --- a/src/lib/rng/stateful_rng/stateful_rng.cpp +++ b/src/lib/rng/stateful_rng/stateful_rng.cpp @@ -32,7 +32,7 @@ void Stateful_RNG::initialize_with(const uint8_t input[], size_t len) if(8*len >= security_level()) { - m_reseed_counter = 1; + reset_reseed_counter(); } } @@ -55,7 +55,7 @@ size_t Stateful_RNG::reseed(Entropy_Sources& srcs, if(bits_collected >= security_level()) { - m_reseed_counter = 1; + reset_reseed_counter(); } return bits_collected; @@ -67,7 +67,7 @@ void Stateful_RNG::reseed_from_rng(RandomNumberGenerator& rng, size_t poll_bits) if(poll_bits >= security_level()) { - m_reseed_counter = 1; + reset_reseed_counter(); } } diff --git a/src/lib/rng/stateful_rng/stateful_rng.h b/src/lib/rng/stateful_rng/stateful_rng.h index ed51aac6a..d02be5659 100644 --- a/src/lib/rng/stateful_rng/stateful_rng.h +++ b/src/lib/rng/stateful_rng/stateful_rng.h @@ -103,15 +103,30 @@ class BOTAN_DLL Stateful_RNG : public RandomNumberGenerator */ virtual size_t security_level() const = 0; + /** + * Some DRBGs have a notion of the maximum number of bytes per + * request. Longer requests (to randomize) will be treated as + * multiple requests, and may initiate reseeding multiple times, + * depending on the values of max_number_of_bytes_per_request and + * reseed_interval(). This function returns zero if the RNG in + * question does not have such a notion. + * + * @return max number of bytes per request (or zero) + */ + virtual size_t max_number_of_bytes_per_request() const = 0; + + size_t reseed_interval() const { return m_reseed_interval; } + void clear() override; protected: - /** - * Called with lock held - */ void reseed_check(); - uint32_t last_pid() const { return m_last_pid; } + /** + * Called by a subclass to notify that a reseed has been + * successfully performed. + */ + void reset_reseed_counter() { m_reseed_counter = 1; } private: // A non-owned and possibly null pointer to shared RNG @@ -123,7 +138,6 @@ class BOTAN_DLL Stateful_RNG : public RandomNumberGenerator const size_t m_reseed_interval; uint32_t m_last_pid = 0; - protected: /* * Set to 1 after a successful seeding, then incremented. Reset * to 0 by clear() or a fork. This logic is used even if diff --git a/src/tests/data/hmac_drbg.vec b/src/tests/data/rng/hmac_drbg.vec index 6b018b0a2..6b018b0a2 100644 --- a/src/tests/data/hmac_drbg.vec +++ b/src/tests/data/rng/hmac_drbg.vec diff --git a/src/tests/test_rng.cpp b/src/tests/test_rng.cpp index db532054c..899ed0050 100644 --- a/src/tests/test_rng.cpp +++ b/src/tests/test_rng.cpp @@ -16,14 +16,14 @@ #include <botan/auto_rng.h> #endif -#if defined(BOTAN_HAS_SYSTEM_RNG) - #include <botan/system_rng.h> -#endif - #if defined(BOTAN_HAS_CHACHA_RNG) #include <botan/chacha_rng.h> #endif +#if defined(BOTAN_HAS_SYSTEM_RNG) + #include <botan/system_rng.h> +#endif + #if defined(BOTAN_HAS_RDRAND_RNG) #include <botan/rdrand_rng.h> #include <botan/cpuid.h> @@ -38,286 +38,139 @@ #include <sys/wait.h> #endif -#include <iostream> - namespace Botan_Tests { namespace { -#if defined(BOTAN_HAS_HMAC_DRBG) +#if defined(BOTAN_HAS_STATEFUL_RNG) -class HMAC_DRBG_Tests : public Text_Based_Test +class Stateful_RNG_Tests : public Test { public: - HMAC_DRBG_Tests() - : Text_Based_Test("hmac_drbg.vec", - "EntropyInput,EntropyInputReseed,Out", - "AdditionalInput1,AdditionalInput2") {} - - Test::Result run_one_test(const std::string& algo, const VarMap& vars) override + std::vector<Test::Result> run() override { - const std::vector<uint8_t> seed_input = get_req_bin(vars, "EntropyInput"); - const std::vector<uint8_t> reseed_input = get_req_bin(vars, "EntropyInputReseed"); - const std::vector<uint8_t> expected = get_req_bin(vars, "Out"); - - const std::vector<uint8_t> ad1 = get_opt_bin(vars, "AdditionalInput1"); - const std::vector<uint8_t> ad2 = get_opt_bin(vars, "AdditionalInput2"); - - Test::Result result("HMAC_DRBG(" + algo + ")"); - - auto mac = Botan::MessageAuthenticationCode::create("HMAC(" + algo + ")"); - - if(!mac) - { - result.note_missing("HMAC(" + algo + ")"); - return result; - } - - std::unique_ptr<Botan::HMAC_DRBG> rng(new Botan::HMAC_DRBG(std::move(mac))); - rng->initialize_with(seed_input.data(), seed_input.size()); - - // now reseed - rng->add_entropy(reseed_input.data(), reseed_input.size()); - - std::vector<uint8_t> out(expected.size()); - // first block is discarded - rng->randomize_with_input(out.data(), out.size(), ad1.data(), ad1.size()); - rng->randomize_with_input(out.data(), out.size(), ad2.data(), ad2.size()); - - result.test_eq("rng", out, expected); - return result; + std::vector<Test::Result> results; + results.push_back(test_reseed_kat()); + results.push_back(test_reseed()); + results.push_back(test_max_number_of_bytes_per_request()); + results.push_back(test_broken_entropy_input()); + results.push_back(test_check_nonce()); + results.push_back(test_prediction_resistance()); + results.push_back(test_fork_safety()); + results.push_back(test_randomize_with_ts_input()); + results.push_back(test_security_level()); + return results; } - }; + protected: + virtual std::string rng_name() const = 0; -BOTAN_REGISTER_TEST("hmac_drbg", HMAC_DRBG_Tests); + virtual std::unique_ptr<Botan::Stateful_RNG> create_rng( + Botan::RandomNumberGenerator* underlying_rng, + Botan::Entropy_Sources* underlying_es, + size_t reseed_interval) = 0; -class HMAC_DRBG_Unit_Tests : public Test - { - private: - class Broken_Entropy_Source : public Botan::Entropy_Source + std::unique_ptr<Botan::Stateful_RNG> make_rng(Botan::RandomNumberGenerator& underlying_rng, + size_t reseed_interval = 1024) { - public: - std::string name() const override - { - return "Broken Entropy Source"; - } - - size_t poll(Botan::RandomNumberGenerator&) override - { - throw Botan::Exception("polling not available"); - } - }; - - class Insufficient_Entropy_Source : public Botan::Entropy_Source - { - public: - std::string name() const override - { - return "Insufficient Entropy Source"; - } - - size_t poll(Botan::RandomNumberGenerator&) override - { - return 0; - } - }; + return create_rng(&underlying_rng, nullptr, reseed_interval); + } - class Request_Counting_RNG : public Botan::RandomNumberGenerator + std::unique_ptr<Botan::Stateful_RNG> make_rng(Botan::Entropy_Sources& underlying_srcs, + size_t reseed_interval = 1024) { - public: - Request_Counting_RNG() : m_randomize_count(0) {} - - bool is_seeded() const override - { - return true; - } - - void clear() override - { - m_randomize_count = 0; - } - - void randomize(uint8_t[], size_t) override - { - m_randomize_count++; - } - - void add_entropy(const uint8_t[], size_t) override {} - - std::string name() const override - { - return "Request_Counting_RNG"; - } - - size_t randomize_count() - { - return m_randomize_count; - } - - private: - size_t m_randomize_count; - }; + return create_rng(nullptr, &underlying_srcs, reseed_interval); + } - public: - Test::Result test_reseed_kat() + std::unique_ptr<Botan::Stateful_RNG> make_rng(Botan::RandomNumberGenerator& underlying_rng, + Botan::Entropy_Sources& underlying_srcs, + size_t reseed_interval = 1024) { - Test::Result result("HMAC_DRBG Reseed KAT"); - - auto mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - if(!mac) - { - result.note_missing("HMAC(SHA-256)"); - return result; - } - - Request_Counting_RNG counting_rng; - Botan::HMAC_DRBG rng(std::move(mac), counting_rng, Botan::Entropy_Sources::global_sources(), 2); - Botan::secure_vector<uint8_t> seed_input( - { - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF - }); - Botan::secure_vector<uint8_t> output_after_initialization( - { - 0x48, 0xD3, 0xB4, 0x5A, 0xAB, 0x65, 0xEF, 0x92, 0xCC, 0xFC, 0xB9, 0x42, 0x7E, 0xF2, 0x0C, 0x90, - 0x29, 0x70, 0x65, 0xEC, 0xC1, 0xB8, 0xA5, 0x25, 0xBF, 0xE4, 0xDC, 0x6F, 0xF3, 0x6D, 0x0E, 0x38 - }); - Botan::secure_vector<uint8_t> output_without_reseed( - {0xC4, 0x90, 0x04, 0x5B, 0x35, 0x4F, 0x50, 0x09, 0x68, 0x45, 0xF0, 0x4B, 0x11, 0x03, 0x58, 0xF0}); - result.test_eq("is_seeded", rng.is_seeded(), false); - - rng.initialize_with(seed_input.data(), seed_input.size()); - - Botan::secure_vector<uint8_t> out(32); - - rng.randomize(out.data(), out.size()); - result.test_eq("underlying RNG calls", counting_rng.randomize_count(), size_t(0)); - result.test_eq("out before reseed", out, output_after_initialization); + return create_rng(&underlying_rng, &underlying_srcs, reseed_interval); + } - // reseed must happen here - rng.randomize(out.data(), out.size()); - result.test_eq("underlying RNG calls", counting_rng.randomize_count(), size_t(1)); - result.test_ne("out after reseed", out, output_without_reseed); + virtual Test::Result test_reseed_kat() = 0; - return result; - } + virtual Test::Result test_security_level() = 0; + virtual Test::Result test_max_number_of_bytes_per_request() = 0; + private: Test::Result test_reseed() { - Test::Result result("HMAC_DRBG Reseed"); - - auto mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - if(!mac) - { - result.note_missing("HMAC(SHA-256)"); - return result; - } + Test::Result result(rng_name() + " Reseed"); // test reseed_interval is enforced Request_Counting_RNG counting_rng; - Botan::HMAC_DRBG rng(std::move(mac), counting_rng, 2); - rng.random_vec(7); + std::unique_ptr<Botan::Stateful_RNG> rng = make_rng(counting_rng, 2); + + rng->random_vec(7); result.test_eq("initial seeding", counting_rng.randomize_count(), 1); - rng.random_vec(9); + rng->random_vec(9); result.test_eq("still initial seed", counting_rng.randomize_count(), 1); - rng.random_vec(1); + rng->random_vec(1); result.test_eq("first reseed", counting_rng.randomize_count(), 2); - rng.random_vec(15); + rng->random_vec(15); result.test_eq("still first reseed", counting_rng.randomize_count(), 2); - rng.random_vec(15); + rng->random_vec(15); result.test_eq("second reseed", counting_rng.randomize_count(), 3); - rng.random_vec(1); + rng->random_vec(1); result.test_eq("still second reseed", counting_rng.randomize_count(), 3); - // request > max_number_of_bits_per_request, do reseeds occur? - rng.random_vec(64 * 1024 + 1); - result.test_eq("request exceeds output limit", counting_rng.randomize_count(), 4); - - rng.random_vec(9 * 64 * 1024 + 1); - result.test_eq("request exceeds output limit", counting_rng.randomize_count(), 9); - - return result; - } - - Test::Result test_max_number_of_bytes_per_request() - { - Test::Result result("HMAC_DRBG max_number_of_bytes_per_request"); - - std::string mac_string = "HMAC(SHA-256)"; - auto mac = Botan::MessageAuthenticationCode::create(mac_string); - if(!mac) - { - result.note_missing(mac_string); - return result; - } - - Request_Counting_RNG counting_rng; - - result.test_throws("HMAC_DRBG does not accept 0 for max_number_of_bytes_per_request", [&mac_string, &counting_rng ]() - { - Botan::HMAC_DRBG failing_rng(Botan::MessageAuthenticationCode::create(mac_string), counting_rng, 2, 0); - }); - - result.test_throws("HMAC_DRBG does not accept values higher than 64KB for max_number_of_bytes_per_request", [ &mac_string, - &counting_rng ]() + if(rng->max_number_of_bytes_per_request() > 0) { - Botan::HMAC_DRBG failing_rng(Botan::MessageAuthenticationCode::create(mac_string), counting_rng, 2, 64 * 1024 + 1); - }); - - // set reseed_interval to 1 so we can test that a long request is split - // into multiple, max_number_of_bytes_per_request long requests - // for each smaller request, reseed_check() calls counting_rng::randomize(), - // which we can compare with - Botan::HMAC_DRBG rng(std::move(mac), counting_rng, 1, 64); - - rng.random_vec(63); - result.test_eq("one request", counting_rng.randomize_count(), 1); - - rng.clear(); - counting_rng.clear(); - - rng.random_vec(64); - result.test_eq("one request", counting_rng.randomize_count(), 1); - - rng.clear(); - counting_rng.clear(); - - rng.random_vec(65); - result.test_eq("two requests", counting_rng.randomize_count(), 2); - - rng.clear(); - counting_rng.clear(); + // request > max_number_of_bytes_per_request, do reseeds occur? + rng->random_vec(64 * 1024 + 1); + result.test_eq("request exceeds output limit", counting_rng.randomize_count(), 4); - rng.random_vec(1025); - result.test_eq("17 requests", counting_rng.randomize_count(), 17); + rng->random_vec(9 * 64 * 1024 + 1); + result.test_eq("request exceeds output limit", counting_rng.randomize_count(), 9); + } return result; } Test::Result test_broken_entropy_input() { - Test::Result result("HMAC_DRBG Broken Entropy Input"); + Test::Result result(rng_name() + " Broken Entropy Input"); - auto mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - if(!mac) + class Broken_Entropy_Source : public Botan::Entropy_Source { - result.note_missing("HMAC(SHA-256)"); - return result; - } + public: + std::string name() const override + { + return "Broken Entropy Source"; + } + + size_t poll(Botan::RandomNumberGenerator&) override + { + throw Botan::Exception("polling not available"); + } + }; + + class Insufficient_Entropy_Source : public Botan::Entropy_Source + { + public: + std::string name() const override + { + return "Insufficient Entropy Source"; + } + + size_t poll(Botan::RandomNumberGenerator&) override + { + return 0; + } + }; // make sure no output is generated when the entropy input source is broken - const size_t reseed_interval = 1024; - // underlying_rng throws exception Botan::Null_RNG broken_entropy_input_rng; - Botan::HMAC_DRBG rng_with_broken_rng(std::move(mac), broken_entropy_input_rng, reseed_interval); + std::unique_ptr<Botan::Stateful_RNG> rng_with_broken_rng = make_rng(broken_entropy_input_rng); - result.test_throws("broken underlying rng", [&rng_with_broken_rng]() { rng_with_broken_rng.random_vec(16); }); + result.test_throws("broken underlying rng", [&rng_with_broken_rng]() { rng_with_broken_rng->random_vec(16); }); // entropy_sources throw exception std::unique_ptr<Broken_Entropy_Source> broken_entropy_source_1(new Broken_Entropy_Source()); @@ -327,75 +180,64 @@ class HMAC_DRBG_Unit_Tests : public Test broken_entropy_sources.add_source(std::move(broken_entropy_source_1)); broken_entropy_sources.add_source(std::move(broken_entropy_source_2)); - mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - Botan::HMAC_DRBG rng_with_broken_es(std::move(mac), broken_entropy_sources, reseed_interval); - result.test_throws("broken entropy sources", [&rng_with_broken_es]() { rng_with_broken_es.random_vec(16); }); + std::unique_ptr<Botan::Stateful_RNG> rng_with_broken_es = make_rng(broken_entropy_sources); + result.test_throws("broken entropy sources", [&rng_with_broken_es]() { rng_with_broken_es->random_vec(16); }); // entropy source returns insufficient entropy Botan::Entropy_Sources insufficient_entropy_sources; std::unique_ptr<Insufficient_Entropy_Source> insufficient_entropy_source(new Insufficient_Entropy_Source()); insufficient_entropy_sources.add_source(std::move(insufficient_entropy_source)); - mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - Botan::HMAC_DRBG rng_with_insufficient_es(std::move(mac), insufficient_entropy_sources, reseed_interval); - result.test_throws("insufficient entropy source", [&rng_with_insufficient_es]() { rng_with_insufficient_es.random_vec(16); }); + std::unique_ptr<Botan::Stateful_RNG> rng_with_insufficient_es = make_rng(insufficient_entropy_sources); + result.test_throws("insufficient entropy source", [&rng_with_insufficient_es]() { rng_with_insufficient_es->random_vec(16); }); // one of or both underlying_rng and entropy_sources throw exception - mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - Botan::HMAC_DRBG rng_with_broken_rng_and_es(std::move(mac), broken_entropy_input_rng, - Botan::Entropy_Sources::global_sources(), reseed_interval); - result.test_throws("broken underlying rng but good entropy sources", [&rng_with_broken_rng_and_es]() - { rng_with_broken_rng_and_es.random_vec(16); }); - - mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - Botan::HMAC_DRBG rng_with_rng_and_broken_es(std::move(mac), Test::rng(), broken_entropy_sources, reseed_interval); - result.test_throws("good underlying rng but broken entropy sources", [&rng_with_rng_and_broken_es]() - { rng_with_rng_and_broken_es.random_vec(16); }); - - mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - Botan::HMAC_DRBG rng_with_broken_rng_and_broken_es(std::move(mac), broken_entropy_input_rng, broken_entropy_sources, - reseed_interval); + + std::unique_ptr<Botan::Stateful_RNG> rng_with_broken_rng_and_good_es = + make_rng(broken_entropy_input_rng, Botan::Entropy_Sources::global_sources()); + + result.test_throws("broken underlying rng but good entropy sources", [&rng_with_broken_rng_and_good_es]() + { rng_with_broken_rng_and_good_es->random_vec(16); }); + + std::unique_ptr<Botan::Stateful_RNG> rng_with_good_rng_and_broken_es = + make_rng(Test::rng(), broken_entropy_sources); + + result.test_throws("good underlying rng but broken entropy sources", [&rng_with_good_rng_and_broken_es]() + { rng_with_good_rng_and_broken_es->random_vec(16); }); + + std::unique_ptr<Botan::Stateful_RNG> rng_with_broken_rng_and_broken_es = + make_rng(broken_entropy_input_rng, broken_entropy_sources); + result.test_throws("underlying rng and entropy sources broken", [&rng_with_broken_rng_and_broken_es]() - { rng_with_broken_rng_and_broken_es.random_vec(16); }); + { rng_with_broken_rng_and_broken_es->random_vec(16); }); return result; } Test::Result test_check_nonce() { - Test::Result result("HMAC_DRBG Nonce Check"); - - auto mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - if(!mac) - { - result.note_missing("HMAC(SHA-256)"); - return result; - } + Test::Result result(rng_name() + " Nonce Check"); - // make sure the nonce has at least 1/2*security_strength bits + // make sure the nonce has at least security_strength bits + std::unique_ptr<Botan::Stateful_RNG> rng = create_rng(nullptr, nullptr, 0); - // SHA-256 -> 256 bits security strength - for(auto nonce_size : { 0, 4, 8, 16, 31, 32, 34 }) + for(size_t nonce_size : { 0, 4, 8, 16, 31, 32, 34, 64 }) { - if(!mac) - { - mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - } + rng->clear(); + result.test_eq("not seeded", rng->is_seeded(), false); - Botan::HMAC_DRBG rng(std::move(mac)); - result.test_eq("not seeded", rng.is_seeded(), false); - std::vector<uint8_t> nonce(nonce_size); - rng.initialize_with(nonce.data(), nonce.size()); + const std::vector<uint8_t> nonce(nonce_size); + rng->initialize_with(nonce.data(), nonce.size()); - if(nonce_size < 32) + if(nonce_size < rng->security_level() / 8) { - result.test_eq("not seeded", rng.is_seeded(), false); - result.test_throws("invalid nonce size", [&rng, &nonce]() { rng.random_vec(32); }); + result.test_eq("not seeded", rng->is_seeded(), false); + result.test_throws("invalid nonce size", [&rng]() { rng->random_vec(32); }); } else { - result.test_eq("is seeded", rng.is_seeded(), true); - rng.random_vec(32); + result.test_eq("is seeded", rng->is_seeded(), true); + rng->random_vec(32); } } @@ -404,26 +246,19 @@ class HMAC_DRBG_Unit_Tests : public Test Test::Result test_prediction_resistance() { - Test::Result result("HMAC_DRBG Prediction Resistance"); - - auto mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - if(!mac) - { - result.note_missing("HMAC(SHA-256)"); - return result; - } + Test::Result result(rng_name() + " Prediction Resistance"); // set reseed_interval = 1, forcing a reseed for every RNG request Request_Counting_RNG counting_rng; - Botan::HMAC_DRBG rng(std::move(mac), counting_rng, 1); + std::unique_ptr<Botan::Stateful_RNG> rng = make_rng(counting_rng, 1); - rng.random_vec(16); + rng->random_vec(16); result.test_eq("first request", counting_rng.randomize_count(), size_t(1)); - rng.random_vec(16); + rng->random_vec(16); result.test_eq("second request", counting_rng.randomize_count(), size_t(2)); - rng.random_vec(16); + rng->random_vec(16); result.test_eq("third request", counting_rng.randomize_count(), size_t(3)); return result; @@ -431,23 +266,16 @@ class HMAC_DRBG_Unit_Tests : public Test Test::Result test_fork_safety() { - Test::Result result("HMAC_DRBG Fork Safety"); + Test::Result result(rng_name() + " Fork Safety"); #if defined(BOTAN_TARGET_OS_TYPE_IS_UNIX) - auto mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - if(!mac) - { - result.note_missing("HMAC(SHA-256)"); - return result; - } - const size_t reseed_interval = 1024; // make sure rng is reseeded after every fork Request_Counting_RNG counting_rng; - Botan::HMAC_DRBG rng(std::move(mac), counting_rng, reseed_interval); + std::unique_ptr<Botan::Stateful_RNG> rng = make_rng(counting_rng, reseed_interval); - rng.random_vec(16); + rng->random_vec(16); result.test_eq("first request", counting_rng.randomize_count(), size_t(1)); // fork and request from parent and child, both should output different sequences @@ -483,7 +311,7 @@ class HMAC_DRBG_Unit_Tests : public Test result.test_failure("Failed to read count size from child process"); } - parent_bytes = rng.random_vec(16); + parent_bytes = rng->random_vec(16); got = ::read(fd[0], &child_bytes[0], child_bytes.size()); if(got > 0) @@ -505,12 +333,12 @@ class HMAC_DRBG_Unit_Tests : public Test { // child process, send randomize_count and first output sequence back to parent ::close(fd[0]); // close read end in child - rng.randomize(&child_bytes[0], child_bytes.size()); + rng->randomize(&child_bytes[0], child_bytes.size()); count = counting_rng.randomize_count(); ssize_t written = ::write(fd[1], &count, sizeof(count)); try { - rng.randomize(&child_bytes[0], child_bytes.size()); + rng->randomize(&child_bytes[0], child_bytes.size()); } catch(std::exception& e) { @@ -525,7 +353,118 @@ class HMAC_DRBG_Unit_Tests : public Test return result; } - Test::Result test_security_level() + Test::Result test_randomize_with_ts_input() + { + Test::Result result(rng_name() + " Randomize With Timestamp Input"); + + const size_t request_bytes = 64; + const std::vector<uint8_t> seed(128); + + // check that randomize_with_ts_input() creates different output based on a timestamp + // and possibly additional data, such as process id even with identical seeds + Fixed_Output_RNG fixed_output_rng1(seed); + Fixed_Output_RNG fixed_output_rng2(seed); + + std::unique_ptr<Botan::Stateful_RNG> rng1 = make_rng(fixed_output_rng1); + std::unique_ptr<Botan::Stateful_RNG> rng2 = make_rng(fixed_output_rng2); + + Botan::secure_vector<uint8_t> output1(request_bytes); + Botan::secure_vector<uint8_t> output2(request_bytes); + + rng1->randomize(output1.data(), output1.size()); + rng2->randomize(output2.data(), output2.size()); + + result.test_eq("equal output due to same seed", output1, output2); + + rng1->randomize_with_ts_input(output1.data(), output1.size()); + rng2->randomize_with_ts_input(output2.data(), output2.size()); + + result.test_ne("output differs due to different timestamp", output1, output2); + + return result; + } + + }; + +#endif + +#if defined(BOTAN_HAS_HMAC_DRBG) && defined(BOTAN_HAS_SHA2_32) + +class HMAC_DRBG_Unit_Tests : public Stateful_RNG_Tests + { + public: + std::string rng_name() const override { return "HMAC_DRBG"; } + + std::unique_ptr<Botan::Stateful_RNG> create_rng(Botan::RandomNumberGenerator* underlying_rng, + Botan::Entropy_Sources* underlying_es, + size_t reseed_interval) override + { + std::unique_ptr<Botan::MessageAuthenticationCode> mac = + Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); + + if(underlying_rng && underlying_es) + return std::unique_ptr<Botan::Stateful_RNG>(new Botan::HMAC_DRBG(std::move(mac), *underlying_rng, *underlying_es, reseed_interval)); + else if(underlying_rng) + return std::unique_ptr<Botan::Stateful_RNG>(new Botan::HMAC_DRBG(std::move(mac), *underlying_rng, reseed_interval)); + else if(underlying_es) + return std::unique_ptr<Botan::Stateful_RNG>(new Botan::HMAC_DRBG(std::move(mac), *underlying_es, reseed_interval)); + else if(reseed_interval == 0) + return std::unique_ptr<Botan::Stateful_RNG>(new Botan::HMAC_DRBG(std::move(mac))); + else + throw Test_Error("Invalid reseed interval in HMAC_DRBG unit test"); + } + + Test::Result test_max_number_of_bytes_per_request() override + { + Test::Result result("HMAC_DRBG max_number_of_bytes_per_request"); + + const std::string mac_string = "HMAC(SHA-256)"; + + Request_Counting_RNG counting_rng; + + result.test_throws("HMAC_DRBG does not accept 0 for max_number_of_bytes_per_request", + [&mac_string, &counting_rng]() + { + Botan::HMAC_DRBG failing_rng(Botan::MessageAuthenticationCode::create(mac_string), counting_rng, 2, 0); + }); + + result.test_throws("HMAC_DRBG does not accept values higher than 64KB for max_number_of_bytes_per_request", + [&mac_string, &counting_rng]() + { + Botan::HMAC_DRBG failing_rng(Botan::MessageAuthenticationCode::create(mac_string), counting_rng, 2, 64 * 1024 + 1); + }); + + // set reseed_interval to 1 so we can test that a long request is split + // into multiple, max_number_of_bytes_per_request long requests + // for each smaller request, reseed_check() calls counting_rng::randomize(), + // which we can compare with + Botan::HMAC_DRBG rng(Botan::MessageAuthenticationCode::create(mac_string), counting_rng, 1, 64); + + rng.random_vec(63); + result.test_eq("one request", counting_rng.randomize_count(), 1); + + rng.clear(); + counting_rng.clear(); + + rng.random_vec(64); + result.test_eq("one request", counting_rng.randomize_count(), 1); + + rng.clear(); + counting_rng.clear(); + + rng.random_vec(65); + result.test_eq("two requests", counting_rng.randomize_count(), 2); + + rng.clear(); + counting_rng.clear(); + + rng.random_vec(1025); + result.test_eq("17 requests", counting_rng.randomize_count(), 17); + + return result; + } + + Test::Result test_security_level() override { Test::Result result("HMAC_DRBG Security Level"); @@ -550,104 +489,112 @@ class HMAC_DRBG_Unit_Tests : public Test return result; } - Test::Result test_randomize_with_ts_input() + Test::Result test_reseed_kat() override { - Test::Result result("HMAC_DRBG Randomize With Timestamp Input"); + Test::Result result("HMAC_DRBG Reseed KAT"); - auto mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - if(!mac) - { - result.note_missing("HMAC(SHA-256)"); - return result; - } + Request_Counting_RNG counting_rng; + std::unique_ptr<Botan::Stateful_RNG> rng = make_rng(counting_rng, 2); - const size_t reseed_interval = 1024; - const size_t request_bytes = 64; - const std::vector<uint8_t> seed(128); + const Botan::secure_vector<uint8_t> seed_input( + { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF + }); - // check that randomize_with_ts_input() creates different output based on a timestamp - // and possibly additional data, such as process id - Fixed_Output_RNG fixed_output_rng1(seed); - Botan::HMAC_DRBG rng1(std::move(mac), fixed_output_rng1, reseed_interval); - Botan::secure_vector<uint8_t> output1(request_bytes); - rng1.randomize(output1.data(), output1.size()); + result.test_eq("is_seeded", rng->is_seeded(), false); - mac = Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"); - Fixed_Output_RNG fixed_output_rng2(seed); - Botan::HMAC_DRBG rng2(std::move(mac), fixed_output_rng2, reseed_interval); - Botan::secure_vector<uint8_t> output2(request_bytes); - rng2.randomize(output2.data(), output2.size()); + rng->initialize_with(seed_input.data(), seed_input.size()); - result.test_eq("equal output due to same seed", output1, output2); + Botan::secure_vector<uint8_t> out(32); - rng1.randomize_with_ts_input(output1.data(), output1.size()); - rng2.randomize_with_ts_input(output2.data(), output2.size()); + rng->randomize(out.data(), out.size()); + result.test_eq("underlying RNG calls", counting_rng.randomize_count(), size_t(0)); + result.test_eq("out before reseed", out, "48D3B45AAB65EF92CCFCB9427EF20C90297065ECC1B8A525BFE4DC6FF36D0E38"); - result.test_ne("output differs due to different timestamp", output1, output2); + // reseed must happen here + rng->randomize(out.data(), out.size()); + result.test_eq("underlying RNG calls", counting_rng.randomize_count(), size_t(1)); + result.test_eq("out after reseed", out, "2F8FCA696832C984781123FD64F4B20C7379A25C87AB29A21C9BF468B0081CE2"); return result; } - std::vector<Test::Result> run() override - { - std::vector<Test::Result> results; - results.push_back(test_reseed_kat()); - results.push_back(test_reseed()); - results.push_back(test_max_number_of_bytes_per_request()); - results.push_back(test_broken_entropy_input()); - results.push_back(test_check_nonce()); - results.push_back(test_prediction_resistance()); - results.push_back(test_fork_safety()); - results.push_back(test_randomize_with_ts_input()); - results.push_back(test_security_level()); - return results; - } }; BOTAN_REGISTER_TEST("hmac_drbg_unit", HMAC_DRBG_Unit_Tests); #endif - #if defined(BOTAN_HAS_CHACHA_RNG) -class ChaCha_RNG_Tests : public Text_Based_Test +class ChaCha_RNG_Unit_Tests : public Stateful_RNG_Tests { public: - ChaCha_RNG_Tests() - : Text_Based_Test("rng/chacha_rng.vec", - "EntropyInput,EntropyInputReseed,Out", - "AdditionalInput1,AdditionalInput2") {} - Test::Result run_one_test(const std::string& algo, const VarMap& vars) override + std::string rng_name() const override { return "ChaCha_RNG"; } + + std::unique_ptr<Botan::Stateful_RNG> create_rng(Botan::RandomNumberGenerator* underlying_rng, + Botan::Entropy_Sources* underlying_es, + size_t reseed_interval) override + { + if(underlying_rng && underlying_es) + return std::unique_ptr<Botan::Stateful_RNG>(new Botan::ChaCha_RNG(*underlying_rng, *underlying_es, reseed_interval)); + else if(underlying_rng) + return std::unique_ptr<Botan::Stateful_RNG>(new Botan::ChaCha_RNG(*underlying_rng, reseed_interval)); + else if(underlying_es) + return std::unique_ptr<Botan::Stateful_RNG>(new Botan::ChaCha_RNG(*underlying_es, reseed_interval)); + else if(reseed_interval == 0) + return std::unique_ptr<Botan::Stateful_RNG>(new Botan::ChaCha_RNG()); + else + throw Test_Error("Invalid reseed interval in ChaCha_RNG unit test"); + } + + Test::Result test_security_level() override + { + Test::Result result("ChaCha_RNG Security Level"); + Botan::ChaCha_RNG rng; + result.test_eq("Expected security level", rng.security_level(), size_t(256)); + return result; + } + + Test::Result test_max_number_of_bytes_per_request() override + { + Test::Result result("ChaCha_RNG max_number_of_bytes_per_request"); + // ChaCha_RNG doesn't have this notion + return result; + } + + Test::Result test_reseed_kat() override { - const std::vector<uint8_t> seed_input = get_req_bin(vars, "EntropyInput"); - const std::vector<uint8_t> reseed_input = get_req_bin(vars, "EntropyInputReseed"); - const std::vector<uint8_t> expected = get_req_bin(vars, "Out"); + Test::Result result("ChaCha_RNG Reseed KAT"); - const std::vector<uint8_t> ad1 = get_opt_bin(vars, "AdditionalInput1"); - const std::vector<uint8_t> ad2 = get_opt_bin(vars, "AdditionalInput2"); + Request_Counting_RNG counting_rng; + std::unique_ptr<Botan::Stateful_RNG> rng = make_rng(counting_rng, 2); - Test::Result result("ChaCha_RNG"); + const Botan::secure_vector<uint8_t> seed_input(32); - Botan::ChaCha_RNG rng; - rng.initialize_with(seed_input.data(), seed_input.size()); + result.test_eq("is_seeded", rng->is_seeded(), false); - // now reseed - rng.add_entropy(reseed_input.data(), reseed_input.size()); + rng->initialize_with(seed_input.data(), seed_input.size()); + + Botan::secure_vector<uint8_t> out(32); + + rng->randomize(out.data(), out.size()); + result.test_eq("underlying RNG calls", counting_rng.randomize_count(), size_t(0)); + result.test_eq("out before reseed", out, "1F0E6F13429D5073B59C057C37CBE9587740A0A894D247E2596C393CE91DDC6F"); - std::vector<uint8_t> out(expected.size()); - // first block is discarded - rng.randomize_with_input(out.data(), out.size(), ad1.data(), ad1.size()); - rng.randomize_with_input(out.data(), out.size(), ad2.data(), ad2.size()); + // reseed must happen here + rng->randomize(out.data(), out.size()); + result.test_eq("underlying RNG calls", counting_rng.randomize_count(), size_t(1)); + result.test_eq("out after reseed", out, "F2CAE73F22684D5D773290B48FDCDA0E6C0661EBA0A854AFEC922832BDBB9C49"); - result.test_eq("rng", out, expected); return result; } }; -BOTAN_REGISTER_TEST("chacha_rng", ChaCha_RNG_Tests); +BOTAN_REGISTER_TEST("chacha_rng_unit", ChaCha_RNG_Unit_Tests); #endif @@ -729,12 +676,9 @@ class AutoSeeded_RNG_Tests : public Test std::vector<Test::Result> run() override { std::vector<Test::Result> results; - results.push_back(auto_rng_tests()); - return results; } - }; BOTAN_REGISTER_TEST("auto_rng_unit", AutoSeeded_RNG_Tests); diff --git a/src/tests/test_rng.h b/src/tests/test_rng.h index 1cfc3a254..6e5486440 100644 --- a/src/tests/test_rng.h +++ b/src/tests/test_rng.h @@ -194,6 +194,47 @@ class SeedCapturing_RNG : public Botan::RandomNumberGenerator size_t m_samples = 0; }; +/* +* RNG that counts the number of requests made to it, for example +* to verify that a reseed attempt was made at the expected time. +*/ +class Request_Counting_RNG : public Botan::RandomNumberGenerator + { + public: + Request_Counting_RNG() : m_randomize_count(0) {} + + size_t randomize_count() const + { + return m_randomize_count; + } + + bool is_seeded() const override + { + return true; + } + + void clear() override + { + m_randomize_count = 0; + } + + void randomize(uint8_t out[], size_t out_len) override + { + std::memset(out, 0x80, out_len); + m_randomize_count++; + } + + void add_entropy(const uint8_t[], size_t) override {} + + std::string name() const override + { + return "Request_Counting_RNG"; + } + + private: + size_t m_randomize_count; + }; + } #endif diff --git a/src/tests/test_rng_kat.cpp b/src/tests/test_rng_kat.cpp new file mode 100644 index 000000000..85d89a609 --- /dev/null +++ b/src/tests/test_rng_kat.cpp @@ -0,0 +1,116 @@ +/* +* (C) 2014,2015,2017 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" + +#if defined(BOTAN_HAS_HMAC_DRBG) + #include <botan/hmac_drbg.h> +#endif + +#if defined(BOTAN_HAS_CHACHA_RNG) + #include <botan/chacha_rng.h> +#endif + +namespace Botan_Tests { + +namespace { + +#if defined(BOTAN_HAS_HMAC_DRBG) + +class HMAC_DRBG_Tests : public Text_Based_Test + { + public: + HMAC_DRBG_Tests() + : Text_Based_Test("rng/hmac_drbg.vec", + "EntropyInput,EntropyInputReseed,Out", + "AdditionalInput1,AdditionalInput2") {} + + Test::Result run_one_test(const std::string& algo, const VarMap& vars) override + { + const std::vector<uint8_t> seed_input = get_req_bin(vars, "EntropyInput"); + const std::vector<uint8_t> reseed_input = get_req_bin(vars, "EntropyInputReseed"); + const std::vector<uint8_t> expected = get_req_bin(vars, "Out"); + + const std::vector<uint8_t> ad1 = get_opt_bin(vars, "AdditionalInput1"); + const std::vector<uint8_t> ad2 = get_opt_bin(vars, "AdditionalInput2"); + + Test::Result result("HMAC_DRBG(" + algo + ")"); + + auto mac = Botan::MessageAuthenticationCode::create("HMAC(" + algo + ")"); + + if(!mac) + { + result.note_missing("HMAC(" + algo + ")"); + return result; + } + + std::unique_ptr<Botan::HMAC_DRBG> rng(new Botan::HMAC_DRBG(std::move(mac))); + rng->initialize_with(seed_input.data(), seed_input.size()); + + // now reseed + rng->add_entropy(reseed_input.data(), reseed_input.size()); + + std::vector<uint8_t> out(expected.size()); + // first block is discarded + rng->randomize_with_input(out.data(), out.size(), ad1.data(), ad1.size()); + rng->randomize_with_input(out.data(), out.size(), ad2.data(), ad2.size()); + + result.test_eq("rng", out, expected); + return result; + } + + }; + +BOTAN_REGISTER_TEST("hmac_drbg", HMAC_DRBG_Tests); + +#endif + +#if defined(BOTAN_HAS_CHACHA_RNG) + +class ChaCha_RNG_Tests : public Text_Based_Test + { + public: + ChaCha_RNG_Tests() + : Text_Based_Test("rng/chacha_rng.vec", + "EntropyInput,EntropyInputReseed,Out", + "AdditionalInput1,AdditionalInput2") {} + + Test::Result run_one_test(const std::string&, const VarMap& vars) override + { + const std::vector<uint8_t> seed_input = get_req_bin(vars, "EntropyInput"); + const std::vector<uint8_t> reseed_input = get_req_bin(vars, "EntropyInputReseed"); + const std::vector<uint8_t> expected = get_req_bin(vars, "Out"); + + const std::vector<uint8_t> ad1 = get_opt_bin(vars, "AdditionalInput1"); + const std::vector<uint8_t> ad2 = get_opt_bin(vars, "AdditionalInput2"); + + Test::Result result("ChaCha_RNG"); + + Botan::ChaCha_RNG rng; + rng.initialize_with(seed_input.data(), seed_input.size()); + + // now reseed + rng.add_entropy(reseed_input.data(), reseed_input.size()); + + std::vector<uint8_t> out(expected.size()); + // first block is discarded + rng.randomize_with_input(out.data(), out.size(), ad1.data(), ad1.size()); + rng.randomize_with_input(out.data(), out.size(), ad2.data(), ad2.size()); + + result.test_eq("rng", out, expected); + return result; + } + + }; + +BOTAN_REGISTER_TEST("chacha_rng", ChaCha_RNG_Tests); + +#endif + +} + +} |