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

randombit / botan / 16225201637

11 Jul 2025 04:45PM UTC coverage: 90.583% (+0.009%) from 90.574%
16225201637

Pull #4880

github

web-flow
Merge 3a965d6b5 into 8cd70aa22
Pull Request #4880: Refactor: Cipher_Mode::finish_msg() using std::span

99158 of 109466 relevant lines covered (90.58%)

12399154.95 hits per line

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

92.0
/src/tests/test_aead.cpp
1
/*
2
* (C) 2014,2015,2016,2018 Jack Lloyd
3
* (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#if defined(BOTAN_HAS_AEAD_MODES)
11
   #include <botan/aead.h>
12
   #include <botan/mem_ops.h>
13
#endif
14

15
namespace Botan_Tests {
16

17
namespace {
18

19
#if defined(BOTAN_HAS_AEAD_MODES)
20

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

86
         // reset message specific state
87
         enc->reset();
2,353✔
88

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

187
         result.test_throws("Unkeyed object throws for encrypt after clear", [&]() { enc->finish(buf); });
7,059✔
188

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

194
         return result;
2,353✔
195
      }
7,059✔
196

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

316
               buf.assign(input.begin(), input.end());
2,218✔
317

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

411
         return result;
2,353✔
412
      }
11,765✔
413

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

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

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

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

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

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

440
         result.test_eq("same provider", enc_provider, dec_provider);
2,353✔
441

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

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

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

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

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

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

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

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

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

469
#endif
470

471
}  // namespace
472

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