• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 22061970273

16 Feb 2026 12:03PM UTC coverage: 90.044% (+0.007%) from 90.037%
22061970273

push

github

web-flow
Merge pull request #5345 from randombit/jack/min-test-rng-h

Minimize includes into test_rng.h

102336 of 113651 relevant lines covered (90.04%)

11395171.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.17
/src/tests/test_rng_behavior.cpp
1
/*
2
* (C) 2014,2015,2017 Jack Lloyd
3
* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#include "test_rng.h"
11

12
#include <botan/exceptn.h>
13
#include <botan/hex.h>
14
#include <botan/internal/target_info.h>
15
#include <cstring>
16

17
#if defined(BOTAN_HAS_STATEFUL_RNG)
18
   #include <botan/stateful_rng.h>
19
#endif
20

21
#if defined(BOTAN_HAS_HMAC_DRBG)
22
   #include <botan/hmac_drbg.h>
23
   #include <botan/mac.h>
24
#endif
25

26
#if defined(BOTAN_HAS_AUTO_RNG)
27
   #include <botan/auto_rng.h>
28
#endif
29

30
#if defined(BOTAN_HAS_CHACHA_RNG)
31
   #include <botan/chacha_rng.h>
32
#endif
33

34
#if defined(BOTAN_HAS_SYSTEM_RNG)
35
   #include <botan/system_rng.h>
36
#endif
37

38
#if defined(BOTAN_HAS_PROCESSOR_RNG)
39
   #include <botan/processor_rng.h>
40
#endif
41

42
#if defined(BOTAN_HAS_ENTROPY_SOURCE)
43
   #include <botan/entropy_src.h>
44
#endif
45

46
#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
47
   #include <sys/wait.h>
48
   #include <unistd.h>
49
#endif
50

51
namespace Botan_Tests {
52

53
namespace {
54

55
#if defined(BOTAN_HAS_STATEFUL_RNG)
56

57
class Stateful_RNG_Tests : public Test {
2✔
58
   public:
59
      std::vector<Test::Result> run() override {
2✔
60
         std::vector<Test::Result> results;
2✔
61
         results.push_back(test_reseed_kat());
4✔
62
         results.push_back(test_reseed());
4✔
63
         results.push_back(test_reseed_interval_limits());
4✔
64
         results.push_back(test_max_number_of_bytes_per_request());
4✔
65
         results.push_back(test_broken_entropy_input());
4✔
66
         results.push_back(test_check_nonce());
4✔
67
         results.push_back(test_prediction_resistance());
4✔
68
         results.push_back(test_randomize_with_ts_input());
4✔
69
         results.push_back(test_security_level());
4✔
70
         results.push_back(test_input_output_edge_cases());
4✔
71

72
         /*
73
         * This test uses the library in both parent and child processes. But
74
         * this causes a race with other threads, where if any other test thread
75
         * is holding the mlock pool mutex, it is killed after the fork. Then,
76
         * in the child, any attempt to allocate or free memory will cause a
77
         * deadlock.
78
         */
79
         if(Test::options().test_threads() == 1) {
2✔
80
            results.push_back(test_fork_safety());
×
81
         }
82

83
         return results;
2✔
84
      }
×
85

86
   protected:
87
      virtual std::string rng_name() const = 0;
88

89
      virtual std::unique_ptr<Botan::Stateful_RNG> create_rng(Botan::RandomNumberGenerator* underlying_rng,
90
                                                              Botan::Entropy_Sources* underlying_es,
91
                                                              size_t reseed_interval) = 0;
92

93
      std::unique_ptr<Botan::Stateful_RNG> make_rng(Botan::RandomNumberGenerator& underlying_rng,
14✔
94
                                                    size_t reseed_interval = 1024) {
95
         return create_rng(&underlying_rng, nullptr, reseed_interval);
14✔
96
      }
97

98
      std::unique_ptr<Botan::Stateful_RNG> make_rng(Botan::Entropy_Sources& underlying_srcs,
4✔
99
                                                    size_t reseed_interval = 1024) {
100
         return create_rng(nullptr, &underlying_srcs, reseed_interval);
4✔
101
      }
102

103
      std::unique_ptr<Botan::Stateful_RNG> make_rng(Botan::RandomNumberGenerator& underlying_rng,
6✔
104
                                                    Botan::Entropy_Sources& underlying_srcs,
105
                                                    size_t reseed_interval = 1024) {
106
         return create_rng(&underlying_rng, &underlying_srcs, reseed_interval);
6✔
107
      }
108

109
      virtual Test::Result test_reseed_kat() = 0;
110

111
      virtual Test::Result test_security_level() = 0;
112

113
      virtual Test::Result test_max_number_of_bytes_per_request() = 0;
114

115
      virtual Test::Result test_reseed_interval_limits() = 0;
116

117
   private:
118
      Test::Result test_reseed() {
2✔
119
         Test::Result result(rng_name() + " Reseed");
6✔
120

121
         // test reseed_interval is enforced
122
         Request_Counting_RNG counting_rng;
2✔
123

124
         auto rng = make_rng(counting_rng, 2);
2✔
125

126
         rng->random_vec(7);
2✔
127
         result.test_sz_eq("initial seeding", counting_rng.randomize_count(), 1);
2✔
128
         rng->random_vec(9);
2✔
129
         result.test_sz_eq("still initial seed", counting_rng.randomize_count(), 1);
2✔
130

131
         rng->random_vec(1);
2✔
132
         result.test_sz_eq("first reseed", counting_rng.randomize_count(), 2);
2✔
133
         rng->random_vec(15);
2✔
134
         result.test_sz_eq("still first reseed", counting_rng.randomize_count(), 2);
2✔
135

136
         rng->random_vec(15);
2✔
137
         result.test_sz_eq("second reseed", counting_rng.randomize_count(), 3);
2✔
138
         rng->random_vec(1);
2✔
139
         result.test_sz_eq("still second reseed", counting_rng.randomize_count(), 3);
2✔
140

141
         if(rng->max_number_of_bytes_per_request() > 0) {
2✔
142
            // request > max_number_of_bytes_per_request, do reseeds occur?
143
            rng->random_vec(64 * 1024 + 1);
1✔
144
            result.test_sz_eq("request exceeds output limit", counting_rng.randomize_count(), 4);
1✔
145

146
            rng->random_vec(9 * 64 * 1024 + 1);
1✔
147
            result.test_sz_eq("request exceeds output limit", counting_rng.randomize_count(), 9);
1✔
148
         }
149

150
         return result;
4✔
151
      }
2✔
152

153
      Test::Result test_broken_entropy_input() {
2✔
154
         Test::Result result(rng_name() + " Broken Entropy Input");
6✔
155

156
   #if defined(BOTAN_HAS_ENTROPY_SOURCE)
157
         class Broken_Entropy_Source final : public Botan::Entropy_Source {
2✔
158
            public:
159
               std::string name() const override { return "Broken Entropy Source"; }
×
160

161
               size_t poll(Botan::RandomNumberGenerator& /*rng*/) override {
4✔
162
                  throw Botan::Not_Implemented("polling not available");
4✔
163
               }
164
         };
165

166
         class Insufficient_Entropy_Source final : public Botan::Entropy_Source {
2✔
167
            public:
168
               std::string name() const override { return "Insufficient Entropy Source"; }
×
169

170
               size_t poll(Botan::RandomNumberGenerator& /*rng*/) override { return 0; }
2✔
171
         };
172
   #endif
173

174
         // make sure no output is generated when the entropy input source is broken
175

176
         // underlying_rng throws exception
177
         Botan::Null_RNG broken_entropy_input_rng;
2✔
178
         result.test_is_false("Null_RNG not seeded", broken_entropy_input_rng.is_seeded());
2✔
179
         auto rng_with_broken_rng = make_rng(broken_entropy_input_rng);
2✔
180

181
         result.test_throws("broken underlying rng", [&rng_with_broken_rng]() { rng_with_broken_rng->random_vec(16); });
4✔
182

183
   #if defined(BOTAN_HAS_ENTROPY_SOURCE)
184

185
         // entropy_sources throw exception
186
         auto broken_entropy_source_1 = std::make_unique<Broken_Entropy_Source>();
2✔
187
         auto broken_entropy_source_2 = std::make_unique<Broken_Entropy_Source>();
2✔
188

189
         Botan::Entropy_Sources broken_entropy_sources;
2✔
190
         broken_entropy_sources.add_source(std::move(broken_entropy_source_1));
2✔
191
         broken_entropy_sources.add_source(std::move(broken_entropy_source_2));
2✔
192

193
         auto rng_with_broken_es = make_rng(broken_entropy_sources);
2✔
194
         result.test_throws("broken entropy sources", [&rng_with_broken_es]() { rng_with_broken_es->random_vec(16); });
4✔
195

196
         // entropy source returns insufficient entropy
197
         Botan::Entropy_Sources insufficient_entropy_sources;
2✔
198
         auto insufficient_entropy_source = std::make_unique<Insufficient_Entropy_Source>();
2✔
199
         insufficient_entropy_sources.add_source(std::move(insufficient_entropy_source));
2✔
200

201
         auto rng_with_insufficient_es = make_rng(insufficient_entropy_sources);
2✔
202
         result.test_throws("insufficient entropy source",
2✔
203
                            [&rng_with_insufficient_es]() { rng_with_insufficient_es->random_vec(16); });
4✔
204

205
         // one of or both underlying_rng and entropy_sources throw exception
206

207
         auto rng_with_broken_rng_and_good_es =
2✔
208
            make_rng(broken_entropy_input_rng, Botan::Entropy_Sources::global_sources());
2✔
209

210
         result.test_throws("broken underlying rng but good entropy sources",
2✔
211
                            [&rng_with_broken_rng_and_good_es]() { rng_with_broken_rng_and_good_es->random_vec(16); });
4✔
212

213
         auto rng_with_good_rng_and_broken_es = make_rng(this->rng(), broken_entropy_sources);
2✔
214

215
         result.test_throws("good underlying rng but broken entropy sources",
2✔
216
                            [&rng_with_good_rng_and_broken_es]() { rng_with_good_rng_and_broken_es->random_vec(16); });
4✔
217

218
         auto rng_with_broken_rng_and_broken_es = make_rng(broken_entropy_input_rng, broken_entropy_sources);
2✔
219

220
         result.test_throws("underlying rng and entropy sources broken", [&rng_with_broken_rng_and_broken_es]() {
2✔
221
            rng_with_broken_rng_and_broken_es->random_vec(16);
2✔
222
         });
×
223
   #endif
224

225
         return result;
4✔
226
      }
12✔
227

228
      Test::Result test_check_nonce() {
2✔
229
         Test::Result result(rng_name() + " Nonce Check");
6✔
230

231
         // make sure the nonce has at least security_strength bits
232
         auto rng = create_rng(nullptr, nullptr, 0);
2✔
233

234
         for(const size_t nonce_size : {0, 4, 8, 16, 31, 32, 34, 64}) {
18✔
235
            rng->clear();
16✔
236
            result.test_is_false("not seeded", rng->is_seeded());
16✔
237

238
            const std::vector<uint8_t> nonce(nonce_size);
16✔
239
            rng->initialize_with(nonce.data(), nonce.size());
16✔
240

241
            if(nonce_size < rng->security_level() / 8) {
16✔
242
               result.test_is_false("not seeded", rng->is_seeded());
10✔
243
               result.test_throws("invalid nonce size", [&rng]() { rng->random_vec(32); });
20✔
244
            } else {
245
               result.test_is_true("is seeded", rng->is_seeded());
6✔
246
               rng->random_vec(32);
12✔
247
            }
248
         }
16✔
249

250
         return result;
2✔
251
      }
2✔
252

253
      Test::Result test_prediction_resistance() {
2✔
254
         Test::Result result(rng_name() + " Prediction Resistance");
6✔
255

256
         // set reseed_interval = 1, forcing a reseed for every RNG request
257
         Request_Counting_RNG counting_rng;
2✔
258
         auto rng = make_rng(counting_rng, 1);
2✔
259

260
         rng->random_vec(16);
2✔
261
         result.test_sz_eq("first request", counting_rng.randomize_count(), size_t(1));
2✔
262

263
         rng->random_vec(16);
2✔
264
         result.test_sz_eq("second request", counting_rng.randomize_count(), size_t(2));
2✔
265

266
         rng->random_vec(16);
2✔
267
         result.test_sz_eq("third request", counting_rng.randomize_count(), size_t(3));
2✔
268

269
         return result;
4✔
270
      }
2✔
271

272
      Test::Result test_fork_safety() {
×
273
         Test::Result result(rng_name() + " Fork Safety");
×
274

275
   #if defined(BOTAN_TARGET_OS_HAS_POSIX1)
276
         const size_t reseed_interval = 1024;
×
277

278
         // make sure rng is reseeded after every fork
279
         Request_Counting_RNG counting_rng;
×
280
         auto rng = make_rng(counting_rng, reseed_interval);
×
281

282
         rng->random_vec(16);
×
283
         result.test_sz_eq("first request", counting_rng.randomize_count(), size_t(1));
×
284

285
         // fork and request from parent and child, both should output different sequences
286
         size_t count = counting_rng.randomize_count();
×
287
         Botan::secure_vector<uint8_t> parent_bytes(16);
×
288
         Botan::secure_vector<uint8_t> child_bytes(16);
×
289
         int fd[2];
×
290
         const int rc = ::pipe(fd);
×
291
         if(rc != 0) {
×
292
            result.test_failure("failed to create pipe");
×
293
         }
294

295
         const pid_t pid = ::fork();
×
296
         if(pid == -1) {
×
297
      #if defined(BOTAN_TARGET_OS_IS_EMSCRIPTEN)
298
            result.test_note("failed to fork process");
299
      #else
300
            result.test_failure("failed to fork process");
×
301
      #endif
302
            return result;
303
         } else if(pid != 0) {
×
304
            // parent process, wait for randomize_count from child's rng
305
            ::close(fd[1]);  // close write end in parent
×
306
            ssize_t got = ::read(fd[0], &count, sizeof(count));
×
307

308
            if(got > 0) {
×
309
               result.test_sz_eq("expected bytes from child", got, sizeof(count));
×
310
               result.test_sz_eq("parent not reseeded", counting_rng.randomize_count(), 1);
×
311
               result.test_sz_eq("child reseed occurred", count, 2);
×
312
            } else {
313
               result.test_failure("Failed to read count size from child process");
×
314
            }
315

316
            parent_bytes = rng->random_vec(16);
×
317
            got = ::read(fd[0], child_bytes.data(), child_bytes.size());
×
318

319
            if(got > 0) {
×
320
               result.test_sz_eq("expected bytes from child", got, child_bytes.size());
×
321
               result.test_bin_ne("parent and child output sequences differ", parent_bytes, child_bytes);
×
322
            } else {
323
               result.test_failure("Failed to read RNG bytes from child process");
×
324
            }
325
            ::close(fd[0]);  // close read end in parent
×
326

327
            // wait for the child to exit
328
            int status = 0;
×
329
            ::waitpid(pid, &status, 0);
×
330
         } else {
331
            // child process, send randomize_count and first output sequence back to parent
332
            ::close(fd[0]);  // close read end in child
×
333
            rng->randomize(child_bytes.data(), child_bytes.size());
×
334
            count = counting_rng.randomize_count();
×
335
            [[maybe_unused]] ssize_t written = ::write(fd[1], &count, sizeof(count));
×
336
            try {
×
337
               rng->randomize(child_bytes.data(), child_bytes.size());
×
338
            } catch(std::exception& e) {
×
339
               static_cast<void>(fprintf(stderr, "%s", e.what()));  // NOLINT(*-vararg)
×
340
            }
×
341
            written = ::write(fd[1], child_bytes.data(), child_bytes.size());
×
342
            ::close(fd[1]);  // close write end in child
×
343

344
            /*
345
            * We can't call exit because it causes the mlock pool to be freed (#602)
346
            * We can't call _exit because it makes valgrind think we leaked memory.
347
            * So instead we execute something that will return 0 for us.
348
            */
349
            ::execl("/bin/true", "true", NULL);  // NOLINT(*-vararg)
×
350
            ::_exit(0);                          // just in case /bin/true isn't available (sandbox?)
×
351
         }
352
   #endif
353
         return result;
×
354
      }
×
355

356
      Test::Result test_randomize_with_ts_input() {
2✔
357
         Test::Result result(rng_name() + " Randomize With Timestamp Input");
6✔
358

359
         const size_t request_bytes = 64;
2✔
360
         const std::vector<uint8_t> seed(128);
2✔
361

362
         // check that randomize_with_ts_input() creates different output based on a timestamp
363
         // and possibly additional data, such as process id even with identical seeds
364
         Fixed_Output_RNG fixed_output_rng1(seed);
2✔
365
         Fixed_Output_RNG fixed_output_rng2(seed);
2✔
366

367
         auto rng1 = make_rng(fixed_output_rng1);
2✔
368
         auto rng2 = make_rng(fixed_output_rng2);
2✔
369

370
         Botan::secure_vector<uint8_t> output1(request_bytes);
2✔
371
         Botan::secure_vector<uint8_t> output2(request_bytes);
2✔
372

373
         rng1->randomize(output1.data(), output1.size());
2✔
374
         rng2->randomize(output2.data(), output2.size());
2✔
375

376
         result.test_bin_eq("equal output due to same seed", output1, output2);
2✔
377

378
         rng1->randomize_with_ts_input(output1.data(), output1.size());
2✔
379
         rng2->randomize_with_ts_input(output2.data(), output2.size());
2✔
380

381
         result.test_bin_ne("output differs due to different timestamp", output1, output2);
2✔
382

383
         return result;
4✔
384
      }
8✔
385

386
      Test::Result test_input_output_edge_cases() {
2✔
387
         Test::Result result(rng_name() + " randomize");
6✔
388

389
         const std::vector<uint8_t> seed(128);
2✔
390
         Fixed_Output_RNG fixed_output_rng(seed);
2✔
391

392
         auto rng = make_rng(fixed_output_rng);
2✔
393

394
         for(size_t i = 0; i != 4096; ++i) {
8,194✔
395
            std::vector<uint8_t> buf(i);
8,192✔
396
            rng->randomize(buf.data(), buf.size());
8,192✔
397
            rng->add_entropy(buf.data(), buf.size());
8,192✔
398

399
            result.test_success("RNG accepted input and output length");
8,192✔
400
         }
8,192✔
401

402
         return result;
4✔
403
      }
2✔
404
};
405

406
#endif
407

408
#if defined(BOTAN_HAS_HMAC_DRBG) && defined(BOTAN_HAS_SHA2_32)
409

410
class HMAC_DRBG_Unit_Tests final : public Stateful_RNG_Tests {
1✔
411
   public:
412
      std::string rng_name() const override { return "HMAC_DRBG"; }
6✔
413

414
      std::unique_ptr<Botan::Stateful_RNG> create_rng(Botan::RandomNumberGenerator* underlying_rng,
13✔
415
                                                      Botan::Entropy_Sources* underlying_es,
416
                                                      size_t reseed_interval) override {
417
         std::unique_ptr<Botan::MessageAuthenticationCode> mac =
13✔
418
            Botan::MessageAuthenticationCode::create("HMAC(SHA-256)");
13✔
419

420
         if(underlying_rng != nullptr && underlying_es != nullptr) {
13✔
421
            return std::make_unique<Botan::HMAC_DRBG>(std::move(mac), *underlying_rng, *underlying_es, reseed_interval);
3✔
422
         } else if(underlying_rng != nullptr) {
10✔
423
            return std::make_unique<Botan::HMAC_DRBG>(std::move(mac), *underlying_rng, reseed_interval);
7✔
424
         } else if(underlying_es != nullptr) {
3✔
425
            return std::make_unique<Botan::HMAC_DRBG>(std::move(mac), *underlying_es, reseed_interval);
2✔
426
         } else if(reseed_interval == 0) {
1✔
427
            return std::make_unique<Botan::HMAC_DRBG>(std::move(mac));
1✔
428
         } else {
429
            throw Test_Error("Invalid reseed interval in HMAC_DRBG unit test");
×
430
         }
431
      }
13✔
432

433
      Test::Result test_max_number_of_bytes_per_request() override {
1✔
434
         Test::Result result("HMAC_DRBG max_number_of_bytes_per_request");
1✔
435

436
         const std::string mac_string = "HMAC(SHA-256)";
1✔
437

438
         Request_Counting_RNG counting_rng;
1✔
439

440
         result.test_throws("HMAC_DRBG does not accept 0 for max_number_of_bytes_per_request",
1✔
441
                            [&mac_string, &counting_rng]() {
1✔
442
                               const Botan::HMAC_DRBG failing_rng(
1✔
443
                                  Botan::MessageAuthenticationCode::create(mac_string), counting_rng, 2, 0);
1✔
444
                            });
×
445

446
         result.test_throws("HMAC_DRBG does not accept values higher than 64KB for max_number_of_bytes_per_request",
1✔
447
                            [&mac_string, &counting_rng]() {
1✔
448
                               const Botan::HMAC_DRBG failing_rng(
1✔
449
                                  Botan::MessageAuthenticationCode::create(mac_string), counting_rng, 2, 64 * 1024 + 1);
1✔
450
                            });
×
451

452
         // set reseed_interval to 1 so we can test that a long request is split
453
         // into multiple, max_number_of_bytes_per_request long requests
454
         // for each smaller request, reseed_check() calls counting_rng::randomize(),
455
         // which we can compare with
456
         Botan::HMAC_DRBG rng(Botan::MessageAuthenticationCode::create(mac_string), counting_rng, 1, 64);
1✔
457

458
         rng.random_vec(63);
1✔
459
         result.test_sz_eq("one request", counting_rng.randomize_count(), 1);
1✔
460

461
         rng.clear();
1✔
462
         counting_rng.clear();
1✔
463

464
         rng.random_vec(64);
1✔
465
         result.test_sz_eq("one request", counting_rng.randomize_count(), 1);
1✔
466

467
         rng.clear();
1✔
468
         counting_rng.clear();
1✔
469

470
         rng.random_vec(65);
1✔
471
         result.test_sz_eq("two requests", counting_rng.randomize_count(), 2);
1✔
472

473
         rng.clear();
1✔
474
         counting_rng.clear();
1✔
475

476
         rng.random_vec(1025);
1✔
477
         result.test_sz_eq("17 requests", counting_rng.randomize_count(), 17);
1✔
478

479
         return result;
2✔
480
      }
1✔
481

482
      Test::Result test_reseed_interval_limits() override {
1✔
483
         Test::Result result("HMAC_DRBG reseed_interval limits");
1✔
484

485
         const std::string mac_string = "HMAC(SHA-256)";
1✔
486

487
         Request_Counting_RNG counting_rng;
1✔
488

489
         result.test_throws("HMAC_DRBG does not accept 0 for reseed_interval", [&mac_string, &counting_rng]() {
1✔
490
            const Botan::HMAC_DRBG failing_rng(Botan::MessageAuthenticationCode::create(mac_string), counting_rng, 0);
1✔
491
         });
×
492

493
         result.test_throws("HMAC_DRBG does not accept values higher than 2^24 for reseed_interval",
1✔
494
                            [&mac_string, &counting_rng]() {
1✔
495
                               const Botan::HMAC_DRBG failing_rng(Botan::MessageAuthenticationCode::create(mac_string),
2✔
496
                                                                  counting_rng,
1✔
497
                                                                  (static_cast<size_t>(1) << 24) + 1);
1✔
498
                            });
×
499

500
         return result;
1✔
501
      }
1✔
502

503
      Test::Result test_security_level() override {
1✔
504
         Test::Result result("HMAC_DRBG Security Level");
1✔
505

506
         std::vector<std::string> approved_hash_fns{"SHA-1", "SHA-224", "SHA-256", "SHA-512/256", "SHA-384", "SHA-512"};
7✔
507
         std::vector<uint32_t> security_strengths{128, 192, 256, 256, 256, 256};
2✔
508

509
         for(size_t i = 0; i < approved_hash_fns.size(); ++i) {
7✔
510
            const auto& hash_fn = approved_hash_fns[i];
6✔
511
            const size_t expected_security_level = security_strengths[i];
6✔
512

513
            const std::string mac_name = "HMAC(" + hash_fn + ")";
12✔
514
            auto mac = Botan::MessageAuthenticationCode::create(mac_name);
6✔
515
            if(!mac) {
6✔
516
               result.note_missing(mac_name);
1✔
517
               continue;
1✔
518
            }
519

520
            const Botan::HMAC_DRBG rng(std::move(mac));
5✔
521
            result.test_sz_eq(hash_fn + " security level", rng.security_level(), expected_security_level);
5✔
522
         }
6✔
523

524
         return result;
1✔
525
      }
3✔
526

527
      Test::Result test_reseed_kat() override {
1✔
528
         Test::Result result("HMAC_DRBG Reseed KAT");
1✔
529

530
         Request_Counting_RNG counting_rng;
1✔
531
         auto rng = make_rng(counting_rng, 2);
1✔
532

533
         const Botan::secure_vector<uint8_t> seed_input(
1✔
534
            {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
535
             0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF});
1✔
536

537
         result.test_is_false("is_seeded", rng->is_seeded());
1✔
538

539
         rng->initialize_with(seed_input.data(), seed_input.size());
1✔
540

541
         Botan::secure_vector<uint8_t> out(32);
1✔
542

543
         rng->randomize(out.data(), out.size());
1✔
544
         result.test_sz_eq("underlying RNG calls", counting_rng.randomize_count(), size_t(0));
1✔
545
         result.test_bin_eq(
1✔
546
            "out before reseed", out, "48D3B45AAB65EF92CCFCB9427EF20C90297065ECC1B8A525BFE4DC6FF36D0E38");
547

548
         // reseed must happen here
549
         rng->randomize(out.data(), out.size());
1✔
550
         result.test_sz_eq("underlying RNG calls", counting_rng.randomize_count(), size_t(1));
1✔
551
         result.test_bin_eq(
1✔
552
            "out after reseed", out, "2F8FCA696832C984781123FD64F4B20C7379A25C87AB29A21C9BF468B0081CE2");
553

554
         return result;
2✔
555
      }
3✔
556
};
557

558
std::vector<Test::Result> hmac_drbg_multiple_requests() {
1✔
559
   auto null_rng = Botan::Null_RNG();
1✔
560
   constexpr auto rng_max_output = 1024;
1✔
561
   const auto seed = Botan::hex_decode("deadbeefbaadcafedeadbeefbaadcafedeadbeefbaadcafedeadbeefbaadcafe");
1✔
562

563
   auto make_seeded_rng = [&](size_t reseed_interval) {
5✔
564
      auto rng = std::make_unique<Botan::HMAC_DRBG>(Botan::MessageAuthenticationCode::create("HMAC(SHA-256)"),
×
565
                                                    null_rng,
566
                                                    reseed_interval + 1 /* off by one */,
8✔
567
                                                    rng_max_output);
4✔
568
      rng->add_entropy(seed);
4✔
569
      return rng;
4✔
570
   };
1✔
571

572
   return {CHECK("bulk and split output without input",
1✔
573
                 [&](auto& result) {
1✔
574
                    auto rng1 = make_seeded_rng(2);
1✔
575
                    auto rng2 = make_seeded_rng(2);
1✔
576

577
                    result.test_is_true("RNG 1 is seeded and ready to go", rng1->is_seeded());
1✔
578
                    result.test_is_true("RNG 2 is seeded and ready to go", rng2->is_seeded());
1✔
579

580
                    auto bulk = rng1->random_vec<std::vector<uint8_t>>(2 * rng_max_output);
1✔
581

582
                    auto split1 = rng2->random_vec<std::vector<uint8_t>>(rng_max_output);
1✔
583
                    auto split2 = rng2->random_vec<std::vector<uint8_t>>(rng_max_output);
1✔
584
                    split1.insert(split1.end(), split2.begin(), split2.end());
1✔
585

586
                    result.test_bin_eq("Output is equal, regardless bulk request", bulk, split1);
1✔
587

588
                    return result;
1✔
589
                 }),
3✔
590

591
           CHECK("bulk and split output with input", [&](auto& result) {
1✔
592
              auto rng1 = make_seeded_rng(3);
1✔
593
              auto rng2 = make_seeded_rng(3);
1✔
594

595
              result.test_is_true("RNG 1 is seeded and ready to go", rng1->is_seeded());
1✔
596
              result.test_is_true("RNG 2 is seeded and ready to go", rng2->is_seeded());
1✔
597

598
              std::vector<uint8_t> bulk(3 * rng_max_output);
1✔
599
              rng1->randomize_with_input(bulk, seed);
2✔
600

601
              std::vector<uint8_t> split(3 * rng_max_output);
1✔
602
              const std::span<uint8_t> split_span(split);
1✔
603
              rng2->randomize_with_input(split_span.subspan(0, rng_max_output), seed);
2✔
604
              rng2->randomize_with_input(split_span.subspan(rng_max_output, rng_max_output), {});
2✔
605
              rng2->randomize_with_input(split_span.subspan(2 * rng_max_output), {});
2✔
606

607
              result.test_bin_eq("Output is equal, regardless bulk request", bulk, split);
1✔
608

609
              return result;
1✔
610
           })};
5✔
611
}
2✔
612

613
BOTAN_REGISTER_TEST("rng", "hmac_drbg_unit", HMAC_DRBG_Unit_Tests);
614
BOTAN_REGISTER_TEST_FN("rng", "hmac_drbg_multi_request", hmac_drbg_multiple_requests);
615

616
#endif
617

618
#if defined(BOTAN_HAS_CHACHA_RNG)
619

620
class ChaCha_RNG_Unit_Tests final : public Stateful_RNG_Tests {
1✔
621
   public:
622
      std::string rng_name() const override { return "ChaCha_RNG"; }
6✔
623

624
      std::unique_ptr<Botan::Stateful_RNG> create_rng(Botan::RandomNumberGenerator* underlying_rng,
13✔
625
                                                      Botan::Entropy_Sources* underlying_es,
626
                                                      size_t reseed_interval) override {
627
         if(underlying_rng != nullptr && underlying_es != nullptr) {
13✔
628
            return std::make_unique<Botan::ChaCha_RNG>(*underlying_rng, *underlying_es, reseed_interval);
3✔
629
         } else if(underlying_rng != nullptr) {
10✔
630
            return std::make_unique<Botan::ChaCha_RNG>(*underlying_rng, reseed_interval);
7✔
631
         } else if(underlying_es != nullptr) {
3✔
632
            return std::make_unique<Botan::ChaCha_RNG>(*underlying_es, reseed_interval);
2✔
633
         } else if(reseed_interval == 0) {
1✔
634
            return std::make_unique<Botan::ChaCha_RNG>();
1✔
635
         } else {
636
            throw Test_Error("Invalid reseed interval in ChaCha_RNG unit test");
×
637
         }
638
      }
639

640
      Test::Result test_security_level() override {
1✔
641
         Test::Result result("ChaCha_RNG Security Level");
1✔
642
         const Botan::ChaCha_RNG rng;
1✔
643
         result.test_sz_eq("Expected security level", rng.security_level(), size_t(256));
1✔
644
         return result;
1✔
645
      }
1✔
646

647
      Test::Result test_max_number_of_bytes_per_request() override {
1✔
648
         Test::Result result("ChaCha_RNG max_number_of_bytes_per_request");
1✔
649
         // ChaCha_RNG doesn't have this notion
650
         return result;
1✔
651
      }
652

653
      Test::Result test_reseed_interval_limits() override {
1✔
654
         Test::Result result("ChaCha_RNG reseed_interval limits");
1✔
655
         // ChaCha_RNG doesn't apply any limits to reseed_interval
656
         return result;
1✔
657
      }
658

659
      Test::Result test_reseed_kat() override {
1✔
660
         Test::Result result("ChaCha_RNG Reseed KAT");
1✔
661

662
         Request_Counting_RNG counting_rng;
1✔
663
         auto rng = make_rng(counting_rng, 2);
1✔
664

665
         const Botan::secure_vector<uint8_t> seed_input(32);
1✔
666

667
         result.test_is_false("is_seeded", rng->is_seeded());
1✔
668

669
         rng->initialize_with(seed_input.data(), seed_input.size());
1✔
670

671
         Botan::secure_vector<uint8_t> out(32);
1✔
672

673
         rng->randomize(out.data(), out.size());
1✔
674
         result.test_sz_eq("underlying RNG calls", counting_rng.randomize_count(), size_t(0));
1✔
675
         result.test_bin_eq(
1✔
676
            "out before reseed", out, "1F0E6F13429D5073B59C057C37CBE9587740A0A894D247E2596C393CE91DDC6F");
677

678
         // reseed must happen here
679
         rng->randomize(out.data(), out.size());
1✔
680
         result.test_sz_eq("underlying RNG calls", counting_rng.randomize_count(), size_t(1));
1✔
681
         result.test_bin_eq(
1✔
682
            "out after reseed", out, "F2CAE73F22684D5D773290B48FDCDA0E6C0661EBA0A854AFEC922832BDBB9C49");
683

684
         return result;
2✔
685
      }
3✔
686
};
687

688
BOTAN_REGISTER_TEST("rng", "chacha_rng_unit", ChaCha_RNG_Unit_Tests);
689

690
#endif
691

692
#if defined(BOTAN_HAS_AUTO_RNG)
693

694
class AutoSeeded_RNG_Tests final : public Test {
1✔
695
   private:
696
      static Test::Result auto_rng_tests() {
1✔
697
         Test::Result result("AutoSeeded_RNG");
1✔
698

699
         Botan::Null_RNG null_rng;
1✔
700

701
         result.test_is_false("Null_RNG is null", null_rng.is_seeded());
1✔
702

703
         try {
1✔
704
            const Botan::AutoSeeded_RNG rng(null_rng);
1✔
705
         } catch(Botan::PRNG_Unseeded&) {
1✔
706
            result.test_success("AutoSeeded_RNG rejected useless RNG");
1✔
707
         }
1✔
708

709
   #if defined(BOTAN_HAS_ENTROPY_SOURCE)
710
         Botan::Entropy_Sources no_entropy_for_you;
1✔
711

712
         try {
1✔
713
            const Botan::AutoSeeded_RNG rng(no_entropy_for_you);
1✔
714
            result.test_failure("AutoSeeded_RNG should have rejected useless entropy source");
×
715
         } catch(Botan::PRNG_Unseeded&) {
1✔
716
            result.test_success("AutoSeeded_RNG rejected empty entropy source");
1✔
717
         }
1✔
718

719
         try {
1✔
720
            const Botan::AutoSeeded_RNG rng(null_rng, no_entropy_for_you);
1✔
721
         } catch(Botan::PRNG_Unseeded&) {
1✔
722
            result.test_success("AutoSeeded_RNG rejected useless RNG+entropy sources");
1✔
723
         }
1✔
724
   #endif
725

726
         Botan::AutoSeeded_RNG rng;
1✔
727

728
         result.test_is_true("AutoSeeded_RNG::name", rng.name().starts_with("HMAC_DRBG(HMAC(SHA-"));
2✔
729

730
         result.test_is_true("AutoSeeded_RNG starts seeded", rng.is_seeded());
1✔
731
         rng.random_vec(16);  // generate and discard output
1✔
732
         rng.clear();
1✔
733
         result.test_is_false("AutoSeeded_RNG unseeded after calling clear", rng.is_seeded());
1✔
734

735
         // AutoSeeded_RNG automatically reseeds as required:
736
         rng.random_vec(16);
1✔
737
         result.test_is_true("AutoSeeded_RNG can be reseeded", rng.is_seeded());
1✔
738

739
         result.test_is_true("AutoSeeded_RNG ", rng.is_seeded());
1✔
740
         rng.random_vec(16);  // generate and discard output
1✔
741
         rng.clear();
1✔
742
         result.test_is_false("AutoSeeded_RNG unseeded after calling clear", rng.is_seeded());
1✔
743

744
   #if defined(BOTAN_HAS_ENTROPY_SOURCE)
745
         const size_t no_entropy_bits = rng.reseed_from(no_entropy_for_you, 256);
1✔
746
         result.test_sz_eq("AutoSeeded_RNG can't reseed from nothing", no_entropy_bits, 0);
1✔
747
         result.test_is_false("AutoSeeded_RNG still unseeded", rng.is_seeded());
1✔
748
   #endif
749

750
         rng.random_vec(16);  // generate and discard output
1✔
751
         result.test_is_true("AutoSeeded_RNG can be reseeded", rng.is_seeded());
1✔
752

753
         for(size_t i = 0; i != 4096; ++i) {
4,097✔
754
            std::vector<uint8_t> buf(i);
4,096✔
755
            rng.randomize(buf.data(), buf.size());
4,096✔
756
            rng.add_entropy(buf.data(), buf.size());
4,096✔
757

758
            result.test_success("AutoSeeded_RNG accepted input and output length");
4,096✔
759
         }
4,096✔
760

761
         rng.clear();
1✔
762

763
         return result;
1✔
764
      }
1✔
765

766
   public:
767
      std::vector<Test::Result> run() override {
1✔
768
         std::vector<Test::Result> results;
1✔
769
         results.push_back(auto_rng_tests());
2✔
770
         return results;
1✔
771
      }
×
772
};
773

774
BOTAN_REGISTER_TEST("rng", "auto_rng_unit", AutoSeeded_RNG_Tests);
775

776
#endif
777

778
#if defined(BOTAN_HAS_SYSTEM_RNG)
779

780
class System_RNG_Tests final : public Test {
1✔
781
   public:
782
      std::vector<Test::Result> run() override {
1✔
783
         Test::Result result("System_RNG");
1✔
784

785
         Botan::System_RNG rng;
1✔
786

787
         result.test_sz_gte("Some non-empty name is returned", rng.name().size(), 1);
2✔
788

789
         result.test_is_true("System RNG always seeded", rng.is_seeded());
2✔
790
         rng.clear();  // clear is a noop for system rng
1✔
791
         result.test_is_true("System RNG always seeded", rng.is_seeded());
2✔
792

793
   #if defined(BOTAN_HAS_ENTROPY_SOURCE)
794
         rng.reseed_from(Botan::Entropy_Sources::global_sources(), 256);
1✔
795
   #endif
796

797
         for(size_t i = 0; i != 128; ++i) {
129✔
798
            std::vector<uint8_t> out_buf(i);
128✔
799
            rng.randomize(out_buf.data(), out_buf.size());
128✔
800
            rng.add_entropy(out_buf.data(), out_buf.size());
128✔
801
         }
128✔
802

803
         if(Test::run_long_tests() && Test::run_memory_intensive_tests() && (sizeof(size_t) > 4)) {
1✔
804
            // Pass buffer with a size greater than 32bit
805
            constexpr size_t maximum_u32 = 0xFFFFFFFF;
1✔
806
            const size_t checkSize = 1024;
1✔
807
            std::vector<uint8_t> large_buf(maximum_u32 + checkSize);
1✔
808
            std::memset(large_buf.data() + maximum_u32, 0xFE, checkSize);
1✔
809

810
            rng.randomize(large_buf.data(), large_buf.size());
1✔
811

812
            std::vector<uint8_t> check_buf(checkSize, 0xFE);
1✔
813

814
            result.test_is_true("System RNG failed to write after 4GB boundary",
1✔
815
                                std::memcmp(large_buf.data() + maximum_u32, check_buf.data(), checkSize) != 0);
1✔
816
         }
1✔
817

818
         return std::vector<Test::Result>{result};
2✔
819
      }
2✔
820
};
821

822
BOTAN_REGISTER_TEST("rng", "system_rng", System_RNG_Tests);
823

824
#endif
825

826
#if defined(BOTAN_HAS_PROCESSOR_RNG)
827

828
class Processor_RNG_Tests final : public Test {
1✔
829
   public:
830
      std::vector<Test::Result> run() override {
1✔
831
         Test::Result result("Processor_RNG");
1✔
832

833
         if(Botan::Processor_RNG::available()) {
1✔
834
            Botan::Processor_RNG rng;
1✔
835

836
            result.test_str_not_empty("Has a name", rng.name());
1✔
837
            result.test_is_true("CPU RNG always seeded", rng.is_seeded());
1✔
838
            rng.clear();  // clear is a noop for rdrand
1✔
839
            result.test_is_true("CPU RNG always seeded", rng.is_seeded());
1✔
840

841
   #if defined(BOTAN_HAS_ENTROPY_SOURCE)
842
            const size_t reseed_bits = rng.reseed_from(Botan::Entropy_Sources::global_sources(), 256);
1✔
843
            result.test_sz_eq("CPU RNG cannot consume inputs", reseed_bits, size_t(0));
1✔
844
   #endif
845

846
            /*
847
            Processor_RNG ignores add_entropy calls - confirm this by passing
848
            an invalid ptr/length field to add_entropy. If it examined its
849
            arguments, it would crash...
850
            */
851
            // NOLINTNEXTLINE(*-no-int-to-ptr)
852
            const uint8_t* invalid_ptr = reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(0xDEADC0DE));
1✔
853
            const size_t invalid_ptr_len = 64 * 1024;
1✔
854
            rng.add_entropy(invalid_ptr, invalid_ptr_len);
1✔
855

856
            for(size_t i = 0; i != 128; ++i) {
129✔
857
               std::vector<uint8_t> out_buf(i);
128✔
858
               rng.randomize(out_buf.data(), out_buf.size());
128✔
859
            }
128✔
860
         } else {
1✔
861
            result.test_throws("Processor_RNG throws if instruction not available",
×
862
                               []() { const Botan::Processor_RNG rng; });
×
863
         }
864

865
         return std::vector<Test::Result>{result};
3✔
866
      }
2✔
867
};
868

869
BOTAN_REGISTER_TEST("rng", "processor_rng", Processor_RNG_Tests);
870

871
#endif
872

873
}  // namespace
874

875
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc