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

randombit / botan / 20579846577

29 Dec 2025 06:24PM UTC coverage: 90.415% (+0.2%) from 90.243%
20579846577

push

github

web-flow
Merge pull request #5167 from randombit/jack/src-size-reductions

Changes to reduce unnecessary inclusions

101523 of 112285 relevant lines covered (90.42%)

12817276.56 hits per line

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

92.03
/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/rng.h>
13
#endif
14

15
namespace Botan_Tests {
16

17
namespace {
18

19
#if defined(BOTAN_HAS_AEAD_MODES)
20

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

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

34
         Test::Result result(algo);
2,474✔
35

36
         auto enc = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption);
2,474✔
37

38
         result.test_eq("AEAD encrypt output_length is correct", enc->output_length(input.size()), expected.size());
2,474✔
39

40
         result.confirm("AEAD name is not empty", !enc->name().empty());
4,948✔
41
         result.confirm("AEAD default nonce size is accepted", enc->valid_nonce_length(enc->default_nonce_length()));
4,948✔
42

43
         auto get_garbage = [&] { return rng.random_vec(enc->update_granularity()); };
11,404✔
44

45
         if(!is_siv) {
2,474✔
46
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for encrypt", [&]() {
6,456✔
47
               auto garbage = get_garbage();
2,152✔
48
               enc->update(garbage);
2,152✔
49
            });
×
50
         }
51

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

57
         if(enc->associated_data_requires_key()) {
2,474✔
58
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD",
2,229✔
59
                                                     [&]() { enc->set_associated_data(ad.data(), ad.size()); });
1,486✔
60
         }
61

62
         result.test_eq("key is not set", enc->has_keying_material(), false);
2,474✔
63

64
         // Ensure that test resets AD and message state
65
         result.test_eq("key is not set", enc->has_keying_material(), false);
2,474✔
66
         enc->set_key(key);
2,474✔
67
         result.test_eq("key is set", enc->has_keying_material(), true);
2,474✔
68

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

80
         enc->set_associated_data(mutate_vec(ad, rng));
2,474✔
81
         enc->start(mutate_vec(nonce, rng));
2,474✔
82

83
         auto garbage = get_garbage();
2,474✔
84
         enc->update(garbage);
2,474✔
85

86
         // reset message specific state
87
         enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
2,474✔
88

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

95
         try {
2,474✔
96
            enc->set_associated_data(ad);
2,474✔
97
         } catch(Botan::Invalid_State&) {
1,950✔
98
            // ad after setting nonce rejected, in this case we need to reset
99
            enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
1,950✔
100
            enc->set_associated_data(ad);
1,950✔
101
            enc->start(nonce);
1,950✔
102
         }
1,950✔
103

104
         Botan::secure_vector<uint8_t> buf(input.begin(), input.end());
2,474✔
105

106
         // have to check here first if input is empty if not we can test update() and eventually process()
107
         if(buf.empty()) {
2,474✔
108
            enc->finish(buf);
87✔
109
            result.test_eq("encrypt with empty input", buf, expected);
174✔
110
         } else {
111
            // test finish() with full input
112
            enc->finish(buf);
2,387✔
113
            result.test_eq("encrypt full", buf, expected);
4,774✔
114

115
            // additionally test update() if possible
116
            const size_t update_granularity = enc->update_granularity();
2,387✔
117
            if(input.size() > update_granularity) {
2,387✔
118
               // reset state first
119
               enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
2,328✔
120

121
               enc->set_associated_data(ad);
2,328✔
122
               enc->start(nonce);
2,328✔
123

124
               buf.assign(input.begin(), input.end());
2,328✔
125
               size_t input_length = buf.size();
2,328✔
126
               uint8_t* p = buf.data();
2,328✔
127
               Botan::secure_vector<uint8_t> block(update_granularity);
2,328✔
128
               Botan::secure_vector<uint8_t> ciphertext;
2,328✔
129
               ciphertext.reserve(enc->output_length(buf.size()));
2,328✔
130
               while(input_length > update_granularity &&
232,736✔
131
                     ((input_length - update_granularity) >= enc->minimum_final_size())) {
115,204✔
132
                  block.assign(p, p + update_granularity);
115,204✔
133
                  enc->update(block);
115,204✔
134
                  p += update_granularity;
115,204✔
135
                  input_length -= update_granularity;
115,204✔
136

137
                  ciphertext.insert(ciphertext.end(), block.begin(), block.end());
115,204✔
138
               }
139

140
               // encrypt remaining bytes
141
               block.assign(p, p + input_length);
2,328✔
142
               enc->finish(block);
2,328✔
143
               ciphertext.insert(ciphertext.end(), block.begin(), block.end());
2,328✔
144

145
               result.test_eq("encrypt update", ciphertext, expected);
4,656✔
146
            }
4,656✔
147

148
            // additionally test process() if possible
149
            const size_t min_final_bytes = enc->minimum_final_size();
2,387✔
150
            if(input.size() > (update_granularity + min_final_bytes)) {
2,387✔
151
               // again reset state first
152
               enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
2,328✔
153

154
               enc->set_associated_data(ad);
2,328✔
155
               enc->start(nonce);
2,328✔
156

157
               buf.assign(input.begin(), input.end());
2,328✔
158

159
               // we can process at max input.size()
160
               const size_t max_blocks_to_process = (input.size() - min_final_bytes) / update_granularity;
2,328✔
161
               const size_t bytes_to_process = max_blocks_to_process * update_granularity;
2,328✔
162

163
               const size_t bytes_written = enc->process(buf.data(), bytes_to_process);
2,328✔
164

165
               result.confirm("Process returns data unless requires_entire_message",
4,656✔
166
                              enc->requires_entire_message(),
2,328✔
167
                              bytes_written == 0);
168

169
               if(bytes_written == 0) {
2,328✔
170
                  // SIV case
171
                  buf.erase(buf.begin(), buf.begin() + bytes_to_process);
340✔
172
                  enc->finish(buf);
340✔
173
               } else {
174
                  result.test_eq("correct number of bytes processed", bytes_written, bytes_to_process);
1,988✔
175
                  enc->finish(buf, bytes_written);
1,988✔
176
               }
177

178
               result.test_eq("encrypt process", buf, expected);
4,656✔
179
            }
180
         }
181

182
         // Make sure we can set the AD after processing a message
183
         enc->set_associated_data(ad);
2,474✔
184
         enc->clear();
2,474✔
185
         result.test_eq("key is not set", enc->has_keying_material(), false);
2,474✔
186

187
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws for encrypt after clear",
4,948✔
188
                                                  [&]() { enc->finish(buf); });
4,948✔
189

190
         if(enc->associated_data_requires_key()) {
2,474✔
191
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD after clear",
2,229✔
192
                                                     [&]() { enc->set_associated_data(ad.data(), ad.size()); });
1,486✔
193
         }
194

195
         return result;
2,474✔
196
      }
7,422✔
197

198
      static Test::Result test_dec(const std::vector<uint8_t>& key,
2,474✔
199
                                   const std::vector<uint8_t>& nonce,
200
                                   const std::vector<uint8_t>& input,
201
                                   const std::vector<uint8_t>& expected,
202
                                   const std::vector<uint8_t>& ad,
203
                                   const std::string& algo,
204
                                   Botan::RandomNumberGenerator& rng) {
205
         const bool is_siv = algo.find("/SIV") != std::string::npos;
2,474✔
206

207
         Test::Result result(algo);
2,474✔
208

209
         auto dec = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Decryption);
2,474✔
210

211
         result.test_eq("AEAD decrypt output_length is correct", dec->output_length(input.size()), expected.size());
2,474✔
212

213
         auto get_garbage = [&] { return rng.random_vec(dec->update_granularity()); };
6,778✔
214
         auto get_ultimate_garbage = [&] { return rng.random_vec(dec->minimum_final_size()); };
4,626✔
215

216
         if(!is_siv) {
2,474✔
217
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for decrypt", [&]() {
6,456✔
218
               auto garbage = get_garbage();
2,152✔
219
               dec->update(garbage);
2,152✔
220
            });
×
221
         }
222

223
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws for decrypt", [&]() {
4,948✔
224
            auto garbage = get_ultimate_garbage();
2,474✔
225
            dec->finish(garbage);
2,474✔
226
         });
×
227

228
         if(dec->associated_data_requires_key()) {
2,474✔
229
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD",
2,229✔
230
                                                     [&]() { dec->set_associated_data(ad.data(), ad.size()); });
1,486✔
231
         }
232

233
         // First some tests for reset() to make sure it resets what we need it to
234
         // set garbage values
235
         result.test_eq("key is not set", dec->has_keying_material(), false);
2,474✔
236
         dec->set_key(key);
2,474✔
237
         result.test_eq("key is set", dec->has_keying_material(), true);
2,474✔
238
         dec->set_associated_data(mutate_vec(ad, rng));
2,474✔
239

240
         if(!is_siv) {
2,474✔
241
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set (dec)", [&]() {
4,304✔
242
               auto garbage = get_garbage();
2,152✔
243
               dec->update(garbage);
2,152✔
244
            });
×
245
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set (dec)", [&]() {
6,456✔
246
               auto garbage = get_ultimate_garbage();
2,152✔
247
               dec->finish(garbage);
2,152✔
248
            });
×
249
         }
250

251
         dec->start(mutate_vec(nonce, rng));
2,474✔
252
         auto garbage = get_garbage();
2,474✔
253
         dec->update(garbage);
2,474✔
254

255
         // reset message specific state
256
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
2,474✔
257

258
         Botan::secure_vector<uint8_t> buf(input.begin(), input.end());
2,474✔
259
         try {
2,474✔
260
            // now try to decrypt with correct values
261

262
            try {
2,474✔
263
               dec->start(nonce);
2,474✔
264
               dec->set_associated_data(ad);
2,474✔
265
            } catch(Botan::Invalid_State&) {
1,950✔
266
               // ad after setting nonce rejected, in this case we need to reset
267
               dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
1,950✔
268
               dec->set_associated_data(ad);
1,950✔
269
               dec->start(nonce);
1,950✔
270
            }
1,950✔
271

272
            // test finish() with full input
273
            dec->finish(buf);
2,474✔
274
            result.test_eq("decrypt full", buf, expected);
4,948✔
275

276
            // additionally test update() if possible
277
            const size_t update_granularity = dec->update_granularity();
2,474✔
278
            if(input.size() > update_granularity) {
2,474✔
279
               // reset state first
280
               dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
2,459✔
281

282
               dec->set_associated_data(ad);
2,459✔
283
               dec->start(nonce);
2,459✔
284

285
               buf.assign(input.begin(), input.end());
2,459✔
286
               size_t input_length = buf.size();
2,459✔
287
               uint8_t* p = buf.data();
2,459✔
288
               Botan::secure_vector<uint8_t> block(update_granularity);
2,459✔
289
               Botan::secure_vector<uint8_t> plaintext;
2,459✔
290
               plaintext.reserve(dec->output_length(buf.size()));
2,459✔
291
               while((input_length > update_granularity) &&
240,048✔
292
                     ((input_length - update_granularity) >= dec->minimum_final_size())) {
120,018✔
293
                  block.assign(p, p + update_granularity);
117,571✔
294
                  dec->update(block);
117,571✔
295
                  p += update_granularity;
117,571✔
296
                  input_length -= update_granularity;
117,571✔
297
                  plaintext.insert(plaintext.end(), block.begin(), block.end());
117,571✔
298
               }
299

300
               // decrypt remaining bytes
301
               block.assign(p, p + input_length);
2,459✔
302
               dec->finish(block);
2,459✔
303
               plaintext.insert(plaintext.end(), block.begin(), block.end());
2,459✔
304

305
               result.test_eq("decrypt update", plaintext, expected);
4,918✔
306
            }
4,918✔
307

308
            // additionally test process() if possible
309
            const size_t min_final_size = dec->minimum_final_size();
2,474✔
310
            if(input.size() > (update_granularity + min_final_size)) {
2,474✔
311
               // again reset state first
312
               dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
2,328✔
313

314
               dec->set_associated_data(ad);
2,328✔
315
               dec->start(nonce);
2,328✔
316

317
               buf.assign(input.begin(), input.end());
2,328✔
318

319
               // we can process at max input.size()
320
               const size_t max_blocks_to_process = (input.size() - min_final_size) / update_granularity;
2,328✔
321
               const size_t bytes_to_process = max_blocks_to_process * update_granularity;
2,328✔
322

323
               const size_t bytes_written = dec->process(buf.data(), bytes_to_process);
2,328✔
324

325
               result.confirm("Process returns data unless requires_entire_message",
4,656✔
326
                              dec->requires_entire_message(),
2,328✔
327
                              bytes_written == 0);
328

329
               if(bytes_written == 0) {
2,328✔
330
                  // SIV case
331
                  buf.erase(buf.begin(), buf.begin() + bytes_to_process);
340✔
332
                  dec->finish(buf);
340✔
333
               } else {
334
                  result.test_eq("correct number of bytes processed", bytes_written, bytes_to_process);
1,988✔
335
                  dec->finish(buf, bytes_to_process);
1,988✔
336
               }
337

338
               result.test_eq("decrypt process", buf, expected);
4,656✔
339
            }
340

341
         } catch(Botan::Exception& e) {
×
342
            result.test_failure("Failure processing AEAD ciphertext", e.what());
×
343
         }
×
344

345
         // test decryption with modified ciphertext
346
         const std::vector<uint8_t> mutated_input = mutate_vec(input, rng, true);
2,474✔
347
         buf.assign(mutated_input.begin(), mutated_input.end());
2,474✔
348

349
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
2,474✔
350

351
         dec->set_associated_data(ad);
2,474✔
352
         dec->start(nonce);
2,474✔
353

354
         try {
2,474✔
355
            dec->finish(buf);
2,474✔
356
            result.test_failure("accepted modified message", mutated_input);
×
357
         } catch(Botan::Integrity_Failure&) {
2,474✔
358
            result.test_success("correctly rejected modified message");
2,474✔
359
         } catch(std::exception& e) {
2,474✔
360
            result.test_failure("unexpected error while rejecting modified message", e.what());
×
361
         }
×
362

363
         // test decryption with modified nonce
364
         if(!nonce.empty()) {
2,474✔
365
            buf.assign(input.begin(), input.end());
2,148✔
366
            std::vector<uint8_t> bad_nonce = mutate_vec(nonce, rng);
2,148✔
367

368
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
2,148✔
369
            dec->set_associated_data(ad);
2,148✔
370
            dec->start(bad_nonce);
2,148✔
371

372
            try {
2,148✔
373
               dec->finish(buf);
2,148✔
374
               result.test_failure("accepted message with modified nonce", bad_nonce);
×
375
            } catch(Botan::Integrity_Failure&) {
2,148✔
376
               result.test_success("correctly rejected modified nonce");
2,148✔
377
            } catch(std::exception& e) {
2,148✔
378
               result.test_failure("unexpected error while rejecting modified nonce", e.what());
×
379
            }
×
380
         }
2,148✔
381

382
         // test decryption with modified associated_data
383
         const std::vector<uint8_t> bad_ad = mutate_vec(ad, rng, true);
2,474✔
384

385
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
2,474✔
386
         dec->set_associated_data(bad_ad);
2,474✔
387

388
         dec->start(nonce);
2,474✔
389

390
         try {
2,474✔
391
            buf.assign(input.begin(), input.end());
2,474✔
392
            dec->finish(buf);
2,474✔
393
            result.test_failure("accepted message with modified ad", bad_ad);
×
394
         } catch(Botan::Integrity_Failure&) {
2,474✔
395
            result.test_success("correctly rejected modified ad");
2,474✔
396
         } catch(std::exception& e) {
2,474✔
397
            result.test_failure("unexpected error while rejecting modified nonce", e.what());
×
398
         }
×
399

400
         // Make sure we can set the AD after processing a message
401
         dec->set_associated_data(ad);
2,474✔
402
         dec->clear();
2,474✔
403
         result.test_eq("key is not set", dec->has_keying_material(), false);
2,474✔
404

405
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws for decrypt", [&]() { dec->finish(buf); });
7,422✔
406

407
         if(dec->associated_data_requires_key()) {
2,474✔
408
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD",
2,229✔
409
                                                     [&]() { dec->set_associated_data(ad.data(), ad.size()); });
1,486✔
410
         }
411

412
         return result;
2,474✔
413
      }
12,370✔
414

415
      Test::Result run_one_test(const std::string& algo, const VarMap& vars) override {
2,474✔
416
         const std::vector<uint8_t> key = vars.get_req_bin("Key");
2,474✔
417
         const std::vector<uint8_t> nonce = vars.get_opt_bin("Nonce");
2,474✔
418
         const std::vector<uint8_t> input = vars.get_req_bin("In");
2,474✔
419
         const std::vector<uint8_t> expected = vars.get_req_bin("Out");
2,474✔
420
         const std::vector<uint8_t> ad = vars.get_opt_bin("AD");
2,474✔
421

422
         Test::Result result(algo);
4,948✔
423

424
         auto enc = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption);
2,474✔
425
         auto dec = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Decryption);
2,474✔
426

427
         if(!enc || !dec) {
2,474✔
428
            result.note_missing(algo);
×
429
            return result;
430
         }
431

432
         // must be authenticated
433
         result.test_eq("Encryption algo is an authenticated mode", enc->authenticated(), true);
2,474✔
434
         result.test_eq("Decryption algo is an authenticated mode", dec->authenticated(), true);
2,474✔
435

436
         const std::string enc_provider = enc->provider();
2,474✔
437
         result.test_is_nonempty("enc provider", enc_provider);
2,474✔
438
         const std::string dec_provider = enc->provider();
2,474✔
439
         result.test_is_nonempty("dec provider", dec_provider);
2,474✔
440

441
         result.test_eq("same provider", enc_provider, dec_provider);
2,474✔
442

443
         // FFI currently requires this, so assure it is true for all modes
444
         result.test_gt("enc buffer sizes ok", enc->ideal_granularity(), enc->minimum_final_size());
2,474✔
445
         result.test_gt("dec buffer sizes ok", dec->ideal_granularity(), dec->minimum_final_size());
2,474✔
446

447
         result.test_gt("update granularity is non-zero", enc->update_granularity(), 0);
2,474✔
448

449
         result.test_eq(
2,474✔
450
            "enc and dec ideal granularity is the same", enc->ideal_granularity(), dec->ideal_granularity());
2,474✔
451

452
         result.test_gt(
2,474✔
453
            "ideal granularity is at least update granularity", enc->ideal_granularity(), enc->update_granularity());
2,474✔
454

455
         result.confirm("ideal granularity is a multiple of update granularity",
4,948✔
456
                        enc->ideal_granularity() % enc->update_granularity() == 0);
2,474✔
457

458
         // test enc
459
         result.merge(test_enc(key, nonce, input, expected, ad, algo, this->rng()));
2,474✔
460

461
         // test dec
462
         // NOLINTNEXTLINE(*-suspicious-call-argument) Yes we are swapping ptext and ctext arguments here
463
         result.merge(test_dec(key, nonce, expected, input, ad, algo, this->rng()));
2,474✔
464

465
         return result;
2,474✔
466
      }
19,352✔
467
};
468

469
BOTAN_REGISTER_SERIALIZED_SMOKE_TEST("modes", "aead", AEAD_Tests);
470

471
#endif
472

473
}  // namespace
474

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