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

randombit / botan / 26323460081

23 May 2026 12:00AM UTC coverage: 89.383% (+0.03%) from 89.349%
26323460081

push

github

web-flow
Merge pull request #5621 from randombit/jack/cli-port-shift

Move the cli test TCP/UDP port range downward

109787 of 122827 relevant lines covered (89.38%)

11032030.9 hits per line

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

93.16
/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
         // test decryption with modified ciphertext
478
         const std::vector<uint8_t> mutated_input = mutate_vec(input, rng, true);
4,355✔
479
         buf.assign(mutated_input.begin(), mutated_input.end());
4,355✔
480

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

483
         dec->set_associated_data(ad);
4,355✔
484
         dec->start(nonce);
4,355✔
485

486
         try {
4,355✔
487
            dec->finish(buf);
4,355✔
488
            result.test_failure("accepted modified message", mutated_input);
×
489
         } catch(Botan::Integrity_Failure&) {
4,355✔
490
            result.test_success("correctly rejected modified message");
4,355✔
491
         } catch(std::exception& e) {
4,355✔
492
            result.test_failure("unexpected error while rejecting modified message", e.what());
×
493
         }
×
494

495
         // test decryption with modified nonce
496
         if(!nonce.empty()) {
4,355✔
497
            buf.assign(input.begin(), input.end());
4,029✔
498
            std::vector<uint8_t> bad_nonce = mutate_vec(nonce, rng);
4,029✔
499

500
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,029✔
501
            dec->set_associated_data(ad);
4,029✔
502
            dec->start(bad_nonce);
4,029✔
503

504
            try {
4,029✔
505
               dec->finish(buf);
4,029✔
506
               result.test_failure("accepted message with modified nonce", bad_nonce);
×
507
            } catch(Botan::Integrity_Failure&) {
4,029✔
508
               result.test_success("correctly rejected modified nonce");
4,029✔
509
            } catch(std::exception& e) {
4,029✔
510
               result.test_failure("unexpected error while rejecting modified nonce", e.what());
×
511
            }
×
512
         }
4,029✔
513

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

517
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
518
         dec->set_associated_data(bad_ad);
4,355✔
519

520
         dec->start(nonce);
4,355✔
521

522
         try {
4,355✔
523
            buf.assign(input.begin(), input.end());
4,355✔
524
            dec->finish(buf);
4,355✔
525
            result.test_failure("accepted message with modified ad", bad_ad);
×
526
         } catch(Botan::Integrity_Failure&) {
4,355✔
527
            result.test_success("correctly rejected modified ad");
4,355✔
528
         } catch(std::exception& e) {
4,355✔
529
            result.test_failure("unexpected error while rejecting modified nonce", e.what());
×
530
         }
×
531

532
         // Verify that the mode checks `start` is called prior to `update`.
533
         // The state check must fire before the buffer is mutated.
534
         if(!is_siv) {
4,355✔
535
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,033✔
536
            dec->set_associated_data(ad);
4,033✔
537
            dec->start(nonce);
4,033✔
538
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,033✔
539
            Botan::secure_vector<uint8_t> tmp(input.begin(), input.end());
4,033✔
540
            const Botan::secure_vector<uint8_t> tmp_orig = tmp;
4,033✔
541
            result.test_throws<Botan::Invalid_State>("finish after reset without start throws (dec)",
4,033✔
542
                                                     [&]() { dec->finish(tmp); });
8,066✔
543
            result.test_bin_eq("finish after reset leaves input buffer unmodified (dec)", tmp, tmp_orig);
4,033✔
544
         }
8,066✔
545

546
         dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
547
         {
4,355✔
548
            const size_t max_ad = dec->maximum_associated_data_inputs();
4,355✔
549
            if(max_ad > 0) {
4,355✔
550
               result.test_throws<Botan::Invalid_Argument>("set_associated_data_n rejects idx == max (dec)",
4,355✔
551
                                                           [&]() { dec->set_associated_data_n(max_ad, ad); });
13,065✔
552
            }
553
         }
554

555
         // Ensure that finish offsets are checked
556
         {
4,355✔
557
            dec->set_associated_data(ad);
4,355✔
558
            dec->start(nonce);
4,355✔
559
            result.test_throws<Botan::Invalid_Argument>("finish with offset > size throws (dec)", [&]() {
4,355✔
560
               Botan::secure_vector<uint8_t> tmp(input.begin(), input.end());
4,355✔
561
               dec->finish(tmp, tmp.size() + 1);
4,355✔
562
            });
×
563
            dec->reset();  // NOLINT(*-ambiguous-smartptr-reset-call)
4,355✔
564
         }
565

566
         // Make sure we can set the AD after processing a message
567
         dec->set_associated_data(ad);
4,355✔
568
         dec->clear();
4,355✔
569
         result.test_is_false("key is not set", dec->has_keying_material());
4,355✔
570

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

573
         if(dec->associated_data_requires_key()) {
4,355✔
574
            result.test_throws<Botan::Invalid_State>("Unkeyed object throws for set AD",
779✔
575
                                                     [&]() { dec->set_associated_data(ad.data(), ad.size()); });
1,558✔
576
         }
577

578
         // Regression test: modes that advertise associated_data_requires_key()
579
         // == false must retain AD set before keying. dec was just cleared
580
         // above so it is in an unkeyed state ready for this check.
581
         if(!dec->associated_data_requires_key()) {
4,355✔
582
            dec->set_associated_data(ad);
3,576✔
583
            dec->set_key(key);
3,576✔
584
            dec->start(nonce);
3,576✔
585
            Botan::secure_vector<uint8_t> tmp(input.begin(), input.end());
3,576✔
586
            try {
3,576✔
587
               dec->finish(tmp);
3,576✔
588
               result.test_bin_eq("AD set before key is retained (dec)", tmp, expected);
3,576✔
589
            } catch(Botan::Exception& e) {
×
590
               result.test_failure("decrypt with AD set pre-key failed", e.what());
×
591
            }
×
592
         }
3,576✔
593

594
         return result;
4,355✔
595
      }
21,775✔
596

597
      Test::Result run_one_test(const std::string& algo, const VarMap& vars) override {
4,355✔
598
         const std::vector<uint8_t> key = vars.get_req_bin("Key");
4,355✔
599
         const std::vector<uint8_t> nonce = vars.get_opt_bin("Nonce");
4,355✔
600
         const std::vector<uint8_t> input = vars.get_req_bin("In");
4,355✔
601
         const std::vector<uint8_t> expected = vars.get_req_bin("Out");
4,355✔
602
         const std::vector<uint8_t> ad = vars.get_opt_bin("AD");
4,355✔
603

604
         Test::Result result(algo);
4,355✔
605

606
         auto enc = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption);
4,355✔
607
         auto dec = Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Decryption);
4,355✔
608

609
         if(!enc || !dec) {
4,355✔
610
            result.note_missing(algo);
×
611
            return result;
612
         }
613

614
         // must be authenticated
615
         result.test_is_true("Encryption algo is an authenticated mode", enc->authenticated());
4,355✔
616
         result.test_is_true("Decryption algo is an authenticated mode", dec->authenticated());
4,355✔
617

618
         const std::string enc_provider = enc->provider();
4,355✔
619
         result.test_str_not_empty("enc provider", enc_provider);
4,355✔
620
         const std::string dec_provider = dec->provider();
4,355✔
621
         result.test_str_not_empty("dec provider", dec_provider);
4,355✔
622

623
         result.test_str_eq("same provider", enc_provider, dec_provider);
4,355✔
624

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

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

631
         result.test_sz_eq(
4,355✔
632
            "enc and dec ideal granularity is the same", enc->ideal_granularity(), dec->ideal_granularity());
4,355✔
633

634
         result.test_sz_gt(
4,355✔
635
            "ideal granularity is at least update granularity", enc->ideal_granularity(), enc->update_granularity());
4,355✔
636

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

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

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

647
         return result;
4,355✔
648
      }
34,397✔
649
};
650

651
BOTAN_REGISTER_SERIALIZED_SMOKE_TEST("modes", "aead", AEAD_Tests);
652

653
#endif
654

655
}  // namespace
656

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