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

randombit / botan / 5079590438

25 May 2023 12:28PM UTC coverage: 92.228% (+0.5%) from 91.723%
5079590438

Pull #3502

github

Pull Request #3502: Apply clang-format to the codebase

75589 of 81959 relevant lines covered (92.23%)

12139530.51 hits per line

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

92.2
/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
#include <botan/hex.h>
22
#include <fstream>
23
#include <sstream>
24

25
#include <botan/rng.h>
26
#include <botan/internal/os_utils.h>
27

28
#if defined(BOTAN_HAS_BIGINT)
29
   #include <botan/bigint.h>
30
#endif
31

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

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

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

44
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_RAW)
45
   #include <botan/pubkey.h>
46
   #include <botan/rsa.h>
47
#endif
48

49
#if defined(BOTAN_HAS_TLS_CBC)
50
   #include <botan/tls_exceptn.h>
51
   #include <botan/internal/tls_cbc.h>
52
#endif
53

54
#if defined(BOTAN_HAS_ECDSA)
55
   #include <botan/ecdsa.h>
56
#endif
57

58
namespace Botan_CLI {
59

60
typedef uint64_t ticks;
61

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

73
      virtual ~Timing_Test() = default;
×
74

75
      Timing_Test(const Timing_Test& other) = delete;
76
      Timing_Test(Timing_Test&& other) = delete;
77
      Timing_Test& operator=(const Timing_Test& other) = delete;
78
      Timing_Test& operator=(Timing_Test&& other) = delete;
79

80
      std::vector<std::vector<ticks>> execute_evaluation(const std::vector<std::string>& inputs,
81
                                                         size_t warmup_runs,
82
                                                         size_t measurement_runs);
83

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

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

88
   protected:
89
      static ticks get_ticks() {
1,026✔
90
         // Returns CPU counter or best approximation (monotonic clock of some kind)
91
         //return Botan::OS::get_high_resolution_clock();
92
         return Botan::OS::get_system_timestamp_ns();
1,026✔
93
      }
94

95
      Botan::RandomNumberGenerator& timing_test_rng() { return (*m_rng); }
3✔
96

97
   private:
98
      std::shared_ptr<Botan::RandomNumberGenerator> m_rng;
99
};
100

101
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_HAS_EME_RAW)
102

103
class Bleichenbacker_Timing_Test final : public Timing_Test {
×
104
   public:
105
      explicit Bleichenbacker_Timing_Test(size_t keysize) :
1✔
106
            m_privkey(timing_test_rng(), keysize),
1✔
107
            m_pubkey(m_privkey),
1✔
108
            m_enc(m_pubkey, timing_test_rng(), "Raw"),
1✔
109
            m_dec(m_privkey, timing_test_rng(), "PKCS1v15") {}
2✔
110

111
      std::vector<uint8_t> prepare_input(const std::string& input) override {
4✔
112
         const std::vector<uint8_t> input_vector = Botan::hex_decode(input);
4✔
113
         return m_enc.encrypt(input_vector, timing_test_rng());
4✔
114
      }
4✔
115

116
      ticks measure_critical_function(const std::vector<uint8_t>& input) override {
76✔
117
         const ticks start = get_ticks();
76✔
118
         m_dec.decrypt_or_random(input.data(), m_ctext_length, m_expected_content_size, timing_test_rng());
76✔
119
         const ticks end = get_ticks();
76✔
120
         return (end - start);
76✔
121
      }
122

123
   private:
124
      const size_t m_expected_content_size = 48;
125
      const size_t m_ctext_length = 256;
126
      Botan::RSA_PrivateKey m_privkey;
127
      Botan::RSA_PublicKey m_pubkey;
128
      Botan::PK_Encryptor_EME m_enc;
129
      Botan::PK_Decryptor_EME m_dec;
130
};
131

132
#endif
133

134
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_OAEP) && defined(BOTAN_HAS_EME_RAW)
135

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

151
      std::vector<uint8_t> prepare_input(const std::string& input) override {
2✔
152
         const std::vector<uint8_t> input_vector = Botan::hex_decode(input);
2✔
153
         return m_enc.encrypt(input_vector, timing_test_rng());
2✔
154
      }
2✔
155

156
      ticks measure_critical_function(const std::vector<uint8_t>& input) override {
38✔
157
         ticks start = get_ticks();
38✔
158
         try {
38✔
159
            m_dec.decrypt(input.data(), m_ctext_length);
38✔
160
         } catch(Botan::Decoding_Error&) {}
38✔
161
         ticks end = get_ticks();
38✔
162

163
         return (end - start);
38✔
164
      }
165

166
   private:
167
      const std::string m_encrypt_padding = "Raw";
168
      const std::string m_decrypt_padding = "EME1(SHA-256)";
169
      const size_t m_ctext_length = 256;
170
      Botan::RSA_PrivateKey m_privkey;
171
      Botan::RSA_PublicKey m_pubkey;
172
      Botan::PK_Encryptor_EME m_enc;
173
      Botan::PK_Decryptor_EME m_dec;
174
};
175

176
#endif
177

178
#if defined(BOTAN_HAS_TLS_CBC)
179

180
/*
181
* Test handling of countermeasure to the Lucky13 attack
182
*/
183
class Lucky13_Timing_Test final : public Timing_Test {
×
184
   public:
185
      Lucky13_Timing_Test(const std::string& mac_name, size_t mac_keylen) :
4✔
186
            m_mac_algo(mac_name),
8✔
187
            m_mac_keylen(mac_keylen),
4✔
188
            m_dec(Botan::BlockCipher::create_or_throw("AES-128"),
12✔
189
                  Botan::MessageAuthenticationCode::create_or_throw("HMAC(" + m_mac_algo + ")"),
8✔
190
                  16,
191
                  m_mac_keylen,
4✔
192
                  Botan::TLS::Protocol_Version::TLS_V12,
193
                  false) {}
8✔
194

195
      std::vector<uint8_t> prepare_input(const std::string& input) override;
196
      ticks measure_critical_function(const std::vector<uint8_t>& input) override;
197

198
   private:
199
      const std::string m_mac_algo;
200
      const size_t m_mac_keylen;
201
      Botan::TLS::TLS_CBC_HMAC_AEAD_Decryption m_dec;
202
};
203

204
std::vector<uint8_t> Lucky13_Timing_Test::prepare_input(const std::string& input) {
12✔
205
   const std::vector<uint8_t> input_vector = Botan::hex_decode(input);
12✔
206
   const std::vector<uint8_t> key(16);
12✔
207
   const std::vector<uint8_t> iv(16);
12✔
208

209
   auto enc = Botan::Cipher_Mode::create("AES-128/CBC/NoPadding", Botan::Cipher_Dir::Encryption);
12✔
210
   enc->set_key(key);
12✔
211
   enc->start(iv);
12✔
212
   Botan::secure_vector<uint8_t> buf(input_vector.begin(), input_vector.end());
12✔
213
   enc->finish(buf);
12✔
214

215
   return unlock(buf);
12✔
216
}
60✔
217

218
ticks Lucky13_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
228✔
219
   Botan::secure_vector<uint8_t> data(input.begin(), input.end());
228✔
220
   Botan::secure_vector<uint8_t> aad(13);
228✔
221
   const Botan::secure_vector<uint8_t> iv(16);
228✔
222
   Botan::secure_vector<uint8_t> key(16 + m_mac_keylen);
228✔
223

224
   m_dec.set_key(unlock(key));
228✔
225
   m_dec.set_associated_data(aad);
228✔
226
   m_dec.start(unlock(iv));
228✔
227

228
   ticks start = get_ticks();
228✔
229
   try {
228✔
230
      m_dec.finish(data);
228✔
231
   } catch(Botan::TLS::TLS_Exception&) {}
228✔
232
   ticks end = get_ticks();
228✔
233
   return (end - start);
228✔
234
}
912✔
235

236
#endif
237

238
#if defined(BOTAN_HAS_ECDSA)
239

240
class ECDSA_Timing_Test final : public Timing_Test {
×
241
   public:
242
      explicit ECDSA_Timing_Test(const std::string& ecgroup);
243

244
      ticks measure_critical_function(const std::vector<uint8_t>& input) override;
245

246
   private:
247
      const Botan::EC_Group m_group;
248
      const Botan::ECDSA_PrivateKey m_privkey;
249
      const Botan::BigInt& m_x;
250
      std::vector<Botan::BigInt> m_ws;
251
      Botan::BigInt m_b, m_b_inv;
252
};
253

254
ECDSA_Timing_Test::ECDSA_Timing_Test(const std::string& ecgroup) :
1✔
255
      m_group(ecgroup), m_privkey(timing_test_rng(), m_group), m_x(m_privkey.private_value()) {
2✔
256
   m_b = m_group.random_scalar(timing_test_rng());
2✔
257
   m_b_inv = m_group.inverse_mod_order(m_b);
2✔
258
}
1✔
259

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

264
   ticks start = get_ticks();
38✔
265

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

270
   m_b = m_group.square_mod_order(m_b);
38✔
271
   m_b_inv = m_group.square_mod_order(m_b_inv);
38✔
272

273
   m = m_group.multiply_mod_order(m_b, m_group.mod_order(m));
76✔
274
   const Botan::BigInt xr_m = m_group.mod_order(m_group.multiply_mod_order(m_x, m_b, r) + m);
76✔
275

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

278
   BOTAN_UNUSED(r, s);
38✔
279

280
   ticks end = get_ticks();
38✔
281

282
   return (end - start);
38✔
283
}
228✔
284

285
#endif
286

287
#if defined(BOTAN_HAS_ECC_GROUP)
288

289
class ECC_Mul_Timing_Test final : public Timing_Test {
×
290
   public:
291
      explicit ECC_Mul_Timing_Test(const std::string& ecgroup) : m_group(ecgroup) {}
1✔
292

293
      ticks measure_critical_function(const std::vector<uint8_t>& input) override;
294

295
   private:
296
      const Botan::EC_Group m_group;
297
      std::vector<Botan::BigInt> m_ws;
298
};
299

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

303
   ticks start = get_ticks();
38✔
304

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

307
   ticks end = get_ticks();
38✔
308

309
   return (end - start);
76✔
310
}
76✔
311

312
#endif
313

314
#if defined(BOTAN_HAS_DL_GROUP)
315

316
class Powmod_Timing_Test final : public Timing_Test {
×
317
   public:
318
      explicit Powmod_Timing_Test(const std::string& dl_group) : m_group(dl_group) {}
1✔
319

320
      ticks measure_critical_function(const std::vector<uint8_t>& input) override;
321

322
   private:
323
      Botan::DL_Group m_group;
324
};
325

326
ticks Powmod_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) {
57✔
327
   const Botan::BigInt x(input.data(), input.size());
57✔
328
   const size_t max_x_bits = m_group.p_bits();
57✔
329

330
   ticks start = get_ticks();
57✔
331

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

334
   ticks end = get_ticks();
57✔
335

336
   return (end - start);
57✔
337
}
114✔
338

339
#endif
340

341
#if defined(BOTAN_HAS_NUMBERTHEORY)
342

343
class Invmod_Timing_Test final : public Timing_Test {
×
344
   public:
345
      explicit Invmod_Timing_Test(size_t p_bits) { m_p = Botan::random_prime(timing_test_rng(), p_bits); }
2✔
346

347
      ticks measure_critical_function(const std::vector<uint8_t>& input) override;
348

349
   private:
350
      Botan::BigInt m_p;
351
};
352

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

356
   ticks start = get_ticks();
38✔
357

358
   const Botan::BigInt inv = inverse_mod(k, m_p);
38✔
359

360
   ticks end = get_ticks();
38✔
361

362
   return (end - start);
38✔
363
}
76✔
364

365
#endif
366

367
std::vector<std::vector<ticks>> 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<ticks>> 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<ticks> 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;
20✔
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 "misc"; }
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";
10✔
433
         }
10✔
434

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

437
         std::vector<std::vector<ticks>> 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
      }
32✔
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(const std::string& 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
BOTAN_REGISTER_COMMAND("timing_test", Timing_Test_Command);
11✔
486

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

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

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

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

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

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

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

536
   BOTAN_UNUSED(test_type);
×
537

538
   return nullptr;
×
539
}
540

541
}
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