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 598 of file crypto.cpp.

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}
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 408 of file crypto.cpp.

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}
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 425 of file crypto.cpp.

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}
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
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 649 of file crypto.cpp.

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}
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
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 508 of file crypto.cpp.

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}

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 687 of file crypto.cpp.

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}

References 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: