Botan 3.12.0
Crypto and TLS for C&
ffi_cipher.cpp
Go to the documentation of this file.
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
19extern "C" {
20
21using namespace Botan_FFI;
22
23struct 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,
26 size_t update_size,
27 size_t ideal_update_size) :
28 botan_struct(std::move(x)), m_update_size(update_size), m_ideal_update_size(ideal_update_size) {
29 BOTAN_DEBUG_ASSERT(ideal_update_size >= update_size);
30 m_buf.reserve(m_ideal_update_size);
31 }
32
33 Botan::secure_vector<uint8_t>& buf() { return m_buf; }
34
35 size_t update_size() const { return m_update_size; }
36
37 size_t ideal_update_size() const { return m_ideal_update_size; }
38
39 private:
41 size_t m_update_size;
42 size_t m_ideal_update_size;
43};
44
45namespace {
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 */
61size_t ffi_choose_update_size(Botan::Cipher_Mode& mode) {
62 const size_t update_granularity = mode.update_granularity();
63 const size_t ideal_update_granularity = mode.ideal_granularity();
64 const size_t minimum_final_size = mode.minimum_final_size();
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) {
69 BOTAN_ASSERT_NOMSG(update_granularity > 0);
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) {
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
99int botan_cipher_init(botan_cipher_t* cipher, const char* cipher_name, uint32_t flags) {
100 return ffi_guard_thunk(__func__, [=]() -> int {
101 if(any_null_pointers(cipher, cipher_name)) {
103 }
104 const bool encrypt_p = ((flags & BOTAN_CIPHER_INIT_FLAG_MASK_DIRECTION) == BOTAN_CIPHER_INIT_FLAG_ENCRYPT);
106
107 std::unique_ptr<Botan::Cipher_Mode> mode(Botan::Cipher_Mode::create(cipher_name, dir));
108 if(!mode) {
110 }
111
112 const size_t update_size = ffi_choose_update_size(*mode);
113 const size_t ideal_update_size = std::max(mode->ideal_granularity(), update_size);
114
115 return ffi_new_object(cipher, std::move(mode), update_size, ideal_update_size);
116 });
117}
118
120 return BOTAN_FFI_CHECKED_DELETE(cipher);
121}
122
124 return BOTAN_FFI_VISIT(cipher, [=](auto& c) {
125 cipher->buf().clear();
126 c.clear();
127 });
128}
129
131 return BOTAN_FFI_VISIT(cipher, [=](auto& c) {
132 cipher->buf().clear();
133 c.reset();
134 });
135}
136
137int botan_cipher_output_length(botan_cipher_t cipher, size_t in_len, size_t* out_len) {
138 if(out_len == nullptr) {
140 }
141
142 return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *out_len = c.output_length(in_len); });
143}
144
145int botan_cipher_query_keylen(botan_cipher_t cipher, size_t* out_minimum_keylength, size_t* out_maximum_keylength) {
146 return BOTAN_FFI_VISIT(cipher, [=](const auto& c) {
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
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) {
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
173int botan_cipher_set_key(botan_cipher_t cipher, const uint8_t* key, size_t key_len) {
174 if(key_len > 0 && key == nullptr) {
176 }
177 return BOTAN_FFI_VISIT(cipher, [=](auto& c) { c.set_key(key, key_len); });
178}
179
180int botan_cipher_start(botan_cipher_t cipher_obj, const uint8_t* nonce, size_t nonce_len) {
181 return ffi_guard_thunk(__func__, [=]() -> int {
182 Botan::Cipher_Mode& cipher = safe_get(cipher_obj);
183 cipher.start(nonce, nonce_len);
184 return BOTAN_FFI_SUCCESS;
185 });
186}
187
189 uint32_t flags,
190 uint8_t output[],
191 size_t output_size,
192 size_t* output_written,
193 const uint8_t input[],
194 size_t input_size,
195 size_t* input_consumed) {
196 if(any_null_pointers(output_written, input_consumed)) {
198 }
199
200 return ffi_guard_thunk(__func__, [=]() -> int {
201 using namespace Botan;
202 Cipher_Mode& cipher = safe_get(cipher_obj);
203 secure_vector<uint8_t>& mbuf = cipher_obj->buf();
204
205 // If the cipher object's internal buffer contains residual data from
206 // a previous invocation, we can be sure that botan_cipher_update() was
207 // called with the final flag set but not enough buffer space was provided
208 // to accommodate the final output.
209 const bool was_finished_before = !mbuf.empty();
210 const bool final_input = (flags & BOTAN_CIPHER_UPDATE_FLAG_FINAL) != 0;
211
212 // Bring the output variables into a defined state.
213 *output_written = 0;
214 *input_consumed = 0;
215
216 // Once the final flag was set once, it must always be set for
217 // consecutive invocations.
218 if(was_finished_before && !final_input) {
220 }
221
222 // If the final flag was set in a previous invocation, no more input
223 // data can be processed.
224 if(was_finished_before && input_size > 0) {
226 }
227
228 // Make sure that we always clear the internal buffer before returning
229 // or aborting this invocation due to an exception.
230 auto clean_buffer = scoped_cleanup([&mbuf] { mbuf.clear(); });
231
232 if(final_input) {
233 // If the final flag is set for the first time, we need to process the
234 // remaining input data and then finalize the cipher object.
235 if(!was_finished_before) {
236 *input_consumed = input_size;
237 mbuf.resize(input_size);
238 copy_mem(mbuf, std::span(input, input_size));
239
240 try {
241 cipher.finish(mbuf);
244 }
245 }
246
247 // At this point, the cipher object is finalized (potentially in a
248 // previous invocation) and we can copy the final output to the caller.
249 *output_written = mbuf.size();
250
251 // Not enough space to copy the final output out to the caller.
252 // Inform them how much space we need for a successful operation.
253 if(output_size < mbuf.size()) {
254 // This is the only place where mbuf is not cleared before returning.
255 clean_buffer.disengage();
257 }
258
259 // Copy the final output to the caller, mbuf is cleared afterwards.
260 copy_mem(std::span(output, mbuf.size()), mbuf);
261 } else {
262 // Process data in a streamed fashion without finalizing. No data is
263 // ever retained in the cipher object's internal buffer. If we run out
264 // of either input data or output capacity, we stop and report that not
265 // all bytes were processed via *output_written and *input_consumed.
266
267 BufferSlicer in({input, input_size});
268 BufferStuffer out({output, output_size});
269
270 // Helper function to do blockwise processing of data.
271 auto blockwise_update = [&](const size_t granularity) {
272 if(granularity == 0) {
273 return;
274 }
275
276 const size_t expected_output_per_iteration = cipher.requires_entire_message() ? 0 : granularity;
277 mbuf.resize(granularity);
278
279 while(in.remaining() >= granularity && out.remaining_capacity() >= expected_output_per_iteration) {
280 copy_mem(mbuf, in.take(granularity));
281 const auto written_bytes = cipher.process(mbuf);
282 BOTAN_DEBUG_ASSERT(written_bytes == expected_output_per_iteration);
283 if(written_bytes > 0) {
284 BOTAN_ASSERT_NOMSG(written_bytes <= granularity);
285 copy_mem(out.next(written_bytes), std::span(mbuf).first(written_bytes));
286 }
287 }
288 };
289
290 // First, process as much data as possible in chunks of ideal granularity
291 blockwise_update(cipher_obj->ideal_update_size());
292
293 // Then process the remaining bytes in chunks of update_size() or, in one go
294 // if update_size() is equal to 1 --> i.e. likely a stream cipher.
295 const bool is_stream_cipher = (cipher_obj->update_size() == 1);
296 const size_t tail_granularity =
297 is_stream_cipher ? std::min(in.remaining(), out.remaining_capacity()) : cipher_obj->update_size();
298 BOTAN_DEBUG_ASSERT(tail_granularity < cipher_obj->ideal_update_size());
299 blockwise_update(tail_granularity);
300
301 // Inform the caller about the amount of data processed.
302 *output_written = output_size - out.remaining_capacity();
303 *input_consumed = input_size - in.remaining();
304 }
305
306 return BOTAN_FFI_SUCCESS;
307 });
308}
309
310int botan_cipher_set_associated_data(botan_cipher_t cipher, const uint8_t* ad, size_t ad_len) {
311 return BOTAN_FFI_VISIT(cipher, [=](auto& c) {
312 if(Botan::AEAD_Mode* aead = dynamic_cast<Botan::AEAD_Mode*>(&c)) {
313 aead->set_associated_data(ad, ad_len);
314 return BOTAN_FFI_SUCCESS;
315 }
317 });
318}
319
321 return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return c.valid_nonce_length(nl) ? 1 : 0; });
322}
323
325 if(nl == nullptr) {
327 }
328 return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *nl = c.default_nonce_length(); });
329}
330
332 if(ug == nullptr) {
334 }
335 return BOTAN_FFI_VISIT(cipher, [=](const auto& /*c*/) { *ug = cipher->update_size(); });
336}
337
339 if(ug == nullptr) {
341 }
342 return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *ug = c.ideal_granularity(); });
343}
344
346 if(tl == nullptr) {
348 }
349 return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { *tl = c.tag_size(); });
350}
351
353 return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return c.authenticated() ? 1 : 0; });
354}
355
357 return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return c.requires_entire_message() ? 1 : 0; });
358}
359
360int botan_cipher_name(botan_cipher_t cipher, char* name, size_t* name_len) {
361 return BOTAN_FFI_VISIT(cipher, [=](const auto& c) { return write_str_output(name, name_len, c.name()); });
362}
363}
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_DEBUG_ASSERT(expr)
Definition assert.h:129
Helper class to ease in-place marshalling of concatenated fixed-length values.
static std::unique_ptr< Cipher_Mode > create(std::string_view algo, Cipher_Dir direction, std::string_view provider="")
void start(std::span< const uint8_t > nonce)
Definition cipher_mode.h:97
void finish(secure_vector< uint8_t > &final_block, size_t offset=0)
virtual bool requires_entire_message() const
virtual size_t ideal_granularity() const =0
size_t process(std::span< uint8_t > msg)
virtual size_t minimum_final_size() const =0
virtual size_t update_granularity() const =0
Helper class to create a RAII-style cleanup callback.
#define BOTAN_CIPHER_INIT_FLAG_ENCRYPT
Definition ffi.h:669
#define BOTAN_CIPHER_UPDATE_FLAG_FINAL
Definition ffi.h:763
#define BOTAN_CIPHER_INIT_FLAG_MASK_DIRECTION
Definition ffi.h:668
@ BOTAN_FFI_ERROR_NOT_IMPLEMENTED
Definition ffi.h:140
@ BOTAN_FFI_ERROR_NULL_POINTER
Definition ffi.h:133
@ BOTAN_FFI_SUCCESS
Definition ffi.h:116
@ BOTAN_FFI_ERROR_INVALID_OBJECT_STATE
Definition ffi.h:137
@ BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE
Definition ffi.h:124
@ BOTAN_FFI_ERROR_BAD_MAC
Definition ffi.h:121
@ BOTAN_FFI_ERROR_BAD_PARAMETER
Definition ffi.h:134
struct botan_cipher_struct * botan_cipher_t
Definition ffi.h:666
int botan_cipher_valid_nonce_length(botan_cipher_t cipher, size_t nl)
int botan_cipher_requires_entire_message(botan_cipher_t cipher)
int botan_cipher_output_length(botan_cipher_t cipher, size_t in_len, size_t *out_len)
int botan_cipher_reset(botan_cipher_t cipher)
int botan_cipher_destroy(botan_cipher_t cipher)
int botan_cipher_name(botan_cipher_t cipher, char *name, size_t *name_len)
int botan_cipher_set_associated_data(botan_cipher_t cipher, const uint8_t *ad, size_t ad_len)
int botan_cipher_start(botan_cipher_t cipher_obj, const uint8_t *nonce, size_t nonce_len)
int botan_cipher_get_tag_length(botan_cipher_t cipher, size_t *tl)
int botan_cipher_update(botan_cipher_t cipher_obj, uint32_t flags, uint8_t output[], size_t output_size, size_t *output_written, const uint8_t input[], size_t input_size, size_t *input_consumed)
Encrypt/Decrypt some data and/or finalize the encryption/decryption.
int botan_cipher_get_keyspec(botan_cipher_t cipher, size_t *out_minimum_keylength, size_t *out_maximum_keylength, size_t *out_keylength_modulo)
int botan_cipher_set_key(botan_cipher_t cipher, const uint8_t *key, size_t key_len)
int botan_cipher_get_ideal_update_granularity(botan_cipher_t cipher, size_t *ug)
int botan_cipher_is_authenticated(botan_cipher_t cipher)
int botan_cipher_clear(botan_cipher_t cipher)
int botan_cipher_get_default_nonce_length(botan_cipher_t cipher, size_t *nl)
int botan_cipher_init(botan_cipher_t *cipher, const char *cipher_name, uint32_t flags)
int botan_cipher_query_keylen(botan_cipher_t cipher, size_t *out_minimum_keylength, size_t *out_maximum_keylength)
int botan_cipher_get_update_granularity(botan_cipher_t cipher, size_t *ug)
#define BOTAN_FFI_VISIT(obj, lambda)
Definition ffi_util.h:158
#define BOTAN_FFI_CHECKED_DELETE(o)
Definition ffi_util.h:188
T & safe_get(botan_struct< T, M > *p)
Definition ffi_util.h:79
BOTAN_FFI_ERROR ffi_new_object(T *obj, Args &&... args)
Definition ffi_util.h:178
int ffi_guard_thunk(const char *func_name, T thunk)
Definition ffi_util.h:95
bool any_null_pointers(Ptrs... ptr)
Definition mem_utils.h:54
int write_str_output(char out[], size_t *out_len, const std::string &str)
Definition ffi_util.h:268
constexpr void copy_mem(T *out, const T *in, size_t n)
Definition mem_ops.h:144
constexpr uint8_t ceil_log2(T x)
Definition bit_ops.h:140
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:68
botan_struct(std::unique_ptr< Botan::Cipher_Mode > obj)
Definition ffi_util.h:38