Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
crypto.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2024, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
8
9#include <openssl/evp.h>
10#include <openssl/kdf.h>
11#include <openssl/rand.h>
12
13#include <algorithm>
14#include <cstring>
15
17{
18
19namespace
20{
21
22// Use the compatibility layer for OpenSSL error handling
24
25auto get_openssl_error_string() -> std::string
26{
27 return get_openssl_error();
28}
29
30constexpr std::array<uint8_t, 9> client_initial_label = {
31 'c', 'l', 'i', 'e', 'n', 't', ' ', 'i', 'n'
32};
33
34constexpr std::array<uint8_t, 9> server_initial_label = {
35 's', 'e', 'r', 'v', 'e', 'r', ' ', 'i', 'n'
36};
37
38constexpr std::array<uint8_t, 8> quic_key_label = {
39 'q', 'u', 'i', 'c', ' ', 'k', 'e', 'y'
40};
41
42constexpr std::array<uint8_t, 7> quic_iv_label = {
43 'q', 'u', 'i', 'c', ' ', 'i', 'v'
44};
45
46constexpr std::array<uint8_t, 7> quic_hp_label = {
47 'q', 'u', 'i', 'c', ' ', 'h', 'p'
48};
49
50} // anonymous namespace
51
52// ============================================================================
53// HKDF Implementation
54// ============================================================================
55
56auto hkdf::extract(std::span<const uint8_t> salt,
57 std::span<const uint8_t> ikm)
59{
60 std::array<uint8_t, secret_size> prk{};
61 size_t prk_len = prk.size();
62
63 EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr);
64 if (!pctx)
65 {
67 -1, "Failed to create HKDF context", "quic::hkdf");
68 }
69
70 int ret = EVP_PKEY_derive_init(pctx);
71 if (ret <= 0)
72 {
73 EVP_PKEY_CTX_free(pctx);
75 -1, "HKDF derive init failed", "quic::hkdf", get_openssl_error_string());
76 }
77
78 ret = EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256());
79 if (ret <= 0)
80 {
81 EVP_PKEY_CTX_free(pctx);
83 -1, "HKDF set md failed", "quic::hkdf", get_openssl_error_string());
84 }
85
86 // RFC 5869 permits an absent salt. Normalize it instead of relying on
87 // OpenSSL version-specific handling of a null, zero-length buffer.
88 const std::array<uint8_t, secret_size> zero_salt{};
89 if (salt.empty()) salt = zero_salt;
90 ret = EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt.data(),
91 static_cast<int>(salt.size()));
92 if (ret <= 0)
93 {
94 EVP_PKEY_CTX_free(pctx);
96 -1, "HKDF set salt failed", "quic::hkdf", get_openssl_error_string());
97 }
98
99 ret = EVP_PKEY_CTX_set1_hkdf_key(pctx, ikm.data(),
100 static_cast<int>(ikm.size()));
101 if (ret <= 0)
102 {
103 EVP_PKEY_CTX_free(pctx);
105 -1, "HKDF set key failed", "quic::hkdf", get_openssl_error_string());
106 }
107
108 ret = EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY);
109 if (ret <= 0)
110 {
111 EVP_PKEY_CTX_free(pctx);
113 -1, "HKDF set mode failed", "quic::hkdf", get_openssl_error_string());
114 }
115
116 ret = EVP_PKEY_derive(pctx, prk.data(), &prk_len);
117 EVP_PKEY_CTX_free(pctx);
118
119 if (ret <= 0)
120 {
122 -1, "HKDF extract failed", "quic::hkdf", get_openssl_error_string());
123 }
124
125 return ok(std::move(prk));
126}
127
128auto hkdf::expand(std::span<const uint8_t> prk,
129 std::span<const uint8_t> info,
130 size_t length)
132{
133 std::vector<uint8_t> okm(length);
134
135 EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr);
136 if (!pctx)
137 {
139 -1, "Failed to create HKDF context", "quic::hkdf");
140 }
141
142 int ret = EVP_PKEY_derive_init(pctx);
143 if (ret <= 0)
144 {
145 EVP_PKEY_CTX_free(pctx);
147 -1, "HKDF derive init failed", "quic::hkdf", get_openssl_error_string());
148 }
149
150 ret = EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256());
151 if (ret <= 0)
152 {
153 EVP_PKEY_CTX_free(pctx);
155 -1, "HKDF set md failed", "quic::hkdf", get_openssl_error_string());
156 }
157
158 ret = EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
159 if (ret <= 0)
160 {
161 EVP_PKEY_CTX_free(pctx);
163 -1, "HKDF set mode failed", "quic::hkdf", get_openssl_error_string());
164 }
165
166 ret = EVP_PKEY_CTX_set1_hkdf_key(pctx, prk.data(),
167 static_cast<int>(prk.size()));
168 if (ret <= 0)
169 {
170 EVP_PKEY_CTX_free(pctx);
172 -1, "HKDF set key failed", "quic::hkdf", get_openssl_error_string());
173 }
174
175 ret = EVP_PKEY_CTX_add1_hkdf_info(pctx, info.data(),
176 static_cast<int>(info.size()));
177 if (ret <= 0)
178 {
179 EVP_PKEY_CTX_free(pctx);
181 -1, "HKDF set info failed", "quic::hkdf", get_openssl_error_string());
182 }
183
184 ret = EVP_PKEY_derive(pctx, okm.data(), &length);
185 EVP_PKEY_CTX_free(pctx);
186
187 if (ret <= 0)
188 {
190 -1, "HKDF expand failed", "quic::hkdf", get_openssl_error_string());
191 }
192
193 okm.resize(length);
194 return ok(std::move(okm));
195}
196
197auto hkdf::expand_label(std::span<const uint8_t> secret,
198 const std::string& label,
199 std::span<const uint8_t> context,
200 size_t length)
202{
203 // TLS 1.3 HKDF-Expand-Label structure:
204 // struct {
205 // uint16 length;
206 // opaque label<7..255> = "tls13 " + Label;
207 // opaque context<0..255>;
208 // } HkdfLabel;
209
210 const std::string prefix = "tls13 ";
211 std::vector<uint8_t> hkdf_label;
212 hkdf_label.reserve(2 + 1 + prefix.size() + label.size() + 1 + context.size());
213
214 // Length (2 bytes, big-endian)
215 hkdf_label.push_back(static_cast<uint8_t>((length >> 8) & 0xFF));
216 hkdf_label.push_back(static_cast<uint8_t>(length & 0xFF));
217
218 // Label length (1 byte) + "tls13 " + label
219 size_t label_len = prefix.size() + label.size();
220 hkdf_label.push_back(static_cast<uint8_t>(label_len));
221 for (char c : prefix)
222 {
223 hkdf_label.push_back(static_cast<uint8_t>(c));
224 }
225 for (char c : label)
226 {
227 hkdf_label.push_back(static_cast<uint8_t>(c));
228 }
229
230 // Context length (1 byte) + context
231 hkdf_label.push_back(static_cast<uint8_t>(context.size()));
232 hkdf_label.insert(hkdf_label.end(), context.begin(), context.end());
233
234 return expand(secret, hkdf_label, length);
235}
236
237// ============================================================================
238// Initial Keys Implementation
239// ============================================================================
240
241auto initial_keys::derive(const connection_id& dest_cid, uint32_t version)
243{
244 // Select salt based on version
245 std::span<const uint8_t> salt;
247 {
248 salt = initial_salt_v2;
249 }
250 else
251 {
252 salt = initial_salt_v1;
253 }
254
255 // Extract initial secret from destination connection ID
256 auto initial_secret_result = hkdf::extract(salt, dest_cid.data());
257 if (initial_secret_result.is_err())
258 {
259 return error<key_pair>(
260 initial_secret_result.error().code,
261 "Failed to derive initial secret",
262 "quic::initial_keys",
263 initial_secret_result.error().message);
264 }
265
266 auto& initial_secret = initial_secret_result.value();
267
268 // Derive client initial secret
269 auto client_secret_result = hkdf::expand_label(
270 initial_secret,
271 std::string(reinterpret_cast<const char*>(client_initial_label.data()),
272 client_initial_label.size()),
273 {},
275 if (client_secret_result.is_err())
276 {
277 return error<key_pair>(
278 client_secret_result.error().code,
279 "Failed to derive client initial secret",
280 "quic::initial_keys",
281 client_secret_result.error().message);
282 }
283
284 // Derive server initial secret
285 auto server_secret_result = hkdf::expand_label(
286 initial_secret,
287 std::string(reinterpret_cast<const char*>(server_initial_label.data()),
288 server_initial_label.size()),
289 {},
291 if (server_secret_result.is_err())
292 {
293 return error<key_pair>(
294 server_secret_result.error().code,
295 "Failed to derive server initial secret",
296 "quic::initial_keys",
297 server_secret_result.error().message);
298 }
299
300 // Derive client keys
301 auto client_keys_result = derive_keys(client_secret_result.value(), true);
302 if (client_keys_result.is_err())
303 {
304 return error<key_pair>(
305 client_keys_result.error().code,
306 "Failed to derive client keys",
307 "quic::initial_keys",
308 client_keys_result.error().message);
309 }
310
311 // Derive server keys
312 auto server_keys_result = derive_keys(server_secret_result.value(), false);
313 if (server_keys_result.is_err())
314 {
315 return error<key_pair>(
316 server_keys_result.error().code,
317 "Failed to derive server keys",
318 "quic::initial_keys",
319 server_keys_result.error().message);
320 }
321
322 // Copy secrets into keys
323 auto& client_keys = client_keys_result.value();
324 auto& server_keys = server_keys_result.value();
325
326 std::copy(client_secret_result.value().begin(),
327 client_secret_result.value().end(),
328 client_keys.secret.begin());
329 std::copy(server_secret_result.value().begin(),
330 server_secret_result.value().end(),
331 server_keys.secret.begin());
332
333 // For a client: write = client keys, read = server keys
334 // For a server: write = server keys, read = client keys
335 // This function returns from client's perspective
336 key_pair result;
337 result.write = std::move(client_keys);
338 result.read = std::move(server_keys);
339
340 return ok(std::move(result));
341}
342
343auto initial_keys::derive_keys(std::span<const uint8_t> initial_secret,
344 bool is_client_keys)
346{
347 (void)is_client_keys; // Not used but kept for API clarity
348
349 quic_keys keys;
350
351 // Derive AEAD key
352 auto key_result = hkdf::expand_label(
353 initial_secret,
354 std::string(reinterpret_cast<const char*>(quic_key_label.data()),
355 quic_key_label.size()),
356 {},
358 if (key_result.is_err())
359 {
360 return error<quic_keys>(
361 key_result.error().code,
362 "Failed to derive AEAD key",
363 "quic::initial_keys",
364 key_result.error().message);
365 }
366 std::copy(key_result.value().begin(), key_result.value().end(),
367 keys.key.begin());
368
369 // Derive IV
370 auto iv_result = hkdf::expand_label(
371 initial_secret,
372 std::string(reinterpret_cast<const char*>(quic_iv_label.data()),
373 quic_iv_label.size()),
374 {},
376 if (iv_result.is_err())
377 {
378 return error<quic_keys>(
379 iv_result.error().code,
380 "Failed to derive IV",
381 "quic::initial_keys",
382 iv_result.error().message);
383 }
384 std::copy(iv_result.value().begin(), iv_result.value().end(),
385 keys.iv.begin());
386
387 // Derive header protection key
388 auto hp_result = hkdf::expand_label(
389 initial_secret,
390 std::string(reinterpret_cast<const char*>(quic_hp_label.data()),
391 quic_hp_label.size()),
392 {},
394 if (hp_result.is_err())
395 {
396 return error<quic_keys>(
397 hp_result.error().code,
398 "Failed to derive HP key",
399 "quic::initial_keys",
400 hp_result.error().message);
401 }
402 std::copy(hp_result.value().begin(), hp_result.value().end(),
403 keys.hp_key.begin());
404
405 return ok(std::move(keys));
406}
407
408// ============================================================================
409// Packet Protection Implementation
410// ============================================================================
411
412auto packet_protection::make_nonce(std::span<const uint8_t> iv,
413 uint64_t packet_number)
414 -> std::array<uint8_t, aead_iv_size>
415{
416 std::array<uint8_t, aead_iv_size> nonce{};
417 std::copy(iv.begin(), iv.end(), nonce.begin());
418
419 // XOR packet number into the rightmost bytes of the IV
420 for (size_t i = 0; i < 8; ++i)
421 {
422 nonce[aead_iv_size - 1 - i] ^=
423 static_cast<uint8_t>((packet_number >> (i * 8)) & 0xFF);
424 }
425
426 return nonce;
427}
428
430 std::span<const uint8_t> header,
431 std::span<const uint8_t> payload,
432 uint64_t packet_number)
434{
435 auto nonce = make_nonce(keys.iv, packet_number);
436
437 // Create output buffer: header + ciphertext + tag
438 std::vector<uint8_t> output;
439 output.reserve(header.size() + payload.size() + aead_tag_size);
440 output.insert(output.end(), header.begin(), header.end());
441 output.resize(output.size() + payload.size() + aead_tag_size);
442
443 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
444 if (!ctx)
445 {
447 -1, "Failed to create cipher context", "quic::packet_protection");
448 }
449
450 int ret = EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr,
451 keys.key.data(), nonce.data());
452 if (ret != 1)
453 {
454 EVP_CIPHER_CTX_free(ctx);
456 -1, "AES-GCM encrypt init failed", "quic::packet_protection",
457 get_openssl_error_string());
458 }
459
460 // Set AAD (header)
461 int len;
462 ret = EVP_EncryptUpdate(ctx, nullptr, &len, header.data(),
463 static_cast<int>(header.size()));
464 if (ret != 1)
465 {
466 EVP_CIPHER_CTX_free(ctx);
468 -1, "AES-GCM AAD update failed", "quic::packet_protection",
469 get_openssl_error_string());
470 }
471
472 // Encrypt payload
473 ret = EVP_EncryptUpdate(ctx, output.data() + header.size(), &len,
474 payload.data(), static_cast<int>(payload.size()));
475 if (ret != 1)
476 {
477 EVP_CIPHER_CTX_free(ctx);
479 -1, "AES-GCM encrypt failed", "quic::packet_protection",
480 get_openssl_error_string());
481 }
482
483 int ciphertext_len = len;
484
485 ret = EVP_EncryptFinal_ex(ctx, output.data() + header.size() + len, &len);
486 if (ret != 1)
487 {
488 EVP_CIPHER_CTX_free(ctx);
490 -1, "AES-GCM encrypt final failed", "quic::packet_protection",
491 get_openssl_error_string());
492 }
493 ciphertext_len += len;
494
495 // Get tag
496 ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, aead_tag_size,
497 output.data() + header.size() + ciphertext_len);
498 if (ret != 1)
499 {
500 EVP_CIPHER_CTX_free(ctx);
502 -1, "AES-GCM get tag failed", "quic::packet_protection",
503 get_openssl_error_string());
504 }
505
506 EVP_CIPHER_CTX_free(ctx);
507 output.resize(header.size() + ciphertext_len + aead_tag_size);
508
509 return ok(std::move(output));
510}
511
513 std::span<const uint8_t> packet,
514 size_t header_length,
515 uint64_t packet_number)
516 -> Result<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>
517{
518 if (packet.size() < header_length + aead_tag_size)
519 {
520 return error<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>(
521 -1, "Packet too short for decryption", "quic::packet_protection");
522 }
523
524 auto nonce = make_nonce(keys.iv, packet_number);
525
526 auto header = packet.subspan(0, header_length);
527 auto ciphertext = packet.subspan(header_length,
528 packet.size() - header_length - aead_tag_size);
529 auto tag = packet.subspan(packet.size() - aead_tag_size, aead_tag_size);
530
531 std::vector<uint8_t> plaintext(ciphertext.size());
532
533 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
534 if (!ctx)
535 {
536 return error<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>(
537 -1, "Failed to create cipher context", "quic::packet_protection");
538 }
539
540 int ret = EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr,
541 keys.key.data(), nonce.data());
542 if (ret != 1)
543 {
544 EVP_CIPHER_CTX_free(ctx);
545 return error<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>(
546 -1, "AES-GCM decrypt init failed", "quic::packet_protection",
547 get_openssl_error_string());
548 }
549
550 // Set AAD (header)
551 int len;
552 ret = EVP_DecryptUpdate(ctx, nullptr, &len, header.data(),
553 static_cast<int>(header.size()));
554 if (ret != 1)
555 {
556 EVP_CIPHER_CTX_free(ctx);
557 return error<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>(
558 -1, "AES-GCM AAD update failed", "quic::packet_protection",
559 get_openssl_error_string());
560 }
561
562 // Decrypt ciphertext
563 ret = EVP_DecryptUpdate(ctx, plaintext.data(), &len,
564 ciphertext.data(), static_cast<int>(ciphertext.size()));
565 if (ret != 1)
566 {
567 EVP_CIPHER_CTX_free(ctx);
568 return error<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>(
569 -1, "AES-GCM decrypt failed", "quic::packet_protection",
570 get_openssl_error_string());
571 }
572
573 int plaintext_len = len;
574
575 // Set expected tag
576 ret = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, aead_tag_size,
577 const_cast<uint8_t*>(tag.data()));
578 if (ret != 1)
579 {
580 EVP_CIPHER_CTX_free(ctx);
581 return error<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>(
582 -1, "AES-GCM set tag failed", "quic::packet_protection",
583 get_openssl_error_string());
584 }
585
586 ret = EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &len);
587 EVP_CIPHER_CTX_free(ctx);
588
589 if (ret != 1)
590 {
591 return error<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>(
592 -1, "AES-GCM authentication failed", "quic::packet_protection");
593 }
594
595 plaintext_len += len;
596 plaintext.resize(plaintext_len);
597
598 std::vector<uint8_t> header_copy(header.begin(), header.end());
599 return ok(std::make_pair(std::move(header_copy), std::move(plaintext)));
600}
601
602auto packet_protection::generate_hp_mask(std::span<const uint8_t> hp_key,
603 std::span<const uint8_t> sample)
605{
606 if (sample.size() < hp_sample_size)
607 {
609 -1, "Sample too short for HP mask", "quic::packet_protection");
610 }
611
612 std::array<uint8_t, 16> mask_full{};
613
614 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
615 if (!ctx)
616 {
618 -1, "Failed to create cipher context", "quic::packet_protection");
619 }
620
621 int ret = EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr,
622 hp_key.data(), nullptr);
623 if (ret != 1)
624 {
625 EVP_CIPHER_CTX_free(ctx);
627 -1, "AES-ECB init failed", "quic::packet_protection",
628 get_openssl_error_string());
629 }
630
631 EVP_CIPHER_CTX_set_padding(ctx, 0);
632
633 int len;
634 ret = EVP_EncryptUpdate(ctx, mask_full.data(), &len,
635 sample.data(), hp_sample_size);
636 if (ret != 1)
637 {
638 EVP_CIPHER_CTX_free(ctx);
640 -1, "AES-ECB encrypt failed", "quic::packet_protection",
641 get_openssl_error_string());
642 }
643
644 EVP_CIPHER_CTX_free(ctx);
645
646 // Return first 5 bytes of the mask
647 std::array<uint8_t, 5> mask{};
648 std::copy(mask_full.begin(), mask_full.begin() + 5, mask.begin());
649
650 return ok(std::move(mask));
651}
652
654 std::span<uint8_t> header,
655 size_t pn_offset,
656 size_t pn_length,
657 std::span<const uint8_t> sample)
658 -> VoidResult
659{
660 auto mask_result = generate_hp_mask(keys.hp_key, sample);
661 if (mask_result.is_err())
662 {
663 return error_void(mask_result.error().code,
664 mask_result.error().message,
665 get_error_source(mask_result.error()));
666 }
667
668 auto& mask = mask_result.value();
669
670 // Apply mask to first byte
671 if ((header[0] & 0x80) != 0)
672 {
673 // Long header: mask lower 4 bits
674 header[0] ^= (mask[0] & 0x0F);
675 }
676 else
677 {
678 // Short header: mask lower 5 bits
679 header[0] ^= (mask[0] & 0x1F);
680 }
681
682 // Apply mask to packet number
683 for (size_t i = 0; i < pn_length; ++i)
684 {
685 header[pn_offset + i] ^= mask[1 + i];
686 }
687
688 return ok();
689}
690
692 std::span<uint8_t> header,
693 size_t pn_offset,
694 std::span<const uint8_t> sample)
696{
697 auto mask_result = generate_hp_mask(keys.hp_key, sample);
698 if (mask_result.is_err())
699 {
701 mask_result.error().code,
702 mask_result.error().message,
703 get_error_source(mask_result.error()));
704 }
705
706 auto& mask = mask_result.value();
707
708 // Unmask first byte
709 if ((header[0] & 0x80) != 0)
710 {
711 // Long header
712 header[0] ^= (mask[0] & 0x0F);
713 }
714 else
715 {
716 // Short header
717 header[0] ^= (mask[0] & 0x1F);
718 }
719
720 // Get packet number length from first byte
721 size_t pn_length = (header[0] & 0x03) + 1;
722
723 // Unmask packet number
724 for (size_t i = 0; i < pn_length; ++i)
725 {
726 header[pn_offset + i] ^= mask[1 + i];
727 }
728
729 return ok(std::make_pair(header[0], pn_length));
730}
731
732// ============================================================================
733// QUIC Crypto Handler Implementation
734// ============================================================================
735
737{
738 SSL_CTX* ssl_ctx{nullptr};
739 SSL* ssl{nullptr};
740 BIO* rbio{nullptr};
741 BIO* wbio{nullptr};
742
743 bool is_server{false};
746 uint8_t key_phase{0};
747
748 std::map<encryption_level, quic_keys> read_keys;
749 std::map<encryption_level, quic_keys> write_keys;
750
751 std::string alpn;
752 std::vector<uint8_t> alpn_data;
753
754 // 0-RTT session resumption support
756 std::vector<uint8_t> session_ticket_data;
760 bool has_zero_rtt_keys{false};
761
763 {
764 if (ssl)
765 {
766 SSL_free(ssl);
767 }
768 if (ssl_ctx)
769 {
770 SSL_CTX_free(ssl_ctx);
771 }
772 // BIOs are freed by SSL_free
773 ssl = nullptr;
774 ssl_ctx = nullptr;
775 rbio = nullptr;
776 wbio = nullptr;
777 }
778
780};
781
783 : impl_(std::make_unique<impl>())
784{
785}
786
787quic_crypto::~quic_crypto() = default;
788
789quic_crypto::quic_crypto(quic_crypto&& other) noexcept = default;
790quic_crypto& quic_crypto::operator=(quic_crypto&& other) noexcept = default;
791
792auto quic_crypto::init_client(const std::string& server_name) -> VoidResult
793{
794 impl_->reset_tls();
795 impl_->is_server = false;
796
797 impl_->ssl_ctx = SSL_CTX_new(TLS_client_method());
798 if (!impl_->ssl_ctx)
799 {
800 return error_void(-1, "Failed to create SSL context", "quic::crypto",
801 get_openssl_error_string());
802 }
803
804 // Set TLS 1.3 only
805 SSL_CTX_set_min_proto_version(impl_->ssl_ctx, TLS1_3_VERSION);
806 SSL_CTX_set_max_proto_version(impl_->ssl_ctx, TLS1_3_VERSION);
807
808 impl_->ssl = SSL_new(impl_->ssl_ctx);
809 if (!impl_->ssl)
810 {
811 return error_void(-1, "Failed to create SSL object", "quic::crypto",
812 get_openssl_error_string());
813 }
814
815 // Set SNI
816 if (!server_name.empty())
817 {
818 SSL_set_tlsext_host_name(impl_->ssl, server_name.c_str());
819 }
820
821 // Create memory BIOs
822 impl_->rbio = BIO_new(BIO_s_mem());
823 impl_->wbio = BIO_new(BIO_s_mem());
824 if (!impl_->rbio || !impl_->wbio)
825 {
826 // Ownership has not been transferred to SSL_set_bio yet.
827 BIO_free(impl_->rbio);
828 BIO_free(impl_->wbio);
829 impl_->rbio = nullptr;
830 impl_->wbio = nullptr;
831 return error_void(-1, "Failed to create BIO objects", "quic::crypto");
832 }
833
834 BIO_set_nbio(impl_->rbio, 1);
835 BIO_set_nbio(impl_->wbio, 1);
836
837 SSL_set_bio(impl_->ssl, impl_->rbio, impl_->wbio);
838 SSL_set_connect_state(impl_->ssl);
839
840 return ok();
841}
842
843auto quic_crypto::init_server(const std::string& cert_file,
844 const std::string& key_file) -> VoidResult
845{
846 impl_->reset_tls();
847 impl_->is_server = true;
848
849 impl_->ssl_ctx = SSL_CTX_new(TLS_server_method());
850 if (!impl_->ssl_ctx)
851 {
852 return error_void(-1, "Failed to create SSL context", "quic::crypto",
853 get_openssl_error_string());
854 }
855
856 // Set TLS 1.3 only
857 SSL_CTX_set_min_proto_version(impl_->ssl_ctx, TLS1_3_VERSION);
858 SSL_CTX_set_max_proto_version(impl_->ssl_ctx, TLS1_3_VERSION);
859
860 // Load certificate
861 if (SSL_CTX_use_certificate_file(impl_->ssl_ctx, cert_file.c_str(),
862 SSL_FILETYPE_PEM) != 1)
863 {
864 return error_void(-1, "Failed to load certificate", "quic::crypto",
865 get_openssl_error_string());
866 }
867
868 // Load private key
869 if (SSL_CTX_use_PrivateKey_file(impl_->ssl_ctx, key_file.c_str(),
870 SSL_FILETYPE_PEM) != 1)
871 {
872 return error_void(-1, "Failed to load private key", "quic::crypto",
873 get_openssl_error_string());
874 }
875
876 impl_->ssl = SSL_new(impl_->ssl_ctx);
877 if (!impl_->ssl)
878 {
879 return error_void(-1, "Failed to create SSL object", "quic::crypto",
880 get_openssl_error_string());
881 }
882
883 // Create memory BIOs
884 impl_->rbio = BIO_new(BIO_s_mem());
885 impl_->wbio = BIO_new(BIO_s_mem());
886 if (!impl_->rbio || !impl_->wbio)
887 {
888 // Ownership has not been transferred to SSL_set_bio yet.
889 BIO_free(impl_->rbio);
890 BIO_free(impl_->wbio);
891 impl_->rbio = nullptr;
892 impl_->wbio = nullptr;
893 return error_void(-1, "Failed to create BIO objects", "quic::crypto");
894 }
895
896 BIO_set_nbio(impl_->rbio, 1);
897 BIO_set_nbio(impl_->wbio, 1);
898
899 SSL_set_bio(impl_->ssl, impl_->rbio, impl_->wbio);
900 SSL_set_accept_state(impl_->ssl);
901
902 return ok();
903}
904
906 -> VoidResult
907{
908 auto keys_result = initial_keys::derive(dest_cid);
909 if (keys_result.is_err())
910 {
911 return error_void(keys_result.error().code,
912 keys_result.error().message,
913 get_error_source(keys_result.error()));
914 }
915
916 auto& keys = keys_result.value();
917
918 if (impl_->is_server)
919 {
920 // Server: read with client keys, write with server keys
921 impl_->read_keys[encryption_level::initial] = keys.write; // Client's write = Server's read
922 impl_->write_keys[encryption_level::initial] = keys.read; // Server's write = Client's read
923 }
924 else
925 {
926 // Client: write with client keys, read with server keys
927 impl_->write_keys[encryption_level::initial] = keys.write;
928 impl_->read_keys[encryption_level::initial] = keys.read;
929 }
930
931 return ok();
932}
933
935 std::span<const uint8_t> data)
937{
938 (void)level; // For now, we don't differentiate by level
939
940 // Write incoming data to read BIO
941 int written = BIO_write(impl_->rbio, data.data(),
942 static_cast<int>(data.size()));
943 if (written <= 0)
944 {
946 -1, "Failed to write to BIO", "quic::crypto");
947 }
948
949 // Continue handshake
950 int result = SSL_do_handshake(impl_->ssl);
951 if (result == 1)
952 {
953 impl_->handshake_complete = true;
954 impl_->current_level = encryption_level::application;
955 }
956 else
957 {
958 int err = SSL_get_error(impl_->ssl, result);
959 if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE)
960 {
962 -1, "SSL handshake failed", "quic::crypto",
963 get_openssl_error_string());
964 }
965 }
966
967 // Read any output data from write BIO
968 std::vector<uint8_t> output;
969 int pending = BIO_ctrl_pending(impl_->wbio);
970 if (pending > 0)
971 {
972 output.resize(static_cast<size_t>(pending));
973 int read = BIO_read(impl_->wbio, output.data(), pending);
974 if (read > 0)
975 {
976 output.resize(static_cast<size_t>(read));
977 }
978 else
979 {
980 output.clear();
981 }
982 }
983
984 return ok(std::move(output));
985}
986
988{
989 int result = SSL_do_handshake(impl_->ssl);
990 if (result == 1)
991 {
992 impl_->handshake_complete = true;
993 }
994 else
995 {
996 int err = SSL_get_error(impl_->ssl, result);
997 if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE)
998 {
1000 -1, "SSL handshake start failed", "quic::crypto",
1001 get_openssl_error_string());
1002 }
1003 }
1004
1005 // Read output from write BIO
1006 std::vector<uint8_t> output;
1007 int pending = BIO_ctrl_pending(impl_->wbio);
1008 if (pending > 0)
1009 {
1010 output.resize(static_cast<size_t>(pending));
1011 int read = BIO_read(impl_->wbio, output.data(), pending);
1012 if (read > 0)
1013 {
1014 output.resize(static_cast<size_t>(read));
1015 }
1016 else
1017 {
1018 output.clear();
1019 }
1020 }
1021
1022 return ok(std::move(output));
1023}
1024
1025auto quic_crypto::is_handshake_complete() const noexcept -> bool
1026{
1027 return impl_->handshake_complete;
1028}
1029
1031{
1032 return impl_->current_level;
1033}
1034
1037{
1038 auto it = impl_->write_keys.find(level);
1039 if (it == impl_->write_keys.end())
1040 {
1041 return error<quic_keys>(
1042 -1, "Write keys not available for level", "quic::crypto",
1044 }
1045 return ok(quic_keys(it->second));
1046}
1047
1050{
1051 auto it = impl_->read_keys.find(level);
1052 if (it == impl_->read_keys.end())
1053 {
1054 return error<quic_keys>(
1055 -1, "Read keys not available for level", "quic::crypto",
1057 }
1058 return ok(quic_keys(it->second));
1059}
1060
1062 const quic_keys& read_keys,
1063 const quic_keys& write_keys)
1064{
1065 impl_->read_keys[level] = read_keys;
1066 impl_->write_keys[level] = write_keys;
1067
1068 if (level > impl_->current_level)
1069 {
1070 impl_->current_level = level;
1071 }
1072}
1073
1075{
1076 if (!impl_->handshake_complete)
1077 {
1078 return error_void(-1, "Handshake not complete", "quic::crypto");
1079 }
1080
1081 auto it = impl_->write_keys.find(encryption_level::application);
1082 if (it == impl_->write_keys.end())
1083 {
1084 return error_void(-1, "Application keys not available", "quic::crypto");
1085 }
1086
1087 // Derive new secrets using "quic ku" label
1088 auto& old_secret = it->second.secret;
1089 auto new_secret_result = hkdf::expand_label(old_secret, "quic ku", {},
1090 secret_size);
1091 if (new_secret_result.is_err())
1092 {
1093 return error_void(new_secret_result.error().code,
1094 new_secret_result.error().message,
1095 get_error_source(new_secret_result.error()));
1096 }
1097
1098 // Derive new keys from new secret
1099 auto new_keys_result = initial_keys::derive_keys(new_secret_result.value(),
1100 true);
1101 if (new_keys_result.is_err())
1102 {
1103 return error_void(new_keys_result.error().code,
1104 new_keys_result.error().message,
1105 get_error_source(new_keys_result.error()));
1106 }
1107
1108 auto& new_keys = new_keys_result.value();
1109 std::copy(new_secret_result.value().begin(),
1110 new_secret_result.value().end(),
1111 new_keys.secret.begin());
1112
1113 impl_->write_keys[encryption_level::application] = new_keys;
1114 impl_->key_phase = 1 - impl_->key_phase;
1115
1116 return ok();
1117}
1118
1119auto quic_crypto::get_alpn() const -> std::string
1120{
1121 return impl_->alpn;
1122}
1123
1124auto quic_crypto::set_alpn(const std::vector<std::string>& protocols)
1125 -> VoidResult
1126{
1127 if (protocols.empty())
1128 {
1129 return ok();
1130 }
1131
1132 // Build ALPN wire format: length-prefixed strings
1133 impl_->alpn_data.clear();
1134 for (const auto& proto : protocols)
1135 {
1136 if (proto.size() > 255)
1137 {
1138 return error_void(-1, "ALPN protocol too long", "quic::crypto");
1139 }
1140 impl_->alpn_data.push_back(static_cast<uint8_t>(proto.size()));
1141 impl_->alpn_data.insert(impl_->alpn_data.end(), proto.begin(), proto.end());
1142 }
1143
1144 if (SSL_CTX_set_alpn_protos(impl_->ssl_ctx, impl_->alpn_data.data(),
1145 static_cast<unsigned int>(impl_->alpn_data.size())) != 0)
1146 {
1147 return error_void(-1, "Failed to set ALPN protocols", "quic::crypto",
1148 get_openssl_error_string());
1149 }
1150
1151 return ok();
1152}
1153
1154auto quic_crypto::is_server() const noexcept -> bool
1155{
1156 return impl_->is_server;
1157}
1158
1159auto quic_crypto::key_phase() const noexcept -> uint8_t
1160{
1161 return impl_->key_phase;
1162}
1163
1164// ============================================================================
1165// 0-RTT Session Resumption Implementation
1166// ============================================================================
1167
1172
1173auto quic_crypto::set_session_ticket(std::span<const uint8_t> ticket_data)
1174 -> VoidResult
1175{
1176 if (ticket_data.empty())
1177 {
1178 return error_void(-1, "Empty session ticket", "quic::crypto");
1179 }
1180
1181 impl_->session_ticket_data.assign(ticket_data.begin(), ticket_data.end());
1182 return ok();
1183}
1184
1185auto quic_crypto::enable_early_data(uint32_t max_early_data) -> VoidResult
1186{
1187 if (impl_->session_ticket_data.empty())
1188 {
1189 return error_void(-1, "Session ticket must be set before enabling early data",
1190 "quic::crypto");
1191 }
1192
1193 impl_->max_early_data_size = max_early_data;
1194 impl_->early_data_enabled = true;
1195
1196 return ok();
1197}
1198
1199auto quic_crypto::is_early_data_accepted() const noexcept -> bool
1200{
1201 return impl_->early_data_accepted;
1202}
1203
1205{
1206 if (impl_->session_ticket_data.empty())
1207 {
1208 return error_void(-1, "No session ticket available", "quic::crypto");
1209 }
1210
1211 // Derive 0-RTT secret from the resumption secret in the ticket
1212 // The early data secret is derived using HKDF-Expand-Label with
1213 // the label "c e traffic" and the ClientHello transcript
1214 //
1215 // For simplicity in this implementation, we derive keys using a
1216 // deterministic derivation from the session ticket. A full implementation
1217 // would properly extract the resumption master secret from TLS.
1218
1219 // Use HKDF to derive a pseudo early secret from the ticket data
1220 // This is a simplified approach - full implementation needs proper TLS PSK handling
1221 auto early_secret_result = hkdf::extract(
1222 initial_salt_v1, // Use QUIC v1 salt as base
1223 impl_->session_ticket_data);
1224
1225 if (early_secret_result.is_err())
1226 {
1227 return error_void(early_secret_result.error().code,
1228 "Failed to derive early secret",
1229 "quic::crypto",
1230 early_secret_result.error().message);
1231 }
1232
1233 auto& early_secret = early_secret_result.value();
1234
1235 // Derive client early traffic secret
1236 auto client_early_secret_result = hkdf::expand_label(
1237 early_secret,
1238 "c e traffic",
1239 {},
1240 secret_size);
1241
1242 if (client_early_secret_result.is_err())
1243 {
1244 return error_void(client_early_secret_result.error().code,
1245 "Failed to derive client early secret",
1246 "quic::crypto",
1247 client_early_secret_result.error().message);
1248 }
1249
1250 // Derive 0-RTT keys from client early secret
1251 auto zero_rtt_keys_result = initial_keys::derive_keys(
1252 client_early_secret_result.value(),
1253 true);
1254
1255 if (zero_rtt_keys_result.is_err())
1256 {
1257 return error_void(zero_rtt_keys_result.error().code,
1258 "Failed to derive 0-RTT keys",
1259 "quic::crypto",
1260 zero_rtt_keys_result.error().message);
1261 }
1262
1263 auto& zero_rtt_keys = zero_rtt_keys_result.value();
1264
1265 // Copy the secret
1266 std::copy(client_early_secret_result.value().begin(),
1267 client_early_secret_result.value().end(),
1268 zero_rtt_keys.secret.begin());
1269
1270 // Store the 0-RTT keys
1271 // For client: 0-RTT is write-only (can't receive 0-RTT data)
1272 // For server: 0-RTT is read-only (can receive but not send 0-RTT)
1273 if (impl_->is_server)
1274 {
1275 impl_->read_keys[encryption_level::zero_rtt] = zero_rtt_keys;
1276 }
1277 else
1278 {
1279 impl_->write_keys[encryption_level::zero_rtt] = zero_rtt_keys;
1280 }
1281
1282 impl_->has_zero_rtt_keys = true;
1283
1284 return ok();
1285}
1286
1287auto quic_crypto::has_zero_rtt_keys() const noexcept -> bool
1288{
1289 return impl_->has_zero_rtt_keys;
1290}
1291
1292} // namespace kcenon::network::protocols::quic
QUIC Connection ID (RFC 9000 Section 5.1)
static auto expand_label(std::span< const uint8_t > secret, const std::string &label, std::span< const uint8_t > context, size_t length) -> Result< std::vector< uint8_t > >
HKDF-Expand-Label function (TLS 1.3 style)
Definition crypto.cpp:197
static auto extract(std::span< const uint8_t > salt, std::span< const uint8_t > ikm) -> Result< std::array< uint8_t, secret_size > >
HKDF-Extract function.
Definition crypto.cpp:56
static auto expand(std::span< const uint8_t > prk, std::span< const uint8_t > info, size_t length) -> Result< std::vector< uint8_t > >
HKDF-Expand function.
Definition crypto.cpp:128
static auto derive_keys(std::span< const uint8_t > initial_secret, bool is_client_keys) -> Result< quic_keys >
Derive keys from an initial secret.
Definition crypto.cpp:343
static auto derive(const connection_id &dest_cid, uint32_t version=0x00000001) -> Result< key_pair >
Derive client and server initial keys.
Definition crypto.cpp:241
QUIC packet number utilities (RFC 9000 Section 17.1)
Definition packet.h:174
static auto unprotect_header(const quic_keys &keys, std::span< uint8_t > header, size_t pn_offset, std::span< const uint8_t > sample) -> Result< std::pair< uint8_t, size_t > >
Remove header protection.
Definition crypto.cpp:691
static auto unprotect(const quic_keys &keys, std::span< const uint8_t > packet, size_t header_length, uint64_t packet_number) -> Result< std::pair< std::vector< uint8_t >, std::vector< uint8_t > > >
Unprotect (decrypt) a QUIC packet.
Definition crypto.cpp:512
static auto make_nonce(std::span< const uint8_t > iv, uint64_t packet_number) -> std::array< uint8_t, aead_iv_size >
Construct nonce from IV and packet number.
Definition crypto.cpp:412
static auto protect_header(const quic_keys &keys, std::span< uint8_t > header, size_t pn_offset, size_t pn_length, std::span< const uint8_t > sample) -> VoidResult
Apply header protection.
Definition crypto.cpp:653
static auto protect(const quic_keys &keys, std::span< const uint8_t > header, std::span< const uint8_t > payload, uint64_t packet_number) -> Result< std::vector< uint8_t > >
Protect (encrypt) a QUIC packet.
Definition crypto.cpp:429
static auto generate_hp_mask(std::span< const uint8_t > hp_key, std::span< const uint8_t > sample) -> Result< std::array< uint8_t, 5 > >
Generate header protection mask using AES-ECB.
Definition crypto.cpp:602
QUIC-TLS integration handler (RFC 9001)
Definition crypto.h:245
auto init_server(const std::string &cert_file, const std::string &key_file) -> VoidResult
Initialize as server.
Definition crypto.cpp:843
auto update_keys() -> VoidResult
Perform a key update (1-RTT only)
Definition crypto.cpp:1074
~quic_crypto()
Destructor (cleans up OpenSSL resources)
auto start_handshake() -> Result< std::vector< uint8_t > >
Start the handshake (generate initial CRYPTO data)
Definition crypto.cpp:987
auto get_read_keys(encryption_level level) const -> Result< quic_keys >
Get read keys for an encryption level.
Definition crypto.cpp:1048
auto get_alpn() const -> std::string
Get the negotiated ALPN protocol.
Definition crypto.cpp:1119
auto is_early_data_accepted() const noexcept -> bool
Check if 0-RTT early data was accepted by the server.
Definition crypto.cpp:1199
auto get_write_keys(encryption_level level) const -> Result< quic_keys >
Get write keys for an encryption level.
Definition crypto.cpp:1035
auto process_crypto_data(encryption_level level, std::span< const uint8_t > data) -> Result< std::vector< uint8_t > >
Process incoming CRYPTO frame data.
Definition crypto.cpp:934
auto is_server() const noexcept -> bool
Check if this is a server instance.
Definition crypto.cpp:1154
void set_keys(encryption_level level, const quic_keys &read_keys, const quic_keys &write_keys)
Set keys for an encryption level (used during handshake)
Definition crypto.cpp:1061
auto set_alpn(const std::vector< std::string > &protocols) -> VoidResult
Set ALPN protocols to offer/accept.
Definition crypto.cpp:1124
std::function< void( std::vector< uint8_t > ticket_data, uint32_t lifetime_hint, uint32_t ticket_age_add, uint32_t max_early_data)> session_ticket_callback_t
Callback type for receiving session tickets.
Definition crypto.h:390
auto set_session_ticket(std::span< const uint8_t > ticket_data) -> VoidResult
Set a session ticket for 0-RTT resumption.
Definition crypto.cpp:1173
quic_crypto & operator=(const quic_crypto &)=delete
auto init_client(const std::string &server_name) -> VoidResult
Initialize as client.
Definition crypto.cpp:792
auto current_level() const noexcept -> encryption_level
Get current encryption level.
Definition crypto.cpp:1030
auto derive_zero_rtt_keys() -> VoidResult
Derive 0-RTT keys from session ticket.
Definition crypto.cpp:1204
auto has_zero_rtt_keys() const noexcept -> bool
Check if 0-RTT keys are available.
Definition crypto.cpp:1287
auto key_phase() const noexcept -> uint8_t
Get current key phase (for key updates)
Definition crypto.cpp:1159
auto derive_initial_secrets(const connection_id &dest_cid) -> VoidResult
Derive initial secrets from destination connection ID.
Definition crypto.cpp:905
void set_session_ticket_callback(session_ticket_callback_t cb)
Set callback for receiving session tickets.
Definition crypto.cpp:1168
auto enable_early_data(uint32_t max_early_data) -> VoidResult
Enable 0-RTT early data.
Definition crypto.cpp:1185
auto is_handshake_complete() const noexcept -> bool
Check if the handshake is complete.
Definition crypto.cpp:1025
struct ssl_ctx_st SSL_CTX
Definition crypto.h:20
struct ssl_st SSL
Definition crypto.h:21
uint32_t code
Definition hpack.cpp:668
std::string get_openssl_error() noexcept
Get last OpenSSL error as string.
constexpr uint32_t version_2
QUIC version 2 (RFC 9369)
Definition packet.h:32
@ error
Black hole detected, reset to base.
constexpr std::array< uint8_t, 20 > initial_salt_v1
QUIC version 1 initial salt (RFC 9001 Section 5.2)
Definition crypto.h:36
auto encryption_level_to_string(encryption_level level) -> std::string
Convert encryption level to string for debugging.
Definition keys.cpp:13
constexpr std::array< uint8_t, 20 > initial_salt_v2
QUIC version 2 initial salt (RFC 9369)
Definition crypto.h:44
constexpr size_t secret_size
Traffic secret size (SHA-256 output)
Definition keys.h:31
constexpr size_t aead_tag_size
AEAD authentication tag size in bytes.
Definition keys.h:28
constexpr size_t hp_sample_size
Header protection sample size.
Definition keys.h:37
constexpr size_t aes_128_key_size
AES-128-GCM key size in bytes.
Definition keys.h:19
encryption_level
QUIC encryption levels (RFC 9001 Section 4)
Definition keys.h:54
@ application
1-RTT application data encryption
@ initial
Initial encryption (derived from DCID)
constexpr size_t aead_iv_size
AEAD IV/nonce size in bytes.
Definition keys.h:25
constexpr size_t hp_key_size
Header protection key size for AES-128.
Definition keys.h:34
const std::string & get_error_source(const simple_error &err)
constexpr const char * version() noexcept
Get the network system version string.
Definition network.cppm:111
VoidResult error_void(int code, const std::string &message, const std::string &source="network_system", const std::string &details="")
VoidResult ok()
OpenSSL utilities and version definitions.
A pair of read and write keys for bidirectional communication.
Definition keys.h:134
quic_keys read
Keys for decrypting received packets.
Definition keys.h:135
quic_keys write
Keys for encrypting outgoing packets.
Definition keys.h:136
session_ticket_callback_t session_ticket_callback
Definition crypto.cpp:755
std::map< encryption_level, quic_keys > write_keys
Definition crypto.cpp:749
std::map< encryption_level, quic_keys > read_keys
Definition crypto.cpp:748
QUIC encryption keys for a single encryption level (RFC 9001 Section 5)
Definition keys.h:92
std::array< uint8_t, hp_key_size > hp_key
Header protection key.
Definition keys.h:103
std::array< uint8_t, aes_128_key_size > key
AEAD encryption key (AES-128-GCM by default)
Definition keys.h:97
std::array< uint8_t, aead_iv_size > iv
AEAD initialization vector.
Definition keys.h:100