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

randombit / botan / 22022566023

14 Feb 2026 06:55PM UTC coverage: 90.067%. Remained the same
22022566023

push

github

web-flow
Merge pull request #5334 from randombit/jack/test-h-remove-confirm

Remove confirm test helper, use test_is_true or test_is_false instead

102260 of 113538 relevant lines covered (90.07%)

11510296.11 hits per line

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

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

8
#include "tests.h"
9

10
#if defined(BOTAN_HAS_AEAD_MODES)
11
   #include <botan/aead.h>
12
   #include <botan/exceptn.h>
13
   #include <botan/rng.h>
14
#endif
15

16
namespace Botan_Tests {
17

18
namespace {
19

20
#if defined(BOTAN_HAS_AEAD_MODES)
21

22
class AEAD_Tests final : public Text_Based_Test {
×
23
   public:
24
      AEAD_Tests() : Text_Based_Test("aead", "Key,In,Out", "Nonce,AD") {}
2✔
25

26
      static Test::Result test_enc(const std::vector<uint8_t>& key,
4,327✔
27
                                   const std::vector<uint8_t>& nonce,
28
                                   const std::vector<uint8_t>& input,
29
                                   const std::vector<uint8_t>& expected,
30
                                   const std::vector<uint8_t>& ad,
31
                                   const std::string& algo,
32
                                   Botan::RandomNumberGenerator& rng) {
33
         const bool is_siv = algo.find("/SIV") != std::string::npos;
4,327✔
34

35
         Test::Result result(algo);
4,327✔
36

37
         auto enc = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption);
4,327✔
38

39
         result.test_sz_eq("AEAD encrypt output_length is correct", enc->output_length(input.size()), expected.size());
4,327✔
40

41
         result.test_is_true("AEAD name is not empty", !enc->name().empty());
4,327✔
42
         result.test_is_true("AEAD default nonce size is accepted",
8,654✔
43
                             enc->valid_nonce_length(enc->default_nonce_length()));
4,327✔
44

45
         auto get_garbage = [&] { return rng.random_vec(enc->update_granularity()); };
20,669✔
46

47
         if(!is_siv) {
4,327✔
48
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for encrypt", [&]() {
4,005✔
49
               auto garbage = get_garbage();
4,005✔
50
               enc->update(garbage);
4,005✔
51
            });
×
52
         }
53

54
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws for encrypt", [&]() {
4,327✔
55
            auto garbage = get_garbage();
4,327✔
56
            enc->finish(garbage);
4,327✔
57
         });
×
58

59
         if(enc->associated_data_requires_key()) {
4,327✔
60
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD",
751✔
61
                                                     [&]() { enc->set_associated_data(ad.data(), ad.size()); });
1,502✔
62
         }
63

64
         result.test_is_false("key is not set", enc->has_keying_material());
4,327✔
65

66
         // Ensure that test resets AD and message state
67
         result.test_is_false("key is not set", enc->has_keying_material());
4,327✔
68
         enc->set_key(key);
4,327✔
69
         result.test_is_true("key is set", enc->has_keying_material());
4,327✔
70

71
         if(!is_siv) {
4,327✔
72
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set (enc)", [&]() {
4,005✔
73
               auto garbage = get_garbage();
4,005✔
74
               enc->update(garbage);
4,005✔
75
            });
×
76
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set (enc)", [&]() {
4,005✔
77
               auto garbage = get_garbage();
4,005✔
78
               enc->finish(garbage);
4,005✔
79
            });
×
80
         }
81

82
         enc->set_associated_data(mutate_vec(ad, rng));
4,327✔
83
         enc->start(mutate_vec(nonce, rng));
4,327✔
84

85
         auto garbage = get_garbage();
4,327✔
86
         enc->update(garbage);
4,327✔
87

88
         // reset message specific state
89
         enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,327✔
90

91
         /*
92
         Now try to set the AD *after* setting the nonce
93
         For some modes this works, for others it does not.
94
         */
95
         enc->start(nonce);
4,327✔
96

97
         try {
4,327✔
98
            enc->set_associated_data(ad);
4,327✔
99
         } catch(Botan::Invalid_State&) {
3,803✔
100
            // ad after setting nonce rejected, in this case we need to reset
101
            enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
3,803✔
102
            enc->set_associated_data(ad);
3,803✔
103
            enc->start(nonce);
3,803✔
104
         }
3,803✔
105

106
         Botan::secure_vector<uint8_t> buf(input.begin(), input.end());
4,327✔
107

108
         // have to check here first if input is empty if not we can test update() and eventually process()
109
         if(buf.empty()) {
4,327✔
110
            enc->finish(buf);
117✔
111
            result.test_eq("encrypt with empty input", buf, expected);
117✔
112
         } else {
113
            // test finish() with full input
114
            enc->finish(buf);
4,210✔
115
            result.test_eq("encrypt full", buf, expected);
4,210✔
116

117
            // additionally test update() if possible
118
            const size_t update_granularity = enc->update_granularity();
4,210✔
119
            if(input.size() > update_granularity) {
4,210✔
120
               // reset state first
121
               enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,121✔
122

123
               enc->set_associated_data(ad);
4,121✔
124
               enc->start(nonce);
4,121✔
125

126
               buf.assign(input.begin(), input.end());
4,121✔
127
               size_t input_length = buf.size();
4,121✔
128
               uint8_t* p = buf.data();
4,121✔
129
               Botan::secure_vector<uint8_t> block(update_granularity);
4,121✔
130
               Botan::secure_vector<uint8_t> ciphertext;
4,121✔
131
               ciphertext.reserve(enc->output_length(buf.size()));
4,121✔
132
               while(input_length > update_granularity &&
462,219✔
133
                     ((input_length - update_granularity) >= enc->minimum_final_size())) {
229,049✔
134
                  block.assign(p, p + update_granularity);
229,049✔
135
                  enc->update(block);
229,049✔
136
                  p += update_granularity;
229,049✔
137
                  input_length -= update_granularity;
229,049✔
138

139
                  ciphertext.insert(ciphertext.end(), block.begin(), block.end());
229,049✔
140
               }
141

142
               // encrypt remaining bytes
143
               block.assign(p, p + input_length);
4,121✔
144
               enc->finish(block);
4,121✔
145
               ciphertext.insert(ciphertext.end(), block.begin(), block.end());
4,121✔
146

147
               result.test_eq("encrypt update", ciphertext, expected);
4,121✔
148
            }
8,242✔
149

150
            // additionally test process() if possible
151
            const size_t min_final_bytes = enc->minimum_final_size();
4,210✔
152
            if(input.size() > (update_granularity + min_final_bytes)) {
4,210✔
153
               // again reset state first
154
               enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,121✔
155

156
               enc->set_associated_data(ad);
4,121✔
157
               enc->start(nonce);
4,121✔
158

159
               buf.assign(input.begin(), input.end());
4,121✔
160

161
               // we can process at max input.size()
162
               const size_t max_blocks_to_process = (input.size() - min_final_bytes) / update_granularity;
4,121✔
163
               const size_t bytes_to_process = max_blocks_to_process * update_granularity;
4,121✔
164

165
               const size_t bytes_written = enc->process(buf.data(), bytes_to_process);
4,121✔
166

167
               if(enc->requires_entire_message()) {
4,121✔
168
                  result.test_sz_eq("If requires_entire_message then no output is produced", bytes_written, 0);
340✔
169
               } else {
170
                  result.test_sz_gt("If !requires_entire_message then some output is produced", bytes_written, 0);
3,781✔
171
               }
172

173
               if(bytes_written == 0) {
4,121✔
174
                  // SIV case
175
                  buf.erase(buf.begin(), buf.begin() + bytes_to_process);
340✔
176
                  enc->finish(buf);
340✔
177
               } else {
178
                  result.test_sz_eq("correct number of bytes processed", bytes_written, bytes_to_process);
3,781✔
179
                  enc->finish(buf, bytes_written);
3,781✔
180
               }
181

182
               result.test_eq("encrypt process", buf, expected);
4,121✔
183
            }
184
         }
185

186
         // Make sure we can set the AD after processing a message
187
         enc->set_associated_data(ad);
4,327✔
188
         enc->clear();
4,327✔
189
         result.test_is_false("key is not set", enc->has_keying_material());
4,327✔
190

191
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws for encrypt after clear",
4,327✔
192
                                                  [&]() { enc->finish(buf); });
8,654✔
193

194
         if(enc->associated_data_requires_key()) {
4,327✔
195
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD after clear",
751✔
196
                                                     [&]() { enc->set_associated_data(ad.data(), ad.size()); });
1,502✔
197
         }
198

199
         return result;
4,327✔
200
      }
12,981✔
201

202
      static Test::Result test_dec(const std::vector<uint8_t>& key,
4,327✔
203
                                   const std::vector<uint8_t>& nonce,
204
                                   const std::vector<uint8_t>& input,
205
                                   const std::vector<uint8_t>& expected,
206
                                   const std::vector<uint8_t>& ad,
207
                                   const std::string& algo,
208
                                   Botan::RandomNumberGenerator& rng) {
209
         const bool is_siv = algo.find("/SIV") != std::string::npos;
4,327✔
210

211
         Test::Result result(algo);
4,327✔
212

213
         auto dec = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Decryption);
4,327✔
214

215
         result.test_sz_eq("AEAD decrypt output_length is correct", dec->output_length(input.size()), expected.size());
4,327✔
216

217
         auto get_garbage = [&] { return rng.random_vec(dec->update_granularity()); };
12,337✔
218
         auto get_ultimate_garbage = [&] { return rng.random_vec(dec->minimum_final_size()); };
8,332✔
219

220
         if(!is_siv) {
4,327✔
221
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for decrypt", [&]() {
4,005✔
222
               auto garbage = get_garbage();
4,005✔
223
               dec->update(garbage);
4,005✔
224
            });
×
225
         }
226

227
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws for decrypt", [&]() {
4,327✔
228
            auto garbage = get_ultimate_garbage();
4,327✔
229
            dec->finish(garbage);
4,327✔
230
         });
×
231

232
         if(dec->associated_data_requires_key()) {
4,327✔
233
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD",
751✔
234
                                                     [&]() { dec->set_associated_data(ad.data(), ad.size()); });
1,502✔
235
         }
236

237
         // First some tests for reset() to make sure it resets what we need it to
238
         // set garbage values
239
         result.test_is_false("key is not set", dec->has_keying_material());
4,327✔
240
         dec->set_key(key);
4,327✔
241
         result.test_is_true("key is set", dec->has_keying_material());
4,327✔
242
         dec->set_associated_data(mutate_vec(ad, rng));
4,327✔
243

244
         if(!is_siv) {
4,327✔
245
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set (dec)", [&]() {
4,005✔
246
               auto garbage = get_garbage();
4,005✔
247
               dec->update(garbage);
4,005✔
248
            });
×
249
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set (dec)", [&]() {
4,005✔
250
               auto garbage = get_ultimate_garbage();
4,005✔
251
               dec->finish(garbage);
4,005✔
252
            });
×
253
         }
254

255
         dec->start(mutate_vec(nonce, rng));
4,327✔
256
         auto garbage = get_garbage();
4,327✔
257
         dec->update(garbage);
4,327✔
258

259
         // reset message specific state
260
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,327✔
261

262
         Botan::secure_vector<uint8_t> buf(input.begin(), input.end());
4,327✔
263
         try {
4,327✔
264
            // now try to decrypt with correct values
265

266
            try {
4,327✔
267
               dec->start(nonce);
4,327✔
268
               dec->set_associated_data(ad);
4,327✔
269
            } catch(Botan::Invalid_State&) {
3,803✔
270
               // ad after setting nonce rejected, in this case we need to reset
271
               dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
3,803✔
272
               dec->set_associated_data(ad);
3,803✔
273
               dec->start(nonce);
3,803✔
274
            }
3,803✔
275

276
            // test finish() with full input
277
            dec->finish(buf);
4,327✔
278
            result.test_eq("decrypt full", buf, expected);
4,327✔
279

280
            // additionally test update() if possible
281
            const size_t update_granularity = dec->update_granularity();
4,327✔
282
            if(input.size() > update_granularity) {
4,327✔
283
               // reset state first
284
               dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,312✔
285

286
               dec->set_associated_data(ad);
4,312✔
287
               dec->start(nonce);
4,312✔
288

289
               buf.assign(input.begin(), input.end());
4,312✔
290
               size_t input_length = buf.size();
4,312✔
291
               uint8_t* p = buf.data();
4,312✔
292
               Botan::secure_vector<uint8_t> block(update_granularity);
4,312✔
293
               Botan::secure_vector<uint8_t> plaintext;
4,312✔
294
               plaintext.reserve(dec->output_length(buf.size()));
4,312✔
295
               while((input_length > update_granularity) &&
475,090✔
296
                     ((input_length - update_granularity) >= dec->minimum_final_size())) {
237,539✔
297
                  block.assign(p, p + update_granularity);
233,239✔
298
                  dec->update(block);
233,239✔
299
                  p += update_granularity;
233,239✔
300
                  input_length -= update_granularity;
233,239✔
301
                  plaintext.insert(plaintext.end(), block.begin(), block.end());
233,239✔
302
               }
303

304
               // decrypt remaining bytes
305
               block.assign(p, p + input_length);
4,312✔
306
               dec->finish(block);
4,312✔
307
               plaintext.insert(plaintext.end(), block.begin(), block.end());
4,312✔
308

309
               result.test_eq("decrypt update", plaintext, expected);
4,312✔
310
            }
8,624✔
311

312
            // additionally test process() if possible
313
            const size_t min_final_size = dec->minimum_final_size();
4,327✔
314
            if(input.size() > (update_granularity + min_final_size)) {
4,327✔
315
               // again reset state first
316
               dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,121✔
317

318
               dec->set_associated_data(ad);
4,121✔
319
               dec->start(nonce);
4,121✔
320

321
               buf.assign(input.begin(), input.end());
4,121✔
322

323
               // we can process at max input.size()
324
               const size_t max_blocks_to_process = (input.size() - min_final_size) / update_granularity;
4,121✔
325
               const size_t bytes_to_process = max_blocks_to_process * update_granularity;
4,121✔
326

327
               const size_t bytes_written = dec->process(buf.data(), bytes_to_process);
4,121✔
328

329
               if(dec->requires_entire_message()) {
4,121✔
330
                  result.test_sz_eq("If requires_entire_message then no output is produced", bytes_written, 0);
340✔
331
               } else {
332
                  result.test_sz_gt("If !requires_entire_message then some output is produced", bytes_written, 0);
3,781✔
333
               }
334

335
               if(bytes_written == 0) {
4,121✔
336
                  // SIV case
337
                  buf.erase(buf.begin(), buf.begin() + bytes_to_process);
340✔
338
                  dec->finish(buf);
340✔
339
               } else {
340
                  result.test_sz_eq("correct number of bytes processed", bytes_written, bytes_to_process);
3,781✔
341
                  dec->finish(buf, bytes_to_process);
3,781✔
342
               }
343

344
               result.test_eq("decrypt process", buf, expected);
4,121✔
345
            }
346

347
         } catch(Botan::Exception& e) {
×
348
            result.test_failure("Failure processing AEAD ciphertext", e.what());
×
349
         }
×
350

351
         // test decryption with modified ciphertext
352
         const std::vector<uint8_t> mutated_input = mutate_vec(input, rng, true);
4,327✔
353
         buf.assign(mutated_input.begin(), mutated_input.end());
4,327✔
354

355
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,327✔
356

357
         dec->set_associated_data(ad);
4,327✔
358
         dec->start(nonce);
4,327✔
359

360
         try {
4,327✔
361
            dec->finish(buf);
4,327✔
362
            result.test_failure("accepted modified message", mutated_input);
×
363
         } catch(Botan::Integrity_Failure&) {
4,327✔
364
            result.test_success("correctly rejected modified message");
4,327✔
365
         } catch(std::exception& e) {
4,327✔
366
            result.test_failure("unexpected error while rejecting modified message", e.what());
×
367
         }
×
368

369
         // test decryption with modified nonce
370
         if(!nonce.empty()) {
4,327✔
371
            buf.assign(input.begin(), input.end());
4,001✔
372
            std::vector<uint8_t> bad_nonce = mutate_vec(nonce, rng);
4,001✔
373

374
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,001✔
375
            dec->set_associated_data(ad);
4,001✔
376
            dec->start(bad_nonce);
4,001✔
377

378
            try {
4,001✔
379
               dec->finish(buf);
4,001✔
380
               result.test_failure("accepted message with modified nonce", bad_nonce);
×
381
            } catch(Botan::Integrity_Failure&) {
4,001✔
382
               result.test_success("correctly rejected modified nonce");
4,001✔
383
            } catch(std::exception& e) {
4,001✔
384
               result.test_failure("unexpected error while rejecting modified nonce", e.what());
×
385
            }
×
386
         }
4,001✔
387

388
         // test decryption with modified associated_data
389
         const std::vector<uint8_t> bad_ad = mutate_vec(ad, rng, true);
4,327✔
390

391
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,327✔
392
         dec->set_associated_data(bad_ad);
4,327✔
393

394
         dec->start(nonce);
4,327✔
395

396
         try {
4,327✔
397
            buf.assign(input.begin(), input.end());
4,327✔
398
            dec->finish(buf);
4,327✔
399
            result.test_failure("accepted message with modified ad", bad_ad);
×
400
         } catch(Botan::Integrity_Failure&) {
4,327✔
401
            result.test_success("correctly rejected modified ad");
4,327✔
402
         } catch(std::exception& e) {
4,327✔
403
            result.test_failure("unexpected error while rejecting modified nonce", e.what());
×
404
         }
×
405

406
         // Make sure we can set the AD after processing a message
407
         dec->set_associated_data(ad);
4,327✔
408
         dec->clear();
4,327✔
409
         result.test_is_false("key is not set", dec->has_keying_material());
4,327✔
410

411
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws for decrypt", [&]() { dec->finish(buf); });
8,654✔
412

413
         if(dec->associated_data_requires_key()) {
4,327✔
414
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD",
751✔
415
                                                     [&]() { dec->set_associated_data(ad.data(), ad.size()); });
1,502✔
416
         }
417

418
         return result;
4,327✔
419
      }
21,635✔
420

421
      Test::Result run_one_test(const std::string& algo, const VarMap& vars) override {
4,327✔
422
         const std::vector<uint8_t> key = vars.get_req_bin("Key");
4,327✔
423
         const std::vector<uint8_t> nonce = vars.get_opt_bin("Nonce");
4,327✔
424
         const std::vector<uint8_t> input = vars.get_req_bin("In");
4,327✔
425
         const std::vector<uint8_t> expected = vars.get_req_bin("Out");
4,327✔
426
         const std::vector<uint8_t> ad = vars.get_opt_bin("AD");
4,327✔
427

428
         Test::Result result(algo);
4,327✔
429

430
         auto enc = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption);
4,327✔
431
         auto dec = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Decryption);
4,327✔
432

433
         if(!enc || !dec) {
4,327✔
434
            result.note_missing(algo);
×
435
            return result;
436
         }
437

438
         // must be authenticated
439
         result.test_is_true("Encryption algo is an authenticated mode", enc->authenticated());
4,327✔
440
         result.test_is_true("Decryption algo is an authenticated mode", dec->authenticated());
4,327✔
441

442
         const std::string enc_provider = enc->provider();
4,327✔
443
         result.test_is_nonempty("enc provider", enc_provider);
4,327✔
444
         const std::string dec_provider = enc->provider();
4,327✔
445
         result.test_is_nonempty("dec provider", dec_provider);
4,327✔
446

447
         result.test_eq("same provider", enc_provider, dec_provider);
4,327✔
448

449
         // FFI currently requires this, so assure it is true for all modes
450
         result.test_sz_gt("enc buffer sizes ok", enc->ideal_granularity(), enc->minimum_final_size());
4,327✔
451
         result.test_sz_gt("dec buffer sizes ok", dec->ideal_granularity(), dec->minimum_final_size());
4,327✔
452

453
         result.test_sz_gt("update granularity is non-zero", enc->update_granularity(), 0);
4,327✔
454

455
         result.test_sz_eq(
4,327✔
456
            "enc and dec ideal granularity is the same", enc->ideal_granularity(), dec->ideal_granularity());
4,327✔
457

458
         result.test_sz_gt(
4,327✔
459
            "ideal granularity is at least update granularity", enc->ideal_granularity(), enc->update_granularity());
4,327✔
460

461
         result.test_is_true("ideal granularity is a multiple of update granularity",
4,327✔
462
                             enc->ideal_granularity() % enc->update_granularity() == 0);
4,327✔
463

464
         // test enc
465
         result.merge(test_enc(key, nonce, input, expected, ad, algo, this->rng()));
4,327✔
466

467
         // test dec
468
         // NOLINTNEXTLINE(*-suspicious-call-argument) Yes we are swapping ptext and ctext arguments here
469
         result.merge(test_dec(key, nonce, expected, input, ad, algo, this->rng()));
4,327✔
470

471
         return result;
4,327✔
472
      }
34,173✔
473
};
474

475
BOTAN_REGISTER_SERIALIZED_SMOKE_TEST("modes", "aead", AEAD_Tests);
476

477
#endif
478

479
}  // namespace
480

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