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