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

randombit / botan / 26864473078

02 Jun 2026 07:58PM UTC coverage: 89.389% (+0.02%) from 89.37%
26864473078

push

github

web-flow
Merge pull request #5639 from randombit/jack/sm4-hwaes-ks

Add hwaes hook for SM4 key schedule

110434 of 123543 relevant lines covered (89.39%)

11152828.24 hits per line

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

93.08
/src/lib/ffi/ffi_cipher.cpp
1
/*
2
* (C) 2015,2017 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include <botan/ffi.h>
8

9
#include <botan/aead.h>
10
#include <botan/mem_ops.h>
11
#include <botan/internal/bit_ops.h>
12
#include <botan/internal/buffer_slicer.h>
13
#include <botan/internal/buffer_stuffer.h>
14
#include <botan/internal/ffi_util.h>
15
#include <botan/internal/scoped_cleanup.h>
16

17
#include <limits>
18

19
extern "C" {
20

21
using namespace Botan_FFI;
22

23
struct botan_cipher_struct final : public botan_struct<Botan::Cipher_Mode, 0xB4A2BF9C> {
24
   public:
25
      explicit botan_cipher_struct(std::unique_ptr<Botan::Cipher_Mode> x,
31✔
26
                                   size_t update_size,
27
                                   size_t ideal_update_size) :
31✔
28
            botan_struct(std::move(x)), m_update_size(update_size), m_ideal_update_size(ideal_update_size) {
31✔
29
         BOTAN_DEBUG_ASSERT(ideal_update_size >= update_size);
31✔
30
         m_buf.reserve(m_ideal_update_size);
31✔
31
      }
31✔
32

33
      Botan::secure_vector<uint8_t>& buf() { return m_buf; }
115✔
34

35
      size_t update_size() const { return m_update_size; }
92✔
36

37
      size_t ideal_update_size() const { return m_ideal_update_size; }
64✔
38

39
   private:
40
      Botan::secure_vector<uint8_t> m_buf;
41
      size_t m_update_size;
42
      size_t m_ideal_update_size;
43
};
44

45
namespace {
46

47
/**
48
 * Select an update size so that the following constraints are satisfies:
49
 *
50
 *   - greater than or equal to the mode's update granularity
51
 *   - greater than the mode's minimum final size
52
 *   - the mode's ideal update granularity is a multiple of this size
53
 *   - (optional) a power of 2
54
 *
55
 * Note that this is necessary mostly for backward-compatibility with previous
56
 * versions of the FFI (prior to Botan 3.5.0). For Botan 4.0.0 we should just
57
 * directly return the update granularity of the cipher mode and instruct users
58
 * to switch to botan_cipher_get_ideal_update_granularity() instead. See also
59
 * the discussion in GitHub Issue #4090.
60
 */
61
size_t ffi_choose_update_size(Botan::Cipher_Mode& mode) {
31✔
62
   const size_t update_granularity = mode.update_granularity();
31✔
63
   const size_t ideal_update_granularity = mode.ideal_granularity();
31✔
64
   const size_t minimum_final_size = mode.minimum_final_size();
31✔
65

66
   // If the minimum final size is zero, or the update_granularity is
67
   // already greater, just use that.
68
   if(minimum_final_size == 0 || update_granularity > minimum_final_size) {
31✔
69
      BOTAN_ASSERT_NOMSG(update_granularity > 0);
15✔
70
      return update_granularity;
71
   }
72

73
   // If the ideal granularity is a multiple of the minimum final size, we
74
   // might be able to use that if it stays within the ideal granularity.
75
   if(ideal_update_granularity % minimum_final_size == 0 && minimum_final_size * 2 <= ideal_update_granularity) {
16✔
76
      return minimum_final_size * 2;
77
   }
78

79
   // Otherwise, try to use the next power of two greater than the minimum
80
   // final size, if the ideal granularity is a multiple of that.
81
   BOTAN_ASSERT_NOMSG(minimum_final_size <= std::numeric_limits<uint16_t>::max());
×
82
   const size_t b1 = size_t(1) << Botan::ceil_log2(static_cast<uint16_t>(minimum_final_size));
×
83
   if(ideal_update_granularity % b1 == 0) {
×
84
      return b1;
85
   }
86

87
   // Last resort: Find the next integer greater than the minimum final size
88
   //              for which the ideal granularity is a multiple of.
89
   //              Most sensible cipher modes should never reach this point.
90
   BOTAN_ASSERT_NOMSG(minimum_final_size < ideal_update_granularity);
×
91
   size_t b2 = minimum_final_size + 1;
×
92
   for(; b2 < ideal_update_granularity && ideal_update_granularity % b2 != 0; ++b2) {}
×
93

94
   return b2;
95
}
96

97
}  // namespace
98

99
int botan_cipher_init(botan_cipher_t* cipher, const char* cipher_name, uint32_t flags) {
31✔
100
   return ffi_guard_thunk(__func__, [=]() -> int {
31✔
101
      if(any_null_pointers(cipher, cipher_name)) {
31✔
102
         return BOTAN_FFI_ERROR_NULL_POINTER;
103
      }
104
      const bool encrypt_p = ((flags & BOTAN_CIPHER_INIT_FLAG_MASK_DIRECTION) == BOTAN_CIPHER_INIT_FLAG_ENCRYPT);
31✔
105
      const Botan::Cipher_Dir dir = encrypt_p ? Botan::Cipher_Dir::Encryption : Botan::Cipher_Dir::Decryption;
31✔
106

107
      std::unique_ptr<Botan::Cipher_Mode> mode(Botan::Cipher_Mode::create(cipher_name, dir));
31✔
108
      if(!mode) {
31✔
109
         return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
110
      }
111

112
      const size_t update_size = ffi_choose_update_size(*mode);
31✔
113
      const size_t ideal_update_size = std::max(mode->ideal_granularity(), update_size);
31✔
114

115
      return ffi_new_object(cipher, std::move(mode), update_size, ideal_update_size);
31✔
116
   });
31✔
117
}
118

119
int botan_cipher_destroy(botan_cipher_t cipher) {
31✔
120
   return BOTAN_FFI_CHECKED_DELETE(cipher);
31✔
121
}
122

123
int botan_cipher_clear(botan_cipher_t cipher) {
8✔
124
   return BOTAN_FFI_VISIT(cipher, [=](auto& c) {
16✔
125
      cipher->buf().clear();
126
      c.clear();
127
   });
128
}
129

130
int botan_cipher_reset(botan_cipher_t cipher) {
4✔
131
   return BOTAN_FFI_VISIT(cipher, [=](auto& c) {
8✔
132
      cipher->buf().clear();
133
      c.reset();
134
   });
135
}
136

137
int botan_cipher_output_length(botan_cipher_t cipher, size_t in_len, size_t* out_len) {
4✔
138
   if(out_len == nullptr) {
4✔
139
      return BOTAN_FFI_ERROR_NULL_POINTER;
140
   }
141

142
   return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *out_len = c.output_length(in_len); });
8✔
143
}
144

145
int botan_cipher_query_keylen(botan_cipher_t cipher, size_t* out_minimum_keylength, size_t* out_maximum_keylength) {
13✔
146
   return BOTAN_FFI_VISIT(cipher, [=](const auto& c) {
26✔
147
      if(out_minimum_keylength) {
148
         *out_minimum_keylength = c.key_spec().minimum_keylength();
149
      }
150
      if(out_maximum_keylength) {
151
         *out_maximum_keylength = c.key_spec().maximum_keylength();
152
      }
153
   });
154
}
155

156
int botan_cipher_get_keyspec(botan_cipher_t cipher,
1✔
157
                             size_t* out_minimum_keylength,
158
                             size_t* out_maximum_keylength,
159
                             size_t* out_keylength_modulo) {
160
   return BOTAN_FFI_VISIT(cipher, [=](const auto& c) {
2✔
161
      if(out_minimum_keylength) {
162
         *out_minimum_keylength = c.key_spec().minimum_keylength();
163
      }
164
      if(out_maximum_keylength) {
165
         *out_maximum_keylength = c.key_spec().maximum_keylength();
166
      }
167
      if(out_keylength_modulo) {
168
         *out_keylength_modulo = c.key_spec().keylength_multiple();
169
      }
170
   });
171
}
172

173
int botan_cipher_set_key(botan_cipher_t cipher, const uint8_t* key, size_t key_len) {
43✔
174
   if(key_len > 0 && key == nullptr) {
43✔
175
      return BOTAN_FFI_ERROR_NULL_POINTER;
176
   }
177
   return BOTAN_FFI_VISIT(cipher, [=](auto& c) { c.set_key(key, key_len); });
86✔
178
}
179

180
int botan_cipher_start(botan_cipher_t cipher_obj, const uint8_t* nonce, size_t nonce_len) {
47✔
181
   if(nonce_len > 0 && nonce == nullptr) {
47✔
182
      return BOTAN_FFI_ERROR_NULL_POINTER;
183
   }
184

185
   return ffi_guard_thunk(__func__, [=]() -> int {
47✔
186
      Botan::Cipher_Mode& cipher = safe_get(cipher_obj);
47✔
187
      cipher.start(nonce, nonce_len);
47✔
188
      return BOTAN_FFI_SUCCESS;
47✔
189
   });
47✔
190
}
191

192
int botan_cipher_update(botan_cipher_t cipher_obj,
103✔
193
                        uint32_t flags,
194
                        uint8_t output[],
195
                        size_t output_size,
196
                        size_t* output_written,
197
                        const uint8_t input[],
198
                        size_t input_size,
199
                        size_t* input_consumed) {
200
   if(any_null_pointers(output_written, input_consumed)) {
103✔
201
      return BOTAN_FFI_ERROR_NULL_POINTER;
202
   }
203
   if(input_size > 0 && input == nullptr) {
103✔
204
      return BOTAN_FFI_ERROR_NULL_POINTER;
205
   }
206
   if(output_size > 0 && output == nullptr) {
103✔
207
      return BOTAN_FFI_ERROR_NULL_POINTER;
208
   }
209

210
   return ffi_guard_thunk(__func__, [=]() -> int {
103✔
211
      using namespace Botan;
103✔
212
      Cipher_Mode& cipher = safe_get(cipher_obj);
103✔
213
      secure_vector<uint8_t>& mbuf = cipher_obj->buf();
103✔
214

215
      // If the cipher object's internal buffer contains residual data from
216
      // a previous invocation, we can be sure that botan_cipher_update() was
217
      // called with the final flag set but not enough buffer space was provided
218
      // to accommodate the final output.
219
      const bool was_finished_before = !mbuf.empty();
103✔
220
      const bool final_input = (flags & BOTAN_CIPHER_UPDATE_FLAG_FINAL) != 0;
103✔
221

222
      // Bring the output variables into a defined state.
223
      *output_written = 0;
103✔
224
      *input_consumed = 0;
103✔
225

226
      // Once the final flag was set once, it must always be set for
227
      // consecutive invocations.
228
      if(was_finished_before && !final_input) {
103✔
229
         return BOTAN_FFI_ERROR_INVALID_OBJECT_STATE;
230
      }
231

232
      // If the final flag was set in a previous invocation, no more input
233
      // data can be processed.
234
      if(was_finished_before && input_size > 0) {
103✔
235
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
236
      }
237

238
      // Make sure that we always clear the internal buffer before returning
239
      // or aborting this invocation due to an exception.
240
      auto clean_buffer = scoped_cleanup([&mbuf] { mbuf.clear(); });
301✔
241

242
      if(final_input) {
103✔
243
         // If the final flag is set for the first time, we need to process the
244
         // remaining input data and then finalize the cipher object.
245
         if(!was_finished_before) {
39✔
246
            *input_consumed = input_size;
34✔
247
            mbuf.resize(input_size);
34✔
248
            copy_mem(mbuf, std::span(input, input_size));
34✔
249

250
            try {
34✔
251
               cipher.finish(mbuf);
34✔
252
            } catch(Invalid_Authentication_Tag&) {
×
253
               return BOTAN_FFI_ERROR_BAD_MAC;
×
254
            }
×
255
         }
256

257
         // At this point, the cipher object is finalized (potentially in a
258
         // previous invocation) and we can copy the final output to the caller.
259
         *output_written = mbuf.size();
39✔
260

261
         // Not enough space to copy the final output out to the caller.
262
         // Inform them how much space we need for a successful operation.
263
         if(output_size < mbuf.size()) {
39✔
264
            // This is the only place where mbuf is not cleared before returning.
265
            clean_buffer.disengage();
266
            return BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE;
267
         }
268

269
         // Copy the final output to the caller, mbuf is cleared afterwards.
270
         copy_mem(std::span(output, mbuf.size()), mbuf);
34✔
271
      } else {
272
         // Process data in a streamed fashion without finalizing. No data is
273
         // ever retained in the cipher object's internal buffer. If we run out
274
         // of either input data or output capacity, we stop and report that not
275
         // all bytes were processed via *output_written and *input_consumed.
276

277
         BufferSlicer in({input, input_size});
64✔
278
         BufferStuffer out({output, output_size});
64✔
279

280
         // Helper function to do blockwise processing of data.
281
         auto blockwise_update = [&](const size_t granularity) {
192✔
282
            if(granularity == 0) {
128✔
283
               return;
284
            }
285

286
            const size_t expected_output_per_iteration = cipher.requires_entire_message() ? 0 : granularity;
100✔
287
            mbuf.resize(granularity);
100✔
288

289
            while(in.remaining() >= granularity && out.remaining_capacity() >= expected_output_per_iteration) {
262✔
290
               copy_mem(mbuf, in.take(granularity));
62✔
291
               const auto written_bytes = cipher.process(mbuf);
62✔
292
               BOTAN_DEBUG_ASSERT(written_bytes == expected_output_per_iteration);
62✔
293
               if(written_bytes > 0) {
62✔
294
                  BOTAN_ASSERT_NOMSG(written_bytes <= granularity);
42✔
295
                  copy_mem(out.next(written_bytes), std::span(mbuf).first(written_bytes));
42✔
296
               }
297
            }
298
         };
64✔
299

300
         // First, process as much data as possible in chunks of ideal granularity
301
         blockwise_update(cipher_obj->ideal_update_size());
64✔
302

303
         // Then process the remaining bytes in chunks of update_size() or, in one go
304
         // if update_size() is equal to 1 --> i.e. likely a stream cipher.
305
         const bool is_stream_cipher = (cipher_obj->update_size() == 1);
64✔
306
         const size_t tail_granularity =
64✔
307
            is_stream_cipher ? std::min(in.remaining(), out.remaining_capacity()) : cipher_obj->update_size();
64✔
308
         BOTAN_DEBUG_ASSERT(tail_granularity < cipher_obj->ideal_update_size());
64✔
309
         blockwise_update(tail_granularity);
64✔
310

311
         // Inform the caller about the amount of data processed.
312
         *output_written = output_size - out.remaining_capacity();
64✔
313
         *input_consumed = input_size - in.remaining();
64✔
314
      }
315

316
      return BOTAN_FFI_SUCCESS;
317
   });
206✔
318
}
319

320
int botan_cipher_set_associated_data(botan_cipher_t cipher, const uint8_t* ad, size_t ad_len) {
8✔
321
   if(ad_len > 0 && ad == nullptr) {
8✔
322
      return BOTAN_FFI_ERROR_NULL_POINTER;
323
   }
324

325
   return BOTAN_FFI_VISIT(cipher, [=](auto& c) {
16✔
326
      if(Botan::AEAD_Mode* aead = dynamic_cast<Botan::AEAD_Mode*>(&c)) {
327
         aead->set_associated_data(ad, ad_len);
328
         return BOTAN_FFI_SUCCESS;
329
      }
330
      return BOTAN_FFI_ERROR_BAD_PARAMETER;
331
   });
332
}
333

334
int botan_cipher_valid_nonce_length(botan_cipher_t cipher, size_t nl) {
6✔
335
   return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return c.valid_nonce_length(nl) ? 1 : 0; });
12✔
336
}
337

338
int botan_cipher_get_default_nonce_length(botan_cipher_t cipher, size_t* nl) {
12✔
339
   if(nl == nullptr) {
12✔
340
      return BOTAN_FFI_ERROR_NULL_POINTER;
341
   }
342
   return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *nl = c.default_nonce_length(); });
24✔
343
}
344

345
int botan_cipher_get_update_granularity(botan_cipher_t cipher, size_t* ug) {
28✔
346
   if(ug == nullptr) {
28✔
347
      return BOTAN_FFI_ERROR_NULL_POINTER;
348
   }
349
   return BOTAN_FFI_VISIT(cipher, [=](const auto& /*c*/) { *ug = cipher->update_size(); });
56✔
350
}
351

352
int botan_cipher_get_ideal_update_granularity(botan_cipher_t cipher, size_t* ug) {
28✔
353
   if(ug == nullptr) {
28✔
354
      return BOTAN_FFI_ERROR_NULL_POINTER;
355
   }
356
   return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *ug = c.ideal_granularity(); });
56✔
357
}
358

359
int botan_cipher_get_tag_length(botan_cipher_t cipher, size_t* tl) {
16✔
360
   if(tl == nullptr) {
16✔
361
      return BOTAN_FFI_ERROR_NULL_POINTER;
362
   }
363
   return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *tl = c.tag_size(); });
32✔
364
}
365

366
int botan_cipher_is_authenticated(botan_cipher_t cipher) {
15✔
367
   return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return c.authenticated() ? 1 : 0; });
30✔
368
}
369

370
int botan_cipher_requires_entire_message(botan_cipher_t cipher) {
5✔
371
   return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return c.requires_entire_message() ? 1 : 0; });
10✔
372
}
373

374
int botan_cipher_name(botan_cipher_t cipher, char* name, size_t* name_len) {
8✔
375
   return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return write_str_output(name, name_len, c.name()); });
16✔
376
}
377
}
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