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

randombit / botan / 15376680397

01 Jun 2025 03:32PM UTC coverage: 90.968% (-0.009%) from 90.977%
15376680397

push

github

web-flow
Merge pull request #4895 from reneme/fix/test_assumption_of_potential_side_effect

98094 of 107834 relevant lines covered (90.97%)

12543271.31 hits per line

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

91.97
/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()); };
10,799✔
213

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

468
#endif
469

470
}  // namespace
471

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