Botan 3.0.0
Crypto and TLS for C&
tls_session.cpp
Go to the documentation of this file.
1/*
2* TLS Session State
3* (C) 2011-2012,2015,2019 Jack Lloyd
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/tls_session.h>
9#include <botan/internal/loadstor.h>
10#include <botan/der_enc.h>
11#include <botan/ber_dec.h>
12#include <botan/asn1_obj.h>
13#include <botan/pem.h>
14#include <botan/aead.h>
15#include <botan/mac.h>
16#include <botan/rng.h>
17
18#include <botan/tls_messages.h>
19#include <botan/tls_callbacks.h>
20
21#include <botan/internal/stl_util.h>
22
23#include <utility>
24
25namespace Botan::TLS {
26
27void Session_Handle::validate_constraints() const
28 {
29 std::visit(overloaded
30 {
31 [](const Session_ID& id)
32 {
33 // RFC 5246 7.4.1.2
34 // opaque SessionID<0..32>;
35 BOTAN_ARG_CHECK(!id.empty(), "Session ID must not be empty");
36 BOTAN_ARG_CHECK(id.size() <= 32,
37 "Session ID cannot be longer than 32 bytes");
38 },
39 [](const Session_Ticket& ticket)
40 {
41 BOTAN_ARG_CHECK(!ticket.empty(), "Ticket most not be empty");
42 BOTAN_ARG_CHECK(ticket.size() <= std::numeric_limits<uint16_t>::max(),
43 "Ticket cannot be longer than 64kB");
44 },
45 [](const Opaque_Session_Handle& handle)
46 {
47 // RFC 8446 4.6.1
48 // opaque ticket<1..2^16-1>;
49 BOTAN_ARG_CHECK(!handle.empty(), "Opaque session handle must not be empty");
50 BOTAN_ARG_CHECK(handle.size() <= std::numeric_limits<uint16_t>::max(),
51 "Opaque session handle cannot be longer than 64kB");
52 },
53 }, m_handle);
54 }
55
57 {
58 // both a Session_ID and a Session_Ticket could be an Opaque_Session_Handle
59 return Opaque_Session_Handle(std::visit([](const auto& handle) { return handle.get(); }, m_handle));
60 }
61
62std::optional<Session_ID> Session_Handle::id() const
63 {
64 if(is_id())
65 { return std::get<Session_ID>(m_handle); }
66
67 // Opaque handles can mimick as a Session_ID if they are short enough
69 {
70 const auto& handle = std::get<Opaque_Session_Handle>(m_handle);
71 if(handle.size() <= 32)
72 { return Session_ID(handle.get()); }
73 }
74
75 return std::nullopt;
76 }
77
78std::optional<Session_Ticket> Session_Handle::ticket() const
79 {
80 if(is_ticket())
81 { return std::get<Session_Ticket>(m_handle); }
82
83 // Opaque handles can mimick 'normal' Session_Tickets at any time
85 { return Session_Ticket(std::get<Opaque_Session_Handle>(m_handle).get()); }
86
87 return std::nullopt;
88 }
89
90
92 {
94 if (!suite.has_value())
95 {
96 throw Decoding_Error("Failed to find cipher suite for ID " +
97 std::to_string(m_ciphersuite));
98 }
99 return suite.value();
100 }
101
102
103Session_Summary::Session_Summary(const Session_Base& base,
104 bool was_resumption)
105 : Session_Base(base)
106 {
107 BOTAN_ARG_CHECK(version().is_pre_tls_13(),
108 "Instantiated a TLS 1.2 session summary with an newer TLS version");
109
110 const auto cs = ciphersuite();
111 m_psk_used = cs.psk_ciphersuite();
112 m_kex_algo = cs.kex_algo();
113 m_was_resumption = was_resumption;
114 }
115
116#if defined(BOTAN_HAS_TLS_13)
117
118Session_Summary::Session_Summary(const Server_Hello_13& server_hello,
119 Connection_Side side,
120 std::vector<X509_Certificate> peer_certs,
121 Server_Information server_info,
122 std::chrono::system_clock::time_point current_timestamp) :
123 Session_Base(
124 current_timestamp,
125 server_hello.selected_version(),
126 server_hello.ciphersuite(),
127 side,
128
129 // TODO: SRTP might become necessary when DTLS 1.3 is being implemented
130 0,
131
132 // RFC 8446 Appendix D
133 // Because TLS 1.3 always hashes in the transcript up to the server
134 // Finished, implementations which support both TLS 1.3 and earlier
135 // versions SHOULD indicate the use of the Extended Master Secret
136 // extension in their APIs whenever TLS 1.3 is used.
137 true,
138
139 // TLS 1.3 uses AEADs, so technically encrypt-then-MAC is not applicable.
140 false,
141 std::move(peer_certs),
142 std::move(server_info))
143 {
144 BOTAN_ARG_CHECK(version().is_tls_13_or_later(),
145 "Instantiated a TLS 1.3 session summary with an older TLS version");
146 set_session_id(server_hello.session_id());
147
148 // Currently, we use PSK exclusively for session resumption. Hence, a
149 // server hello with a PSK extension indicates a session resumption.
150 //
151 // TODO: once manually configured PSKs are implemented, this will need
152 // extra consideration to tell PSK usage and resumption apart.
153 m_psk_used = server_hello.extensions().has<PSK>();
154 m_was_resumption = m_psk_used;
155
156 // In TLS 1.3 the key exchange algorithm is not negotiated in the ciphersuite
157 // anymore. This provides a compatible identifier for applications to use.
158 m_kex_algo = kex_method_to_string([&]
159 {
160 if(m_psk_used)
161 {
162 if(const auto keyshare = server_hello.extensions().get<Key_Share>())
163 {
164 const auto group = keyshare->selected_group();
165 if(is_dh(group))
166 { return Kex_Algo::DHE_PSK; }
167 else if(is_ecdh(group) || is_x25519(group))
168 { return Kex_Algo::ECDHE_PSK; }
169 }
170 else
171 { return Kex_Algo::PSK; }
172 }
173 else
174 {
175 const auto keyshare = server_hello.extensions().get<Key_Share>();
176 BOTAN_ASSERT_NONNULL(keyshare);
177 const auto group = keyshare->selected_group();
178 if(is_dh(group))
179 { return Kex_Algo::DH; }
180 else if(is_ecdh(group) || is_x25519(group))
181 { return Kex_Algo::ECDH; }
182 }
183
184 return Kex_Algo::UNDEFINED;
185 }());
186 }
187
188#endif
189
190
191Session::Session(const secure_vector<uint8_t>& master_secret,
192 Protocol_Version version,
193 uint16_t ciphersuite,
194 Connection_Side side,
195 bool extended_master_secret,
196 bool encrypt_then_mac,
197 const std::vector<X509_Certificate>& certs,
198 const Server_Information& server_info,
199 uint16_t srtp_profile,
200 std::chrono::system_clock::time_point current_timestamp,
201 std::chrono::seconds lifetime_hint) :
203 current_timestamp,
204 version,
205 ciphersuite,
206 side,
207 srtp_profile,
208 extended_master_secret,
209 encrypt_then_mac,
210 certs,
211 server_info),
212 m_master_secret(master_secret),
213 m_early_data_allowed(false),
214 m_max_early_data_bytes(0),
215 m_ticket_age_add(0),
216 m_lifetime_hint(lifetime_hint)
217 {
219 "Instantiated a TLS 1.2 session object with a TLS version newer than 1.2");
220 }
221
222#if defined(BOTAN_HAS_TLS_13)
223
225 const std::optional<uint32_t>& max_early_data_bytes,
226 uint32_t ticket_age_add,
227 std::chrono::seconds lifetime_hint,
228 Protocol_Version version,
229 uint16_t ciphersuite,
230 Connection_Side side,
231 const std::vector<X509_Certificate>& peer_certs,
232 const Server_Information& server_info,
233 std::chrono::system_clock::time_point current_timestamp) :
235 current_timestamp,
236 version,
237 ciphersuite,
238 side,
239
240 // TODO: SRTP might become necessary when DTLS 1.3 is being implemented
241 0,
242
243 // RFC 8446 Appendix D
244 // Because TLS 1.3 always hashes in the transcript up to the server
245 // Finished, implementations which support both TLS 1.3 and earlier
246 // versions SHOULD indicate the use of the Extended Master Secret
247 // extension in their APIs whenever TLS 1.3 is used.
248 true,
249
250 // TLS 1.3 uses AEADs, so technically encrypt-then-MAC is not applicable.
251 false,
252 peer_certs,
253 server_info),
254 m_master_secret(session_psk),
255 m_early_data_allowed(max_early_data_bytes.has_value()),
256 m_max_early_data_bytes(max_early_data_bytes.value_or(0)),
257 m_ticket_age_add(ticket_age_add),
258 m_lifetime_hint(lifetime_hint)
259 {
261 "Instantiated a TLS 1.3 session object with a TLS version older than 1.3");
262 }
263
265 const std::optional<uint32_t>& max_early_data_bytes,
266 std::chrono::seconds lifetime_hint,
267 const std::vector<X509_Certificate>& peer_certs,
268 const Client_Hello_13& client_hello,
269 const Server_Hello_13& server_hello,
270 Callbacks& callbacks,
272 Session_Base(
273 callbacks.tls_current_timestamp(),
274 server_hello.selected_version(),
275 server_hello.ciphersuite(),
276 Connection_Side::Server,
277 0, true, false, // see constructor above for rationales
278 peer_certs,
279 Server_Information(client_hello.sni_hostname())),
280 m_master_secret(std::move(session_psk)),
281 m_early_data_allowed(max_early_data_bytes.has_value()),
282 m_max_early_data_bytes(max_early_data_bytes.value_or(0)),
283 m_ticket_age_add(load_be<uint32_t>(rng.random_vec(4).data(), 0)),
284 m_lifetime_hint(lifetime_hint)
285 {
287 "Instantiated a TLS 1.3 session object with a TLS version older than 1.3");
288 }
289
290#endif
291
292Session::Session(std::string_view pem)
293 : Session(PEM_Code::decode_check_label(pem, "TLS SESSION")) {}
294
295Session::Session(std::span<const uint8_t> ber_data)
296 {
297 uint8_t side_code = 0;
298
299 ASN1_String server_hostname;
300 ASN1_String server_service;
301 size_t server_port;
302
303 uint8_t major_version = 0, minor_version = 0;
304
305 size_t start_time = 0;
306 size_t srtp_profile = 0;
307 uint16_t ciphersuite_code = 0;
308 uint64_t lifetime_hint = 0;
309
310 BER_Decoder(ber_data.data(), ber_data.size())
312 .decode_and_check(static_cast<size_t>(TLS_SESSION_PARAM_STRUCT_VERSION),
313 "Unknown version in serialized TLS session")
315 .decode_integer_type(major_version)
316 .decode_integer_type(minor_version)
318 .decode_integer_type(side_code)
321 .decode(m_master_secret, ASN1_Type::OctetString)
323 .decode(server_hostname)
324 .decode(server_service)
325 .decode(server_port)
326 .decode(srtp_profile)
327 .decode(m_early_data_allowed)
328 .decode_integer_type(m_max_early_data_bytes)
329 .decode_integer_type(m_ticket_age_add)
330 .decode_integer_type(lifetime_hint)
331 .end_cons()
332 .verify_end();
333
335 {
336 throw Decoding_Error("Serialized TLS session contains unknown cipher suite "
337 "(" + std::to_string(ciphersuite_code) + ")");
338 }
339
341 m_version = Protocol_Version(major_version, minor_version);
342 m_start_time = std::chrono::system_clock::from_time_t(start_time);
343 m_connection_side = static_cast<Connection_Side>(side_code);
344 m_srtp_profile = static_cast<uint16_t>(srtp_profile);
345
346 m_server_info = Server_Information(server_hostname.value(),
347 server_service.value(),
348 static_cast<uint16_t>(server_port));
349
350 m_lifetime_hint = std::chrono::seconds(lifetime_hint);
351 }
352
354 {
355 return DER_Encoder()
357 .encode(static_cast<size_t>(TLS_SESSION_PARAM_STRUCT_VERSION))
358 .encode(static_cast<size_t>(std::chrono::system_clock::to_time_t(m_start_time)))
359 .encode(static_cast<size_t>(m_version.major_version()))
360 .encode(static_cast<size_t>(m_version.minor_version()))
361 .encode(static_cast<size_t>(m_ciphersuite))
362 .encode(static_cast<size_t>(m_connection_side))
365 .encode(m_master_secret, ASN1_Type::OctetString)
368 .end_cons()
371 .encode(static_cast<size_t>(m_server_info.port()))
372 .encode(static_cast<size_t>(m_srtp_profile))
373
374 // the fields below were introduced for TLS 1.3 session tickets
375 .encode(m_early_data_allowed)
376 .encode(static_cast<size_t>(m_max_early_data_bytes))
377 .encode(static_cast<size_t>(m_ticket_age_add))
378 .encode(static_cast<size_t>(m_lifetime_hint.count()))
379 .end_cons()
380 .get_contents();
381 }
382
383std::string Session::PEM_encode() const
384 {
385 return PEM_Code::encode(this->DER_encode(), "TLS SESSION");
386 }
387
389 {
390 BOTAN_STATE_CHECK(!m_master_secret.empty());
391 return std::exchange(m_master_secret, {});
392 }
393
394namespace {
395
396// The output length of the HMAC must be a valid keylength for the AEAD
397const char* const TLS_SESSION_CRYPT_HMAC = "HMAC(SHA-512-256)";
398// SIV would be better, but we can't assume it is available
399const char* const TLS_SESSION_CRYPT_AEAD = "AES-256/GCM";
400const char* const TLS_SESSION_CRYPT_KEY_NAME = "BOTAN TLS SESSION KEY NAME";
401const uint64_t TLS_SESSION_CRYPT_MAGIC = 0x068B5A9D396C0000;
402const size_t TLS_SESSION_CRYPT_MAGIC_LEN = 8;
403const size_t TLS_SESSION_CRYPT_KEY_NAME_LEN = 4;
404const size_t TLS_SESSION_CRYPT_AEAD_NONCE_LEN = 12;
405const size_t TLS_SESSION_CRYPT_AEAD_KEY_SEED_LEN = 16;
406const size_t TLS_SESSION_CRYPT_AEAD_TAG_SIZE = 16;
407
408const size_t TLS_SESSION_CRYPT_HDR_LEN =
409 TLS_SESSION_CRYPT_MAGIC_LEN +
410 TLS_SESSION_CRYPT_KEY_NAME_LEN +
411 TLS_SESSION_CRYPT_AEAD_NONCE_LEN +
412 TLS_SESSION_CRYPT_AEAD_KEY_SEED_LEN;
413
414const size_t TLS_SESSION_CRYPT_OVERHEAD =
415 TLS_SESSION_CRYPT_HDR_LEN + TLS_SESSION_CRYPT_AEAD_TAG_SIZE;
416
417}
418
419std::vector<uint8_t>
421 {
422 auto hmac = MessageAuthenticationCode::create_or_throw(TLS_SESSION_CRYPT_HMAC);
423 hmac->set_key(key);
424
425 // First derive the "key name"
426 std::vector<uint8_t> key_name(hmac->output_length());
427 hmac->update(TLS_SESSION_CRYPT_KEY_NAME);
428 hmac->final(key_name.data());
429 key_name.resize(TLS_SESSION_CRYPT_KEY_NAME_LEN);
430
431 std::vector<uint8_t> aead_nonce;
432 std::vector<uint8_t> key_seed;
433
434 rng.random_vec(aead_nonce, TLS_SESSION_CRYPT_AEAD_NONCE_LEN);
435 rng.random_vec(key_seed, TLS_SESSION_CRYPT_AEAD_KEY_SEED_LEN);
436
437 hmac->update(key_seed);
438 const secure_vector<uint8_t> aead_key = hmac->final();
439
440 secure_vector<uint8_t> bits = this->DER_encode();
441
442 // create the header
443 std::vector<uint8_t> buf;
444 buf.reserve(TLS_SESSION_CRYPT_OVERHEAD + bits.size());
445 buf.resize(TLS_SESSION_CRYPT_MAGIC_LEN);
446 store_be(TLS_SESSION_CRYPT_MAGIC, &buf[0]);
447 buf += key_name;
448 buf += key_seed;
449 buf += aead_nonce;
450
451 auto aead = AEAD_Mode::create_or_throw(TLS_SESSION_CRYPT_AEAD, Cipher_Dir::Encryption);
452 BOTAN_ASSERT_NOMSG(aead->valid_nonce_length(TLS_SESSION_CRYPT_AEAD_NONCE_LEN));
453 BOTAN_ASSERT_NOMSG(aead->tag_size() == TLS_SESSION_CRYPT_AEAD_TAG_SIZE);
454 aead->set_key(aead_key);
455 aead->set_associated_data(buf);
456 aead->start(aead_nonce);
457 aead->finish(bits, 0);
458
459 // append the ciphertext
460 buf += bits;
461 return buf;
462 }
463
464Session Session::decrypt(std::span<const uint8_t> in, const SymmetricKey& key)
465 {
466 try
467 {
468 const size_t min_session_size = 48 + 4; // serious under-estimate
469 if(in.size() < TLS_SESSION_CRYPT_OVERHEAD + min_session_size)
470 throw Decoding_Error("Encrypted session too short to be valid");
471
472 // TODO: replace those raw pointers with sub-spans into `in`
473 const uint8_t* magic = in.data();
474 const uint8_t* key_name = magic + TLS_SESSION_CRYPT_MAGIC_LEN;
475 const uint8_t* key_seed = key_name + TLS_SESSION_CRYPT_KEY_NAME_LEN;
476 const uint8_t* aead_nonce = key_seed + TLS_SESSION_CRYPT_AEAD_KEY_SEED_LEN;
477 const uint8_t* ctext = aead_nonce + TLS_SESSION_CRYPT_AEAD_NONCE_LEN;
478 const size_t ctext_len = in.size() - TLS_SESSION_CRYPT_HDR_LEN; // includes the tag
479
480 if(load_be<uint64_t>(magic, 0) != TLS_SESSION_CRYPT_MAGIC)
481 throw Decoding_Error("Missing expected magic numbers");
482
483 auto hmac = MessageAuthenticationCode::create_or_throw(TLS_SESSION_CRYPT_HMAC);
484 hmac->set_key(key);
485
486 // First derive and check the "key name"
487 std::vector<uint8_t> cmp_key_name(hmac->output_length());
488 hmac->update(TLS_SESSION_CRYPT_KEY_NAME);
489 hmac->final(cmp_key_name.data());
490
491 if(same_mem(cmp_key_name.data(), key_name, TLS_SESSION_CRYPT_KEY_NAME_LEN) == false)
492 throw Decoding_Error("Wrong key name for encrypted session");
493
494 hmac->update(key_seed, TLS_SESSION_CRYPT_AEAD_KEY_SEED_LEN);
495 const secure_vector<uint8_t> aead_key = hmac->final();
496
497 auto aead = AEAD_Mode::create_or_throw(TLS_SESSION_CRYPT_AEAD, Cipher_Dir::Decryption);
498 aead->set_key(aead_key);
499 aead->set_associated_data(in.data(), TLS_SESSION_CRYPT_HDR_LEN);
500 aead->start(aead_nonce, TLS_SESSION_CRYPT_AEAD_NONCE_LEN);
501 secure_vector<uint8_t> buf(ctext, ctext + ctext_len);
502 aead->finish(buf, 0);
503 return Session(buf);
504 }
505 catch(std::exception& e)
506 {
507 throw Decoding_Error("Failed to decrypt serialized TLS session: " +
508 std::string(e.what()));
509 }
510 }
511
512}
#define BOTAN_ASSERT_NOMSG(expr)
Definition: assert.h:67
#define BOTAN_STATE_CHECK(expr)
Definition: assert.h:48
#define BOTAN_ASSERT_NONNULL(ptr)
Definition: assert.h:106
#define BOTAN_ARG_CHECK(expr, msg)
Definition: assert.h:36
static std::unique_ptr< AEAD_Mode > create_or_throw(std::string_view algo, Cipher_Dir direction, std::string_view provider="")
Definition: aead.cpp:42
const std::string & value() const
Definition: asn1_obj.h:436
BER_Decoder & decode(bool &out)
Definition: ber_dec.h:193
BER_Decoder & decode_list(std::vector< T > &out, ASN1_Type type_tag=ASN1_Type::Sequence, ASN1_Class class_tag=ASN1_Class::Universal)
Definition: ber_dec.h:432
BER_Decoder start_sequence()
Definition: ber_dec.h:117
BER_Decoder & decode_and_check(const T &expected, std::string_view error_msg)
Definition: ber_dec.h:300
BER_Decoder & decode_integer_type(T &out)
Definition: ber_dec.h:265
secure_vector< uint8_t > get_contents()
Definition: der_enc.cpp:157
DER_Encoder & encode_list(const std::vector< T > &values)
Definition: der_enc.h:149
DER_Encoder & start_sequence()
Definition: der_enc.h:66
DER_Encoder & end_cons()
Definition: der_enc.cpp:196
DER_Encoder & encode(bool b)
Definition: der_enc.cpp:290
static std::unique_ptr< MessageAuthenticationCode > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition: mac.cpp:134
void random_vec(std::span< uint8_t > v)
Definition: rng.h:178
static std::optional< Ciphersuite > by_id(uint16_t suite)
uint8_t major_version() const
Definition: tls_version.h:86
uint8_t minor_version() const
Definition: tls_version.h:91
std::string hostname() const
std::vector< X509_Certificate > m_peer_certs
Definition: tls_session.h:232
Protocol_Version version() const
Definition: tls_session.h:177
Protocol_Version m_version
Definition: tls_session.h:224
std::chrono::system_clock::time_point m_start_time
Definition: tls_session.h:222
Server_Information m_server_info
Definition: tls_session.h:233
std::chrono::system_clock::time_point start_time() const
Definition: tls_session.h:172
uint16_t ciphersuite_code() const
Definition: tls_session.h:182
Ciphersuite ciphersuite() const
Definition: tls_session.cpp:91
Connection_Side m_connection_side
Definition: tls_session.h:226
std::optional< Session_Ticket > ticket() const
Definition: tls_session.cpp:78
decltype(auto) get() const
Definition: tls_session.h:125
bool is_opaque_handle() const
Definition: tls_session.h:101
Opaque_Session_Handle opaque_handle() const
Definition: tls_session.cpp:56
std::optional< Session_ID > id() const
Definition: tls_session.cpp:62
secure_vector< uint8_t > DER_encode() const
std::vector< uint8_t > encrypt(const SymmetricKey &key, RandomNumberGenerator &rng) const
std::chrono::seconds lifetime_hint() const
Definition: tls_session.h:425
static Session decrypt(const uint8_t ctext[], size_t ctext_size, const SymmetricKey &key)
Definition: tls_session.h:378
std::string PEM_encode() const
Session(const secure_vector< uint8_t > &master_secret, Protocol_Version version, uint16_t ciphersuite, Connection_Side side, bool supports_extended_master_secret, bool supports_encrypt_then_mac, const std::vector< X509_Certificate > &peer_certs, const Server_Information &server_info, uint16_t srtp_profile, std::chrono::system_clock::time_point current_timestamp, std::chrono::seconds lifetime_hint=std::chrono::seconds::max())
secure_vector< uint8_t > extract_master_secret()
std::string encode(const uint8_t der[], size_t length, std::string_view label, size_t width)
Definition: pem.cpp:42
Strong< std::vector< uint8_t >, struct Session_Ticket_ > Session_Ticket
holds a TLS 1.2 session ticket for stateless resumption
Definition: tls_session.h:37
Strong< std::vector< uint8_t >, struct Opaque_Session_Handle_ > Opaque_Session_Handle
holds an opaque session handle as used in TLS 1.3 that could be either a ticket for stateless resumpt...
Definition: tls_session.h:41
constexpr bool is_x25519(const Group_Params group)
Definition: tls_algos.h:102
constexpr bool is_ecdh(const Group_Params group)
Definition: tls_algos.h:107
std::string kex_method_to_string(Kex_Algo method)
Definition: tls_algos.cpp:29
constexpr bool is_dh(const Group_Params group)
Definition: tls_algos.h:118
Strong< std::vector< uint8_t >, struct Session_ID_ > Session_ID
holds a TLS 1.2 session ID for stateful resumption
Definition: tls_session.h:34
constexpr uint64_t load_be< uint64_t >(const uint8_t in[], size_t off)
Definition: loadstor.h:228
constexpr void store_be(uint16_t in, uint8_t out[2])
Definition: loadstor.h:449
bool same_mem(const T *p1, const T *p2, size_t n)
Definition: mem_ops.h:210
std::vector< T, secure_allocator< T > > secure_vector
Definition: secmem.h:64
constexpr T load_be(const uint8_t in[], size_t off)
Definition: loadstor.h:118
Definition: bigint.h:1092