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

randombit / botan / 26323460081

23 May 2026 12:00AM UTC coverage: 89.383% (+0.03%) from 89.349%
26323460081

push

github

web-flow
Merge pull request #5621 from randombit/jack/cli-port-shift

Move the cli test TCP/UDP port range downward

109787 of 122827 relevant lines covered (89.38%)

11032030.9 hits per line

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

93.04
/src/tests/test_modes.cpp
1
/*
2
* (C) 2014,2015,2017 Jack Lloyd
3
* (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity
4
* (C) 2018 Ribose Inc
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8

9
#include "tests.h"
10

11
#if defined(BOTAN_HAS_CIPHER_MODES)
12
   #include <botan/cipher_mode.h>
13
   #include <botan/exceptn.h>
14
   #include <botan/hex.h>
15
   #include <botan/rng.h>
16
#endif
17

18
namespace Botan_Tests {
19

20
namespace {
21

22
#if defined(BOTAN_HAS_CIPHER_MODES)
23

24
class Cipher_Mode_Tests final : public Text_Based_Test {
×
25
   public:
26
      Cipher_Mode_Tests() : Text_Based_Test("modes", "Key,Nonce,In,Out") {}
2✔
27

28
      std::vector<std::string> possible_providers(const std::string& algo) override {
1,246✔
29
         return provider_filter(Botan::Cipher_Mode::providers(algo));
1,246✔
30
      }
31

32
      Test::Result run_one_test(const std::string& algo, const VarMap& vars) override {
1,246✔
33
         const std::vector<uint8_t> key = vars.get_req_bin("Key");
1,246✔
34
         const std::vector<uint8_t> nonce = vars.get_req_bin("Nonce");
1,246✔
35
         const std::vector<uint8_t> input = vars.get_req_bin("In");
1,246✔
36
         const std::vector<uint8_t> expected = vars.get_req_bin("Out");
1,246✔
37

38
         Test::Result result(algo);
1,246✔
39

40
         const std::vector<std::string> providers = possible_providers(algo);
1,246✔
41

42
         if(providers.empty()) {
1,246✔
43
            result.note_missing("cipher mode " + algo);
×
44
            return result;
×
45
         }
46

47
         for(auto&& provider_ask : providers) {
2,492✔
48
            auto enc = Botan::Cipher_Mode::create(algo, Botan::Cipher_Dir::Encryption, provider_ask);
1,246✔
49

50
            auto dec = Botan::Cipher_Mode::create(algo, Botan::Cipher_Dir::Decryption, provider_ask);
1,246✔
51

52
            if(!enc || !dec) {
1,246✔
53
               if(enc) {
×
54
                  result.test_failure("Provider " + provider_ask + " has encrypt but not decrypt");
×
55
               }
56
               if(dec) {
×
57
                  result.test_failure("Provider " + provider_ask + " has decrypt but not encrypt");
×
58
               }
59
               result.note_missing(algo);
×
60
               return result;
×
61
            }
62

63
            result.test_sz_eq(
1,246✔
64
               "enc and dec granularity is the same", enc->update_granularity(), dec->update_granularity());
1,246✔
65

66
            result.test_sz_gt("update granularity is non-zero", enc->update_granularity(), 0);
1,246✔
67

68
            result.test_sz_eq(
1,246✔
69
               "enc and dec ideal granularity is the same", enc->ideal_granularity(), dec->ideal_granularity());
1,246✔
70

71
            result.test_sz_gt(
1,246✔
72
               "ideal granularity is at least update granularity", enc->ideal_granularity(), enc->update_granularity());
1,246✔
73

74
            result.test_is_true("ideal granularity is a multiple of update granularity",
1,246✔
75
                                enc->ideal_granularity() % enc->update_granularity() == 0);
1,246✔
76

77
            try {
1,246✔
78
               test_mode(result, algo, provider_ask, "encryption", *enc, key, nonce, input, expected, this->rng());
2,492✔
79
            } catch(Botan::Exception& e) {
×
80
               result.test_failure("Encryption tests failed", e.what());
×
81
            }
×
82

83
            try {
1,246✔
84
               // NOLINTNEXTLINE(*-suspicious-call-argument) intentionally swapping ptext and ctext arguments here
85
               test_mode(result, algo, provider_ask, "decryption", *dec, key, nonce, expected, input, this->rng());
2,492✔
86
            } catch(Botan::Exception& e) {
×
87
               result.test_failure("Decryption tests failed", e.what());
×
88
            }
×
89
         }
2,492✔
90

91
         return result;
92
      }
6,230✔
93

94
   private:
95
      static void test_mode(Test::Result& result,
2,492✔
96
                            const std::string& algo,
97
                            const std::string& provider,
98
                            const std::string& direction,
99
                            Botan::Cipher_Mode& mode,
100
                            const std::vector<uint8_t>& key,
101
                            const std::vector<uint8_t>& nonce,
102
                            const std::vector<uint8_t>& input,
103
                            const std::vector<uint8_t>& expected,
104
                            Botan::RandomNumberGenerator& rng) {
105
         const bool is_cbc = (algo.find("/CBC") != std::string::npos);
2,492✔
106
         const bool is_ctr = (algo.find("CTR") != std::string::npos);
2,492✔
107

108
         result.test_str_eq("name", mode.name(), algo);
2,492✔
109

110
         // Some modes report base even if got from another provider
111
         if(mode.provider() != "base") {
2,492✔
112
            result.test_str_eq("provider", mode.provider(), provider);
×
113
         }
114

115
         result.test_is_false("mode not authenticated", mode.authenticated());
2,492✔
116

117
         const size_t update_granularity = mode.update_granularity();
2,492✔
118
         const size_t min_final_bytes = mode.minimum_final_size();
2,492✔
119

120
         // FFI currently requires this, so assure it is true for all modes
121
         result.test_sz_gt("buffer sizes ok", mode.ideal_granularity(), min_final_bytes);
2,492✔
122

123
         result.test_is_false("key not set", mode.has_keying_material());
2,492✔
124

125
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws", [&]() {
2,492✔
126
            Botan::secure_vector<uint8_t> bad(min_final_bytes);
2,492✔
127
            mode.finish(bad);
2,492✔
128
         });
×
129

130
         if(is_cbc) {
2,492✔
131
            // can't test equal due to CBC padding
132

133
            if(direction == "encryption") {
658✔
134
               result.test_sz_lte("output_length", mode.output_length(input.size()), expected.size());
329✔
135
            } else {
136
               result.test_sz_gte("output_length", mode.output_length(input.size()), expected.size());
329✔
137
            }
138
         } else {
139
            // assume all other modes are not expanding (currently true)
140
            result.test_sz_eq("output_length", mode.output_length(input.size()), expected.size());
1,834✔
141
         }
142

143
         result.test_is_true("default nonce size is allowed", mode.valid_nonce_length(mode.default_nonce_length()));
2,492✔
144

145
         // Test that disallowed nonce sizes result in an exception
146
         static constexpr size_t large_nonce_size = 65000;
2,492✔
147
         result.test_is_false("Large nonce not allowed", mode.valid_nonce_length(large_nonce_size));
2,492✔
148
         result.test_throws<Botan::Invalid_Argument>("Large nonce causes exception",
2,492✔
149
                                                     [&mode]() { mode.start(nullptr, large_nonce_size); });
4,984✔
150

151
         Botan::secure_vector<uint8_t> garbage = rng.random_vec(update_granularity);
2,492✔
152
         Botan::secure_vector<uint8_t> ultimate_garbage = rng.random_vec(min_final_bytes);
2,492✔
153

154
         // Test to make sure reset() resets what we need it to
155
         result.test_throws<Botan::Invalid_State>("Cannot process data (update) until key is set",
2,492✔
156
                                                  [&]() { mode.update(garbage); });
4,984✔
157
         result.test_throws<Botan::Invalid_State>("Cannot process data (finish) until key is set",
2,492✔
158
                                                  [&]() { mode.finish(ultimate_garbage); });
4,984✔
159

160
         mode.set_key(mutate_vec(key, rng));
2,492✔
161

162
         if(!is_ctr) {
2,492✔
163
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set",
2,488✔
164
                                                     [&]() { mode.update(garbage); });
4,976✔
165

166
            // Regression: finish_msg without start_msg must throw before
167
            // touching the user buffer, even on input sizes that hit a
168
            // mode's partial-block / ciphertext-stealing path.
169
            Botan::secure_vector<uint8_t> partial(min_final_bytes + update_granularity + 1, 0xAB);
2,488✔
170
            const auto partial_orig = partial;
2,488✔
171
            result.test_throws<Botan::Invalid_State>("finish without start throws on partial-block input",
2,488✔
172
                                                     [&]() { mode.finish(partial); });
4,976✔
173
            result.test_bin_eq("buffer unmodified when finish throws on no-start", partial, partial_orig);
2,488✔
174
         }
4,976✔
175

176
         mode.start(mutate_vec(nonce, rng));
2,492✔
177
         mode.reset();
2,492✔
178

179
         if(!is_ctr) {
2,492✔
180
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set (after start/reset)",
2,488✔
181
                                                     [&]() { mode.update(garbage); });
4,976✔
182
         }
183

184
         mode.start(mutate_vec(nonce, rng));
2,492✔
185
         mode.update(garbage);
2,492✔
186

187
         mode.reset();
2,492✔
188

189
         mode.set_key(key);
2,492✔
190
         result.test_is_true("key is set", mode.has_keying_material());
2,492✔
191
         mode.start(nonce);
2,492✔
192

193
         Botan::secure_vector<uint8_t> buf;
2,492✔
194

195
         buf.assign(input.begin(), input.end());
2,492✔
196
         mode.finish(buf);
2,492✔
197
         result.test_bin_eq(direction + " all-in-one", buf, expected);
2,492✔
198

199
         // Test finish() with non-zero offset
200
         {
2,492✔
201
            const size_t test_offset = 1 + rng.next_byte() % 32;
2,492✔
202
            buf.assign(test_offset, 0xAB);
2,492✔
203
            buf.insert(buf.end(), input.begin(), input.end());
2,492✔
204

205
            mode.start(nonce);
2,492✔
206
            mode.finish(buf, test_offset);
2,492✔
207

208
            for(size_t i = 0; i < test_offset; ++i) {
44,085✔
209
               result.test_u8_eq(direction + " prefix byte", buf[i], 0xAB);
83,186✔
210
            }
211
            result.test_bin_eq(direction + " finish with offset", std::span{buf}.subspan(test_offset), expected);
2,492✔
212
         }
213

214
         // Test update() + finish() with non-zero offset
215
         if(input.size() >= update_granularity + min_final_bytes) {
2,492✔
216
            const size_t test_offset = 1 + rng.next_byte() % 32;
2,167✔
217
            const size_t max_blocks = (input.size() - min_final_bytes) / update_granularity;
2,167✔
218
            const size_t bytes_to_update = max_blocks * update_granularity;
2,167✔
219

220
            buf.assign(test_offset, 0xAB);
2,167✔
221
            buf.insert(buf.end(), input.begin(), input.begin() + bytes_to_update);
2,167✔
222

223
            Botan::secure_vector<uint8_t> final_buf(test_offset, 0xAB);
2,167✔
224
            final_buf.insert(final_buf.end(), input.begin() + bytes_to_update, input.end());
2,167✔
225

226
            mode.start(nonce);
2,167✔
227
            mode.update(buf, test_offset);
2,167✔
228
            mode.finish(final_buf, test_offset);
2,167✔
229

230
            for(size_t i = 0; i < test_offset; ++i) {
37,909✔
231
               result.test_u8_eq(direction + " update offset prefix byte", buf[i], 0xAB);
35,742✔
232
               result.test_u8_eq(direction + " finish offset prefix byte", final_buf[i], 0xAB);
71,484✔
233
            }
234

235
            Botan::secure_vector<uint8_t> combined;
2,167✔
236
            combined.insert(combined.end(), buf.begin() + test_offset, buf.end());
2,167✔
237
            combined.insert(combined.end(), final_buf.begin() + test_offset, final_buf.end());
2,167✔
238
            result.test_bin_eq(direction + " update+finish with offset", combined, expected);
4,334✔
239
         }
4,334✔
240

241
         // additionally test update() and process() if possible
242
         if(input.size() >= update_granularity + min_final_bytes) {
2,492✔
243
            const size_t max_blocks_to_process = (input.size() - min_final_bytes) / update_granularity;
2,167✔
244
            const size_t bytes_to_process = max_blocks_to_process * update_granularity;
2,167✔
245

246
            // test update, 1 block at a time
247
            if(max_blocks_to_process > 1) {
2,167✔
248
               Botan::secure_vector<uint8_t> block(update_granularity);
1,787✔
249
               buf.clear();
1,787✔
250

251
               mode.start(nonce);
1,787✔
252
               for(size_t i = 0; i != max_blocks_to_process; ++i) {
15,607✔
253
                  block.assign(input.data() + i * update_granularity, input.data() + (i + 1) * update_granularity);
13,820✔
254

255
                  mode.update(block);
13,820✔
256
                  buf += block;
13,820✔
257
               }
258

259
               Botan::secure_vector<uint8_t> last_bits(input.data() + bytes_to_process, input.data() + input.size());
1,787✔
260
               mode.finish(last_bits);
1,787✔
261
               buf += last_bits;
1,787✔
262

263
               result.test_bin_eq(direction + " update-1", buf, expected);
3,574✔
264
            }
3,574✔
265

266
            // test update with maximum length input
267
            buf.assign(input.data(), input.data() + bytes_to_process);
2,167✔
268
            Botan::secure_vector<uint8_t> last_bits(input.data() + bytes_to_process, input.data() + input.size());
2,167✔
269

270
            mode.start(nonce);
2,167✔
271
            mode.update(buf);
2,167✔
272
            mode.finish(last_bits);
2,167✔
273

274
            buf += last_bits;
2,167✔
275

276
            result.test_bin_eq(direction + " update-all", buf, expected);
2,167✔
277

278
            // test process with maximum length input
279
            mode.start(nonce);
2,167✔
280
            buf.assign(input.begin(), input.end());
2,167✔
281

282
            const size_t bytes_written = mode.process(buf.data(), bytes_to_process);
2,167✔
283

284
            result.test_sz_eq("correct number of bytes processed", bytes_written, bytes_to_process);
2,167✔
285

286
            mode.finish(buf, bytes_to_process);
2,167✔
287
            result.test_bin_eq(direction + " process", buf, expected);
4,334✔
288
         }
2,167✔
289

290
         // Regression: re-keying must drop any prior message state so the
291
         // user can't accidentally process data under the new key with
292
         // leftover IV/feedback/tweak from the previous key.
293
         if(!is_ctr) {
2,492✔
294
            mode.set_key(key);
2,488✔
295
            mode.start(nonce);
2,488✔
296
            mode.update(garbage);
2,488✔
297
            mode.set_key(mutate_vec(key, rng));
2,488✔
298
            result.test_throws<Botan::Invalid_State>("Cannot process data after re-key without restart",
2,488✔
299
                                                     [&]() { mode.update(garbage); });
4,976✔
300
         }
301

302
         mode.clear();
2,492✔
303
         result.test_is_false("key is not set", mode.has_keying_material());
2,492✔
304

305
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws after clear", [&]() {
2,492✔
306
            Botan::secure_vector<uint8_t> bad(min_final_bytes);
2,492✔
307
            mode.finish(bad);
2,492✔
308
         });
×
309
      }
9,525✔
310
};
311

312
BOTAN_REGISTER_SMOKE_TEST("modes", "cipher_modes", Cipher_Mode_Tests);
313

314
class Cipher_Mode_IV_Carry_Tests final : public Test {
1✔
315
   public:
316
      std::vector<Test::Result> run() override {
1✔
317
         std::vector<Test::Result> results;
1✔
318
         results.push_back(test_cbc_iv_carry());
2✔
319
         results.push_back(test_cfb_iv_carry());
2✔
320
         results.push_back(test_ctr_iv_carry());
2✔
321
         return results;
1✔
322
      }
×
323

324
   private:
325
      static Test::Result test_cbc_iv_carry() {
1✔
326
         Test::Result result("CBC IV carry");
1✔
327

328
   #if defined(BOTAN_HAS_MODE_CBC) && defined(BOTAN_HAS_AES)
329
         std::unique_ptr<Botan::Cipher_Mode> enc(
1✔
330
            Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::Cipher_Dir::Encryption));
1✔
331
         std::unique_ptr<Botan::Cipher_Mode> dec(
1✔
332
            Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::Cipher_Dir::Decryption));
1✔
333

334
         const std::vector<uint8_t> key(16, 0xAA);
1✔
335
         const std::vector<uint8_t> iv(16, 0xAA);
1✔
336

337
         Botan::secure_vector<uint8_t> msg1 =
1✔
338
            Botan::hex_decode_locked("446F6E27742075736520706C61696E20434243206D6F6465");
1✔
339
         Botan::secure_vector<uint8_t> msg2 = Botan::hex_decode_locked("49562063617272796F766572");
1✔
340
         Botan::secure_vector<uint8_t> msg3 = Botan::hex_decode_locked("49562063617272796F76657232");
1✔
341

342
         enc->set_key(key);
1✔
343
         dec->set_key(key);
1✔
344

345
         enc->start(iv);
1✔
346
         enc->finish(msg1);
1✔
347
         result.test_bin_eq(
1✔
348
            "First ciphertext", msg1, "9BDD7300E0CB61CA71FFF957A71605DB6836159C36781246A1ADF50982757F4B");
349

350
         enc->start();
1✔
351
         enc->finish(msg2);
1✔
352

353
         result.test_bin_eq("Second ciphertext", msg2, "AA8D682958A4A044735DAC502B274DB2");
1✔
354

355
         enc->start();
1✔
356
         enc->finish(msg3);
1✔
357

358
         result.test_bin_eq("Third ciphertext", msg3, "1241B9976F73051BCF809525D6E86C25");
1✔
359

360
         dec->start(iv);
1✔
361
         dec->finish(msg1);
1✔
362

363
         dec->start();
1✔
364
         dec->finish(msg2);
1✔
365

366
         dec->start();
1✔
367
         dec->finish(msg3);
1✔
368
         result.test_bin_eq("Third plaintext", msg3, "49562063617272796F76657232");
1✔
369

370
   #endif
371
         return result;
1✔
372
      }
7✔
373

374
      static Test::Result test_cfb_iv_carry() {
1✔
375
         Test::Result result("CFB IV carry");
1✔
376
   #if defined(BOTAN_HAS_MODE_CFB) && defined(BOTAN_HAS_AES)
377
         std::unique_ptr<Botan::Cipher_Mode> enc(
1✔
378
            Botan::Cipher_Mode::create("AES-128/CFB(8)", Botan::Cipher_Dir::Encryption));
1✔
379
         std::unique_ptr<Botan::Cipher_Mode> dec(
1✔
380
            Botan::Cipher_Mode::create("AES-128/CFB(8)", Botan::Cipher_Dir::Decryption));
1✔
381

382
         const std::vector<uint8_t> key(16, 0xAA);
1✔
383
         const std::vector<uint8_t> iv(16, 0xAB);
1✔
384

385
         Botan::secure_vector<uint8_t> msg1 = Botan::hex_decode_locked("ABCDEF01234567");
1✔
386
         Botan::secure_vector<uint8_t> msg2 = Botan::hex_decode_locked("0000123456ABCDEF");
1✔
387
         Botan::secure_vector<uint8_t> msg3 = Botan::hex_decode_locked("012345");
1✔
388

389
         enc->set_key(key);
1✔
390
         dec->set_key(key);
1✔
391

392
         enc->start(iv);
1✔
393
         enc->finish(msg1);
1✔
394
         result.test_bin_eq("First ciphertext", msg1, "a51522387c4c9b");
1✔
395

396
         enc->start();
1✔
397
         enc->finish(msg2);
1✔
398

399
         result.test_bin_eq("Second ciphertext", msg2, "105457dc2e0649d4");
1✔
400

401
         enc->start();
1✔
402
         enc->finish(msg3);
1✔
403

404
         result.test_bin_eq("Third ciphertext", msg3, "53bd65");
1✔
405

406
         dec->start(iv);
1✔
407
         dec->finish(msg1);
1✔
408
         result.test_bin_eq("First plaintext", msg1, "ABCDEF01234567");
1✔
409

410
         dec->start();
1✔
411
         dec->finish(msg2);
1✔
412
         result.test_bin_eq("Second plaintext", msg2, "0000123456ABCDEF");
1✔
413

414
         dec->start();
1✔
415
         dec->finish(msg3);
1✔
416
         result.test_bin_eq("Third plaintext", msg3, "012345");
1✔
417
   #endif
418
         return result;
1✔
419
      }
7✔
420

421
      static Test::Result test_ctr_iv_carry() {
1✔
422
         Test::Result result("CTR IV carry");
1✔
423
   #if defined(BOTAN_HAS_CTR_BE) && defined(BOTAN_HAS_AES)
424

425
         std::unique_ptr<Botan::Cipher_Mode> enc(
1✔
426
            Botan::Cipher_Mode::create("AES-128/CTR-BE", Botan::Cipher_Dir::Encryption));
1✔
427
         std::unique_ptr<Botan::Cipher_Mode> dec(
1✔
428
            Botan::Cipher_Mode::create("AES-128/CTR-BE", Botan::Cipher_Dir::Decryption));
1✔
429

430
         const std::vector<uint8_t> key = Botan::hex_decode("2B7E151628AED2A6ABF7158809CF4F3C");
1✔
431
         const std::vector<uint8_t> iv = Botan::hex_decode("F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF");
1✔
432

433
         enc->set_key(key);
1✔
434
         dec->set_key(key);
1✔
435

436
         const std::vector<std::string> exp_ciphertext = {
1✔
437
            "EC",
438
            "8CDF",
439
            "739860",
440
            "7CB0F2D2",
441
            "1675EA9EA1",
442
            "E4362B7C3C67",
443
            "73516318A077D7",
444
            "FC5073AE6A2CC378",
445
            "7889374FBEB4C81B17",
446
            "BA6C44E89C399FF0F198C",
447
         };
1✔
448

449
         for(size_t i = 1; i != 10; ++i) {
10✔
450
            if(i == 1) {
9✔
451
               enc->start(iv);
1✔
452
               dec->start(iv);
1✔
453
            } else {
454
               enc->start();
8✔
455
               dec->start();
8✔
456
            }
457

458
            Botan::secure_vector<uint8_t> msg(i, 0);
9✔
459
            enc->finish(msg);
9✔
460

461
            result.test_bin_eq("Ciphertext", msg, exp_ciphertext[i - 1]);
9✔
462

463
            dec->finish(msg);
9✔
464

465
            for(const uint8_t b : msg) {
54✔
466
               result.test_u8_eq("Plaintext zeros", b, 0);
45✔
467
            }
468
         }
9✔
469
   #endif
470
         return result;
2✔
471
      }
5✔
472
};
473

474
BOTAN_REGISTER_TEST("modes", "iv_carryover", Cipher_Mode_IV_Carry_Tests);
475

476
#endif
477

478
}  // namespace
479

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