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

randombit / botan / 27324610210

10 Jun 2026 06:04PM UTC coverage: 89.378% (+0.01%) from 89.367%
27324610210

push

github

web-flow
Merge pull request #5660 from randombit/jack/x509-dfs-order

Fix order of X.509 DFS iteration

110896 of 124075 relevant lines covered (89.38%)

11140723.17 hits per line

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

93.38
/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,355✔
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,355✔
34

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

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

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

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

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

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

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

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

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

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

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

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

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

88
         // reset message specific state; AD persists per the AEAD contract
89
         enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
90

91
         // Setting AD after start_msg must always throw
92
         enc->start(nonce);
4,355✔
93
         result.test_throws<Botan::Invalid_State>("set_associated_data after start_msg throws (enc)",
4,355✔
94
                                                  [&]() { enc->set_associated_data(ad); });
8,710✔
95

96
         // start_msg called twice without finish/reset must always throw.
97
         result.test_throws<Botan::Invalid_State>("double start_msg throws (enc)", [&]() { enc->start(nonce); });
8,710✔
98

99
         // Recover into the proper state for the actual encryption tests.
100
         enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
101
         enc->set_associated_data(ad);
4,355✔
102
         enc->start(nonce);
4,355✔
103

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

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

115
            // AD should be persisted between messages unless reset
116
            if(!ad.empty()) {
4,238✔
117
               enc->start(nonce);
3,827✔
118
               buf.assign(input.begin(), input.end());
3,827✔
119
               enc->finish(buf);
3,827✔
120
               result.test_bin_eq("AD persists across messages without re-setting", buf, expected);
3,827✔
121

122
               // AD must also persist across reset() per the AEAD contract:
123
               // reset() only resets message-specific state.
124
               enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
3,827✔
125
               enc->start(nonce);
3,827✔
126
               buf.assign(input.begin(), input.end());
3,827✔
127
               enc->finish(buf);
3,827✔
128
               result.test_bin_eq("AD persists across reset()", buf, expected);
3,827✔
129
            }
130

131
            // additionally test update() if possible
132
            const size_t update_granularity = enc->update_granularity();
4,238✔
133
            if(input.size() > update_granularity) {
4,238✔
134
               // reset state first
135
               enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,149✔
136

137
               enc->set_associated_data(ad);
4,149✔
138
               enc->start(nonce);
4,149✔
139

140
               buf.assign(input.begin(), input.end());
4,149✔
141
               size_t input_length = buf.size();
4,149✔
142
               uint8_t* p = buf.data();
4,149✔
143
               Botan::secure_vector<uint8_t> block(update_granularity);
4,149✔
144
               Botan::secure_vector<uint8_t> ciphertext;
4,149✔
145
               ciphertext.reserve(enc->output_length(buf.size()));
4,149✔
146
               while(input_length > update_granularity &&
519,535✔
147
                     ((input_length - update_granularity) >= enc->minimum_final_size())) {
257,693✔
148
                  block.assign(p, p + update_granularity);
257,693✔
149
                  enc->update(block);
257,693✔
150
                  p += update_granularity;
257,693✔
151
                  input_length -= update_granularity;
257,693✔
152

153
                  ciphertext.insert(ciphertext.end(), block.begin(), block.end());
257,693✔
154
               }
155

156
               // encrypt remaining bytes
157
               block.assign(p, p + input_length);
4,149✔
158
               enc->finish(block);
4,149✔
159
               ciphertext.insert(ciphertext.end(), block.begin(), block.end());
4,149✔
160

161
               result.test_bin_eq("encrypt update", ciphertext, expected);
4,149✔
162
            }
8,298✔
163

164
            // additionally test process() if possible
165
            const size_t min_final_bytes = enc->minimum_final_size();
4,238✔
166
            if(input.size() > (update_granularity + min_final_bytes)) {
4,238✔
167
               // again reset state first
168
               enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,149✔
169

170
               enc->set_associated_data(ad);
4,149✔
171
               enc->start(nonce);
4,149✔
172

173
               buf.assign(input.begin(), input.end());
4,149✔
174

175
               // we can process at max input.size()
176
               const size_t max_blocks_to_process = (input.size() - min_final_bytes) / update_granularity;
4,149✔
177
               const size_t bytes_to_process = max_blocks_to_process * update_granularity;
4,149✔
178

179
               const size_t bytes_written = enc->process(buf.data(), bytes_to_process);
4,149✔
180

181
               if(enc->requires_entire_message()) {
4,149✔
182
                  result.test_sz_eq("If requires_entire_message then no output is produced", bytes_written, 0);
340✔
183
               } else {
184
                  result.test_sz_gt("If !requires_entire_message then some output is produced", bytes_written, 0);
3,809✔
185
               }
186

187
               if(bytes_written == 0) {
4,149✔
188
                  // SIV case
189
                  buf.erase(buf.begin(), buf.begin() + bytes_to_process);
340✔
190
                  enc->finish(buf);
340✔
191
               } else {
192
                  result.test_sz_eq("correct number of bytes processed", bytes_written, bytes_to_process);
3,809✔
193
                  enc->finish(buf, bytes_written);
3,809✔
194
               }
195

196
               result.test_bin_eq("encrypt process", buf, expected);
4,149✔
197
            }
198
         }
199

200
         // After reset, a new call to start must be made before finish is called.
201
         // Also verify that after the exception, the input buffer was not modified.
202
         if(!is_siv) {
4,355✔
203
            enc->start(nonce);
4,033✔
204
            enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,033✔
205
            Botan::secure_vector<uint8_t> tmp(input.begin(), input.end());
4,033✔
206
            const Botan::secure_vector<uint8_t> tmp_orig = tmp;
4,033✔
207
            result.test_throws<Botan::Invalid_State>("finish after reset without start throws",
4,033✔
208
                                                     [&]() { enc->finish(tmp); });
8,066✔
209
            result.test_bin_eq("finish after reset leaves input buffer unmodified", tmp, tmp_orig);
4,033✔
210
         }
7,957✔
211

212
         // Verify that set_associated_data_n checks its index
213
         enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
214
         {
4,355✔
215
            const size_t max_ad = enc->maximum_associated_data_inputs();
4,355✔
216
            if(max_ad > 0) {
4,355✔
217
               result.test_throws<Botan::Invalid_Argument>("set_associated_data_n rejects idx == max",
4,355✔
218
                                                           [&]() { enc->set_associated_data_n(max_ad, ad); });
13,065✔
219
            }
220
         }
221

222
         // Verify that finish() with an offset past the end of the buffer throws
223
         {
4,355✔
224
            enc->set_associated_data(ad);
4,355✔
225
            enc->start(nonce);
4,355✔
226
            result.test_throws<Botan::Invalid_Argument>("finish with offset > size throws", [&]() {
4,355✔
227
               Botan::secure_vector<uint8_t> tmp(input.begin(), input.end());
4,355✔
228
               enc->finish(tmp, tmp.size() + 1);
4,355✔
229
            });
×
230
            enc->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
231
         }
232

233
         // Make sure we can set the AD after processing a message
234
         enc->set_associated_data(ad);
4,355✔
235
         enc->clear();
4,355✔
236
         result.test_is_false("key is not set", enc->has_keying_material());
4,355✔
237

238
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws for encrypt after clear",
4,355✔
239
                                                  [&]() { enc->finish(buf); });
8,710✔
240

241
         if(enc->associated_data_requires_key()) {
4,355✔
242
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD after clear",
779✔
243
                                                     [&]() { enc->set_associated_data(ad.data(), ad.size()); });
1,558✔
244
         }
245

246
         // Regression test: modes that advertise !associated_data_requires_key()
247
         // must retain AD set before keying. enc was just cleared above so it
248
         // is in an unkeyed state ready for this check.
249
         if(!enc->associated_data_requires_key()) {
4,355✔
250
            enc->set_associated_data(ad);
3,576✔
251
            enc->set_key(key);
3,576✔
252
            enc->start(nonce);
3,576✔
253
            Botan::secure_vector<uint8_t> tmp(input.begin(), input.end());
3,576✔
254
            enc->finish(tmp);
3,576✔
255
            result.test_bin_eq("AD set before key is retained", tmp, expected);
3,576✔
256
         }
3,576✔
257

258
         // Regression test: modes that advertise associated_data_requires_key()
259
         // must drop ALL key-dependent state on re-key, so that anything set
260
         // under one key cannot contaminate operations under another.
261
         if(enc->associated_data_requires_key()) {
4,355✔
262
            auto enc2 = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption);
779✔
263
            const std::vector<uint8_t> stale_key(key.size(), 0x42);
779✔
264
            const std::vector<uint8_t> stale_ad{0xCC, 0xDD, 0xEE, 0xFF};
779✔
265
            enc2->set_key(stale_key);
779✔
266
            enc2->set_associated_data(stale_ad);
779✔
267

268
            // Populate per-nonce caches under the stale key. Crucially we
269
            // do not call finish_msg here, since that would clear the
270
            // caches via the mode's internal reset() and hide the bug.
271
            enc2->start(nonce);
779✔
272

273
            // Re-key, re-set AD, restart with the same nonce. After re-key,
274
            // every key-dependent piece of state must be dropped; the
275
            // ciphertext must match what a fresh instance would produce.
276
            enc2->set_key(key);
779✔
277
            enc2->set_associated_data(ad);
779✔
278
            enc2->start(nonce);
779✔
279
            Botan::secure_vector<uint8_t> tmp(input.begin(), input.end());
779✔
280
            enc2->finish(tmp);
779✔
281
            result.test_bin_eq("re-key drops all stale key-dependent state", tmp, expected);
779✔
282
         }
3,116✔
283

284
         // SIV-specific: after finish_msg, the nonce must not be carried
285
         // over. A subsequent finish_msg without an intervening start_msg
286
         // must run nonce-less SIV, not silently reuse the prior nonce.
287
         if(is_siv && !nonce.empty()) {
4,355✔
288
            auto siv_enc = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption);
1✔
289
            siv_enc->set_key(key);
1✔
290
            siv_enc->set_associated_data(ad);
1✔
291
            siv_enc->start(nonce);
1✔
292
            Botan::secure_vector<uint8_t> with_nonce(input.begin(), input.end());
1✔
293
            siv_enc->finish(with_nonce);
1✔
294

295
            // No start_msg here.
296
            Botan::secure_vector<uint8_t> nonceless(input.begin(), input.end());
1✔
297
            siv_enc->finish(nonceless);
1✔
298

299
            // Compute the reference nonce-less ciphertext from a fresh
300
            // instance for the same (key, AD, input).
301
            auto siv_fresh = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption);
1✔
302
            siv_fresh->set_key(key);
1✔
303
            siv_fresh->set_associated_data(ad);
1✔
304
            Botan::secure_vector<uint8_t> nonceless_ref(input.begin(), input.end());
1✔
305
            siv_fresh->finish(nonceless_ref);
1✔
306

307
            result.test_bin_eq("SIV nonce dropped after finish_msg", nonceless, nonceless_ref);
1✔
308
         }
5✔
309

310
         return result;
4,355✔
311
      }
13,065✔
312

313
      static Test::Result test_dec(const std::vector<uint8_t>& key,
4,355✔
314
                                   const std::vector<uint8_t>& nonce,
315
                                   const std::vector<uint8_t>& input,
316
                                   const std::vector<uint8_t>& expected,
317
                                   const std::vector<uint8_t>& ad,
318
                                   const std::string& algo,
319
                                   Botan::RandomNumberGenerator& rng) {
320
         const bool is_siv = algo.find("/SIV") != std::string::npos;
4,355✔
321

322
         Test::Result result(algo);
4,355✔
323

324
         auto dec = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Decryption);
4,355✔
325

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

328
         auto get_garbage = [&] { return rng.random_vec(dec->update_granularity()); };
12,421✔
329
         auto get_ultimate_garbage = [&] { return rng.random_vec(dec->minimum_final_size()); };
8,388✔
330

331
         if(!is_siv) {
4,355✔
332
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for decrypt", [&]() {
4,033✔
333
               auto garbage = get_garbage();
4,033✔
334
               dec->update(garbage);
4,033✔
335
            });
×
336
         }
337

338
         result.test_throws<Botan::Invalid_State>("Unkeyed object throws for decrypt", [&]() {
4,355✔
339
            auto garbage = get_ultimate_garbage();
4,355✔
340
            dec->finish(garbage);
4,355✔
341
         });
×
342

343
         if(dec->associated_data_requires_key()) {
4,355✔
344
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD",
779✔
345
                                                     [&]() { dec->set_associated_data(ad.data(), ad.size()); });
1,558✔
346
         }
347

348
         // First some tests for reset() to make sure it resets what we need it to
349
         // set garbage values
350
         result.test_is_false("key is not set", dec->has_keying_material());
4,355✔
351
         dec->set_key(key);
4,355✔
352
         result.test_is_true("key is set", dec->has_keying_material());
4,355✔
353
         dec->set_associated_data(mutate_vec(ad, rng));
4,355✔
354

355
         if(!is_siv) {
4,355✔
356
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set (dec)", [&]() {
4,033✔
357
               auto garbage = get_garbage();
4,033✔
358
               dec->update(garbage);
4,033✔
359
            });
×
360
            result.test_throws<Botan::Invalid_State>("Cannot process data until nonce is set (dec)", [&]() {
4,033✔
361
               auto garbage = get_ultimate_garbage();
4,033✔
362
               dec->finish(garbage);
4,033✔
363
            });
×
364
         }
365

366
         dec->start(mutate_vec(nonce, rng));
4,355✔
367
         auto garbage = get_garbage();
4,355✔
368
         dec->update(garbage);
4,355✔
369

370
         // reset message specific state; AD persists per the AEAD contract
371
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
372

373
         // Setting AD after start_msg must always throw (uniform behavior).
374
         dec->start(nonce);
4,355✔
375
         result.test_throws<Botan::Invalid_State>("set_associated_data after start_msg throws (dec)",
4,355✔
376
                                                  [&]() { dec->set_associated_data(ad); });
8,710✔
377

378
         // start_msg called twice without finish/reset must always throw.
379
         result.test_throws<Botan::Invalid_State>("double start_msg throws (dec)", [&]() { dec->start(nonce); });
8,710✔
380

381
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
382
         dec->set_associated_data(ad);
4,355✔
383
         dec->start(nonce);
4,355✔
384

385
         Botan::secure_vector<uint8_t> buf(input.begin(), input.end());
4,355✔
386
         try {
4,355✔
387
            // test finish() with full input
388
            dec->finish(buf);
4,355✔
389
            result.test_bin_eq("decrypt full", buf, expected);
4,355✔
390

391
            // Verify that AD is retained across messages
392
            if(!ad.empty()) {
4,355✔
393
               dec->start(nonce);
3,912✔
394
               buf.assign(input.begin(), input.end());
3,912✔
395
               dec->finish(buf);
3,912✔
396
               result.test_bin_eq("AD persists across messages without re-setting (dec)", buf, expected);
3,912✔
397

398
               // AD must also persist across reset() per the AEAD contract.
399
               dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
3,912✔
400
               dec->start(nonce);
3,912✔
401
               buf.assign(input.begin(), input.end());
3,912✔
402
               dec->finish(buf);
3,912✔
403
               result.test_bin_eq("AD persists across reset() (dec)", buf, expected);
3,912✔
404
            }
405

406
            // additionally test update() if possible
407
            const size_t update_granularity = dec->update_granularity();
4,355✔
408
            if(input.size() > update_granularity) {
4,355✔
409
               // reset state first
410
               dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,340✔
411

412
               dec->set_associated_data(ad);
4,340✔
413
               dec->start(nonce);
4,340✔
414

415
               buf.assign(input.begin(), input.end());
4,340✔
416
               size_t input_length = buf.size();
4,340✔
417
               uint8_t* p = buf.data();
4,340✔
418
               Botan::secure_vector<uint8_t> block(update_granularity);
4,340✔
419
               Botan::secure_vector<uint8_t> plaintext;
4,340✔
420
               plaintext.reserve(dec->output_length(buf.size()));
4,340✔
421
               while((input_length > update_granularity) &&
532,490✔
422
                     ((input_length - update_granularity) >= dec->minimum_final_size())) {
266,239✔
423
                  block.assign(p, p + update_granularity);
261,911✔
424
                  dec->update(block);
261,911✔
425
                  p += update_granularity;
261,911✔
426
                  input_length -= update_granularity;
261,911✔
427
                  plaintext.insert(plaintext.end(), block.begin(), block.end());
261,911✔
428
               }
429

430
               // decrypt remaining bytes
431
               block.assign(p, p + input_length);
4,340✔
432
               dec->finish(block);
4,340✔
433
               plaintext.insert(plaintext.end(), block.begin(), block.end());
4,340✔
434

435
               result.test_bin_eq("decrypt update", plaintext, expected);
4,340✔
436
            }
8,680✔
437

438
            // additionally test process() if possible
439
            const size_t min_final_size = dec->minimum_final_size();
4,355✔
440
            if(input.size() > (update_granularity + min_final_size)) {
4,355✔
441
               // again reset state first
442
               dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,149✔
443

444
               dec->set_associated_data(ad);
4,149✔
445
               dec->start(nonce);
4,149✔
446

447
               buf.assign(input.begin(), input.end());
4,149✔
448

449
               // we can process at max input.size()
450
               const size_t max_blocks_to_process = (input.size() - min_final_size) / update_granularity;
4,149✔
451
               const size_t bytes_to_process = max_blocks_to_process * update_granularity;
4,149✔
452

453
               const size_t bytes_written = dec->process(buf.data(), bytes_to_process);
4,149✔
454

455
               if(dec->requires_entire_message()) {
4,149✔
456
                  result.test_sz_eq("If requires_entire_message then no output is produced", bytes_written, 0);
340✔
457
               } else {
458
                  result.test_sz_gt("If !requires_entire_message then some output is produced", bytes_written, 0);
3,809✔
459
               }
460

461
               if(bytes_written == 0) {
4,149✔
462
                  // SIV case
463
                  buf.erase(buf.begin(), buf.begin() + bytes_to_process);
340✔
464
                  dec->finish(buf);
340✔
465
               } else {
466
                  result.test_sz_eq("correct number of bytes processed", bytes_written, bytes_to_process);
3,809✔
467
                  dec->finish(buf, bytes_to_process);
3,809✔
468
               }
469

470
               result.test_bin_eq("decrypt process", buf, expected);
4,149✔
471
            }
472

473
         } catch(Botan::Exception& e) {
×
474
            result.test_failure("Failure processing AEAD ciphertext", e.what());
×
475
         }
×
476

477
         // On tag check failure, every AEAD must zero the unauthenticated
478
         // plaintext in the caller's buffer before throwing
479
         auto check_plaintext_cleared =
4,355✔
480
            [&result, &dec](const std::string& msg, const Botan::secure_vector<uint8_t>& b, size_t ctext_and_tag_len) {
12,739✔
481
               const size_t ptext_len = ctext_and_tag_len - dec->tag_size();
12,739✔
482
               bool cleared = (b.size() >= ptext_len);
12,739✔
483
               if(cleared) {
12,739✔
484
                  uint8_t accum = 0;
485
                  for(size_t i = 0; i != ptext_len; ++i) {
805,790✔
486
                     accum |= b[i];
793,051✔
487
                  }
488
                  cleared = (accum == 0);
12,739✔
489
               }
490
               result.test_is_true(msg, cleared);
12,739✔
491
            };
17,094✔
492

493
         // test decryption with modified ciphertext
494
         const std::vector<uint8_t> mutated_input = mutate_vec(input, rng, true);
4,355✔
495
         buf.assign(mutated_input.begin(), mutated_input.end());
4,355✔
496

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

499
         dec->set_associated_data(ad);
4,355✔
500
         dec->start(nonce);
4,355✔
501

502
         try {
4,355✔
503
            dec->finish(buf);
4,355✔
504
            result.test_failure("accepted modified message", mutated_input);
×
505
         } catch(Botan::Integrity_Failure&) {
4,355✔
506
            result.test_success("correctly rejected modified message");
4,355✔
507
            check_plaintext_cleared("plaintext zeroed after rejecting modified message", buf, mutated_input.size());
4,355✔
508
         } catch(std::exception& e) {
4,355✔
509
            result.test_failure("unexpected error while rejecting modified message", e.what());
×
510
         }
×
511

512
         // test decryption with modified nonce
513
         if(!nonce.empty()) {
4,355✔
514
            buf.assign(input.begin(), input.end());
4,029✔
515
            std::vector<uint8_t> bad_nonce = mutate_vec(nonce, rng);
4,029✔
516

517
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,029✔
518
            dec->set_associated_data(ad);
4,029✔
519
            dec->start(bad_nonce);
4,029✔
520

521
            try {
4,029✔
522
               dec->finish(buf);
4,029✔
523
               result.test_failure("accepted message with modified nonce", bad_nonce);
×
524
            } catch(Botan::Integrity_Failure&) {
4,029✔
525
               result.test_success("correctly rejected modified nonce");
4,029✔
526
               check_plaintext_cleared("plaintext zeroed after rejecting modified nonce", buf, input.size());
4,029✔
527
            } catch(std::exception& e) {
4,029✔
528
               result.test_failure("unexpected error while rejecting modified nonce", e.what());
×
529
            }
×
530
         }
4,029✔
531

532
         // test decryption with modified associated_data
533
         const std::vector<uint8_t> bad_ad = mutate_vec(ad, rng, true);
4,355✔
534

535
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
536
         dec->set_associated_data(bad_ad);
4,355✔
537

538
         dec->start(nonce);
4,355✔
539

540
         try {
4,355✔
541
            buf.assign(input.begin(), input.end());
4,355✔
542
            dec->finish(buf);
4,355✔
543
            result.test_failure("accepted message with modified ad", bad_ad);
×
544
         } catch(Botan::Integrity_Failure&) {
4,355✔
545
            result.test_success("correctly rejected modified ad");
4,355✔
546
            check_plaintext_cleared("plaintext zeroed after rejecting modified ad", buf, input.size());
4,355✔
547
         } catch(std::exception& e) {
4,355✔
548
            result.test_failure("unexpected error while rejecting modified nonce", e.what());
×
549
         }
×
550

551
         // Verify that the mode checks `start` is called prior to `update`.
552
         // The state check must fire before the buffer is mutated.
553
         if(!is_siv) {
4,355✔
554
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,033✔
555
            dec->set_associated_data(ad);
4,033✔
556
            dec->start(nonce);
4,033✔
557
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,033✔
558
            Botan::secure_vector<uint8_t> tmp(input.begin(), input.end());
4,033✔
559
            const Botan::secure_vector<uint8_t> tmp_orig = tmp;
4,033✔
560
            result.test_throws<Botan::Invalid_State>("finish after reset without start throws (dec)",
4,033✔
561
                                                     [&]() { dec->finish(tmp); });
8,066✔
562
            result.test_bin_eq("finish after reset leaves input buffer unmodified (dec)", tmp, tmp_orig);
4,033✔
563
         }
8,066✔
564

565
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
566
         {
4,355✔
567
            const size_t max_ad = dec->maximum_associated_data_inputs();
4,355✔
568
            if(max_ad > 0) {
4,355✔
569
               result.test_throws<Botan::Invalid_Argument>("set_associated_data_n rejects idx == max (dec)",
4,355✔
570
                                                           [&]() { dec->set_associated_data_n(max_ad, ad); });
13,065✔
571
            }
572
         }
573

574
         // Ensure that finish offsets are checked
575
         {
4,355✔
576
            dec->set_associated_data(ad);
4,355✔
577
            dec->start(nonce);
4,355✔
578
            result.test_throws<Botan::Invalid_Argument>("finish with offset > size throws (dec)", [&]() {
4,355✔
579
               Botan::secure_vector<uint8_t> tmp(input.begin(), input.end());
4,355✔
580
               dec->finish(tmp, tmp.size() + 1);
4,355✔
581
            });
×
582
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
583
         }
584

585
         // Make sure we can set the AD after processing a message
586
         dec->set_associated_data(ad);
4,355✔
587
         dec->clear();
4,355✔
588
         result.test_is_false("key is not set", dec->has_keying_material());
4,355✔
589

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

592
         if(dec->associated_data_requires_key()) {
4,355✔
593
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD",
779✔
594
                                                     [&]() { dec->set_associated_data(ad.data(), ad.size()); });
1,558✔
595
         }
596

597
         // Regression test: modes that advertise associated_data_requires_key()
598
         // == false must retain AD set before keying. dec was just cleared
599
         // above so it is in an unkeyed state ready for this check.
600
         if(!dec->associated_data_requires_key()) {
4,355✔
601
            dec->set_associated_data(ad);
3,576✔
602
            dec->set_key(key);
3,576✔
603
            dec->start(nonce);
3,576✔
604
            Botan::secure_vector<uint8_t> tmp(input.begin(), input.end());
3,576✔
605
            try {
3,576✔
606
               dec->finish(tmp);
3,576✔
607
               result.test_bin_eq("AD set before key is retained (dec)", tmp, expected);
3,576✔
608
            } catch(Botan::Exception& e) {
×
609
               result.test_failure("decrypt with AD set pre-key failed", e.what());
×
610
            }
×
611
         }
3,576✔
612

613
         return result;
4,355✔
614
      }
21,775✔
615

616
      Test::Result run_one_test(const std::string& algo, const VarMap& vars) override {
4,355✔
617
         const std::vector<uint8_t> key = vars.get_req_bin("Key");
4,355✔
618
         const std::vector<uint8_t> nonce = vars.get_opt_bin("Nonce");
4,355✔
619
         const std::vector<uint8_t> input = vars.get_req_bin("In");
4,355✔
620
         const std::vector<uint8_t> expected = vars.get_req_bin("Out");
4,355✔
621
         const std::vector<uint8_t> ad = vars.get_opt_bin("AD");
4,355✔
622

623
         Test::Result result(algo);
4,355✔
624

625
         auto enc = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption);
4,355✔
626
         auto dec = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Decryption);
4,355✔
627

628
         if(!enc || !dec) {
4,355✔
629
            result.note_missing(algo);
×
630
            return result;
631
         }
632

633
         // must be authenticated
634
         result.test_is_true("Encryption algo is an authenticated mode", enc->authenticated());
4,355✔
635
         result.test_is_true("Decryption algo is an authenticated mode", dec->authenticated());
4,355✔
636

637
         const std::string enc_provider = enc->provider();
4,355✔
638
         result.test_str_not_empty("enc provider", enc_provider);
4,355✔
639
         const std::string dec_provider = dec->provider();
4,355✔
640
         result.test_str_not_empty("dec provider", dec_provider);
4,355✔
641

642
         result.test_str_eq("same provider", enc_provider, dec_provider);
4,355✔
643

644
         // FFI currently requires this, so assure it is true for all modes
645
         result.test_sz_gt("enc buffer sizes ok", enc->ideal_granularity(), enc->minimum_final_size());
4,355✔
646
         result.test_sz_gt("dec buffer sizes ok", dec->ideal_granularity(), dec->minimum_final_size());
4,355✔
647

648
         result.test_sz_gt("update granularity is non-zero", enc->update_granularity(), 0);
4,355✔
649

650
         result.test_sz_eq(
4,355✔
651
            "enc and dec ideal granularity is the same", enc->ideal_granularity(), dec->ideal_granularity());
4,355✔
652

653
         result.test_sz_gt(
4,355✔
654
            "ideal granularity is at least update granularity", enc->ideal_granularity(), enc->update_granularity());
4,355✔
655

656
         result.test_is_true("ideal granularity is a multiple of update granularity",
4,355✔
657
                             enc->ideal_granularity() % enc->update_granularity() == 0);
4,355✔
658

659
         // test enc
660
         result.merge(test_enc(key, nonce, input, expected, ad, algo, this->rng()));
4,355✔
661

662
         // test dec
663
         // NOLINTNEXTLINE(*-suspicious-call-argument) Yes we are swapping ptext and ctext arguments here
664
         result.merge(test_dec(key, nonce, expected, input, ad, algo, this->rng()));
4,355✔
665

666
         return result;
4,355✔
667
      }
34,397✔
668
};
669

670
BOTAN_REGISTER_SERIALIZED_SMOKE_TEST("modes", "aead", AEAD_Tests);
671

672
#endif
673

674
}  // namespace
675

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