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

randombit / botan / 6519894114

13 Oct 2023 09:13PM UTC coverage: 91.71% (+0.006%) from 91.704%
6519894114

push

github

web-flow
Merge pull request #3749 from randombit/jack/marvin-tool

Add a cli util for testing RSA using MARVIN

80097 of 87337 relevant lines covered (91.71%)

8549325.26 hits per line

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

91.73
/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/os_utils.h>
28
#include <botan/internal/parsing.h>
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
typedef uint64_t ticks;
69

70
class Timing_Test {
71
   public:
72
      Timing_Test() {
10✔
73
         /*
74
         A constant seed is ok here since the timing test rng just needs to be
75
         "random" but not cryptographically secure - even std::rand() would be ok.
76
         */
77
         const std::string drbg_seed(64, 'A');
10✔
78
         m_rng = cli_make_rng("", drbg_seed);  // throws if it can't find anything to use
10✔
79
      }
10✔
80

81
      virtual ~Timing_Test() = default;
×
82

83
      Timing_Test(const Timing_Test& other) = delete;
84
      Timing_Test(Timing_Test&& other) = delete;
85
      Timing_Test& operator=(const Timing_Test& other) = delete;
86
      Timing_Test& operator=(Timing_Test&& other) = delete;
87

88
      std::vector<std::vector<ticks>> execute_evaluation(const std::vector<std::string>& inputs,
89
                                                         size_t warmup_runs,
90
                                                         size_t measurement_runs);
91

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

94
      virtual ticks measure_critical_function(const std::vector<uint8_t>& input) = 0;
95

96
   protected:
97
      static ticks get_ticks() {
1,026✔
98
         // Returns CPU counter or best approximation (monotonic clock of some kind)
99
         //return Botan::OS::get_high_resolution_clock();
100
         return Botan::OS::get_system_timestamp_ns();
1,026✔
101
      }
102

103
      Botan::RandomNumberGenerator& timing_test_rng() { return (*m_rng); }
3✔
104

105
   private:
106
      std::shared_ptr<Botan::RandomNumberGenerator> m_rng;
107
};
108

109
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_HAS_EME_RAW)
110

111
class Bleichenbacker_Timing_Test final : public Timing_Test {
×
112
   public:
113
      explicit Bleichenbacker_Timing_Test(size_t keysize) :
1✔
114
            m_privkey(timing_test_rng(), keysize),
1✔
115
            m_pubkey(m_privkey),
1✔
116
            m_enc(m_pubkey, timing_test_rng(), "Raw"),
1✔
117
            m_dec(m_privkey, timing_test_rng(), "PKCS1v15") {}
2✔
118

119
      std::vector<uint8_t> prepare_input(const std::string& input) override {
4✔
120
         const std::vector<uint8_t> input_vector = Botan::hex_decode(input);
4✔
121
         return m_enc.encrypt(input_vector, timing_test_rng());
4✔
122
      }
4✔
123

124
      ticks measure_critical_function(const std::vector<uint8_t>& input) override {
76✔
125
         const ticks start = get_ticks();
76✔
126
         m_dec.decrypt_or_random(input.data(), m_ctext_length, m_expected_content_size, timing_test_rng());
76✔
127
         const ticks end = get_ticks();
76✔
128
         return (end - start);
76✔
129
      }
130

131
   private:
132
      const size_t m_expected_content_size = 48;
133
      const size_t m_ctext_length = 256;
134
      Botan::RSA_PrivateKey m_privkey;
135
      Botan::RSA_PublicKey m_pubkey;
136
      Botan::PK_Encryptor_EME m_enc;
137
      Botan::PK_Decryptor_EME m_dec;
138
};
139

140
#endif
141

142
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_OAEP) && defined(BOTAN_HAS_EME_RAW)
143

144
/*
145
* Test Manger OAEP side channel
146
*
147
* "A Chosen Ciphertext Attack on RSA Optimal Asymmetric Encryption
148
* Padding (OAEP) as Standardized in PKCS #1 v2.0" James Manger
149
* http://archiv.infsec.ethz.ch/education/fs08/secsem/Manger01.pdf
150
*/
151
class Manger_Timing_Test final : public Timing_Test {
×
152
   public:
153
      explicit Manger_Timing_Test(size_t keysize) :
1✔
154
            m_privkey(timing_test_rng(), keysize),
1✔
155
            m_pubkey(m_privkey),
1✔
156
            m_enc(m_pubkey, timing_test_rng(), m_encrypt_padding),
1✔
157
            m_dec(m_privkey, timing_test_rng(), m_decrypt_padding) {}
2✔
158

159
      std::vector<uint8_t> prepare_input(const std::string& input) override {
2✔
160
         const std::vector<uint8_t> input_vector = Botan::hex_decode(input);
2✔
161
         return m_enc.encrypt(input_vector, timing_test_rng());
2✔
162
      }
2✔
163

164
      ticks measure_critical_function(const std::vector<uint8_t>& input) override {
38✔
165
         ticks start = get_ticks();
38✔
166
         try {
38✔
167
            m_dec.decrypt(input.data(), m_ctext_length);
38✔
168
         } catch(Botan::Decoding_Error&) {}
38✔
169
         ticks end = get_ticks();
38✔
170

171
         return (end - start);
38✔
172
      }
173

174
   private:
175
      const std::string m_encrypt_padding = "Raw";
176
      const std::string m_decrypt_padding = "EME1(SHA-256)";
177
      const size_t m_ctext_length = 256;
178
      Botan::RSA_PrivateKey m_privkey;
179
      Botan::RSA_PublicKey m_pubkey;
180
      Botan::PK_Encryptor_EME m_enc;
181
      Botan::PK_Decryptor_EME m_dec;
182
};
183

184
#endif
185

186
#if defined(BOTAN_HAS_TLS_CBC)
187

188
/*
189
* Test handling of countermeasure to the Lucky13 attack
190
*/
191
class Lucky13_Timing_Test final : public Timing_Test {
×
192
   public:
193
      Lucky13_Timing_Test(const std::string& mac_name, size_t mac_keylen) :
4✔
194
            m_mac_algo(mac_name),
8✔
195
            m_mac_keylen(mac_keylen),
4✔
196
            m_dec(Botan::BlockCipher::create_or_throw("AES-128"),
12✔
197
                  Botan::MessageAuthenticationCode::create_or_throw("HMAC(" + m_mac_algo + ")"),
8✔
198
                  16,
199
                  m_mac_keylen,
4✔
200
                  Botan::TLS::Protocol_Version::TLS_V12,
201
                  false) {}
8✔
202

203
      std::vector<uint8_t> prepare_input(const std::string& input) override;
204
      ticks measure_critical_function(const std::vector<uint8_t>& input) override;
205

206
   private:
207
      const std::string m_mac_algo;
208
      const size_t m_mac_keylen;
209
      Botan::TLS::TLS_CBC_HMAC_AEAD_Decryption m_dec;
210
};
211

212
std::vector<uint8_t> Lucky13_Timing_Test::prepare_input(const std::string& input) {
12✔
213
   const std::vector<uint8_t> input_vector = Botan::hex_decode(input);
12✔
214
   const std::vector<uint8_t> key(16);
12✔
215
   const std::vector<uint8_t> iv(16);
12✔
216

217
   auto enc = Botan::Cipher_Mode::create("AES-128/CBC/NoPadding", Botan::Cipher_Dir::Encryption);
12✔
218
   enc->set_key(key);
12✔
219
   enc->start(iv);
12✔
220
   Botan::secure_vector<uint8_t> buf(input_vector.begin(), input_vector.end());
12✔
221
   enc->finish(buf);
12✔
222

223
   return unlock(buf);
12✔
224
}
60✔
225

226
ticks Lucky13_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
228✔
227
   Botan::secure_vector<uint8_t> data(input.begin(), input.end());
228✔
228
   Botan::secure_vector<uint8_t> aad(13);
228✔
229
   const Botan::secure_vector<uint8_t> iv(16);
228✔
230
   Botan::secure_vector<uint8_t> key(16 + m_mac_keylen);
228✔
231

232
   m_dec.set_key(unlock(key));
228✔
233
   m_dec.set_associated_data(aad);
228✔
234
   m_dec.start(unlock(iv));
228✔
235

236
   ticks start = get_ticks();
228✔
237
   try {
228✔
238
      m_dec.finish(data);
228✔
239
   } catch(Botan::TLS::TLS_Exception&) {}
228✔
240
   ticks end = get_ticks();
228✔
241
   return (end - start);
228✔
242
}
912✔
243

244
#endif
245

246
#if defined(BOTAN_HAS_ECDSA)
247

248
class ECDSA_Timing_Test final : public Timing_Test {
×
249
   public:
250
      explicit ECDSA_Timing_Test(const std::string& ecgroup);
251

252
      ticks measure_critical_function(const std::vector<uint8_t>& input) override;
253

254
   private:
255
      const Botan::EC_Group m_group;
256
      const Botan::ECDSA_PrivateKey m_privkey;
257
      const Botan::BigInt& m_x;
258
      std::vector<Botan::BigInt> m_ws;
259
      Botan::BigInt m_b, m_b_inv;
260
};
261

262
ECDSA_Timing_Test::ECDSA_Timing_Test(const std::string& ecgroup) :
1✔
263
      m_group(ecgroup), m_privkey(timing_test_rng(), m_group), m_x(m_privkey.private_value()) {
2✔
264
   m_b = m_group.random_scalar(timing_test_rng());
2✔
265
   m_b_inv = m_group.inverse_mod_order(m_b);
2✔
266
}
1✔
267

268
ticks ECDSA_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
38✔
269
   const Botan::BigInt k(input.data(), input.size());
38✔
270
   Botan::BigInt m(5);  // fixed message to minimize noise
38✔
271

272
   ticks start = get_ticks();
38✔
273

274
   // the following ECDSA operations involve and should not leak any information about k
275
   const Botan::BigInt r = m_group.mod_order(m_group.blinded_base_point_multiply_x(k, timing_test_rng(), m_ws));
38✔
276
   const Botan::BigInt k_inv = m_group.inverse_mod_order(k);
38✔
277

278
   m_b = m_group.square_mod_order(m_b);
38✔
279
   m_b_inv = m_group.square_mod_order(m_b_inv);
38✔
280

281
   m = m_group.multiply_mod_order(m_b, m_group.mod_order(m));
76✔
282
   const Botan::BigInt xr_m = m_group.mod_order(m_group.multiply_mod_order(m_x, m_b, r) + m);
76✔
283

284
   const Botan::BigInt s = m_group.multiply_mod_order(k_inv, xr_m, m_b_inv);
38✔
285

286
   BOTAN_UNUSED(r, s);
38✔
287

288
   ticks end = get_ticks();
38✔
289

290
   return (end - start);
38✔
291
}
228✔
292

293
#endif
294

295
#if defined(BOTAN_HAS_ECC_GROUP)
296

297
class ECC_Mul_Timing_Test final : public Timing_Test {
×
298
   public:
299
      explicit ECC_Mul_Timing_Test(const std::string& ecgroup) : m_group(ecgroup) {}
1✔
300

301
      ticks measure_critical_function(const std::vector<uint8_t>& input) override;
302

303
   private:
304
      const Botan::EC_Group m_group;
305
      std::vector<Botan::BigInt> m_ws;
306
};
307

308
ticks ECC_Mul_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
38✔
309
   const Botan::BigInt k(input.data(), input.size());
38✔
310

311
   ticks start = get_ticks();
38✔
312

313
   const Botan::EC_Point k_times_P = m_group.blinded_base_point_multiply(k, timing_test_rng(), m_ws);
38✔
314

315
   ticks end = get_ticks();
38✔
316

317
   return (end - start);
76✔
318
}
76✔
319

320
#endif
321

322
#if defined(BOTAN_HAS_DL_GROUP)
323

324
class Powmod_Timing_Test final : public Timing_Test {
×
325
   public:
326
      explicit Powmod_Timing_Test(const std::string& dl_group) : m_group(dl_group) {}
1✔
327

328
      ticks measure_critical_function(const std::vector<uint8_t>& input) override;
329

330
   private:
331
      Botan::DL_Group m_group;
332
};
333

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

338
   ticks start = get_ticks();
57✔
339

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

342
   ticks end = get_ticks();
57✔
343

344
   return (end - start);
57✔
345
}
114✔
346

347
#endif
348

349
#if defined(BOTAN_HAS_NUMBERTHEORY)
350

351
class Invmod_Timing_Test final : public Timing_Test {
×
352
   public:
353
      explicit Invmod_Timing_Test(size_t p_bits) { m_p = Botan::random_prime(timing_test_rng(), p_bits); }
2✔
354

355
      ticks measure_critical_function(const std::vector<uint8_t>& input) override;
356

357
   private:
358
      Botan::BigInt m_p;
359
};
360

361
ticks Invmod_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
38✔
362
   const Botan::BigInt k(input.data(), input.size());
38✔
363

364
   ticks start = get_ticks();
38✔
365

366
   const Botan::BigInt inv = inverse_mod(k, m_p);
38✔
367

368
   ticks end = get_ticks();
38✔
369

370
   return (end - start);
38✔
371
}
76✔
372

373
#endif
374

375
std::vector<std::vector<ticks>> Timing_Test::execute_evaluation(const std::vector<std::string>& raw_inputs,
10✔
376
                                                                size_t warmup_runs,
377
                                                                size_t measurement_runs) {
378
   std::vector<std::vector<ticks>> all_results(raw_inputs.size());
10✔
379
   std::vector<std::vector<uint8_t>> inputs(raw_inputs.size());
10✔
380

381
   for(auto& result : all_results) {
37✔
382
      result.reserve(measurement_runs);
27✔
383
   }
384

385
   for(size_t i = 0; i != inputs.size(); ++i) {
37✔
386
      inputs[i] = prepare_input(raw_inputs[i]);
27✔
387
   }
388

389
   // arbitrary upper bounds of 1 and 10 million resp
390
   if(warmup_runs > 1000000 || measurement_runs > 100000000) {
10✔
391
      throw CLI_Error("Requested execution counts too large, rejecting");
×
392
   }
393

394
   size_t total_runs = 0;
10✔
395
   std::vector<ticks> results(inputs.size());
10✔
396

397
   while(total_runs < (warmup_runs + measurement_runs)) {
200✔
398
      for(size_t i = 0; i != inputs.size(); ++i) {
703✔
399
         results[i] = measure_critical_function(inputs[i]);
513✔
400
      }
401

402
      total_runs++;
190✔
403

404
      if(total_runs > warmup_runs) {
190✔
405
         for(size_t i = 0; i != results.size(); ++i) {
592✔
406
            all_results[i].push_back(results[i]);
432✔
407
         }
408
      }
409
   }
410

411
   return all_results;
20✔
412
}
10✔
413

414
class Timing_Test_Command final : public Command {
415
   public:
416
      Timing_Test_Command() :
11✔
417
            Command(
418
               "timing_test test_type --test-data-file= --test-data-dir=src/tests/data/timing "
419
               "--warmup-runs=5000 --measurement-runs=50000") {}
22✔
420

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

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

425
      void go() override {
10✔
426
         const std::string test_type = get_arg("test_type");
10✔
427
         const size_t warmup_runs = get_arg_sz("warmup-runs");
10✔
428
         const size_t measurement_runs = get_arg_sz("measurement-runs");
10✔
429

430
         std::unique_ptr<Timing_Test> test = lookup_timing_test(test_type);
10✔
431

432
         if(!test) {
10✔
433
            throw CLI_Error("Unknown or unavailable test type '" + test_type + "'");
×
434
         }
435

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

438
         if(filename.empty()) {
10✔
439
            const std::string test_data_dir = get_arg("test-data-dir");
10✔
440
            filename = test_data_dir + "/" + test_type + ".vec";
10✔
441
         }
10✔
442

443
         std::vector<std::string> lines = read_testdata(filename);
10✔
444

445
         std::vector<std::vector<ticks>> results = test->execute_evaluation(lines, warmup_runs, measurement_runs);
10✔
446

447
         size_t unique_id = 0;
10✔
448
         std::ostringstream oss;
10✔
449
         for(size_t secret_id = 0; secret_id != results.size(); ++secret_id) {
37✔
450
            for(size_t i = 0; i != results[secret_id].size(); ++i) {
459✔
451
               oss << unique_id++ << ";" << secret_id << ";" << results[secret_id][i] << "\n";
432✔
452
            }
453
         }
454

455
         output() << oss.str();
10✔
456
      }
32✔
457

458
   private:
459
      static std::vector<std::string> read_testdata(const std::string& filename) {
10✔
460
         std::vector<std::string> lines;
10✔
461
         std::ifstream infile(filename);
10✔
462
         if(infile.good() == false) {
10✔
463
            throw CLI_Error("Error reading test data from '" + filename + "'");
×
464
         }
465
         std::string line;
10✔
466
         while(std::getline(infile, line)) {
70✔
467
            if(!line.empty() && line.at(0) != '#') {
60✔
468
               lines.push_back(line);
27✔
469
            }
470
         }
471
         return lines;
20✔
472
      }
10✔
473

474
      static std::unique_ptr<Timing_Test> lookup_timing_test(const std::string& test_type);
475

476
      std::string help_text() const override {
×
477
         // TODO check feature macros
478
         return (Command::help_text() +
×
479
                 "\ntest_type can take on values "
480
                 "bleichenbacher "
481
                 "manger "
482
                 "ecdsa "
483
                 "ecc_mul "
484
                 "inverse_mod "
485
                 "pow_mod "
486
                 "lucky13sec3 "
487
                 "lucky13sec4sha1 "
488
                 "lucky13sec4sha256 "
489
                 "lucky13sec4sha384 ");
×
490
      }
491
};
492

493
std::unique_ptr<Timing_Test> Timing_Test_Command::lookup_timing_test(const std::string& test_type) {
10✔
494
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_HAS_EME_RAW)
495
   if(test_type == "bleichenbacher") {
10✔
496
      return std::make_unique<Bleichenbacker_Timing_Test>(2048);
1✔
497
   }
498
#endif
499

500
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_OAEP) && defined(BOTAN_HAS_EME_RAW)
501
   if(test_type == "manger") {
9✔
502
      return std::make_unique<Manger_Timing_Test>(2048);
1✔
503
   }
504
#endif
505

506
#if defined(BOTAN_HAS_ECDSA)
507
   if(test_type == "ecdsa") {
8✔
508
      return std::make_unique<ECDSA_Timing_Test>("secp384r1");
1✔
509
   }
510
#endif
511

512
#if defined(BOTAN_HAS_ECC_GROUP)
513
   if(test_type == "ecc_mul") {
7✔
514
      return std::make_unique<ECC_Mul_Timing_Test>("brainpool512r1");
1✔
515
   }
516
#endif
517

518
#if defined(BOTAN_HAS_NUMBERTHEORY)
519
   if(test_type == "inverse_mod") {
6✔
520
      return std::make_unique<Invmod_Timing_Test>(512);
1✔
521
   }
522
#endif
523

524
#if defined(BOTAN_HAS_DL_GROUP)
525
   if(test_type == "pow_mod") {
5✔
526
      return std::make_unique<Powmod_Timing_Test>("modp/ietf/1024");
1✔
527
   }
528
#endif
529

530
#if defined(BOTAN_HAS_TLS_CBC)
531
   if(test_type == "lucky13sec3" || test_type == "lucky13sec4sha1") {
4✔
532
      return std::make_unique<Lucky13_Timing_Test>("SHA-1", 20);
2✔
533
   }
534
   if(test_type == "lucky13sec4sha256") {
2✔
535
      return std::make_unique<Lucky13_Timing_Test>("SHA-256", 32);
1✔
536
   }
537
   if(test_type == "lucky13sec4sha384") {
1✔
538
      return std::make_unique<Lucky13_Timing_Test>("SHA-384", 48);
1✔
539
   }
540
#endif
541

542
   BOTAN_UNUSED(test_type);
×
543

544
   return nullptr;
×
545
}
546

547
BOTAN_REGISTER_COMMAND("timing_test", Timing_Test_Command);
11✔
548

549
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1)
550

551
class MARVIN_Test_Command final : public Command {
552
   public:
553
      MARVIN_Test_Command() : Command("marvin_test key_file ctext_dir --runs=10 --output-nsec --expect-pt-len=0") {}
4✔
554

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

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

559
      void go() override {
1✔
560
         const std::string key_file = get_arg("key_file");
1✔
561
         const std::string ctext_dir = get_arg("ctext_dir");
1✔
562
         const size_t measurement_runs = get_arg_sz("runs");
1✔
563
         const size_t expect_pt_len = get_arg_sz("expect-pt-len");
1✔
564
         const bool output_nsec = flag_set("output-nsec");
1✔
565

566
         Botan::DataSource_Stream key_src(key_file);
1✔
567
         const auto key = Botan::PKCS8::load_key(key_src);
1✔
568

569
         if(key->algo_name() != "RSA") {
1✔
570
            throw CLI_Usage_Error("Unexpected key type for MARVIN test");
×
571
         }
572

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

575
         std::vector<std::string> names;
1✔
576
         std::vector<uint8_t> ciphertext_data;
1✔
577

578
         for(const auto& filename : Botan::get_files_recursive(ctext_dir)) {
5✔
579
            const auto contents = this->slurp_file(filename);
4✔
580

581
            if(contents.size() != modulus_bytes) {
4✔
582
               throw CLI_Usage_Error(
×
583
                  Botan::fmt("The ciphertext file {} had different size ({}) than the RSA modulus ({})",
×
584
                             filename,
585
                             contents.size(),
×
586
                             modulus_bytes));
×
587
            }
588

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

591
            names.push_back(parts[parts.size() - 1]);
4✔
592
            ciphertext_data.insert(ciphertext_data.end(), contents.begin(), contents.end());
4✔
593
         }
9✔
594

595
         if(names.empty()) {
1✔
596
            throw CLI_Usage_Error("Empty ciphertext directory for MARVIN test");
×
597
         }
598

599
         Botan::PK_Decryptor_EME op(*key, rng(), "PKCS1v15");
1✔
600

601
         std::vector<size_t> indexes;
1✔
602
         for(size_t i = 0; i != names.size(); ++i) {
5✔
603
            indexes.push_back(i);
4✔
604
         }
605

606
         std::vector<std::vector<uint64_t>> measurements(names.size());
1✔
607
         for(auto& m : measurements) {
5✔
608
            m.reserve(measurement_runs);
4✔
609
         }
610

611
         for(size_t r = 0; r != measurement_runs; ++r) {
33✔
612
            shuffle(indexes, rng());
32✔
613

614
            std::vector<uint8_t> ciphertext(modulus_bytes);
32✔
615
            for(size_t i = 0; i != indexes.size(); ++i) {
160✔
616
               const size_t testcase = indexes[i];
128✔
617

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

621
               const uint64_t start = Botan::OS::get_system_timestamp_ns();
128✔
622

623
   #if 0
624
               try {
625
                  op.decrypt(ciphertext.data(), modulus_bytes);
626
               } catch(...) {}
627
   #else
628
               op.decrypt_or_random(ciphertext.data(), modulus_bytes, expect_pt_len, rng());
128✔
629
   #endif
630

631
               const uint64_t duration = Botan::OS::get_system_timestamp_ns() - start;
128✔
632
               BOTAN_ASSERT_NOMSG(measurements[testcase].size() == r);
128✔
633
               measurements[testcase].push_back(duration);
128✔
634
            }
635
         }
32✔
636

637
         for(size_t t = 0; t != names.size(); ++t) {
5✔
638
            if(t > 0) {
4✔
639
               output() << ",";
3✔
640
            }
641
            output() << names[t];
4✔
642
         }
643
         output() << "\n";
1✔
644

645
         for(size_t r = 0; r != measurement_runs; ++r) {
33✔
646
            for(size_t t = 0; t != names.size(); ++t) {
160✔
647
               if(t > 0) {
128✔
648
                  output() << ",";
96✔
649
               }
650

651
               const uint64_t dur_nsec = measurements[t][r];
128✔
652
               if(output_nsec) {
128✔
653
                  output() << dur_nsec;
×
654
               } else {
655
                  const double dur_s = static_cast<double>(dur_nsec) / 1000000000.0;
128✔
656
                  output() << dur_s;
128✔
657
               }
658
            }
659
            output() << "\n";
32✔
660
         }
661
      }
6✔
662

663
      template <typename T>
664
      void shuffle(std::vector<T>& vec, Botan::RandomNumberGenerator& rng) {
32✔
665
         const size_t n = vec.size();
32✔
666
         for(size_t i = 0; i != n; ++i) {
160✔
667
            uint8_t jb[sizeof(uint64_t)];
668
            rng.randomize(jb, sizeof(jb));
128✔
669
            uint64_t j8 = Botan::load_le<uint64_t>(jb, 0);
128✔
670
            size_t j = i + static_cast<size_t>(j8) % (n - i);
128✔
671
            std::swap(vec[i], vec[j]);
128✔
672
         }
673
      }
32✔
674
};
675

676
BOTAN_REGISTER_COMMAND("marvin_test", MARVIN_Test_Command);
2✔
677

678
#endif
679

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

© 2025 Coveralls, Inc