diff options
author | Jack Lloyd <lloyd@randombit.net> | 2016-06-30 13:15:30 -0400 |
---|---|---|
committer | Jack Lloyd <lloyd@randombit.net> | 2016-07-17 10:43:40 -0400 |
commit | 93922f20f04058ec624f7db3c74d8aa5a3d06440 (patch) | |
tree | 81144cfacced43c68c4385683ee0c123a1987042 | |
parent | 4c5847412d41756aab738a3746666cfaffe5d4af (diff) |
Add Stateful_RNG
Handles fork checking for HMAC_RNG and HMAC_DRBG
AutoSeeded_RNG change - switch to HMAC_DRBG as default.
Start removing the io buffer from entropy poller.
Update default RNG poll bits to 256.
Fix McEliece test, was using wrong RNG API.
Update docs.
-rw-r--r-- | doc/manual/rng.rst | 133 | ||||
-rw-r--r-- | src/build-data/buildh.in | 15 | ||||
-rw-r--r-- | src/build-data/policy/bsi.txt | 1 | ||||
-rw-r--r-- | src/build-data/policy/modern.txt | 2 | ||||
-rw-r--r-- | src/cli/speed.cpp | 6 | ||||
-rw-r--r-- | src/lib/entropy/darwin_secrandom/darwin_secrandom.cpp | 6 | ||||
-rw-r--r-- | src/lib/entropy/darwin_secrandom/darwin_secrandom.h | 2 | ||||
-rw-r--r-- | src/lib/entropy/dev_random/dev_random.cpp | 75 | ||||
-rw-r--r-- | src/lib/entropy/dev_random/dev_random.h | 6 | ||||
-rw-r--r-- | src/lib/prov/tpm/tpm.h | 5 | ||||
-rw-r--r-- | src/lib/rng/auto_rng.h (renamed from src/lib/rng/auto_rng/auto_rng.h) | 14 | ||||
-rw-r--r-- | src/lib/rng/auto_rng/info.txt | 9 | ||||
-rw-r--r-- | src/lib/rng/hmac_drbg/hmac_drbg.cpp | 14 | ||||
-rw-r--r-- | src/lib/rng/hmac_drbg/hmac_drbg.h | 10 | ||||
-rw-r--r-- | src/lib/rng/hmac_rng/hmac_rng.cpp | 109 | ||||
-rw-r--r-- | src/lib/rng/hmac_rng/hmac_rng.h | 14 | ||||
-rw-r--r-- | src/lib/rng/rng.cpp | 59 | ||||
-rw-r--r-- | src/lib/rng/rng.h | 46 | ||||
-rw-r--r-- | src/tests/test_mceliece.cpp | 2 | ||||
-rw-r--r-- | src/tests/test_rng.cpp | 51 |
20 files changed, 281 insertions, 298 deletions
diff --git a/doc/manual/rng.rst b/doc/manual/rng.rst index 300570c3a..7eb229a5e 100644 --- a/doc/manual/rng.rst +++ b/doc/manual/rng.rst @@ -3,108 +3,77 @@ Random Number Generators ======================================== -The random number generators provided in Botan are meant for creating -keys, IVs, padding, nonces, and anything else that requires 'random' -data. It is important to remember that the output of these classes -will vary, even if they are supplied with the same seed (ie, two -``Randpool`` objects with similar initial states will not produce the -same output, because the value of high resolution timers is added to -the state at various points). - -To create a random number generator, instantiate a ``AutoSeeded_RNG`` -object. This object will handle choosing the right algorithms from the -set of enabled ones and doing seeding using OS specific -routines. The main service a RandomNumberGenerator provides is, of -course, random numbers: +The base class ``RandomNumberGenerator`` is in the header ``botan/rng.h``. -.. cpp:function:: byte RandomNumberGenerator::next_byte() +The major interfaces are + +.. cpp:function:: void RandomNumberGenerator::randomize(byte* output_array, size_t length) - Generates a single random byte and returns it + Places *length* random bytes into the provided buffer. -.. cpp:function:: void RandomNumberGenerator::randomize(byte* data, size_t length) +.. cpp:function:: void RandomNumberGenerator::add_entropy(const byte* data, size_t length) - Places *length* bytes into the array pointed to by *data* + Incorporates provided data into the state of the PRNG, if at all + possible. This works for most RNG types, including the system and + TPM RNGs. But if the RNG doesn't support this operation, the data is + dropped, no error is indicated. -To ensure good quality output, a PRNG needs to be seeded with truly -random data. Normally this is done for you. However it may happen that -your application has access to data that is potentially unpredictable -to an attacker. If so, use +.. cpp:function:: void RandomNumberGenerator::randomize_with_input(byte* data, size_t length, \ + const byte* ad, size_t ad_len) -.. cpp:function:: void RandomNumberGenerator::add_entropy(const byte* data, \ - size_t length) + Like randomize, but first incorporates the additional input field + into the state of the RNG. The additional input could be anything which + parameterizes this request. -which incorporates the data into the current randomness state. Don't -worry about filtering the data or doing any kind of cryptographic -preprocessing (such as hashing); the RNG objects in botan are designed -such that you can feed them any arbitrary non-random or even -maliciously chosen data - as long as at some point some of the seed -data was good the output will be secure. +.. cpp:function:: byte RandomNumberGenerator::next_byte() + Generates a single random byte and returns it. Note that calling this + function several times is much slower than calling ``randomize`` once + to produce multiple bytes at a time. -Implementation Notes +RNG Types ---------------------------------------- -Randpool -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The following RNG types are included + +HMAC_DRBG +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +HMAC DRBG is a random number generator designed by NIST and specified +in SP 800-90A. It can be instantiated with any hash function but is +typically used with SHA-256, SHA-384, or SHA-512. -``Randpool`` is the primary PRNG within Botan. In recent versions all -uses of it have been wrapped by an implementation of the X9.31 PRNG -(see below). If for some reason you should have cause to create a PRNG -instead of using the "global" one owned by the library, it would be -wise to consider the same on the grounds of general caution; while -``Randpool`` is designed with known attacks and PRNG weaknesses in -mind, it is not an standard/official PRNG. The remainder of this -section is a (fairly technical, though high-level) description of the -algorithms used in this PRNG. Unless you have a specific interest in -this subject, the rest of this section might prove somewhat -uninteresting. - -``Randpool`` has an internal state called pool, which is 512 bytes -long. This is where entropy is mixed into and extracted from. There is also a -small output buffer (called buffer), which holds the data which has already -been generated but has just not been output yet. - -It is based around a MAC and a block cipher (which are currently -HMAC(SHA-256) and AES-256). Where a specific size is mentioned, it -should be taken as a multiple of the cipher's block size. For example, -if a 256-bit block cipher were used instead of AES, all the sizes -internally would double. Every time some new output is needed, we -compute the MAC of a counter and a high resolution timer. The -resulting MAC is XORed into the output buffer (wrapping as needed), -and the output buffer is then encrypted with AES, producing 16 bytes -of output. - -After 8 blocks (or 128 bytes) have been produced, we mix the pool. To -do this, we first rekey both the MAC and the cipher; the new MAC key -is the MAC of the current pool under the old MAC key, while the new -cipher key is the MAC of the current pool under the just-chosen MAC -key. We then encrypt the entire pool in CBC mode, using the current -(unused) output buffer as the IV. We then generate a new output -buffer, using the mechanism described in the previous paragraph. - -To add randomness to the PRNG, we compute the MAC of the input and XOR -the output into the start of the pool. Then we remix the pool and -produce a new output buffer. The initial MAC operation should make it -very hard for chosen inputs to harm the security of ``Randpool``, and -as HMAC should be able to hold roughly 256 bits of state, it is -unlikely that we are wasting much input entropy (or, if we are, it -doesn't matter, because we have a very abundant supply). +HMAC DRBG seems to be the most conservative generator of the NIST +approved options. + +System_RNG +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In ``system_rng.h``, objects of ``System_RNG`` reference a single +(process global) reference to the system PRNG (/dev/urandom or +CryptGenRandom). + +AutoSeeded_RNG +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This instantiates a new instance of a userspace PRNG, seeds it with +a default entropy pool. ANSI X9.31 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +This generator is deprecated and will be removed in a future release. + ``ANSI_X931_PRNG`` is the standard issue X9.31 Appendix A.2.4 PRNG, though using AES-256 instead of 3DES as the block cipher. This PRNG implementation has been checked against official X9.31 test vectors. -Internally, the PRNG holds a pointer to another PRNG (typically -Randpool). This internal PRNG generates the key and seed used by the -X9.31 algorithm, as well as the date/time vectors. Each time an X9.31 -PRNG object receives entropy, it passes it along to the PRNG it is -holding, and then pulls out some random bits to generate a new key and -seed. This PRNG considers itself seeded as soon as the internal PRNG -is seeded. - +Internally, the PRNG holds a pointer to another RNG object. This +internal PRNG generates the key and seed used by the X9.31 algorithm, +as well as the date/time vectors. Each time an X9.31 PRNG object +receives entropy, it passes it along to the PRNG it is holding, and +then pulls out some random bits to generate a new key and seed. This +PRNG considers itself seeded as soon as the internal PRNG is seeded. Entropy Sources --------------------------------- diff --git a/src/build-data/buildh.in b/src/build-data/buildh.in index bd8cb6899..82a4ecd50 100644 --- a/src/build-data/buildh.in +++ b/src/build-data/buildh.in @@ -99,13 +99,20 @@ /* * RNGs will automatically poll the system for additional seed material -* after producing this many bytes of output. +* after producing this many bytes of output. Set to zero to disable +* automatic reseeding. */ -#define BOTAN_RNG_MAX_OUTPUT_BEFORE_RESEED 4096 -#define BOTAN_RNG_RESEED_POLL_BITS 128 +#define BOTAN_RNG_MAX_OUTPUT_BEFORE_RESEED 16384 +#define BOTAN_RNG_RESEED_POLL_BITS 256 #define BOTAN_RNG_AUTO_RESEED_TIMEOUT std::chrono::milliseconds(10) #define BOTAN_RNG_RESEED_DEFAULT_TIMEOUT std::chrono::milliseconds(50) -#define BOTAN_AUTO_RNG_DRBG_HASH_FUNCTION "SHA-384" + +/** +* Controls how AutoSeeded_RNG is instantiated +*/ +#define BOTAN_AUTO_RNG_DRBG HMAC_DRBG +#define BOTAN_AUTO_RNG_HASH "SHA-256" +#define BOTAN_AUTO_RNG_ENTROPY_TARGET 256 /* * Specifies (in order) the list of entropy sources that will be used diff --git a/src/build-data/policy/bsi.txt b/src/build-data/policy/bsi.txt index 5e3c5b921..2ae2ac3b2 100644 --- a/src/build-data/policy/bsi.txt +++ b/src/build-data/policy/bsi.txt @@ -41,7 +41,6 @@ eckcdsa ecdh # rng -auto_rng hmac_rng hmac_drbg </required> diff --git a/src/build-data/policy/modern.txt b/src/build-data/policy/modern.txt index f0b6934f0..5a8a2f126 100644 --- a/src/build-data/policy/modern.txt +++ b/src/build-data/policy/modern.txt @@ -38,8 +38,8 @@ eme_oaep emsa_pssr emsa1 -auto_rng hmac_rng +hmac_drbg ffi </required> diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp index c6149bf68..1fc0d7343 100644 --- a/src/cli/speed.cpp +++ b/src/cli/speed.cpp @@ -427,12 +427,10 @@ class Speed final : public Command #if defined(BOTAN_HAS_HMAC_DRBG) for(std::string hash : { "SHA-256", "SHA-384", "SHA-512" }) { - - auto hmac = Botan::MessageAuthenticationCode::create("HMAC(" + hash + ")"); - Botan::HMAC_DRBG hmac_drbg(hmac->clone()); + Botan::HMAC_DRBG hmac_drbg(hash); bench_rng(hmac_drbg, hmac_drbg.name(), msec, buf_size); - Botan::HMAC_RNG hmac_rng(hmac->clone(), hmac->clone()); + Botan::HMAC_RNG hmac_rng(hash); bench_rng(hmac_rng, hmac_rng.name(), msec, buf_size); } #endif diff --git a/src/lib/entropy/darwin_secrandom/darwin_secrandom.cpp b/src/lib/entropy/darwin_secrandom/darwin_secrandom.cpp index 0a6b85955..7dde17155 100644 --- a/src/lib/entropy/darwin_secrandom/darwin_secrandom.cpp +++ b/src/lib/entropy/darwin_secrandom/darwin_secrandom.cpp @@ -16,11 +16,11 @@ namespace Botan { */ void Darwin_SecRandom::poll(Entropy_Accumulator& accum) { - secure_vector<byte>& buf = accum.get_io_buf(BOTAN_SYSTEM_RNG_POLL_REQUEST); + m_io_buf.resize(BOTAN_SYSTEM_RNG_POLL_REQUEST); - if(0 == SecRandomCopyBytes(kSecRandomDefault, buf.size(), buf.data())) + if(0 == SecRandomCopyBytes(kSecRandomDefault, m_io_buf.size(), m_io_buf.data())) { - accum.add(buf.data(), buf.size(), BOTAN_ENTROPY_ESTIMATE_STRONG_RNG); + accum.add(m_io_buf.data(), m_io_buf.size(), BOTAN_ENTROPY_ESTIMATE_STRONG_RNG); } } diff --git a/src/lib/entropy/darwin_secrandom/darwin_secrandom.h b/src/lib/entropy/darwin_secrandom/darwin_secrandom.h index 09cdc208d..267d177f0 100644 --- a/src/lib/entropy/darwin_secrandom/darwin_secrandom.h +++ b/src/lib/entropy/darwin_secrandom/darwin_secrandom.h @@ -21,6 +21,8 @@ class Darwin_SecRandom final : public Entropy_Source std::string name() const override { return "darwin_secrandom"; } void poll(Entropy_Accumulator& accum) override; + private: + secure_vector<uint8_t> m_io_buf; }; } diff --git a/src/lib/entropy/dev_random/dev_random.cpp b/src/lib/entropy/dev_random/dev_random.cpp index aca161d64..ff746f34e 100644 --- a/src/lib/entropy/dev_random/dev_random.cpp +++ b/src/lib/entropy/dev_random/dev_random.cpp @@ -6,6 +6,7 @@ */ #include <botan/internal/dev_random.h> +#include <botan/exceptn.h> #include <sys/types.h> #include <sys/select.h> @@ -31,15 +32,39 @@ Device_EntropySource::Device_EntropySource(const std::vector<std::string>& fsnam const int flags = O_RDONLY | O_NONBLOCK | O_NOCTTY; + m_max_fd = 0; + for(auto fsname : fsnames) { - fd_type fd = ::open(fsname.c_str(), flags); + int fd = ::open(fsname.c_str(), flags); - if(fd >= 0 && fd < FD_SETSIZE) - m_devices.push_back(fd); - else if(fd >= 0) - ::close(fd); + if(fd > 0) + { + if(fd > FD_SETSIZE) + { + ::close(fd); + throw Exception("Open of OS RNG succeeded but fd is too large for fd_set"); + } + + m_dev_fds.push_back(fd); + m_max_fd = std::max(m_max_fd, fd); + } + else + { + /* + ENOENT or EACCES is normal as some of the named devices may not exist + on this system. But any other errno value probably indicates + either a bug in the application or file descriptor exhaustion. + */ + if(errno != ENOENT && errno != EACCES) + { + throw Exception("Opening OS RNG device failed with errno " + + std::to_string(errno)); + } + } } + + m_io_buf.resize(BOTAN_SYSTEM_RNG_POLL_REQUEST); } /** @@ -47,8 +72,11 @@ Device_EntropySource destructor: close all open devices */ Device_EntropySource::~Device_EntropySource() { - for(size_t i = 0; i != m_devices.size(); ++i) - ::close(m_devices[i]); + for(int fd : m_dev_fds) + { + // ignoring return value here, can't throw in destructor anyway + ::close(fd); + } } /** @@ -56,35 +84,36 @@ Device_EntropySource::~Device_EntropySource() */ void Device_EntropySource::poll(Entropy_Accumulator& accum) { - if(m_devices.empty()) + if(m_dev_fds.empty()) return; - fd_type max_fd = m_devices[0]; fd_set read_set; FD_ZERO(&read_set); - for(size_t i = 0; i != m_devices.size(); ++i) + + for(int dev_fd : m_dev_fds) { - FD_SET(m_devices[i], &read_set); - max_fd = std::max(m_devices[i], max_fd); + FD_SET(dev_fd, &read_set); } struct ::timeval timeout; - timeout.tv_sec = (BOTAN_SYSTEM_RNG_POLL_TIMEOUT_MS / 1000); timeout.tv_usec = (BOTAN_SYSTEM_RNG_POLL_TIMEOUT_MS % 1000) * 1000; - if(::select(max_fd + 1, &read_set, nullptr, nullptr, &timeout) < 0) - return; - - secure_vector<byte>& buf = accum.get_io_buf(BOTAN_SYSTEM_RNG_POLL_REQUEST); - - for(size_t i = 0; i != m_devices.size(); ++i) + if(::select(m_max_fd + 1, &read_set, nullptr, nullptr, &timeout) > 0) { - if(FD_ISSET(m_devices[i], &read_set)) + for(int dev_fd : m_dev_fds) { - const ssize_t got = ::read(m_devices[i], buf.data(), buf.size()); - if(got > 0) - accum.add(buf.data(), got, BOTAN_ENTROPY_ESTIMATE_STRONG_RNG); + if(FD_ISSET(dev_fd, &read_set)) + { + const ssize_t got = ::read(dev_fd, m_io_buf.data(), m_io_buf.size()); + + if(got > 0) + { + accum.add(m_io_buf.data(), + static_cast<size_t>(got), + BOTAN_ENTROPY_ESTIMATE_STRONG_RNG); + } + } } } } diff --git a/src/lib/entropy/dev_random/dev_random.h b/src/lib/entropy/dev_random/dev_random.h index 1f29b2f64..05b36f3eb 100644 --- a/src/lib/entropy/dev_random/dev_random.h +++ b/src/lib/entropy/dev_random/dev_random.h @@ -25,10 +25,12 @@ class Device_EntropySource final : public Entropy_Source void poll(Entropy_Accumulator& accum) override; Device_EntropySource(const std::vector<std::string>& fsnames); + ~Device_EntropySource(); private: - typedef int fd_type; - std::vector<fd_type> m_devices; + secure_vector<uint8_t> m_io_buf; + std::vector<int> m_dev_fds; + int m_max_fd; }; } diff --git a/src/lib/prov/tpm/tpm.h b/src/lib/prov/tpm/tpm.h index 15bc216ab..b8093518c 100644 --- a/src/lib/prov/tpm/tpm.h +++ b/src/lib/prov/tpm/tpm.h @@ -1,3 +1,4 @@ + /* * TPM 1.2 interface * (C) 2015 Jack Lloyd @@ -71,7 +72,7 @@ class BOTAN_DLL TPM_Context TSS_HTPM m_tpm; }; -class BOTAN_DLL TPM_RNG : public RandomNumberGenerator +class BOTAN_DLL TPM_RNG : public Hardware_RNG { public: TPM_RNG(TPM_Context& ctx) : m_ctx(ctx) {} @@ -90,7 +91,7 @@ class BOTAN_DLL TPM_RNG : public RandomNumberGenerator bool is_seeded() const override { return true; } - void clear() const override {} + void clear() override {} private: TPM_Context& m_ctx; diff --git a/src/lib/rng/auto_rng/auto_rng.h b/src/lib/rng/auto_rng.h index 72ea88d3e..b51390ae2 100644 --- a/src/lib/rng/auto_rng/auto_rng.h +++ b/src/lib/rng/auto_rng.h @@ -9,19 +9,20 @@ #define BOTAN_AUTO_SEEDING_RNG_H__ #include <botan/rng.h> -#include <string> namespace Botan { -class AutoSeeded_RNG : public RandomNumberGenerator +class BOTAN_DLL AutoSeeded_RNG final : public RandomNumberGenerator { public: - void randomize(byte out[], size_t len) override - { m_rng->randomize(out, len); } + void randomize(byte out[], size_t len) override; + + void randomize_with_input(byte output[], size_t output_len, + const byte input[], size_t input_len) override; bool is_seeded() const override { return m_rng->is_seeded(); } - void clear() override { m_rng->clear(); } + void clear() override { m_rng->clear(); m_counter = 0; } std::string name() const override { return m_rng->name(); } @@ -35,9 +36,10 @@ class AutoSeeded_RNG : public RandomNumberGenerator void add_entropy(const byte in[], size_t len) override { m_rng->add_entropy(in, len); } - AutoSeeded_RNG() : m_rng(RandomNumberGenerator::make_rng()) {} + AutoSeeded_RNG(size_t bytes_before_reseed = BOTAN_RNG_MAX_OUTPUT_BEFORE_RESEED); private: std::unique_ptr<RandomNumberGenerator> m_rng; + uint32_t m_counter = 0; }; } diff --git a/src/lib/rng/auto_rng/info.txt b/src/lib/rng/auto_rng/info.txt deleted file mode 100644 index 4f48f484b..000000000 --- a/src/lib/rng/auto_rng/info.txt +++ /dev/null @@ -1,9 +0,0 @@ -define AUTO_SEEDING_RNG 20131128 - -<requires> -hmac_rng -hmac -sha2_32 -sha2_64 -#dev_random|cryptoapi_rng|unix_procs|proc_walk -</requires> diff --git a/src/lib/rng/hmac_drbg/hmac_drbg.cpp b/src/lib/rng/hmac_drbg/hmac_drbg.cpp index 201a9f39b..6fdd7daf9 100644 --- a/src/lib/rng/hmac_drbg/hmac_drbg.cpp +++ b/src/lib/rng/hmac_drbg/hmac_drbg.cpp @@ -10,9 +10,14 @@ namespace Botan { -HMAC_DRBG::HMAC_DRBG(const std::string& hmac_hash) : - HMAC_DRBG(hmac_hash, BOTAN_RNG_MAX_OUTPUT_BEFORE_RESEED) - {} +HMAC_DRBG::HMAC_DRBG(MessageAuthenticationCode* hmac, + size_t max_bytes_before_reseed) : + Stateful_RNG(max_bytes_before_reseed), + m_mac(hmac) + { + m_V.resize(m_mac->output_length()); + clear(); + } HMAC_DRBG::HMAC_DRBG(const std::string& hmac_hash, size_t max_bytes_before_reseed) : @@ -27,12 +32,13 @@ HMAC_DRBG::HMAC_DRBG(const std::string& hmac_hash, } m_V.resize(m_mac->output_length()); - clear(); } void HMAC_DRBG::clear() { + Stateful_RNG::clear(); + for(size_t i = 0; i != m_V.size(); ++i) m_V[i] = 0x01; m_mac->set_key(std::vector<byte>(m_mac->output_length(), 0x00)); diff --git a/src/lib/rng/hmac_drbg/hmac_drbg.h b/src/lib/rng/hmac_drbg/hmac_drbg.h index f52ae9de1..8ee598470 100644 --- a/src/lib/rng/hmac_drbg/hmac_drbg.h +++ b/src/lib/rng/hmac_drbg/hmac_drbg.h @@ -19,10 +19,14 @@ namespace Botan { class BOTAN_DLL HMAC_DRBG final : public Stateful_RNG { public: - HMAC_DRBG(const std::string& hmac_hash); - + /** + * Initialize an HMAC_DRBG instance with the given hash function + */ HMAC_DRBG(const std::string& hmac_hash, - size_t max_bytes_before_reseed); + size_t max_bytes_before_reseed = BOTAN_RNG_MAX_OUTPUT_BEFORE_RESEED); + + HMAC_DRBG(MessageAuthenticationCode* hmac, + size_t max_bytes_before_reseed = BOTAN_RNG_MAX_OUTPUT_BEFORE_RESEED); std::string name() const override; diff --git a/src/lib/rng/hmac_rng/hmac_rng.cpp b/src/lib/rng/hmac_rng/hmac_rng.cpp index 7a9e4dbc5..410e3040a 100644 --- a/src/lib/rng/hmac_rng/hmac_rng.cpp +++ b/src/lib/rng/hmac_rng/hmac_rng.cpp @@ -9,15 +9,35 @@ #include <botan/entropy_src.h> #include <botan/internal/os_utils.h> #include <algorithm> -#include <chrono> namespace Botan { +HMAC_RNG::HMAC_RNG(const std::string& hash, size_t max_before_reseed) : + Stateful_RNG(max_before_reseed) + { + m_extractor = MAC::create("HMAC(" + hash + ")"); + if(!m_extractor) + throw Invalid_Argument("HMAC_RNG hash not found"); + + m_prf.reset(m_extractor->clone()); + + if(!m_prf->valid_keylength(m_extractor->output_length()) || + !m_extractor->valid_keylength(m_prf->output_length())) + { + throw Invalid_Argument("HMAC_RNG: Bad algo combination " + + m_extractor->name() + " and " + + m_prf->name()); + } + + this->clear(); + } + /* * HMAC_RNG Constructor */ HMAC_RNG::HMAC_RNG(MessageAuthenticationCode* extractor, MessageAuthenticationCode* prf) : + Stateful_RNG(BOTAN_RNG_MAX_OUTPUT_BEFORE_RESEED), m_extractor(extractor), m_prf(prf) { if(!m_prf->valid_keylength(m_extractor->output_length()) || @@ -33,7 +53,7 @@ HMAC_RNG::HMAC_RNG(MessageAuthenticationCode* extractor, void HMAC_RNG::clear() { - m_collected_entropy_estimate = 0; + Stateful_RNG::clear(); m_counter = 0; // First PRF inputs are all zero, as specified in section 2 @@ -71,7 +91,7 @@ void HMAC_RNG::clear() void HMAC_RNG::new_K_value(byte label) { m_prf->update(m_K); - m_prf->update_be(m_pid); + m_prf->update_be(last_pid()); m_prf->update_be(OS::get_processor_timestamp()); m_prf->update_be(OS::get_system_timestamp_ns()); m_prf->update_be(m_counter++); @@ -84,76 +104,38 @@ void HMAC_RNG::new_K_value(byte label) */ void HMAC_RNG::randomize(byte out[], size_t length) { - if(!is_seeded() || m_pid != OS::get_process_id()) - { - reseed(256); - if(!is_seeded()) - throw PRNG_Unseeded(name()); - } - - const size_t max_per_prf_iter = m_prf->output_length() / 2; - - m_output_since_reseed += length; - - if(m_output_since_reseed >= BOTAN_RNG_MAX_OUTPUT_BEFORE_RESEED) - { - reseed_with_sources(Entropy_Sources::global_sources(), - BOTAN_RNG_RESEED_POLL_BITS, - BOTAN_RNG_AUTO_RESEED_TIMEOUT); - } + reseed_check(length); - /* - HMAC KDF as described in E-t-E, using a CTXinfo of "rng" - */ while(length) { new_K_value(Running); - const size_t copied = std::min<size_t>(length, max_per_prf_iter); + const size_t copied = std::min<size_t>(length, m_prf->output_length()); copy_mem(out, m_K.data(), copied); out += copied; length -= copied; } + + new_K_value(BlockFinished); } size_t HMAC_RNG::reseed_with_sources(Entropy_Sources& srcs, size_t poll_bits, std::chrono::milliseconds timeout) { + new_K_value(Reseed); + m_extractor->update(m_K); // m_K is the PRF output + /* - Using the terminology of E-t-E, XTR is the MAC function (normally - HMAC) seeded with XTS (below) and we form SKM, the key material, by - polling as many sources as we think needed to reach our polling - goal. We then also include feedback of the current PRK so that - a bad poll doesn't wipe us out. + * This ends up calling add_entropy which provides input to the extractor */ - - typedef std::chrono::system_clock clock; - auto deadline = clock::now() + timeout; - - double bits_collected = 0; - - Entropy_Accumulator accum([&](const byte in[], size_t in_len, double entropy_estimate) { - m_extractor->update(in, in_len); - bits_collected += entropy_estimate; - return (bits_collected >= poll_bits || clock::now() > deadline); - }); - - srcs.poll(accum); + size_t bits_collected = Stateful_RNG::reseed_with_sources(srcs, poll_bits, timeout); /* - * It is necessary to feed forward poll data. Otherwise, a good poll - * (collecting a large amount of conditional entropy) followed by a - * bad one (collecting little) would be unsafe. Do this by - * generating new PRF outputs using the previous key and feeding - * them into the extractor function. + Now derive the new PRK using everything that has been fed into + the extractor, and set the PRF key to that */ - new_K_value(Reseed); - m_extractor->update(m_K); // K is the CTXinfo=reseed PRF output - - /* Now derive the new PRK using everything that has been fed into - the extractor, and set the PRF key to that */ m_prf->set_key(m_extractor->final()); // Now generate a new PRF output to use as the XTS extractor salt @@ -164,32 +146,17 @@ size_t HMAC_RNG::reseed_with_sources(Entropy_Sources& srcs, zeroise(m_K); m_counter = 0; - m_collected_entropy_estimate = - std::min<size_t>(m_collected_entropy_estimate + static_cast<size_t>(bits_collected), - m_extractor->output_length() * 8); - - m_output_since_reseed = 0; - m_pid = OS::get_process_id(); - - return static_cast<size_t>(bits_collected); - } - -bool HMAC_RNG::is_seeded() const - { - return (m_collected_entropy_estimate >= 256); + return bits_collected; } /* -* Add user-supplied entropy to the extractor input then reseed -* to incorporate it into the state +* Add user-supplied entropy to the extractor input then set remaining +* output length to for a reseed on next use. */ void HMAC_RNG::add_entropy(const byte input[], size_t length) { m_extractor->update(input, length); - - reseed_with_sources(Entropy_Sources::global_sources(), - BOTAN_RNG_RESEED_POLL_BITS, - BOTAN_RNG_RESEED_DEFAULT_TIMEOUT); + force_reseed(); } /* diff --git a/src/lib/rng/hmac_rng/hmac_rng.h b/src/lib/rng/hmac_rng/hmac_rng.h index 95ae25e39..f2f8a610d 100644 --- a/src/lib/rng/hmac_rng/hmac_rng.h +++ b/src/lib/rng/hmac_rng/hmac_rng.h @@ -24,11 +24,10 @@ namespace Botan { * Krawczyk's paper), for instance one could use HMAC(SHA-512) as the * extractor and CMAC(AES-256) as the PRF. */ -class BOTAN_DLL HMAC_RNG : public RandomNumberGenerator +class BOTAN_DLL HMAC_RNG : public Stateful_RNG { public: void randomize(byte buf[], size_t len) override; - bool is_seeded() const override; void clear() override; std::string name() const override; @@ -44,23 +43,26 @@ class BOTAN_DLL HMAC_RNG : public RandomNumberGenerator */ HMAC_RNG(MessageAuthenticationCode* extractor, MessageAuthenticationCode* prf); + + /** + * Use the specified hash for both the extractor and PRF functions + */ + HMAC_RNG(const std::string& hash, + size_t max_before_reseed = BOTAN_RNG_MAX_OUTPUT_BEFORE_RESEED); private: std::unique_ptr<MessageAuthenticationCode> m_extractor; std::unique_ptr<MessageAuthenticationCode> m_prf; enum HMAC_PRF_Label { Running, + BlockFinished, Reseed, ExtractorSeed, }; void new_K_value(byte label); - size_t m_collected_entropy_estimate = 0; - size_t m_output_since_reseed = 0; - secure_vector<byte> m_K; u32bit m_counter = 0; - u32bit m_pid = 0; }; } diff --git a/src/lib/rng/rng.cpp b/src/lib/rng/rng.cpp index 526693561..8144ac293 100644 --- a/src/lib/rng/rng.cpp +++ b/src/lib/rng/rng.cpp @@ -6,12 +6,18 @@ */ #include <botan/rng.h> -#include <botan/hmac_drbg.h> #include <botan/auto_rng.h> #include <botan/entropy_src.h> #include <botan/loadstor.h> #include <botan/internal/os_utils.h> -#include <chrono> + +#if defined(BOTAN_HAS_HMAC_DRBG) + #include <botan/hmac_drbg.h> +#endif + +#if defined(BOTAN_HAS_HMAC_RNG) + #include <botan/hmac_rng.h> +#endif namespace Botan { @@ -50,9 +56,15 @@ size_t RandomNumberGenerator::reseed_with_sources(Entropy_Sources& srcs, return bits_collected; } -Stateful_RNG::Stateful_RNG(size_t bytes_before_reseed) : - m_max_bytes_before_reseed_required(bytes_before_reseed) +Stateful_RNG::Stateful_RNG(size_t bytes_before_reseed) : m_bytes_before_reseed(bytes_before_reseed) + { + } + +void Stateful_RNG::clear() { + m_successful_initialization = false; + m_bytes_since_reseed = 0; + m_last_pid = 0; } size_t Stateful_RNG::reseed_with_sources(Entropy_Sources& srcs, @@ -81,10 +93,9 @@ void Stateful_RNG::reseed_check(size_t bytes_requested) { this->reseed(BOTAN_RNG_RESEED_POLL_BITS); } - else if(m_max_bytes_before_reseed_required > 0 && - m_bytes_since_reseed >= m_max_bytes_before_reseed_required) + else if(m_bytes_before_reseed > 0 && m_bytes_since_reseed >= m_bytes_before_reseed) { - this->reseed_with_timeout(BOTAN_RNG_AUTO_RESEED_POLL_BITS, + this->reseed_with_timeout(BOTAN_RNG_RESEED_POLL_BITS, BOTAN_RNG_AUTO_RESEED_TIMEOUT); } @@ -112,8 +123,10 @@ RandomNumberGenerator* RandomNumberGenerator::make_rng() AutoSeeded_RNG::AutoSeeded_RNG(size_t max_bytes_before_reseed) { - m_rng.reset(new HMAC_DRBG(BOTAN_AUTO_RNG_DRBG_HASH_FUNCTION, max_bytes_before_reseed)); - size_t bits = m_rng->reseed(384); + m_rng.reset(new BOTAN_AUTO_RNG_DRBG(BOTAN_AUTO_RNG_HASH, max_bytes_before_reseed)); + + size_t bits = m_rng->reseed(BOTAN_AUTO_RNG_ENTROPY_TARGET); + if(!m_rng->is_seeded()) { throw Exception("AutoSeeded_RNG failed to gather enough entropy only got " + @@ -124,24 +137,22 @@ AutoSeeded_RNG::AutoSeeded_RNG(size_t max_bytes_before_reseed) void AutoSeeded_RNG::randomize(byte output[], size_t output_len) { /* - This data is not secret so skipping a vector/secure_vector allows - avoiding an allocation. + Form additional input which is provided to the PRNG implementation + to paramaterize the KDF output. */ - typedef std::chrono::high_resolution_clock clock; - - byte nonce_buf[16] = { 0 }; - const uint32_t cur_ctr = m_counter++; - const uint32_t cur_pid = OS::get_process_id(); - const uint64_t cur_time = clock::now().time_since_epoch().count(); - - store_le(cur_ctr, nonce_buf); - store_le(cur_pid, nonce_buf + 4); - store_le(cur_time, nonce_buf + 8); + byte additional_input[24] = { 0 }; + store_le(OS::get_system_timestamp_ns(), additional_input); + store_le(OS::get_processor_timestamp(), additional_input + 8); + store_le(OS::get_process_id(), additional_input + 16); + store_le(m_counter++, additional_input + 20); - m_rng->randomize_with_input(output, output_len, - nonce_buf, sizeof(nonce_buf)); + randomize_with_input(output, output_len, additional_input, sizeof(additional_input)); + } - ++m_counter; +void AutoSeeded_RNG::randomize_with_input(byte output[], size_t output_len, + const byte ad[], size_t ad_len) + { + m_rng->randomize_with_input(output, output_len, ad, ad_len); } } diff --git a/src/lib/rng/rng.h b/src/lib/rng/rng.h index 2e08ce553..d5fae0261 100644 --- a/src/lib/rng/rng.h +++ b/src/lib/rng/rng.h @@ -154,7 +154,7 @@ class BOTAN_DLL RandomNumberGenerator * bytes have been output. * * Not implemented by RNGs which access an external RNG, such as the -* system PRNG or an hardware RNG. +* system PRNG or a hardware RNG. */ class BOTAN_DLL Stateful_RNG : public RandomNumberGenerator { @@ -182,16 +182,37 @@ class BOTAN_DLL Stateful_RNG : public RandomNumberGenerator protected: void reseed_check(size_t bytes_requested); + void clear() override; + + /** + * Mark state as requiring a reseed on next use + */ + void force_reseed() { m_bytes_since_reseed = m_bytes_before_reseed; } + + uint32_t last_pid() const { return m_last_pid; } + + mutable std::mutex m_mutex; + private: - const size_t m_max_bytes_before_reseed_required; + const size_t m_bytes_before_reseed; size_t m_bytes_since_reseed = 0; uint32_t m_last_pid = 0; bool m_successful_initialization = false; }; +/** +* Convenience typedef +*/ typedef RandomNumberGenerator RNG; /** +* Hardware RNG has no members but exists to tag hardware RNG types +*/ +class BOTAN_DLL Hardware_RNG : public RandomNumberGenerator + { + }; + +/** * Null/stub RNG - fails if you try to use it for anything * This is not generally useful except for in certain tests */ @@ -212,7 +233,6 @@ class BOTAN_DLL Null_RNG final : public RandomNumberGenerator std::string name() const override { return "Null_RNG"; } }; - /** * Wraps access to a RNG in a mutex */ @@ -225,20 +245,6 @@ class BOTAN_DLL Serialized_RNG final : public RandomNumberGenerator m_rng->randomize(out, len); } - void randomize_with_input(byte output[], size_t output_length, - const byte input[], size_t input_length) override - { - std::lock_guard<std::mutex> lock(m_mutex); - m_rng->randomize_with_input(output, output_length, - input, input_length); - } - - void add_entropy(const byte in[], size_t len) override - { - std::lock_guard<std::mutex> lock(m_mutex); - m_rng->add_entropy(in, len); - } - bool is_seeded() const override { std::lock_guard<std::mutex> lock(m_mutex); @@ -265,6 +271,12 @@ class BOTAN_DLL Serialized_RNG final : public RandomNumberGenerator return m_rng->reseed_with_sources(src, bits, msec); } + void add_entropy(const byte in[], size_t len) override + { + std::lock_guard<std::mutex> lock(m_mutex); + m_rng->add_entropy(in, len); + } + Serialized_RNG() : m_rng(RandomNumberGenerator::make_rng()) {} explicit Serialized_RNG(RandomNumberGenerator* rng) : m_rng(rng) {} private: diff --git a/src/tests/test_mceliece.cpp b/src/tests/test_mceliece.cpp index 8c0ad4564..8658bf5e6 100644 --- a/src/tests/test_mceliece.cpp +++ b/src/tests/test_mceliece.cpp @@ -77,7 +77,7 @@ class McEliece_Keygen_Encrypt_Test : public Text_Based_Test result.test_eq("private key fingerprint", hash_bytes(mce_priv.pkcs8_private_key()), fprint_priv); rng.clear(); - rng.add_entropy(encrypt_seed.data(), encrypt_seed.size()); + rng.initialize_with(encrypt_seed.data(), encrypt_seed.size()); try { diff --git a/src/tests/test_rng.cpp b/src/tests/test_rng.cpp index 7f1c1f123..d8c10bf55 100644 --- a/src/tests/test_rng.cpp +++ b/src/tests/test_rng.cpp @@ -21,37 +21,10 @@ namespace { Botan::RandomNumberGenerator* get_rng(const std::string& algo_str, const std::vector<byte>& ikm) { - class AllOnce_RNG : public Fixed_Output_RNG - { - public: - explicit AllOnce_RNG(const std::vector<byte>& in) : Fixed_Output_RNG(in) {} - - Botan::secure_vector<byte> random_vec(size_t) override - { - Botan::secure_vector<byte> vec(this->remaining()); - this->randomize(vec.data(), vec.size()); - return vec; - } - }; - const std::vector<std::string> algo_name = Botan::parse_algorithm_name(algo_str); const std::string rng_name = algo_name[0]; -#if defined(BOTAN_HAS_HMAC_DRBG) - if(rng_name == "HMAC_DRBG") - { - auto mac = Botan::MessageAuthenticationCode::create("HMAC(" + algo_name[1] + ")"); - - if(!mac) - { - return nullptr; - } - - return new Botan::HMAC_DRBG(mac.release(), new AllOnce_RNG(ikm)); - } - -#endif #if defined(BOTAN_HAS_X931_RNG) if(rng_name == "X9.31-RNG") @@ -110,7 +83,8 @@ class HMAC_DRBG_Tests : public Text_Based_Test { public: HMAC_DRBG_Tests() : Text_Based_Test("hmac_drbg.vec", - {"EntropyInput", "EntropyInputReseed", "Out"}) {} + {"EntropyInput", "EntropyInputReseed", "Out"}, + {"AdditionalInput1", "AdditionalInput2"}) {} Test::Result run_one_test(const std::string& algo, const VarMap& vars) override { @@ -118,23 +92,30 @@ class HMAC_DRBG_Tests : public Text_Based_Test const std::vector<byte> reseed_input = get_req_bin(vars, "EntropyInputReseed"); const std::vector<byte> expected = get_req_bin(vars, "Out"); - Test::Result result(algo); + const std::vector<byte> ad1 = get_opt_bin(vars, "AdditionalInput1"); + const std::vector<byte> ad2 = get_opt_bin(vars, "AdditionalInput2"); - std::unique_ptr<Botan::RandomNumberGenerator> rng(get_rng(algo, seed_input)); - if(!rng) + Test::Result result("HMAC_DRBG(" + algo + ")"); + + auto mac = Botan::MessageAuthenticationCode::create("HMAC(" + algo + ")"); + if(!mac) { - result.note_missing("RNG " + algo); + result.note_missing("HMAC(" + algo + ")"); return result; } - rng->reseed(0); // force initialization + std::unique_ptr<Botan::HMAC_DRBG> rng(new Botan::HMAC_DRBG(mac.release(), 0)); + rng->initialize_with(seed_input.data(), seed_input.size()); // now reseed rng->add_entropy(reseed_input.data(), reseed_input.size()); - rng->random_vec(expected.size()); // discard 1st block + std::vector<byte> 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", rng->random_vec(expected.size()), expected); + result.test_eq("rng", out, expected); return result; } |