Coverage Report

Created: 2026-02-07 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/botan/src/lib/modes/aead/ocb/ocb.cpp
Line
Count
Source
1
/*
2
* OCB Mode
3
* (C) 2013,2017 Jack Lloyd
4
* (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8
9
#include <botan/internal/ocb.h>
10
11
#include <botan/block_cipher.h>
12
#include <botan/mem_ops.h>
13
#include <botan/internal/bit_ops.h>
14
#include <botan/internal/ct_utils.h>
15
#include <botan/internal/poly_dbl.h>
16
17
namespace Botan {
18
19
// Has to be in Botan namespace so unique_ptr can reference it
20
class L_computer final {
21
   public:
22
      explicit L_computer(const BlockCipher& cipher) :
23
0
            m_BS(cipher.block_size()), m_max_blocks(cipher.parallel_bytes() / m_BS) {
24
0
         m_L_star.resize(m_BS);
25
0
         cipher.encrypt(m_L_star);
26
0
         m_L_dollar = poly_double(star());
27
28
         // Preallocate the m_L vector to the maximum expected size to avoid
29
         // re-allocations during runtime. This had caused a use-after-free in
30
         // earlier versions, due to references into this buffer becoming stale
31
         // in `compute_offset()`, after calling `get()` in the hot path.
32
         //
33
         // Note, that the list member won't be pre-allocated, so the expected
34
         // memory overhead is negligible.
35
         //
36
         // See also https://github.com/randombit/botan/issues/3812
37
0
         m_L.reserve(31);
38
0
         m_L.push_back(poly_double(dollar()));
39
40
0
         while(m_L.size() < 8) {
41
0
            m_L.push_back(poly_double(m_L.back()));
42
0
         }
43
44
0
         m_offset_buf.resize(m_BS * m_max_blocks);
45
0
      }
46
47
0
      void init(const secure_vector<uint8_t>& offset) { m_offset = offset; }
48
49
0
      bool initialized() const { return !m_offset.empty(); }
50
51
0
      const secure_vector<uint8_t>& star() const { return m_L_star; }
52
53
0
      const secure_vector<uint8_t>& dollar() const { return m_L_dollar; }
54
55
0
      const secure_vector<uint8_t>& offset() const { return m_offset; }
56
57
0
      const secure_vector<uint8_t>& get(size_t i) const {
58
0
         while(m_L.size() <= i) {
59
0
            m_L.push_back(poly_double(m_L.back()));
60
0
         }
61
62
0
         return m_L[i];
63
0
      }
64
65
0
      const uint8_t* compute_offsets(size_t block_index, size_t blocks) {
66
0
         BOTAN_ASSERT(blocks <= m_max_blocks, "OCB offsets");
67
68
0
         uint8_t* offsets = m_offset_buf.data();
69
70
0
         if(block_index % 4 == 0) {
71
0
            const secure_vector<uint8_t>& L0 = get(0);
72
0
            const secure_vector<uint8_t>& L1 = get(1);
73
74
0
            while(blocks >= 4) {
75
               // ntz(4*i+1) == 0
76
               // ntz(4*i+2) == 1
77
               // ntz(4*i+3) == 0
78
0
               block_index += 4;
79
0
               const size_t ntz4 = var_ctz32(static_cast<uint32_t>(block_index));
80
81
0
               xor_buf(offsets, m_offset.data(), L0.data(), m_BS);
82
0
               offsets += m_BS;
83
84
0
               xor_buf(offsets, offsets - m_BS, L1.data(), m_BS);
85
0
               offsets += m_BS;
86
87
0
               xor_buf(m_offset.data(), L1.data(), m_BS);
88
0
               copy_mem(offsets, m_offset.data(), m_BS);
89
0
               offsets += m_BS;
90
91
0
               xor_buf(m_offset.data(), get(ntz4).data(), m_BS);
92
0
               copy_mem(offsets, m_offset.data(), m_BS);
93
0
               offsets += m_BS;
94
95
0
               blocks -= 4;
96
0
            }
97
0
         }
98
99
0
         for(size_t i = 0; i != blocks; ++i) {  // could be done in parallel
100
0
            const size_t ntz = var_ctz32(static_cast<uint32_t>(block_index + i + 1));
101
0
            xor_buf(m_offset.data(), get(ntz).data(), m_BS);
102
0
            copy_mem(offsets, m_offset.data(), m_BS);
103
0
            offsets += m_BS;
104
0
         }
105
106
0
         return m_offset_buf.data();
107
0
      }
108
109
   private:
110
0
      static secure_vector<uint8_t> poly_double(const secure_vector<uint8_t>& in) {
111
0
         secure_vector<uint8_t> out(in.size());
112
0
         poly_double_n(out.data(), in.data(), out.size());
113
0
         return out;
114
0
      }
115
116
      const size_t m_BS, m_max_blocks;
117
      secure_vector<uint8_t> m_L_dollar, m_L_star;
118
      secure_vector<uint8_t> m_offset;
119
      mutable std::vector<secure_vector<uint8_t>> m_L;
120
      secure_vector<uint8_t> m_offset_buf;
121
};
122
123
namespace {
124
125
/*
126
* OCB's HASH
127
*/
128
0
secure_vector<uint8_t> ocb_hash(const L_computer& L, const BlockCipher& cipher, const uint8_t ad[], size_t ad_len) {
129
0
   const size_t BS = cipher.block_size();
130
0
   secure_vector<uint8_t> sum(BS);
131
0
   secure_vector<uint8_t> offset(BS);
132
133
0
   secure_vector<uint8_t> buf(BS);
134
135
0
   const size_t ad_blocks = (ad_len / BS);
136
0
   const size_t ad_remainder = (ad_len % BS);
137
138
0
   for(size_t i = 0; i != ad_blocks; ++i) {
139
      // this loop could run in parallel
140
0
      offset ^= L.get(var_ctz32(static_cast<uint32_t>(i + 1)));
141
0
      buf = offset;
142
0
      xor_buf(buf.data(), &ad[BS * i], BS);
143
0
      cipher.encrypt(buf);
144
0
      sum ^= buf;
145
0
   }
146
147
0
   if(ad_remainder > 0) {
148
0
      offset ^= L.star();
149
0
      buf = offset;
150
0
      xor_buf(buf.data(), &ad[BS * ad_blocks], ad_remainder);
151
0
      buf[ad_remainder] ^= 0x80;
152
0
      cipher.encrypt(buf);
153
0
      sum ^= buf;
154
0
   }
155
156
0
   return sum;
157
0
}
158
159
}  // namespace
160
161
OCB_Mode::OCB_Mode(std::unique_ptr<BlockCipher> cipher, size_t tag_size) :
162
0
      m_cipher(std::move(cipher)),
163
0
      m_checksum(m_cipher->parallel_bytes()),
164
0
      m_ad_hash(m_cipher->block_size()),
165
0
      m_tag_size(tag_size),
166
0
      m_block_size(m_cipher->block_size()),
167
0
      m_par_blocks(m_cipher->parallel_bytes() / m_block_size) {
168
0
   const size_t BS = block_size();
169
170
   /*
171
   * draft-krovetz-ocb-wide-d1 specifies OCB for several other block
172
   * sizes but only 128, 192, 256 and 512 bit are currently supported
173
   * by this implementation.
174
   */
175
0
   BOTAN_ARG_CHECK(BS == 16 || BS == 24 || BS == 32 || BS == 64, "Invalid block size for OCB");
176
177
0
   BOTAN_ARG_CHECK(m_tag_size % 4 == 0 && m_tag_size >= 8 && m_tag_size <= BS && m_tag_size <= 32,
178
0
                   "Invalid OCB tag length");
179
0
}
180
181
0
OCB_Mode::~OCB_Mode() = default;
182
183
0
void OCB_Mode::clear() {
184
0
   m_cipher->clear();
185
0
   m_L.reset();  // add clear here?
186
0
   reset();
187
0
}
188
189
0
void OCB_Mode::reset() {
190
0
   m_block_index = 0;
191
0
   zeroise(m_ad_hash);
192
0
   zeroise(m_checksum);
193
0
   m_last_nonce.clear();
194
0
   m_stretch.clear();
195
0
}
196
197
0
bool OCB_Mode::valid_nonce_length(size_t length) const {
198
0
   if(length == 0) {
199
0
      return false;
200
0
   }
201
0
   if(block_size() == 16) {
202
0
      return length < 16;
203
0
   } else {
204
0
      return length < (block_size() - 1);
205
0
   }
206
0
}
207
208
0
std::string OCB_Mode::name() const {
209
0
   return m_cipher->name() + "/OCB";  // include tag size?
210
0
}
211
212
0
size_t OCB_Mode::update_granularity() const {
213
0
   return block_size();
214
0
}
215
216
0
size_t OCB_Mode::ideal_granularity() const {
217
0
   return (m_par_blocks * block_size());
218
0
}
219
220
0
Key_Length_Specification OCB_Mode::key_spec() const {
221
0
   return m_cipher->key_spec();
222
0
}
223
224
0
bool OCB_Mode::has_keying_material() const {
225
0
   return m_cipher->has_keying_material();
226
0
}
227
228
0
void OCB_Mode::key_schedule(std::span<const uint8_t> key) {
229
0
   m_cipher->set_key(key);
230
0
   m_L = std::make_unique<L_computer>(*m_cipher);
231
0
}
232
233
0
void OCB_Mode::set_associated_data_n(size_t idx, std::span<const uint8_t> ad) {
234
0
   BOTAN_ARG_CHECK(idx == 0, "OCB: cannot handle non-zero index in set_associated_data_n");
235
0
   assert_key_material_set();
236
0
   m_ad_hash = ocb_hash(*m_L, *m_cipher, ad.data(), ad.size());
237
0
}
238
239
0
const secure_vector<uint8_t>& OCB_Mode::update_nonce(const uint8_t nonce[], size_t nonce_len) {
240
0
   const size_t BS = block_size();
241
242
0
   BOTAN_ASSERT(BS == 16 || BS == 24 || BS == 32 || BS == 64, "OCB block size is supported");
243
244
   // NOLINTNEXTLINE(readability-avoid-nested-conditional-operator)
245
0
   const size_t MASKLEN = (BS == 16 ? 6 : ((BS == 24) ? 7 : 8));
246
247
0
   const uint8_t BOTTOM_MASK = static_cast<uint8_t>((static_cast<uint16_t>(1) << MASKLEN) - 1);
248
249
0
   m_nonce_buf.resize(BS);
250
0
   clear_mem(m_nonce_buf.data(), m_nonce_buf.size());
251
252
0
   copy_mem(&m_nonce_buf[BS - nonce_len], nonce, nonce_len);
253
0
   m_nonce_buf[0] = static_cast<uint8_t>(((tag_size() * 8) % (BS * 8)) << (BS <= 16 ? 1 : 0));
254
255
0
   m_nonce_buf[BS - nonce_len - 1] ^= 1;
256
257
0
   const uint8_t bottom = m_nonce_buf[BS - 1] & BOTTOM_MASK;
258
0
   m_nonce_buf[BS - 1] &= ~BOTTOM_MASK;
259
260
0
   const bool need_new_stretch = (m_last_nonce != m_nonce_buf);
261
262
0
   if(need_new_stretch) {
263
0
      m_last_nonce = m_nonce_buf;
264
265
0
      m_cipher->encrypt(m_nonce_buf);
266
267
      /*
268
      The loop bounds (BS vs BS/2) are derived from the relation
269
      between the block size and the MASKLEN. Using the terminology
270
      of draft-krovetz-ocb-wide, we have to derive enough bits in
271
      ShiftedKtop to read up to BLOCKLEN+bottom bits from Stretch.
272
273
                 +----------+---------+-------+---------+
274
                 | BLOCKLEN | RESIDUE | SHIFT | MASKLEN |
275
                 +----------+---------+-------+---------+
276
                 |       32 |     141 |    17 |    4    |
277
                 |       64 |      27 |    25 |    5    |
278
                 |       96 |    1601 |    33 |    6    |
279
                 |      128 |     135 |     8 |    6    |
280
                 |      192 |     135 |    40 |    7    |
281
                 |      256 |    1061 |     1 |    8    |
282
                 |      384 |    4109 |    80 |    8    |
283
                 |      512 |     293 |   176 |    8    |
284
                 |     1024 |  524355 |   352 |    9    |
285
                 +----------+---------+-------+---------+
286
      */
287
0
      if(BS == 16) {
288
0
         for(size_t i = 0; i != BS / 2; ++i) {
289
0
            m_nonce_buf.push_back(m_nonce_buf[i] ^ m_nonce_buf[i + 1]);
290
0
         }
291
0
      } else if(BS == 24) {
292
0
         for(size_t i = 0; i != 16; ++i) {
293
0
            m_nonce_buf.push_back(m_nonce_buf[i] ^ m_nonce_buf[i + 5]);
294
0
         }
295
0
      } else if(BS == 32) {
296
0
         for(size_t i = 0; i != BS; ++i) {
297
0
            m_nonce_buf.push_back(m_nonce_buf[i] ^ (m_nonce_buf[i] << 1) ^ (m_nonce_buf[i + 1] >> 7));
298
0
         }
299
0
      } else if(BS == 64) {
300
0
         for(size_t i = 0; i != BS / 2; ++i) {
301
0
            m_nonce_buf.push_back(m_nonce_buf[i] ^ m_nonce_buf[i + 22]);
302
0
         }
303
0
      }
304
305
0
      m_stretch = m_nonce_buf;
306
0
   }
307
308
   // now set the offset from stretch and bottom
309
0
   const size_t shift_bytes = bottom / 8;
310
0
   const size_t shift_bits = bottom % 8;
311
312
0
   BOTAN_ASSERT(m_stretch.size() >= BS + shift_bytes + 1, "Size ok");
313
314
0
   m_offset.resize(BS);
315
0
   for(size_t i = 0; i != BS; ++i) {
316
0
      m_offset[i] = (m_stretch[i + shift_bytes] << shift_bits);
317
0
      m_offset[i] |= (m_stretch[i + shift_bytes + 1] >> (8 - shift_bits));
318
0
   }
319
320
0
   return m_offset;
321
0
}
322
323
0
void OCB_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) {
324
0
   if(!valid_nonce_length(nonce_len)) {
325
0
      throw Invalid_IV_Length(name(), nonce_len);
326
0
   }
327
328
0
   assert_key_material_set();
329
330
0
   m_L->init(update_nonce(nonce, nonce_len));
331
0
   zeroise(m_checksum);
332
0
   m_block_index = 0;
333
0
}
334
335
0
void OCB_Encryption::encrypt(uint8_t buffer[], size_t blocks) {
336
0
   assert_key_material_set();
337
0
   BOTAN_STATE_CHECK(m_L->initialized());
338
339
0
   const size_t BS = block_size();
340
341
0
   while(blocks > 0) {
342
0
      const size_t proc_blocks = std::min(blocks, par_blocks());
343
0
      const size_t proc_bytes = proc_blocks * BS;
344
345
0
      const uint8_t* offsets = m_L->compute_offsets(m_block_index, proc_blocks);
346
347
0
      xor_buf(m_checksum.data(), buffer, proc_bytes);
348
349
0
      xor_buf(buffer, offsets, proc_bytes);
350
0
      m_cipher->encrypt_n(buffer, buffer, proc_blocks);
351
0
      xor_buf(buffer, offsets, proc_bytes);
352
353
0
      buffer += proc_bytes;
354
0
      blocks -= proc_blocks;
355
0
      m_block_index += proc_blocks;
356
0
   }
357
0
}
358
359
0
size_t OCB_Encryption::process_msg(uint8_t buf[], size_t sz) {
360
0
   BOTAN_ARG_CHECK(sz % update_granularity() == 0, "Invalid OCB input size");
361
0
   encrypt(buf, sz / block_size());
362
0
   return sz;
363
0
}
364
365
0
void OCB_Encryption::finish_msg(secure_vector<uint8_t>& buffer, size_t offset) {
366
0
   assert_key_material_set();
367
0
   BOTAN_STATE_CHECK(m_L->initialized());
368
369
0
   const size_t BS = block_size();
370
371
0
   BOTAN_ARG_CHECK(buffer.size() >= offset, "Offset is out of range");
372
0
   const size_t sz = buffer.size() - offset;
373
0
   uint8_t* buf = buffer.data() + offset;
374
375
0
   secure_vector<uint8_t> mac(BS);
376
377
0
   if(sz > 0) {
378
0
      const size_t final_full_blocks = sz / BS;
379
0
      const size_t remainder_bytes = sz - (final_full_blocks * BS);
380
381
0
      encrypt(buf, final_full_blocks);
382
0
      mac = m_L->offset();
383
384
0
      if(remainder_bytes > 0) {
385
0
         BOTAN_ASSERT(remainder_bytes < BS, "Only a partial block left");
386
0
         uint8_t* remainder = &buf[sz - remainder_bytes];
387
388
0
         xor_buf(m_checksum.data(), remainder, remainder_bytes);
389
0
         m_checksum[remainder_bytes] ^= 0x80;
390
391
         // Offset_*
392
0
         mac ^= m_L->star();
393
394
0
         secure_vector<uint8_t> pad(BS);
395
0
         m_cipher->encrypt(mac, pad);
396
0
         xor_buf(remainder, pad.data(), remainder_bytes);
397
0
      }
398
0
   } else {
399
0
      mac = m_L->offset();
400
0
   }
401
402
   // now compute the tag
403
404
   // fold checksum
405
0
   for(size_t i = 0; i != m_checksum.size(); i += BS) {
406
0
      xor_buf(mac.data(), m_checksum.data() + i, BS);
407
0
   }
408
409
0
   xor_buf(mac.data(), m_L->dollar().data(), BS);
410
0
   m_cipher->encrypt(mac);
411
0
   xor_buf(mac.data(), m_ad_hash.data(), BS);
412
413
0
   buffer += std::make_pair(mac.data(), tag_size());
414
415
0
   zeroise(m_checksum);
416
0
   m_block_index = 0;
417
0
}
418
419
0
void OCB_Decryption::decrypt(uint8_t buffer[], size_t blocks) {
420
0
   assert_key_material_set();
421
0
   BOTAN_STATE_CHECK(m_L->initialized());
422
423
0
   const size_t BS = block_size();
424
425
0
   while(blocks > 0) {
426
0
      const size_t proc_blocks = std::min(blocks, par_blocks());
427
0
      const size_t proc_bytes = proc_blocks * BS;
428
429
0
      const uint8_t* offsets = m_L->compute_offsets(m_block_index, proc_blocks);
430
431
0
      xor_buf(buffer, offsets, proc_bytes);
432
0
      m_cipher->decrypt_n(buffer, buffer, proc_blocks);
433
0
      xor_buf(buffer, offsets, proc_bytes);
434
435
0
      xor_buf(m_checksum.data(), buffer, proc_bytes);
436
437
0
      buffer += proc_bytes;
438
0
      blocks -= proc_blocks;
439
0
      m_block_index += proc_blocks;
440
0
   }
441
0
}
442
443
0
size_t OCB_Decryption::process_msg(uint8_t buf[], size_t sz) {
444
0
   BOTAN_ARG_CHECK(sz % update_granularity() == 0, "Invalid OCB input size");
445
0
   decrypt(buf, sz / block_size());
446
0
   return sz;
447
0
}
448
449
0
void OCB_Decryption::finish_msg(secure_vector<uint8_t>& buffer, size_t offset) {
450
0
   assert_key_material_set();
451
0
   BOTAN_STATE_CHECK(m_L->initialized());
452
453
0
   const size_t BS = block_size();
454
455
0
   BOTAN_ARG_CHECK(buffer.size() >= offset, "Offset is out of range");
456
0
   const size_t sz = buffer.size() - offset;
457
0
   uint8_t* buf = buffer.data() + offset;
458
459
0
   BOTAN_ARG_CHECK(sz >= tag_size(), "input did not include the tag");
460
461
0
   const size_t remaining = sz - tag_size();
462
463
0
   secure_vector<uint8_t> mac(BS);
464
465
0
   if(remaining > 0) {
466
0
      const size_t final_full_blocks = remaining / BS;
467
0
      const size_t final_bytes = remaining - (final_full_blocks * BS);
468
469
0
      decrypt(buf, final_full_blocks);
470
0
      mac ^= m_L->offset();
471
472
0
      if(final_bytes > 0) {
473
0
         BOTAN_ASSERT(final_bytes < BS, "Only a partial block left");
474
475
0
         uint8_t* remainder = &buf[remaining - final_bytes];
476
477
0
         mac ^= m_L->star();
478
0
         secure_vector<uint8_t> pad(BS);
479
0
         m_cipher->encrypt(mac, pad);  // P_*
480
0
         xor_buf(remainder, pad.data(), final_bytes);
481
482
0
         xor_buf(m_checksum.data(), remainder, final_bytes);
483
0
         m_checksum[final_bytes] ^= 0x80;
484
0
      }
485
0
   } else {
486
0
      mac = m_L->offset();
487
0
   }
488
489
   // compute the mac
490
491
   // fold checksum
492
0
   for(size_t i = 0; i != m_checksum.size(); i += BS) {
493
0
      xor_buf(mac.data(), m_checksum.data() + i, BS);
494
0
   }
495
496
0
   mac ^= m_L->dollar();
497
0
   m_cipher->encrypt(mac);
498
0
   mac ^= m_ad_hash;
499
500
   // reset state
501
0
   zeroise(m_checksum);
502
0
   m_block_index = 0;
503
504
   // compare mac
505
0
   const uint8_t* included_tag = &buf[remaining];
506
507
0
   if(!CT::is_equal(mac.data(), included_tag, tag_size()).as_bool()) {
508
0
      throw Invalid_Authentication_Tag("OCB tag check failed");
509
0
   }
510
511
   // remove tag from end of message
512
0
   buffer.resize(remaining + offset);
513
0
}
514
515
}  // namespace Botan