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

randombit / botan / 21753596263

06 Feb 2026 02:13PM UTC coverage: 90.063% (-0.01%) from 90.073%
21753596263

Pull #5289

github

web-flow
Merge 587099284 into 8ea0ca252
Pull Request #5289: Further misc header reductions, forward declarations, etc

102237 of 113517 relevant lines covered (90.06%)

11402137.11 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/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_eq("AEAD encrypt output_length is correct", enc->output_length(input.size()), expected.size());
4,327✔
40

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

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

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

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

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

63
         result.test_eq("key is not set", enc->has_keying_material(), false);
4,327✔
64

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

166
               result.confirm("Process returns data unless requires_entire_message",
8,242✔
167
                              enc->requires_entire_message(),
4,121✔
168
                              bytes_written == 0);
169

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

179
               result.test_eq("encrypt process", buf, expected);
8,242✔
180
            }
181
         }
182

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

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

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

196
         return result;
4,327✔
197
      }
12,981✔
198

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

208
         Test::Result result(algo);
4,327✔
209

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

212
         result.test_eq("AEAD decrypt output_length is correct", dec->output_length(input.size()), expected.size());
4,327✔
213

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

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

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

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

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

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

252
         dec->start(mutate_vec(nonce, rng));
4,327✔
253
         auto garbage = get_garbage();
4,327✔
254
         dec->update(garbage);
4,327✔
255

256
         // reset message specific state
257
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,327✔
258

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

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

273
            // test finish() with full input
274
            dec->finish(buf);
4,327✔
275
            result.test_eq("decrypt full", buf, expected);
8,654✔
276

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

283
               dec->set_associated_data(ad);
4,312✔
284
               dec->start(nonce);
4,312✔
285

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

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

306
               result.test_eq("decrypt update", plaintext, expected);
8,624✔
307
            }
8,624✔
308

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

315
               dec->set_associated_data(ad);
4,121✔
316
               dec->start(nonce);
4,121✔
317

318
               buf.assign(input.begin(), input.end());
4,121✔
319

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

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

326
               result.confirm("Process returns data unless requires_entire_message",
8,242✔
327
                              dec->requires_entire_message(),
4,121✔
328
                              bytes_written == 0);
329

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

339
               result.test_eq("decrypt process", buf, expected);
8,242✔
340
            }
341

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

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

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

352
         dec->set_associated_data(ad);
4,327✔
353
         dec->start(nonce);
4,327✔
354

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

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

369
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,001✔
370
            dec->set_associated_data(ad);
4,001✔
371
            dec->start(bad_nonce);
4,001✔
372

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

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

386
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,327✔
387
         dec->set_associated_data(bad_ad);
4,327✔
388

389
         dec->start(nonce);
4,327✔
390

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

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

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

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

413
         return result;
4,327✔
414
      }
21,635✔
415

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

423
         Test::Result result(algo);
8,654✔
424

425
         auto enc = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption);
4,327✔
426
         auto dec = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Decryption);
4,327✔
427

428
         if(!enc || !dec) {
4,327✔
429
            result.note_missing(algo);
×
430
            return result;
431
         }
432

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

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

442
         result.test_eq("same provider", enc_provider, dec_provider);
4,327✔
443

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

448
         result.test_gt("update granularity is non-zero", enc->update_granularity(), 0);
4,327✔
449

450
         result.test_eq(
4,327✔
451
            "enc and dec ideal granularity is the same", enc->ideal_granularity(), dec->ideal_granularity());
4,327✔
452

453
         result.test_gt(
4,327✔
454
            "ideal granularity is at least update granularity", enc->ideal_granularity(), enc->update_granularity());
4,327✔
455

456
         result.confirm("ideal granularity is a multiple of update granularity",
8,654✔
457
                        enc->ideal_granularity() % enc->update_granularity() == 0);
4,327✔
458

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

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

466
         return result;
4,327✔
467
      }
34,173✔
468
};
469

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

472
#endif
473

474
}  // namespace
475

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