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

randombit / botan / 26703548991

30 May 2026 09:57PM UTC coverage: 89.37% (+0.009%) from 89.361%
26703548991

push

github

web-flow
Merge pull request #5629 from randombit/jack/pwhash-limit-mem

In Argon2 and Scrypt provide some reasonable upper bound on memory consumption

110243 of 123356 relevant lines covered (89.37%)

11053253.82 hits per line

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

93.21
/src/tests/test_stream.cpp
1
/*
2
* (C) 2014,2015,2016 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "tests.h"
8

9
#if defined(BOTAN_HAS_STREAM_CIPHER)
10
   #include <botan/exceptn.h>
11
   #include <botan/rng.h>
12
   #include <botan/stream_cipher.h>
13
   #include <botan/internal/fmt.h>
14
#endif
15

16
namespace Botan_Tests {
17

18
#if defined(BOTAN_HAS_STREAM_CIPHER)
19

20
namespace {
21

22
class Stream_Cipher_Tests final : public Text_Based_Test {
×
23
   public:
24
      Stream_Cipher_Tests() : Text_Based_Test("stream", "Key,Out", "In,Nonce,Seek") {}
2✔
25

26
      Test::Result run_one_test(const std::string& algo, const VarMap& vars) override {
4,363✔
27
         const std::vector<uint8_t> key = vars.get_req_bin("Key");
4,363✔
28
         const std::vector<uint8_t> expected = vars.get_req_bin("Out");
4,363✔
29
         const std::vector<uint8_t> nonce = vars.get_opt_bin("Nonce");
4,363✔
30
         const uint64_t seek = vars.get_opt_u64("Seek", 0);
4,363✔
31
         std::vector<uint8_t> input = vars.get_opt_bin("In");
4,363✔
32

33
         if(input.empty()) {
4,363✔
34
            input.resize(expected.size());
2,675✔
35
         }
36

37
         Test::Result result(algo);
4,363✔
38

39
         const std::vector<std::string> providers = provider_filter(Botan::StreamCipher::providers(algo));
4,363✔
40

41
         if(providers.empty()) {
4,363✔
42
            result.note_missing("stream cipher " + algo);
2✔
43
            return result;
2✔
44
         }
45

46
         for(const auto& provider_ask : providers) {
8,722✔
47
            auto cipher = Botan::StreamCipher::create(algo, provider_ask);
4,361✔
48

49
            if(!cipher) {
4,361✔
50
               result.test_failure(Botan::fmt("Stream cipher {} supported by {} but not found", algo, provider_ask));
×
51
               continue;
×
52
            }
53

54
            const std::string provider(cipher->provider());
4,361✔
55
            result.test_str_not_empty("provider", provider);
4,361✔
56
            result.test_str_eq(provider, cipher->name(), algo);
4,361✔
57

58
            result.test_is_true("default iv length is valid", cipher->valid_iv_length(cipher->default_iv_length()));
4,361✔
59

60
            result.test_is_true("advertised buffer size is > 0", cipher->buffer_size() > 0);
4,361✔
61

62
            if(cipher->default_iv_length() == 0) {
4,361✔
63
               result.test_is_true("if default iv length is zero, no iv supported", nonce.empty());
2,365✔
64

65
               // This should still succeed
66
               cipher->set_iv(nullptr, 0);
2,365✔
67
            }
68

69
            try {
4,361✔
70
               std::vector<uint8_t> buf(128);
4,361✔
71
               cipher->cipher1(buf.data(), buf.size());
4,361✔
72
               result.test_failure("Was able to encrypt without a key being set");
×
73
            } catch(Botan::Invalid_State&) {
4,361✔
74
               result.test_success("Trying to encrypt with no key set fails");
4,361✔
75
            }
4,361✔
76

77
            const bool supports_seek = cipher->supports_seek();
4,361✔
78

79
            if(supports_seek) {
4,361✔
80
               try {
1,979✔
81
                  cipher->seek(0);
1,979✔
82
                  result.test_failure("Was able to seek without a key being set");
×
83
               } catch(Botan::Invalid_State&) {
1,979✔
84
                  result.test_success("Trying to seek with no key set fails");
1,979✔
85
               }
1,979✔
86
            } else {
87
               result.test_throws<Botan::Not_Implemented>("seek() throws Not_Implemented when supports_seek() is false",
2,382✔
88
                                                          [&]() { cipher->seek(0); });
4,764✔
89
            }
90

91
            if(!cipher->valid_iv_length(nonce.size())) {
4,361✔
92
               throw Test_Error("Invalid nonce for " + algo);
×
93
            }
94

95
            bool accepted_nonce_early = false;
4,361✔
96
            if(!nonce.empty()) {
4,361✔
97
               try {
1,992✔
98
                  cipher->set_iv(nonce.data(), nonce.size());
1,992✔
99
                  accepted_nonce_early = true;
100
               } catch(Botan::Invalid_State&) {}
1,992✔
101
            }
102

103
            /*
104
            * Different providers may have additional restrictions on key sizes.
105
            * Avoid testing the cipher with a key size that it does not natively support.
106
            */
107
            if(!cipher->valid_keylength(key.size())) {
4,361✔
108
               result.test_note("Skipping test with provider " + provider + " as it does not support key length " +
×
109
                                std::to_string(key.size()));
×
110
               continue;
×
111
            }
112

113
            result.test_is_false("key not set", cipher->has_keying_material());
4,361✔
114
            cipher->set_key(key);
4,361✔
115
            result.test_is_true("key set", cipher->has_keying_material());
4,361✔
116

117
            /*
118
            Test invalid nonce sizes. this assumes no implemented cipher supports a nonce of 65000
119
            */
120
            const size_t large_nonce_size = 65000;
4,361✔
121
            result.test_is_true("Stream cipher does not support very large nonce",
4,361✔
122
                                cipher->valid_iv_length(large_nonce_size) == false);
4,361✔
123

124
            result.test_throws("Throws if invalid nonce size given",
4,361✔
125
                               [&]() { cipher->set_iv(nullptr, large_nonce_size); });
8,722✔
126

127
            /*
128
            If the set_nonce call earlier succeeded, then we require that it also
129
            worked (ie saved the nonce for later use) even though the key was
130
            not set. So, don't set the nonce now, to ensure the previous call
131
            had an effect.
132
            */
133
            if(!nonce.empty() && accepted_nonce_early == false) {
4,361✔
134
               cipher->set_iv(nonce.data(), nonce.size());
1,992✔
135
            }
136

137
            if(seek != 0) {
4,361✔
138
               cipher->seek(seek);
1,029✔
139
            }
140

141
            // Test that clone works and does not affect parent object
142
            auto clone = cipher->new_object();
4,361✔
143
            result.test_is_true("Clone has different pointer", cipher.get() != clone.get());
4,361✔
144
            result.test_str_eq("Clone has same name", cipher->name(), clone->name());
4,361✔
145
            clone->set_key(this->rng().random_vec(cipher->maximum_keylength()));
4,361✔
146

147
            {
4,361✔
148
               std::vector<uint8_t> buf = input;
4,361✔
149
               cipher->encrypt(buf);
4,361✔
150
               result.test_bin_eq(provider + " encrypt", buf, expected);
8,722✔
151
            }
×
152

153
            /*
154
            * Verify that seek is idempotent
155
            */
156
            if(supports_seek && seek > 0) {
4,361✔
157
               if(!nonce.empty()) {
1,029✔
158
                  cipher->set_iv(nonce.data(), nonce.size());
1,029✔
159
               }
160
               cipher->seek(seek);
1,029✔
161
               cipher->seek(0);
1,029✔
162
               cipher->seek(seek);
1,029✔
163
               std::vector<uint8_t> seek_buf = input;
1,029✔
164
               cipher->encrypt(seek_buf);
1,029✔
165
               result.test_bin_eq(provider + " seek is idempotent", seek_buf, expected);
1,029✔
166

167
               // After seeking, seek(0) must reset back to original keystream.
168
               cipher->seek(0);
1,029✔
169
               std::vector<uint8_t> seek0_buf(input.size());
1,029✔
170
               cipher->encrypt(seek0_buf);
1,029✔
171

172
               auto fresh = cipher->new_object();
1,029✔
173
               fresh->set_key(key);
1,029✔
174
               if(!nonce.empty()) {
1,029✔
175
                  fresh->set_iv(nonce.data(), nonce.size());
1,029✔
176
               }
177
               std::vector<uint8_t> fresh_buf(input.size());
1,029✔
178
               fresh->encrypt(fresh_buf);
1,029✔
179
               result.test_bin_eq(provider + " seek(0) after high seek round-trips", fresh_buf, seek0_buf);
2,058✔
180
            }
4,116✔
181

182
            {
4,361✔
183
               if(nonce.empty()) {
4,361✔
184
                  cipher->set_key(key);
2,369✔
185
               } else {
186
                  cipher->set_iv(nonce.data(), nonce.size());
1,992✔
187
               }
188
               if(seek != 0) {
4,361✔
189
                  cipher->seek(seek);
1,029✔
190
               }
191
               std::vector<uint8_t> buf = input;
4,361✔
192
               cipher->encrypt(buf);
4,361✔
193
               result.test_bin_eq(provider + " encrypt 2", buf, expected);
8,722✔
194
            }
×
195

196
            if(!nonce.empty()) {
4,361✔
197
               cipher->set_iv(nonce.data(), nonce.size());
1,992✔
198
               if(seek != 0) {
1,992✔
199
                  cipher->seek(seek);
1,029✔
200
               }
201
               std::vector<uint8_t> buf = input;
1,992✔
202
               cipher->encrypt(buf);
1,992✔
203
               result.test_bin_eq(provider + " second encrypt", buf, expected);
3,984✔
204
            }
1,992✔
205

206
            {
4,361✔
207
               cipher->set_key(key);
4,361✔
208

209
               cipher->set_iv(nonce.data(), nonce.size());
4,361✔
210

211
               if(seek != 0) {
4,361✔
212
                  cipher->seek(seek);
1,029✔
213
               }
214

215
               std::vector<uint8_t> buf(input.size(), 0xAB);
4,361✔
216

217
               uint8_t* buf_ptr = buf.data();
4,361✔
218
               size_t buf_len = buf.size();
4,361✔
219

220
               while(buf_len > 0) {
11,023✔
221
                  const size_t next = std::min<size_t>(buf_len, this->rng().next_byte());
6,662✔
222
                  cipher->write_keystream(buf_ptr, next);
6,662✔
223
                  buf_ptr += next;
6,662✔
224
                  buf_len -= next;
6,662✔
225
               }
226

227
               for(size_t i = 0; i != input.size(); ++i) {
421,291✔
228
                  buf[i] ^= input[i];
416,930✔
229
               }
230
               result.test_bin_eq(provider + " write_keystream", buf, expected);
8,722✔
231
            }
×
232

233
            result.test_is_true("key set", cipher->has_keying_material());
4,361✔
234
            cipher->clear();
4,361✔
235
            result.test_is_false("key not set", cipher->has_keying_material());
4,361✔
236

237
            try {
4,361✔
238
               std::vector<uint8_t> buf(128);
4,361✔
239
               cipher->cipher1(buf.data(), buf.size());
4,361✔
240
               result.test_failure("Was able to encrypt without a key being set (after clear)");
×
241
            } catch(Botan::Invalid_State&) {
4,361✔
242
               result.test_success("Trying to encrypt with no key set (after clear) fails");
4,361✔
243
            }
4,361✔
244
         }
8,722✔
245

246
         return result;
247
      }
36,579✔
248
};
249

250
BOTAN_REGISTER_SERIALIZED_SMOKE_TEST("stream", "stream_ciphers", Stream_Cipher_Tests);
251

252
class Stream_Cipher_Seek_Tests final : public Test {
1✔
253
   public:
254
      std::vector<Test::Result> run() override {
1✔
255
         std::vector<Test::Result> results;
1✔
256
         results.push_back(test_idempotent_and_round_trip());
2✔
257
         results.push_back(test_strict_counter_limits());
2✔
258
         return results;
1✔
259
      }
×
260

261
   private:
262
      static std::unique_ptr<Botan::StreamCipher> create_with_iv(std::string_view algo, size_t iv_len) {
25✔
263
         auto cipher = Botan::StreamCipher::create(algo);
25✔
264

265
         if(cipher) {
25✔
266
            std::vector<uint8_t> key(cipher->maximum_keylength(), 0);
25✔
267
            std::vector<uint8_t> iv(iv_len, 0);
25✔
268
            cipher->set_key(key);
25✔
269
            if(iv_len > 0) {
25✔
270
               cipher->set_iv(iv);
25✔
271
            }
272
         }
50✔
273

274
         return cipher;
25✔
275
      }
×
276

277
      Test::Result test_idempotent_and_round_trip() {
1✔
278
         Test::Result result("StreamCipher seek idempotence and round-trip at high offsets");
1✔
279

280
         struct Case {
12✔
281
               std::string algo;
282
               size_t iv_len;
283
               uint64_t seek_bytes;
284
         };
285

286
         // Seeks chosen so the high counter word is non-zero
287
         const std::vector<Case> cases = {
1✔
288
            {"ChaCha(20)", 8, (uint64_t{1} << 32) * 64},
289
            {"ChaCha(20)", 8, (uint64_t{1} << 32) * 64 + 5 * 64 + 17},
290
            {"ChaCha(20)", 24, (uint64_t{1} << 32) * 64},
291
            {"Salsa20", 8, (uint64_t{1} << 32) * 64},
292
            {"Salsa20", 8, (uint64_t{1} << 32) * 64 + 5 * 64 + 17},
293
            {"Salsa20", 24, (uint64_t{1} << 32) * 64},
294
         };
8✔
295

296
         for(const auto& c : cases) {
7✔
297
            const std::string tag = Botan::fmt("{} iv={} seek={}", c.algo, c.iv_len, c.seek_bytes);
6✔
298

299
            auto a = create_with_iv(c.algo, c.iv_len);
6✔
300
            if(!a) {
6✔
301
               result.note_missing(c.algo);
×
302
               continue;
×
303
            }
304

305
            constexpr size_t sample_bytes = 128;
6✔
306

307
            // Take reference value: seek to offset, output sample_bytes bytes of keystream.
308
            a->seek(c.seek_bytes);
6✔
309
            const auto ks_a = a->keystream_bytes<std::vector<uint8_t>>(sample_bytes);
6✔
310

311
            auto b = create_with_iv(c.algo, c.iv_len);
6✔
312
            b->seek(c.seek_bytes);
6✔
313
            b->seek(c.seek_bytes);
6✔
314
            const auto ks_b = b->keystream_bytes<std::vector<uint8_t>>(sample_bytes);
6✔
315
            result.test_bin_eq(tag + " idempotent", ks_a, ks_b);
6✔
316

317
            // seek(0) after a high seek must reproduce the keystream of a fresh cipher.
318
            auto fresh = create_with_iv(c.algo, c.iv_len);
6✔
319
            const auto ks_fresh = fresh->keystream_bytes<std::vector<uint8_t>>(sample_bytes);
6✔
320

321
            auto rt = create_with_iv(c.algo, c.iv_len);
6✔
322
            rt->seek(c.seek_bytes);
6✔
323
            (void)rt->keystream_bytes<std::vector<uint8_t>>(64);
6✔
324
            rt->seek(0);
6✔
325
            const auto ks_rt = rt->keystream_bytes<std::vector<uint8_t>>(sample_bytes);
6✔
326
            result.test_bin_eq(tag + " seek(0) round-trip", ks_rt, ks_fresh);
12✔
327
         }
48✔
328

329
         return result;
1✔
330
      }
3✔
331

332
      Test::Result test_strict_counter_limits() {
1✔
333
         Test::Result result("StreamCipher seek rejection past counter limits");
1✔
334

335
         if(auto chacha = create_with_iv("ChaCha(20)", 12)) {
1✔
336
            // Last addressable byte: block 2^32 - 1, offset 63.
337
            const uint64_t max_ok = (uint64_t{1} << 32) * 64 - 1;
1✔
338
            result.test_no_throw("ChaCha 12-byte nonce seek at counter limit", [&]() { chacha->seek(max_ok); });
2✔
339

340
            // First rejected byte: block 2^32, offset 0.
341
            const uint64_t seek_limit = (uint64_t{1} << 32) * 64;
1✔
342

343
            chacha->seek(seek_limit - 1);  // ok
1✔
344

345
            result.test_throws<Botan::Invalid_Argument>("ChaCha 12-byte nonce seek past counter limit throws",
1✔
346
                                                        [&]() { chacha->seek(seek_limit); });
2✔
347

348
            // Test a seek way past that limit:
349
            result.test_throws<Botan::Invalid_Argument>("ChaCha 12-byte nonce seek well past counter limit throws",
1✔
350
                                                        [&]() { chacha->seek((uint64_t{1} << 40) * 64); });
2✔
351
         }
×
352

353
         if(auto ctr_be = Botan::StreamCipher::create("CTR-BE(AES-128,4)")) {
1✔
354
            std::vector<uint8_t> key(16, 0);
1✔
355
            std::vector<uint8_t> iv(16, 0xFF);
1✔
356
            ctr_be->set_key(key);
1✔
357
            ctr_be->set_iv(iv);
1✔
358

359
            constexpr uint64_t ctr32_max = (uint64_t{1} << 32) * 16 - 1;
1✔
360

361
            result.test_no_throw("CTR-BE(AES,4) seek at counter limit", [&]() { ctr_be->seek(ctr32_max); });
2✔
362

363
            result.test_throws<Botan::Invalid_Argument>("CTR-BE(AES,4) seek past 2^32 blocks throws",
1✔
364
                                                        [&]() { ctr_be->seek(ctr32_max + 1); });
2✔
365
         }
2✔
366

367
         // With a 64-bit counter, you can go anywhere you want
368
         if(auto ctr_be = Botan::StreamCipher::create("CTR-BE(AES-128,8)")) {
1✔
369
            std::vector<uint8_t> key(16, 0);
1✔
370
            std::vector<uint8_t> iv(16, 0);
1✔
371
            ctr_be->set_key(key);
1✔
372
            ctr_be->set_iv(iv);
1✔
373
            result.test_no_throw("CTR-BE(AES,8) high seek accepted", [&]() { ctr_be->seek((uint64_t{1} << 40) * 16); });
2✔
374
         }
2✔
375

376
         return result;
1✔
377
      }
×
378
};
379

380
BOTAN_REGISTER_TEST("stream", "stream_cipher_seek", Stream_Cipher_Seek_Tests);
381

382
class Stream_Cipher_Keystream_Cap_Tests final : public Test {
1✔
383
   public:
384
      std::vector<Test::Result> run() override {
1✔
385
         std::vector<Test::Result> results;
1✔
386
         results.push_back(test_remaining_getter());
2✔
387
         results.push_back(test_exhaustion());
2✔
388
         return results;
1✔
389
      }
×
390

391
   private:
392
      Test::Result test_remaining_getter() {
1✔
393
         Test::Result result("StreamCipher::remaining_keystream_bytes");
1✔
394

395
         if(auto chacha = Botan::StreamCipher::create("ChaCha(20)")) {
1✔
396
            // Unkeyed cipher: nullopt regardless
397
            result.test_is_true("Unkeyed ChaCha returns nullopt", !chacha->remaining_keystream_bytes().has_value());
1✔
398

399
            // With a 64-bit counter, you can go anywhere you want
400
            const std::vector<uint8_t> key(32, 0);
1✔
401
            const std::vector<uint8_t> iv8(8, 0);
1✔
402
            chacha->set_key(key);
1✔
403
            chacha->set_iv(iv8);
1✔
404
            result.test_is_true("ChaCha 8-byte nonce returns nullopt",
1✔
405
                                !chacha->remaining_keystream_bytes().has_value());
1✔
406

407
            const std::vector<uint8_t> iv24(24, 0);
1✔
408
            chacha->set_iv(iv24);
1✔
409
            result.test_is_true("ChaCha 24-byte nonce returns nullopt",
1✔
410
                                !chacha->remaining_keystream_bytes().has_value());
1✔
411

412
            // 96-bit nonce: cap = 2^32 * 64 = 2^38 bytes from a fresh IV.
413
            const std::vector<uint8_t> iv12(12, 0);
1✔
414
            chacha->set_key(key);
1✔
415
            chacha->set_iv(iv12);
1✔
416
            constexpr auto cap = uint64_t{1} << 38;
1✔
417
            const auto remaining = chacha->remaining_keystream_bytes();
1✔
418
            result.test_is_true("ChaCha 12-byte nonce returns a value", remaining.has_value());
1✔
419
            result.test_u64_eq("ChaCha 12-byte nonce fresh capacity", *remaining, cap);
1✔
420

421
            // Consume some bytes, the available keystream decreases
422
            std::vector<uint8_t> buf(100);
1✔
423
            chacha->write_keystream(buf);
1✔
424
            result.test_opt_u64_eq(
1✔
425
               "ChaCha 12-byte nonce after 100 byte write", chacha->remaining_keystream_bytes(), cap - buf.size());
1✔
426

427
            // After seek the count tracks the new offset
428
            chacha->seek(cap - 64);
1✔
429
            result.test_opt_u64_eq("ChaCha 12-byte nonce after near-end seek", chacha->remaining_keystream_bytes(), 64);
1✔
430
         }
5✔
431

432
         // CTR-BE with 64-bit counter
433
         if(auto ctr_be = Botan::StreamCipher::create("CTR-BE(AES-128,8)")) {
1✔
434
            const std::vector<uint8_t> key(16, 0);
1✔
435
            const std::vector<uint8_t> iv(16, 0);
1✔
436
            ctr_be->set_key(key);
1✔
437
            ctr_be->set_iv(iv);
1✔
438
            result.test_opt_is_null("CTR-BE(AES,8) remaining_keystream_bytes", ctr_be->remaining_keystream_bytes());
1✔
439
         }
2✔
440

441
         if(auto ctr_be = Botan::StreamCipher::create("CTR-BE(AES-128,4)")) {
1✔
442
            const std::vector<uint8_t> key(16, 0);
1✔
443
            const std::vector<uint8_t> iv(16, 0);
1✔
444
            ctr_be->set_key(key);
1✔
445
            ctr_be->set_iv(iv);
1✔
446

447
            constexpr auto cap = (uint64_t{1} << 32) * 16;
1✔
448
            const auto remaining = ctr_be->remaining_keystream_bytes();
1✔
449
            result.test_is_true("CTR-BE(AES,4) returns a value", remaining.has_value());
1✔
450
            result.test_u64_eq("CTR-BE(AES,4) fresh capacity", *remaining, cap);
1✔
451
         }
2✔
452

453
         // Ciphers without seek also return nullopt.
454
         if(auto rc4 = Botan::StreamCipher::create("RC4")) {
1✔
455
            std::vector<uint8_t> key(16, 0);
1✔
456
            rc4->set_key(key);
1✔
457
            result.test_is_true("RC4 returns nullopt", !rc4->remaining_keystream_bytes().has_value());
1✔
458
         }
1✔
459

460
         return result;
1✔
461
      }
×
462

463
      Test::Result test_exhaustion() {
1✔
464
         Test::Result result("StreamCipher keystream exhaustion");
1✔
465

466
         /*
467
         * ChaCha 96-bit nonce, near the cap: consume the last 200
468
         * bytes successfully, then any further byte must throw.
469
         */
470
         if(auto chacha = Botan::StreamCipher::create("ChaCha(20)")) {
1✔
471
            const std::vector<uint8_t> key(32, 0);
1✔
472
            const std::vector<uint8_t> iv(12, 0xFF);
1✔
473
            chacha->set_key(key);
1✔
474
            chacha->set_iv(iv);
1✔
475
            constexpr uint64_t cap = uint64_t{1} << 38;
1✔
476
            chacha->seek(cap - 200);
1✔
477

478
            std::vector<uint8_t> buf(200);
1✔
479
            result.test_no_throw("ChaCha 12-byte nonce: consume up to cap", [&]() { chacha->write_keystream(buf); });
2✔
480
            result.test_opt_u64_eq(
1✔
481
               "ChaCha 12-byte nonce: remaining is 0 at cap", chacha->remaining_keystream_bytes(), 0);
1✔
482

483
            std::vector<uint8_t> one(1);
1✔
484
            result.test_throws<Botan::Invalid_State>("ChaCha 12-byte nonce: write past cap throws",
1✔
485
                                                     [&]() { chacha->write_keystream(one); });
2✔
486

487
            chacha->seek(cap - 100);
1✔
488

489
            const auto orig = buf;
1✔
490
            result.test_throws<Botan::Invalid_State>("ChaCha 12-byte nonce: oversize encrypt throws",
1✔
491
                                                     [&]() { chacha->encrypt(buf); });
2✔
492
            result.test_bin_eq("ChaCha 12-byte nonce: oversize encrypt leaves buffer untouched", buf, orig);
1✔
493
         }
5✔
494

495
         /*
496
         * CTR-BE(AES,4) near the end of the counter cycle (set up by
497
         * a high seek): the cap is 2^36 bytes regardless of IV, and
498
         * we approach it from the bottom via seek. Consume the last
499
         * 64 bytes successfully, then the 65th must throw without
500
         * writing.
501
         */
502
         if(auto ctr_be = Botan::StreamCipher::create("CTR-BE(AES-128,4)")) {
1✔
503
            const std::vector<uint8_t> key(16, 0);
1✔
504
            const std::vector<uint8_t> iv(16, 0xFF);
1✔
505
            ctr_be->set_key(key);
1✔
506
            ctr_be->set_iv(iv);
1✔
507

508
            constexpr uint64_t cap = (uint64_t{1} << 32) * 16;
1✔
509

510
            result.test_opt_u64_eq("CTR-BE(AES,4): remaining at 0", ctr_be->remaining_keystream_bytes(), cap);
1✔
511

512
            ctr_be->seek(cap - 64);
1✔
513
            result.test_opt_u64_eq("CTR-BE(AES,4): remaining at 0", ctr_be->remaining_keystream_bytes(), 64);
1✔
514

515
            std::vector<uint8_t> buf(64);
1✔
516
            result.test_no_throw("CTR-BE(AES,4): consume last 64 bytes before counter cycle",
1✔
517
                                 [&]() { ctr_be->write_keystream(buf); });
2✔
518
            result.test_opt_u64_eq("CTR-BE(AES,4): remaining at 0", ctr_be->remaining_keystream_bytes(), 0);
1✔
519

520
            const auto orig = buf;
1✔
521
            result.test_throws<Botan::Invalid_State>("CTR-BE(AES,4): write past cap throws",
1✔
522
                                                     [&]() { ctr_be->encrypt(buf); });
2✔
523
            result.test_bin_eq("CTR-BE(AES,4): throw leaves buffer untouched", buf, orig);
1✔
524
         }
4✔
525

526
         return result;
1✔
527
      }
×
528
};
529

530
BOTAN_REGISTER_TEST("stream", "stream_cipher_keystream_cap", Stream_Cipher_Keystream_Cap_Tests);
531

532
}  // namespace
533

534
#endif
535

536
}  // 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