Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
kcenon::network::protocols::quic::packet_protection Class Reference

QUIC packet protection (encryption/decryption) (RFC 9001 Section 5) More...

#include <crypto.h>

Collaboration diagram for kcenon::network::protocols::quic::packet_protection:
Collaboration graph

Static Public Member Functions

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.
 
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.
 
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.
 
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.
 
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.
 

Static Private Member Functions

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.
 

Detailed Description

QUIC packet protection (encryption/decryption) (RFC 9001 Section 5)

Provides AEAD encryption for packet payloads and header protection to prevent linkability attacks.

Definition at line 145 of file crypto.h.

Member Function Documentation

◆ generate_hp_mask()

auto kcenon::network::protocols::quic::packet_protection::generate_hp_mask ( std::span< const uint8_t > hp_key,
std::span< const uint8_t > sample ) -> Result<std::array<uint8_t, 5>>
staticnodiscard

Generate header protection mask using AES-ECB.

Parameters
hp_keyHeader protection key
sample16-byte sample from ciphertext
Returns
5-byte mask or error

Definition at line 602 of file crypto.cpp.

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}
constexpr uint8_t len
Length field present.
Definition frame_types.h:64
constexpr uint8_t mask
Mask for all flags.
Definition frame_types.h:66
@ error
Black hole detected, reset to base.
constexpr size_t hp_sample_size
Header protection sample size.
Definition keys.h:37
VoidResult ok()

References kcenon::network::protocols::quic::error, kcenon::network::protocols::quic::hp_sample_size, and kcenon::network::ok().

Here is the call graph for this function:

◆ make_nonce()

auto kcenon::network::protocols::quic::packet_protection::make_nonce ( std::span< const uint8_t > iv,
uint64_t packet_number ) -> std::array<uint8_t, aead_iv_size>
staticnodiscardprivate

Construct nonce from IV and packet number.

Parameters
ivInitialization vector
packet_numberPacket number
Returns
Nonce for AEAD

Definition at line 412 of file crypto.cpp.

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}
constexpr size_t aead_iv_size
AEAD IV/nonce size in bytes.
Definition keys.h:25

References kcenon::network::protocols::quic::aead_iv_size.

◆ protect()

auto kcenon::network::protocols::quic::packet_protection::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>>
staticnodiscard

Protect (encrypt) a QUIC packet.

Parameters
keysEncryption keys for the current level
headerPacket header (will be used as AAD)
payloadPlaintext payload to encrypt
packet_numberPacket number (used for nonce derivation)
Returns
Protected packet (header + encrypted payload + tag) or error

Definition at line 429 of file crypto.cpp.

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}
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
constexpr size_t aead_tag_size
AEAD authentication tag size in bytes.
Definition keys.h:28

References kcenon::network::protocols::quic::aead_tag_size, kcenon::network::protocols::quic::error, and kcenon::network::ok().

Referenced by kcenon::network::protocols::quic::connection::build_packet(), and kcenon::network::internal::quic_socket::send_packet().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ protect_header()

auto kcenon::network::protocols::quic::packet_protection::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
staticnodiscard

Apply header protection.

Parameters
keysKeys containing the HP key
headerHeader bytes (modified in place)
pn_offsetOffset of packet number in header
pn_lengthLength of packet number (1-4)
sampleSample from encrypted payload (16 bytes)
Returns
Success or error

Definition at line 653 of file crypto.cpp.

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}
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
const std::string & get_error_source(const simple_error &err)
VoidResult error_void(int code, const std::string &message, const std::string &source="network_system", const std::string &details="")

References kcenon::network::error_void(), kcenon::network::get_error_source(), and kcenon::network::ok().

Here is the call graph for this function:

◆ unprotect()

auto kcenon::network::protocols::quic::packet_protection::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>>>
staticnodiscard

Unprotect (decrypt) a QUIC packet.

Parameters
keysDecryption keys for the current level
packetFull packet data (header + encrypted payload + tag)
header_lengthLength of the header (including packet number)
packet_numberDecoded packet number
Returns
Pair of (header, decrypted payload) or error

Definition at line 512 of file crypto.cpp.

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}

References kcenon::network::protocols::quic::aead_tag_size, kcenon::network::protocols::quic::error, and kcenon::network::ok().

Referenced by kcenon::network::internal::quic_socket::handle_packet().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ unprotect_header()

auto kcenon::network::protocols::quic::packet_protection::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>>
staticnodiscard

Remove header protection.

Parameters
keysKeys containing the HP key
headerHeader bytes (modified in place)
pn_offsetOffset of packet number in header
sampleSample from encrypted payload (16 bytes)
Returns
Pair of (first_byte_unprotected, pn_length) or error

Definition at line 691 of file crypto.cpp.

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}
uint32_t code
Definition hpack.cpp:668

References code, kcenon::network::protocols::quic::error, kcenon::network::get_error_source(), and kcenon::network::ok().

Referenced by kcenon::network::internal::quic_socket::handle_packet().

Here is the call graph for this function:
Here is the caller graph for this function:

The documentation for this class was generated from the following files: