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

randombit / botan / 12800428018

16 Jan 2025 01:44AM UTC coverage: 91.191% (-0.003%) from 91.194%
12800428018

Pull #4555

github

web-flow
Merge 26491cefe into 52e0661e2
Pull Request #4555: Remove the workspace argument to various ECC interfaces

93328 of 102343 relevant lines covered (91.19%)

11502783.22 hits per line

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

91.61
/src/cli/timing_tests.cpp
1
/*
2
* Timing Analysis Tests
3
*
4
* These tests are not for performance, but verifying that two inputs are not handled
5
* in a way that is vulnerable to simple timing attacks.
6
*
7
* Produces output which can be analyzed with the Mona reporting library
8
*
9
* $ git clone https://github.com/seecurity/mona-timing-report.git
10
* $ cd mona-timing-report && ant
11
* $ java -jar ReportingTool.jar --lowerBound=0.4 --upperBound=0.5 --inputFile=$file --name=$file
12
*
13
* (C) 2016 Juraj Somorovsky - juraj.somorovsky@hackmanit.de
14
* (C) 2017 Neverhub
15
* (C) 2017,2018,2019 Jack Lloyd
16
*
17
* Botan is released under the Simplified BSD License (see license.txt)
18
*/
19

20
#include "cli.h"
21

22
#include <botan/hex.h>
23
#include <botan/rng.h>
24
#include <botan/internal/filesystem.h>
25
#include <botan/internal/fmt.h>
26
#include <botan/internal/loadstor.h>
27
#include <botan/internal/parsing.h>
28
#include <chrono>
29
#include <fstream>
30
#include <sstream>
31

32
#if defined(BOTAN_HAS_BIGINT)
33
   #include <botan/bigint.h>
34
#endif
35

36
#if defined(BOTAN_HAS_NUMBERTHEORY)
37
   #include <botan/numthry.h>
38
#endif
39

40
#if defined(BOTAN_HAS_ECC_GROUP)
41
   #include <botan/ec_group.h>
42
#endif
43

44
#if defined(BOTAN_HAS_DL_GROUP)
45
   #include <botan/dl_group.h>
46
#endif
47

48
#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO)
49
   #include <botan/pkcs8.h>
50
   #include <botan/pubkey.h>
51
#endif
52

53
#if defined(BOTAN_HAS_RSA)
54
   #include <botan/rsa.h>
55
#endif
56

57
#if defined(BOTAN_HAS_TLS_CBC)
58
   #include <botan/tls_exceptn.h>
59
   #include <botan/internal/tls_cbc.h>
60
#endif
61

62
#if defined(BOTAN_HAS_ECDSA)
63
   #include <botan/ecdsa.h>
64
#endif
65

66
namespace Botan_CLI {
67

68
namespace {
69

70
class TimingTestTimer {
71
   public:
72
      TimingTestTimer() { m_start = get_high_resolution_clock(); }
1,282✔
73

74
      uint64_t complete() const { return get_high_resolution_clock() - m_start; }
1,282✔
75

76
   private:
77
      static uint64_t get_high_resolution_clock() {
1,282✔
78
         // TODO use cpu clock where possible/relevant incl serializing instructions
79
         auto now = std::chrono::high_resolution_clock::now().time_since_epoch();
1,206✔
80
         return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
1,282✔
81
      }
82

83
      uint64_t m_start;
84
};
85

86
}  // namespace
87

88
class Timing_Test {
89
   public:
90
      Timing_Test() {
10✔
91
         /*
92
         A constant seed is ok here since the timing test rng just needs to be
93
         "random" but not cryptographically secure - even std::rand() would be ok.
94
         */
95
         const std::string drbg_seed(64, 'A');
10✔
96
         m_rng = cli_make_rng("", drbg_seed);  // throws if it can't find anything to use
10✔
97
      }
10✔
98

99
      virtual ~Timing_Test() = default;
×
100

101
      Timing_Test(const Timing_Test& other) = delete;
102
      Timing_Test(Timing_Test&& other) = delete;
103
      Timing_Test& operator=(const Timing_Test& other) = delete;
104
      Timing_Test& operator=(Timing_Test&& other) = delete;
105

106
      std::vector<std::vector<uint64_t>> execute_evaluation(const std::vector<std::string>& inputs,
107
                                                            size_t warmup_runs,
108
                                                            size_t measurement_runs);
109

110
      virtual std::vector<uint8_t> prepare_input(const std::string& input) { return Botan::hex_decode(input); }
9✔
111

112
      virtual uint64_t measure_critical_function(const std::vector<uint8_t>& input) = 0;
113

114
   protected:
115
      Botan::RandomNumberGenerator& timing_test_rng() { return (*m_rng); }
2✔
116

117
   private:
118
      std::shared_ptr<Botan::RandomNumberGenerator> m_rng;
119
};
120

121
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_HAS_EME_RAW)
122

123
class Bleichenbacker_Timing_Test final : public Timing_Test {
×
124
   public:
125
      explicit Bleichenbacker_Timing_Test(size_t keysize) :
1✔
126
            m_privkey(timing_test_rng(), keysize),
1✔
127
            m_pubkey(m_privkey),
1✔
128
            m_enc(m_pubkey, timing_test_rng(), "Raw"),
1✔
129
            m_dec(m_privkey, timing_test_rng(), "PKCS1v15") {}
2✔
130

131
      std::vector<uint8_t> prepare_input(const std::string& input) override {
4✔
132
         const std::vector<uint8_t> input_vector = Botan::hex_decode(input);
4✔
133
         return m_enc.encrypt(input_vector, timing_test_rng());
4✔
134
      }
4✔
135

136
      uint64_t measure_critical_function(const std::vector<uint8_t>& input) override {
76✔
137
         TimingTestTimer timer;
76✔
138
         m_dec.decrypt_or_random(input.data(), m_ctext_length, m_expected_content_size, timing_test_rng());
76✔
139
         return timer.complete();
76✔
140
      }
141

142
   private:
143
      const size_t m_expected_content_size = 48;
144
      const size_t m_ctext_length = 256;
145
      Botan::RSA_PrivateKey m_privkey;
146
      Botan::RSA_PublicKey m_pubkey;
147
      Botan::PK_Encryptor_EME m_enc;
148
      Botan::PK_Decryptor_EME m_dec;
149
};
150

151
#endif
152

153
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_OAEP) && defined(BOTAN_HAS_EME_RAW)
154

155
/*
156
* Test Manger OAEP side channel
157
*
158
* "A Chosen Ciphertext Attack on RSA Optimal Asymmetric Encryption
159
* Padding (OAEP) as Standardized in PKCS #1 v2.0" James Manger
160
* http://archiv.infsec.ethz.ch/education/fs08/secsem/Manger01.pdf
161
*/
162
class Manger_Timing_Test final : public Timing_Test {
×
163
   public:
164
      explicit Manger_Timing_Test(size_t keysize) :
1✔
165
            m_privkey(timing_test_rng(), keysize),
1✔
166
            m_pubkey(m_privkey),
1✔
167
            m_enc(m_pubkey, timing_test_rng(), m_encrypt_padding),
1✔
168
            m_dec(m_privkey, timing_test_rng(), m_decrypt_padding) {}
2✔
169

170
      std::vector<uint8_t> prepare_input(const std::string& input) override {
2✔
171
         const std::vector<uint8_t> input_vector = Botan::hex_decode(input);
2✔
172
         return m_enc.encrypt(input_vector, timing_test_rng());
2✔
173
      }
2✔
174

175
      uint64_t measure_critical_function(const std::vector<uint8_t>& input) override {
38✔
176
         TimingTestTimer timer;
38✔
177
         try {
38✔
178
            m_dec.decrypt(input.data(), m_ctext_length);
38✔
179
         } catch(Botan::Decoding_Error&) {}
38✔
180
         return timer.complete();
38✔
181
      }
182

183
   private:
184
      const std::string m_encrypt_padding = "Raw";
185
      const std::string m_decrypt_padding = "EME1(SHA-256)";
186
      const size_t m_ctext_length = 256;
187
      Botan::RSA_PrivateKey m_privkey;
188
      Botan::RSA_PublicKey m_pubkey;
189
      Botan::PK_Encryptor_EME m_enc;
190
      Botan::PK_Decryptor_EME m_dec;
191
};
192

193
#endif
194

195
#if defined(BOTAN_HAS_TLS_CBC)
196

197
/*
198
* Test handling of countermeasure to the Lucky13 attack
199
*/
200
class Lucky13_Timing_Test final : public Timing_Test {
×
201
   public:
202
      Lucky13_Timing_Test(const std::string& mac_name, size_t mac_keylen) :
4✔
203
            m_mac_algo(mac_name),
8✔
204
            m_mac_keylen(mac_keylen),
4✔
205
            m_dec(Botan::BlockCipher::create_or_throw("AES-128"),
12✔
206
                  Botan::MessageAuthenticationCode::create_or_throw("HMAC(" + m_mac_algo + ")"),
12✔
207
                  16,
208
                  m_mac_keylen,
4✔
209
                  Botan::TLS::Protocol_Version::TLS_V12,
210
                  false) {}
8✔
211

212
      std::vector<uint8_t> prepare_input(const std::string& input) override;
213
      uint64_t measure_critical_function(const std::vector<uint8_t>& input) override;
214

215
   private:
216
      const std::string m_mac_algo;
217
      const size_t m_mac_keylen;
218
      Botan::TLS::TLS_CBC_HMAC_AEAD_Decryption m_dec;
219
};
220

221
std::vector<uint8_t> Lucky13_Timing_Test::prepare_input(const std::string& input) {
12✔
222
   const std::vector<uint8_t> input_vector = Botan::hex_decode(input);
12✔
223
   const std::vector<uint8_t> key(16);
12✔
224
   const std::vector<uint8_t> iv(16);
12✔
225

226
   auto enc = Botan::Cipher_Mode::create("AES-128/CBC/NoPadding", Botan::Cipher_Dir::Encryption);
12✔
227
   enc->set_key(key);
12✔
228
   enc->start(iv);
12✔
229
   Botan::secure_vector<uint8_t> buf(input_vector.begin(), input_vector.end());
12✔
230
   enc->finish(buf);
12✔
231

232
   return unlock(buf);
12✔
233
}
60✔
234

235
uint64_t Lucky13_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
228✔
236
   Botan::secure_vector<uint8_t> data(input.begin(), input.end());
228✔
237
   Botan::secure_vector<uint8_t> aad(13);
228✔
238
   const Botan::secure_vector<uint8_t> iv(16);
228✔
239
   Botan::secure_vector<uint8_t> key(16 + m_mac_keylen);
228✔
240

241
   m_dec.set_key(unlock(key));
228✔
242
   m_dec.set_associated_data(aad);
228✔
243
   m_dec.start(unlock(iv));
228✔
244

245
   TimingTestTimer timer;
228✔
246
   try {
228✔
247
      m_dec.finish(data);
228✔
248
   } catch(Botan::TLS::TLS_Exception&) {}
228✔
249
   return timer.complete();
228✔
250
}
912✔
251

252
#endif
253

254
#if defined(BOTAN_HAS_ECDSA)
255

256
class ECDSA_Timing_Test final : public Timing_Test {
×
257
   public:
258
      explicit ECDSA_Timing_Test(const std::string& ecgroup);
259

260
      uint64_t measure_critical_function(const std::vector<uint8_t>& input) override;
261

262
   private:
263
      const Botan::EC_Group m_group;
264
      const Botan::ECDSA_PrivateKey m_privkey;
265
      const Botan::EC_Scalar m_x;
266
      Botan::EC_Scalar m_b;
267
      Botan::EC_Scalar m_b_inv;
268
};
269

270
ECDSA_Timing_Test::ECDSA_Timing_Test(const std::string& ecgroup) :
1✔
271
      m_group(Botan::EC_Group::from_name(ecgroup)),
1✔
272
      m_privkey(timing_test_rng(), m_group),
1✔
273
      m_x(m_privkey._private_key()),
1✔
274
      m_b(Botan::EC_Scalar::random(m_group, timing_test_rng())),
1✔
275
      m_b_inv(m_b.invert()) {}
2✔
276

277
uint64_t ECDSA_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
38✔
278
   const auto k = Botan::EC_Scalar::from_bytes_with_trunc(m_group, input);
38✔
279
   // fixed message to minimize noise
280
   const auto m = Botan::EC_Scalar::from_bytes_with_trunc(m_group, std::vector<uint8_t>{5});
38✔
281

282
   TimingTestTimer timer;
38✔
283

284
   // the following ECDSA operations involve and should not leak any information about k
285
   const auto r = Botan::EC_Scalar::gk_x_mod_order(k, timing_test_rng());
38✔
286
   const auto k_inv = k.invert();
38✔
287
   m_b.square_self();
38✔
288
   m_b_inv.square_self();
38✔
289
   const auto xr_m = ((m_x * m_b) * r) + (m * m_b);
38✔
290
   const auto s = (k_inv * xr_m) * m_b_inv;
38✔
291
   BOTAN_UNUSED(r, s);
38✔
292

293
   return timer.complete();
76✔
294
}
38✔
295

296
#endif
297

298
#if defined(BOTAN_HAS_ECC_GROUP)
299

300
class ECC_Mul_Timing_Test final : public Timing_Test {
×
301
   public:
302
      explicit ECC_Mul_Timing_Test(std::string_view ecgroup) : m_group(Botan::EC_Group::from_name(ecgroup)) {}
1✔
303

304
      uint64_t measure_critical_function(const std::vector<uint8_t>& input) override;
305

306
   private:
307
      const Botan::EC_Group m_group;
308
};
309

310
uint64_t ECC_Mul_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
38✔
311
   const auto k = Botan::EC_Scalar::from_bytes_with_trunc(m_group, input);
38✔
312

313
   TimingTestTimer timer;
38✔
314
   const auto kG = Botan::EC_AffinePoint::g_mul(k, timing_test_rng());
38✔
315
   return timer.complete();
76✔
316
}
38✔
317

318
#endif
319

320
#if defined(BOTAN_HAS_DL_GROUP)
321

322
class Powmod_Timing_Test final : public Timing_Test {
×
323
   public:
324
      explicit Powmod_Timing_Test(std::string_view dl_group) : m_group(dl_group) {}
1✔
325

326
      uint64_t measure_critical_function(const std::vector<uint8_t>& input) override;
327

328
   private:
329
      Botan::DL_Group m_group;
330
};
331

332
uint64_t Powmod_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
57✔
333
   const Botan::BigInt x(input.data(), input.size());
57✔
334
   const size_t max_x_bits = m_group.p_bits();
57✔
335

336
   TimingTestTimer timer;
57✔
337

338
   const Botan::BigInt g_x_p = m_group.power_g_p(x, max_x_bits);
57✔
339

340
   return timer.complete();
57✔
341
}
114✔
342

343
#endif
344

345
#if defined(BOTAN_HAS_NUMBERTHEORY)
346

347
class Invmod_Timing_Test final : public Timing_Test {
×
348
   public:
349
      explicit Invmod_Timing_Test(size_t p_bits) { m_p = Botan::random_prime(timing_test_rng(), p_bits); }
2✔
350

351
      uint64_t measure_critical_function(const std::vector<uint8_t>& input) override;
352

353
   private:
354
      Botan::BigInt m_p;
355
};
356

357
uint64_t Invmod_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
38✔
358
   const Botan::BigInt k(input.data(), input.size());
38✔
359

360
   TimingTestTimer timer;
38✔
361
   const Botan::BigInt inv = inverse_mod(k, m_p);
38✔
362
   return timer.complete();
38✔
363
}
76✔
364

365
#endif
366

367
std::vector<std::vector<uint64_t>> Timing_Test::execute_evaluation(const std::vector<std::string>& raw_inputs,
10✔
368
                                                                   size_t warmup_runs,
369
                                                                   size_t measurement_runs) {
370
   std::vector<std::vector<uint64_t>> all_results(raw_inputs.size());
10✔
371
   std::vector<std::vector<uint8_t>> inputs(raw_inputs.size());
10✔
372

373
   for(auto& result : all_results) {
37✔
374
      result.reserve(measurement_runs);
27✔
375
   }
376

377
   for(size_t i = 0; i != inputs.size(); ++i) {
37✔
378
      inputs[i] = prepare_input(raw_inputs[i]);
27✔
379
   }
380

381
   // arbitrary upper bounds of 1 and 10 million resp
382
   if(warmup_runs > 1000000 || measurement_runs > 100000000) {
10✔
383
      throw CLI_Error("Requested execution counts too large, rejecting");
×
384
   }
385

386
   size_t total_runs = 0;
10✔
387
   std::vector<uint64_t> results(inputs.size());
10✔
388

389
   while(total_runs < (warmup_runs + measurement_runs)) {
200✔
390
      for(size_t i = 0; i != inputs.size(); ++i) {
703✔
391
         results[i] = measure_critical_function(inputs[i]);
513✔
392
      }
393

394
      total_runs++;
190✔
395

396
      if(total_runs > warmup_runs) {
190✔
397
         for(size_t i = 0; i != results.size(); ++i) {
592✔
398
            all_results[i].push_back(results[i]);
432✔
399
         }
400
      }
401
   }
402

403
   return all_results;
10✔
404
}
10✔
405

406
class Timing_Test_Command final : public Command {
407
   public:
408
      Timing_Test_Command() :
11✔
409
            Command(
410
               "timing_test test_type --test-data-file= --test-data-dir=src/tests/data/timing "
411
               "--warmup-runs=5000 --measurement-runs=50000") {}
22✔
412

413
      std::string group() const override { return "testing"; }
1✔
414

415
      std::string description() const override { return "Run various timing side channel tests"; }
1✔
416

417
      void go() override {
10✔
418
         const std::string test_type = get_arg("test_type");
10✔
419
         const size_t warmup_runs = get_arg_sz("warmup-runs");
10✔
420
         const size_t measurement_runs = get_arg_sz("measurement-runs");
10✔
421

422
         std::unique_ptr<Timing_Test> test = lookup_timing_test(test_type);
10✔
423

424
         if(!test) {
10✔
425
            throw CLI_Error("Unknown or unavailable test type '" + test_type + "'");
×
426
         }
427

428
         std::string filename = get_arg_or("test-data-file", "");
20✔
429

430
         if(filename.empty()) {
10✔
431
            const std::string test_data_dir = get_arg("test-data-dir");
10✔
432
            filename = test_data_dir + "/" + test_type + ".vec";
20✔
433
         }
10✔
434

435
         std::vector<std::string> lines = read_testdata(filename);
10✔
436

437
         std::vector<std::vector<uint64_t>> results = test->execute_evaluation(lines, warmup_runs, measurement_runs);
10✔
438

439
         size_t unique_id = 0;
10✔
440
         std::ostringstream oss;
10✔
441
         for(size_t secret_id = 0; secret_id != results.size(); ++secret_id) {
37✔
442
            for(size_t i = 0; i != results[secret_id].size(); ++i) {
459✔
443
               oss << unique_id++ << ";" << secret_id << ";" << results[secret_id][i] << "\n";
432✔
444
            }
445
         }
446

447
         output() << oss.str();
10✔
448
      }
20✔
449

450
   private:
451
      static std::vector<std::string> read_testdata(const std::string& filename) {
10✔
452
         std::vector<std::string> lines;
10✔
453
         std::ifstream infile(filename);
10✔
454
         if(infile.good() == false) {
10✔
455
            throw CLI_Error("Error reading test data from '" + filename + "'");
×
456
         }
457
         std::string line;
10✔
458
         while(std::getline(infile, line)) {
70✔
459
            if(!line.empty() && line.at(0) != '#') {
60✔
460
               lines.push_back(line);
27✔
461
            }
462
         }
463
         return lines;
20✔
464
      }
10✔
465

466
      static std::unique_ptr<Timing_Test> lookup_timing_test(std::string_view test_type);
467

468
      std::string help_text() const override {
×
469
         // TODO check feature macros
470
         return (Command::help_text() +
×
471
                 "\ntest_type can take on values "
472
                 "bleichenbacher "
473
                 "manger "
474
                 "ecdsa "
475
                 "ecc_mul "
476
                 "inverse_mod "
477
                 "pow_mod "
478
                 "lucky13sec3 "
479
                 "lucky13sec4sha1 "
480
                 "lucky13sec4sha256 "
481
                 "lucky13sec4sha384 ");
×
482
      }
483
};
484

485
std::unique_ptr<Timing_Test> Timing_Test_Command::lookup_timing_test(std::string_view test_type) {
10✔
486
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_HAS_EME_RAW)
487
   if(test_type == "bleichenbacher") {
10✔
488
      return std::make_unique<Bleichenbacker_Timing_Test>(2048);
1✔
489
   }
490
#endif
491

492
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_OAEP) && defined(BOTAN_HAS_EME_RAW)
493
   if(test_type == "manger") {
9✔
494
      return std::make_unique<Manger_Timing_Test>(2048);
1✔
495
   }
496
#endif
497

498
#if defined(BOTAN_HAS_ECDSA)
499
   if(test_type == "ecdsa") {
8✔
500
      return std::make_unique<ECDSA_Timing_Test>("secp384r1");
1✔
501
   }
502
#endif
503

504
#if defined(BOTAN_HAS_ECC_GROUP)
505
   if(test_type == "ecc_mul") {
7✔
506
      return std::make_unique<ECC_Mul_Timing_Test>("brainpool512r1");
1✔
507
   }
508
#endif
509

510
#if defined(BOTAN_HAS_NUMBERTHEORY)
511
   if(test_type == "inverse_mod") {
6✔
512
      return std::make_unique<Invmod_Timing_Test>(512);
1✔
513
   }
514
#endif
515

516
#if defined(BOTAN_HAS_DL_GROUP)
517
   if(test_type == "pow_mod") {
5✔
518
      return std::make_unique<Powmod_Timing_Test>("modp/ietf/1024");
1✔
519
   }
520
#endif
521

522
#if defined(BOTAN_HAS_TLS_CBC)
523
   if(test_type == "lucky13sec3" || test_type == "lucky13sec4sha1") {
6✔
524
      return std::make_unique<Lucky13_Timing_Test>("SHA-1", 20);
2✔
525
   }
526
   if(test_type == "lucky13sec4sha256") {
2✔
527
      return std::make_unique<Lucky13_Timing_Test>("SHA-256", 32);
1✔
528
   }
529
   if(test_type == "lucky13sec4sha384") {
1✔
530
      return std::make_unique<Lucky13_Timing_Test>("SHA-384", 48);
1✔
531
   }
532
#endif
533

534
   BOTAN_UNUSED(test_type);
×
535

536
   return nullptr;
×
537
}
538

539
BOTAN_REGISTER_COMMAND("timing_test", Timing_Test_Command);
11✔
540

541
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
542

543
class MARVIN_Test_Command final : public Command {
544
   public:
545
      MARVIN_Test_Command() : Command("marvin_test key_file ctext_dir --runs=10 --output-nsec --expect-pt-len=0") {}
4✔
546

547
      std::string group() const override { return "testing"; }
1✔
548

549
      std::string description() const override { return "Run a test for MARVIN attack"; }
1✔
550

551
      void go() override {
1✔
552
         const std::string key_file = get_arg("key_file");
1✔
553
         const std::string ctext_dir = get_arg("ctext_dir");
1✔
554
         const size_t measurement_runs = get_arg_sz("runs");
1✔
555
         const size_t expect_pt_len = get_arg_sz("expect-pt-len");
1✔
556
         const bool output_nsec = flag_set("output-nsec");
1✔
557

558
         Botan::DataSource_Stream key_src(key_file);
1✔
559
         const auto key = Botan::PKCS8::load_key(key_src);
1✔
560

561
         if(key->algo_name() != "RSA") {
1✔
562
            throw CLI_Usage_Error("Unexpected key type for MARVIN test");
×
563
         }
564

565
         const size_t modulus_bytes = (key->key_length() + 7) / 8;
1✔
566

567
         std::vector<std::string> names;
1✔
568
         std::vector<uint8_t> ciphertext_data;
1✔
569

570
         for(const auto& filename : Botan::get_files_recursive(ctext_dir)) {
5✔
571
            const auto contents = this->slurp_file(filename);
4✔
572

573
            if(contents.size() != modulus_bytes) {
4✔
574
               throw CLI_Usage_Error(
×
575
                  Botan::fmt("The ciphertext file {} had different size ({}) than the RSA modulus ({})",
×
576
                             filename,
577
                             contents.size(),
×
578
                             modulus_bytes));
×
579
            }
580

581
            const auto parts = Botan::split_on(filename, '/');
4✔
582

583
            names.push_back(parts[parts.size() - 1]);
4✔
584
            ciphertext_data.insert(ciphertext_data.end(), contents.begin(), contents.end());
4✔
585
         }
9✔
586

587
         if(names.empty()) {
1✔
588
            throw CLI_Usage_Error("Empty ciphertext directory for MARVIN test");
×
589
         }
590

591
         Botan::PK_Decryptor_EME op(*key, rng(), "PKCS1v15");
1✔
592

593
         std::vector<size_t> indexes;
1✔
594
         for(size_t i = 0; i != names.size(); ++i) {
5✔
595
            indexes.push_back(i);
4✔
596
         }
597

598
         std::vector<std::vector<uint64_t>> measurements(names.size());
1✔
599
         for(auto& m : measurements) {
5✔
600
            m.reserve(measurement_runs);
4✔
601
         }
602

603
         for(size_t r = 0; r != measurement_runs; ++r) {
33✔
604
            shuffle(indexes, rng());
32✔
605

606
            std::vector<uint8_t> ciphertext(modulus_bytes);
32✔
607
            for(size_t i = 0; i != indexes.size(); ++i) {
160✔
608
               const size_t testcase = indexes[i];
128✔
609

610
               // FIXME should this load be constant time?
611
               Botan::copy_mem(&ciphertext[0], &ciphertext_data[testcase * modulus_bytes], modulus_bytes);
128✔
612

613
               TimingTestTimer timer;
128✔
614
               op.decrypt_or_random(ciphertext.data(), modulus_bytes, expect_pt_len, rng());
128✔
615
               const uint64_t duration = timer.complete();
128✔
616
               BOTAN_ASSERT_NOMSG(measurements[testcase].size() == r);
128✔
617
               measurements[testcase].push_back(duration);
128✔
618
            }
619
         }
32✔
620

621
         for(size_t t = 0; t != names.size(); ++t) {
5✔
622
            if(t > 0) {
4✔
623
               output() << ",";
3✔
624
            }
625
            output() << names[t];
4✔
626
         }
627
         output() << "\n";
1✔
628

629
         for(size_t r = 0; r != measurement_runs; ++r) {
33✔
630
            for(size_t t = 0; t != names.size(); ++t) {
160✔
631
               if(t > 0) {
128✔
632
                  output() << ",";
96✔
633
               }
634

635
               const uint64_t dur_nsec = measurements[t][r];
128✔
636
               if(output_nsec) {
128✔
637
                  output() << dur_nsec;
×
638
               } else {
639
                  const double dur_s = static_cast<double>(dur_nsec) / 1000000000.0;
128✔
640
                  output() << dur_s;
128✔
641
               }
642
            }
643
            output() << "\n";
32✔
644
         }
645
      }
3✔
646

647
      template <typename T>
648
      void shuffle(std::vector<T>& vec, Botan::RandomNumberGenerator& rng) {
32✔
649
         const size_t n = vec.size();
32✔
650
         for(size_t i = 0; i != n; ++i) {
160✔
651
            uint8_t jb[sizeof(uint64_t)];
652
            rng.randomize(jb, sizeof(jb));
128✔
653
            uint64_t j8 = Botan::load_le<uint64_t>(jb, 0);
128✔
654
            size_t j = i + static_cast<size_t>(j8) % (n - i);
128✔
655
            std::swap(vec[i], vec[j]);
128✔
656
         }
657
      }
32✔
658
};
659

660
BOTAN_REGISTER_COMMAND("marvin_test", MARVIN_Test_Command);
2✔
661

662
#endif
663

664
}  // namespace Botan_CLI
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