aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2017-08-03 07:31:03 -0400
committerJack Lloyd <[email protected]>2017-08-03 10:37:00 -0400
commitd1af646c365197de243c844d138f245d1dcac6ba (patch)
treede18e27d17c368dbcfdbe16c0d2ecdeb01ca8b06 /src
parent7edeec69f09fbed01881e93b0f45dbb038bfac73 (diff)
Refactor stateful RNG tests to cover both HMAC_DRBG and ChaCha_RNG
Diffstat (limited to 'src')
-rw-r--r--src/lib/rng/chacha_rng/chacha_rng.cpp33
-rw-r--r--src/lib/rng/chacha_rng/chacha_rng.h2
-rw-r--r--src/lib/rng/hmac_drbg/hmac_drbg.h3
-rw-r--r--src/lib/rng/stateful_rng/stateful_rng.h17
-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.cpp677
-rw-r--r--src/tests/test_rng.h41
-rw-r--r--src/tests/test_rng_kat.cpp116
8 files changed, 506 insertions, 383 deletions
diff --git a/src/lib/rng/chacha_rng/chacha_rng.cpp b/src/lib/rng/chacha_rng/chacha_rng.cpp
index 86c71f9fe..ceccfef2c 100644
--- a/src/lib/rng/chacha_rng/chacha_rng.cpp
+++ b/src/lib/rng/chacha_rng/chacha_rng.cpp
@@ -57,7 +57,6 @@ void ChaCha_RNG::clear()
Stateful_RNG::clear();
m_hmac->set_key(std::vector<uint8_t>(m_hmac->output_length(), 0x00));
- m_chacha->set_key(m_hmac->final());
}
void ChaCha_RNG::randomize(uint8_t output[], size_t output_len)
@@ -68,28 +67,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);
+ }
- if(8*input_len >= security_level())
- {
- m_reseed_counter = 1;
- }
+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;
}
}
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.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.h b/src/lib/rng/stateful_rng/stateful_rng.h
index ed51aac6a..0d59d396b 100644
--- a/src/lib/rng/stateful_rng/stateful_rng.h
+++ b/src/lib/rng/stateful_rng/stateful_rng.h
@@ -103,12 +103,23 @@ 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; }
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 470bddcdf..2a8477c30 100644
--- a/src/tests/test_rng.cpp
+++ b/src/tests/test_rng.cpp
@@ -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;
}
- };
-
-BOTAN_REGISTER_TEST("hmac_drbg", HMAC_DRBG_Tests);
+ protected:
+ virtual std::string rng_name() const = 0;
-class HMAC_DRBG_Unit_Tests : public Test
- {
- private:
- class Broken_Entropy_Source : public Botan::Entropy_Source
- {
- public:
- std::string name() const override
- {
- return "Broken Entropy Source";
- }
+ virtual std::unique_ptr<Botan::Stateful_RNG> create_rng(
+ Botan::RandomNumberGenerator* underlying_rng,
+ Botan::Entropy_Sources* underlying_es,
+ size_t reseed_interval) = 0;
- size_t poll(Botan::RandomNumberGenerator&) override
- {
- throw Botan::Exception("polling not available");
- }
- };
-
- class Insufficient_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 "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 ]()
+ if(rng->max_number_of_bytes_per_request() > 0)
{
- 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(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 { 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,107 +489,116 @@ 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
{
- 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");
+ 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");
+ }
- const std::vector<uint8_t> ad1 = get_opt_bin(vars, "AdditionalInput1");
- const std::vector<uint8_t> ad2 = get_opt_bin(vars, "AdditionalInput2");
+ 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 result("ChaCha_RNG");
+ 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;
+ }
- Botan::ChaCha_RNG rng;
- rng.initialize_with(seed_input.data(), seed_input.size());
+ Test::Result test_reseed_kat() override
+ {
+ Test::Result result("ChaCha_RNG Reseed KAT");
+
+ Request_Counting_RNG counting_rng;
+ std::unique_ptr<Botan::Stateful_RNG> rng = make_rng(counting_rng, 2);
+
+ const Botan::secure_vector<uint8_t> seed_input(32);
- // now reseed
- rng.add_entropy(reseed_input.data(), reseed_input.size());
+ result.test_eq("is_seeded", rng->is_seeded(), false);
- 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());
+ 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");
+
+ // 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
+
#if defined(BOTAN_HAS_AUTO_RNG)
class AutoSeeded_RNG_Tests : public Test
@@ -729,12 +677,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..484ae9af5
--- /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& 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("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
+
+}
+
+}