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

randombit / botan / 16237758306

12 Jul 2025 11:58AM UTC coverage: 90.57%. Remained the same
16237758306

Pull #4970

github

web-flow
Merge 77f71b6df into 8e9985cba
Pull Request #4970: Test: Improve AEAD and cipher mode error condition checks

99081 of 109397 relevant lines covered (90.57%)

12412518.26 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/mem_ops.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,353✔
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,353✔
33

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

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

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

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

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

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

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

57
         if(enc->associated_data_requires_key()) {
2,353✔
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,353✔
63

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

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

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

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

86
         // reset message specific state
87
         enc->reset();
2,353✔
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,353✔
94

95
         try {
2,353✔
96
            enc->set_associated_data(ad);
2,353✔
97
         } catch(Botan::Invalid_State&) {
1,950✔
98
            // ad after setting nonce rejected, in this case we need to reset
99
            enc->reset();
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,353✔
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,353✔
108
            enc->finish(buf);
76✔
109
            result.test_eq("encrypt with empty input", buf, expected);
152✔
110
         } else {
111
            // test finish() with full input
112
            enc->finish(buf);
2,277✔
113
            result.test_eq("encrypt full", buf, expected);
4,554✔
114

115
            // additionally test update() if possible
116
            const size_t update_granularity = enc->update_granularity();
2,277✔
117
            if(input.size() > update_granularity) {
2,277✔
118
               // reset state first
119
               enc->reset();
2,218✔
120

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

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

137
                  ciphertext.insert(ciphertext.end(), block.begin(), block.end());
113,499✔
138
               }
139

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

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

148
            // additionally test process() if possible
149
            size_t min_final_bytes = enc->minimum_final_size();
2,277✔
150
            if(input.size() > (update_granularity + min_final_bytes)) {
2,277✔
151
               // again reset state first
152
               enc->reset();
2,218✔
153

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

157
               buf.assign(input.begin(), input.end());
2,218✔
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,218✔
161
               const size_t bytes_to_process = max_blocks_to_process * update_granularity;
2,218✔
162

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

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

169
               if(bytes_written == 0) {
2,218✔
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,878✔
175
                  enc->finish(buf, bytes_written);
1,878✔
176
               }
177

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

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

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

190
         if(enc->associated_data_requires_key()) {
2,353✔
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,353✔
196
      }
7,059✔
197

198
      static Test::Result test_dec(const std::vector<uint8_t>& key,
2,353✔
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,353✔
206

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

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

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

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

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

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

228
         if(dec->associated_data_requires_key()) {
2,353✔
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,353✔
236
         dec->set_key(key);
2,353✔
237
         result.test_eq("key is set", dec->has_keying_material(), true);
2,353✔
238
         dec->set_associated_data(mutate_vec(ad, rng));
2,353✔
239

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

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

255
         // reset message specific state
256
         dec->reset();
2,353✔
257

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

262
            try {
2,353✔
263
               dec->start(nonce);
2,353✔
264
               dec->set_associated_data(ad);
2,353✔
265
            } catch(Botan::Invalid_State&) {
1,950✔
266
               // ad after setting nonce rejected, in this case we need to reset
267
               dec->reset();
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,353✔
274
            result.test_eq("decrypt full", buf, expected);
4,706✔
275

276
            // additionally test update() if possible
277
            const size_t update_granularity = dec->update_granularity();
2,353✔
278
            if(input.size() > update_granularity) {
2,353✔
279
               // reset state first
280
               dec->reset();
2,338✔
281

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

285
               buf.assign(input.begin(), input.end());
2,338✔
286
               size_t input_length = buf.size();
2,338✔
287
               uint8_t* p = buf.data();
2,338✔
288
               Botan::secure_vector<uint8_t> block(update_granularity);
2,338✔
289
               Botan::secure_vector<uint8_t> plaintext;
2,338✔
290
               plaintext.reserve(dec->output_length(buf.size()));
2,338✔
291
               while((input_length > update_granularity) &&
236,176✔
292
                     ((input_length - update_granularity) >= dec->minimum_final_size())) {
118,082✔
293
                  block.assign(p, p + update_granularity);
115,756✔
294
                  dec->update(block);
115,756✔
295
                  p += update_granularity;
115,756✔
296
                  input_length -= update_granularity;
115,756✔
297
                  plaintext.insert(plaintext.end(), block.begin(), block.end());
115,756✔
298
               }
299

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

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

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

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

317
               buf.assign(input.begin(), input.end());
2,218✔
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,218✔
321
               const size_t bytes_to_process = max_blocks_to_process * update_granularity;
2,218✔
322

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

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

329
               if(bytes_written == 0) {
2,218✔
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,878✔
335
                  dec->finish(buf, bytes_to_process);
1,878✔
336
               }
337

338
               result.test_eq("decrypt process", buf, expected);
4,436✔
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,353✔
347
         buf.assign(mutated_input.begin(), mutated_input.end());
2,353✔
348

349
         dec->reset();
2,353✔
350

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

354
         try {
2,353✔
355
            dec->finish(buf);
2,353✔
356
            result.test_failure("accepted modified message", mutated_input);
×
357
         } catch(Botan::Integrity_Failure&) {
2,353✔
358
            result.test_success("correctly rejected modified message");
2,353✔
359
         } catch(std::exception& e) {
2,353✔
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,353✔
365
            buf.assign(input.begin(), input.end());
2,027✔
366
            std::vector<uint8_t> bad_nonce = mutate_vec(nonce, rng);
2,027✔
367

368
            dec->reset();
2,027✔
369
            dec->set_associated_data(ad);
2,027✔
370
            dec->start(bad_nonce);
2,027✔
371

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

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

385
         dec->reset();
2,353✔
386
         dec->set_associated_data(bad_ad);
2,353✔
387

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

390
         try {
2,353✔
391
            buf.assign(input.begin(), input.end());
2,353✔
392
            dec->finish(buf);
2,353✔
393
            result.test_failure("accepted message with modified ad", bad_ad);
×
394
         } catch(Botan::Integrity_Failure&) {
2,353✔
395
            result.test_success("correctly rejected modified ad");
2,353✔
396
         } catch(std::exception& e) {
2,353✔
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,353✔
402
         dec->clear();
2,353✔
403
         result.test_eq("key is not set", dec->has_keying_material(), false);
2,353✔
404

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

407
         if(dec->associated_data_requires_key()) {
2,353✔
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,353✔
413
      }
11,765✔
414

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

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

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

427
         if(!enc || !dec) {
2,353✔
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,353✔
434
         result.test_eq("Decryption algo is an authenticated mode", dec->authenticated(), true);
2,353✔
435

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

441
         result.test_eq("same provider", enc_provider, dec_provider);
2,353✔
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,353✔
445
         result.test_gt("dec buffer sizes ok", dec->ideal_granularity(), dec->minimum_final_size());
2,353✔
446

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

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

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

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

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

461
         // test dec
462
         result.merge(test_dec(key, nonce, expected, input, ad, algo, this->rng()));
2,353✔
463

464
         return result;
2,353✔
465
      }
18,384✔
466
};
467

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

470
#endif
471

472
}  // namespace
473

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