quic: Initial implementation

This commit is contained in:
Joost 2022-08-23 14:47:23 +02:00 committed by Arne Welzel
parent ffc35d90ba
commit 44d7c45723
12 changed files with 848 additions and 0 deletions

View file

@ -0,0 +1 @@
@load ./main

View file

@ -0,0 +1,2 @@
module QUIC;

View file

@ -0,0 +1,5 @@
spicy_add_analyzer(
NAME QUIC
PACKAGE_NAME QUIC
SOURCES decrypt_crypto.cc QUIC.spicy QUIC.evt zeek_QUIC.spicy
SCRIPTS __load__.zeek main.zeek)

View file

@ -0,0 +1,10 @@
protocol analyzer spicy::QUIC over UDP:
parse originator with QUIC::RequestFrame,
parse responder with QUIC::ResponseFrame,
ports { 443/udp };
import QUIC;
import Zeek_QUIC;
# TODO: Add actual events, instead of this dummy event
on QUIC::ResponseFrame -> event QUIC::example($conn);

View file

@ -0,0 +1,411 @@
module QUIC;
import spicy;
import zeek;
# The interface to the C++ code that handles the decryption of the INITIAL packet payload using well-known keys
public function decrypt_crypto_payload(entire_packet: bytes, connection_id: bytes, encrypted_offset: uint64, payload_offset: uint64, from_client: bool): bytes &cxxname="decrypt_crypto_payload";
##############
## Context - tracked in one connection
##############
type ConnectionIDInfo = unit {
var client_cid_len: uint8;
var server_cid_len: uint8;
var initial_destination_conn_id: bytes;
var initial_packets_exchanged: bool;
var initialized: bool;
on %init {
self.client_cid_len = 0;
self.server_cid_len = 0;
self.initial_packets_exchanged = False;
self.initialized = False;
}
};
##############
# Definitions
##############
type LongPacketType = enum {
INITIAL = 0,
ZERO_RTT = 1,
HANDSHAKE = 2,
RETRY = 3,
};
type HeaderForm = enum {
SHORT = 0,
LONG = 1,
};
type FrameType = enum {
PADDING = 0x00,
PING = 0x01,
ACK1 = 0x02,
ACK2 = 0x03,
RESET_STREAM = 0x04,
STOP_SENDING = 0x05,
CRYPTO = 0x06,
NEW_TOKEN = 0x07,
STREAM1 = 0x08,
STREAM2 = 0x09,
STREAM3 = 0x0a,
STREAM4 = 0x0b,
STREAM5 = 0x0c,
STREAM6 = 0x0d,
STREAM7 = 0x0e,
STREAM8 = 0x0f,
MAX_DATA = 0x10,
MAX_STREAM_DATA = 0x11,
MAX_STREAMS1 = 0x12,
MAX_STREAMS2 = 0x13,
DATA_BLOCKED = 0x14,
STREAM_DATA_BLOCKED = 0x15,
STREAMS_BLOCKED1 = 0x16,
STREAMS_BLOCKED2 = 0x17,
NEW_CONNECTION_ID = 0x18,
RETIRE_CONNECTION_ID = 0x19,
PATH_CHALLENGE = 0x1a,
PATH_RESPONSE = 0x1b,
CONNECTION_CLOSE1 = 0x1c,
CONNECTION_CLOSE2 = 0x1d,
HANDSHAKE_DONE = 0x1e,
};
##############
# Helper units
##############
# Used to peek into the next byte and determine if it's a long or short packet
public type InitialByte = unit {
initialbyte: bitfield(8) {
header_form: 7 &convert=cast<HeaderForm>(cast<uint8>($$));
};
on %done{
self.backtrack();
}
};
# Used to peek into the next byte and check it's value
type InitialUint8 = unit {
var bt: uint8;
: uint8 {
self.bt = $$;
}
on %done{
self.backtrack();
}
};
# https://datatracker.ietf.org/doc/rfc9000/
# Section 16 and Appendix A
type VariableLengthIntegerLength = unit {
var length: uint8;
a: bitfield(8) {
length: 6..7 &convert=cast<uint8>($$) &byte-order=spicy::ByteOrder::Big;
};
on %done {
self.length = self.a.length;
self.backtrack();
}
};
type VariableLengthInteger = unit {
var bytes_to_parse: uint64;
var result: uint64;
var result_bytes: bytes;
: VariableLengthIntegerLength &try {
switch ( $$.length ) {
case 0:
self.bytes_to_parse = 1;
case 1:
self.bytes_to_parse = 2;
case 2:
self.bytes_to_parse = 4;
case 3:
self.bytes_to_parse = 8;
}
}
# Parse the required amount of bytes and apply a mask to clear the
# first two bits, leaving the actual length
remainder: bytes &size=self.bytes_to_parse {
switch ( self.bytes_to_parse ) {
case 1:
self.result = $$.to_uint(spicy::ByteOrder::Big) & 0x3f;
case 2:
self.result = $$.to_uint(spicy::ByteOrder::Big) & 0x3fff;
case 4:
self.result = $$.to_uint(spicy::ByteOrder::Big) & 0x3fffffff;
case 8:
self.result = $$.to_uint(spicy::ByteOrder::Big) & 0x3fffffffffffffff;
}
}
};
##############
# Long packets
# Generic units
##############
# Used to capture all data form the entire frame. May be inefficient, but works for now.
# This is passed to the decryption function, as this function needs both the header and the payload
# Performs a backtrack() at the end
type AllData = unit {
var data: bytes;
: bytes &eod {
self.data = $$;
}
on %done {
self.backtrack();
}
};
public type LongHeader = unit {
var encrypted_offset: uint64;
var payload_length: uint64;
var client_conn_id_length: uint8;
var server_conn_id_length: uint8;
first_byte: bitfield(8) {
header_form: 7 &convert=cast<HeaderForm>(cast<uint8>($$));
fixed_bit: 6;
packet_type: 4..5 &convert=cast<LongPacketType>(cast<uint8>($$));
type_specific_bits: 0..3 &convert=cast<uint8>($$);
};
version: uint32;
dest_conn_id_len: uint8 { self.server_conn_id_length = $$; }
dest_conn_id: bytes &size=self.server_conn_id_length;
src_conn_id_len: uint8 { self.client_conn_id_length = $$; }
src_conn_id: bytes &size=self.client_conn_id_length;
# We pass the type specific 4 bits too and don't parse them again
switch ( self.first_byte.packet_type ) {
LongPacketType::INITIAL -> initial_hdr : InitialLongPacketHeader(self.first_byte.type_specific_bits) {
self.encrypted_offset = self.offset() +
self.initial_hdr.payload_length.bytes_to_parse +
self.initial_hdr.token_length.bytes_to_parse +
self.initial_hdr.token_length.result;
self.payload_length = self.initial_hdr.payload_length.result;
}
LongPacketType::ZERO_RTT -> zerortt_hdr : ZeroRTTLongPacketHeader(self.first_byte.type_specific_bits);
LongPacketType::HANDSHAKE -> handshake_hdr : HandshakeLongPacketHeader(self.first_byte.type_specific_bits);
LongPacketType::RETRY -> retry_hdr : RetryLongPacketHeader(self.first_byte.type_specific_bits);
};
};
# Decrypted long packet payload that can actually be parsed
public type DecryptedLongPacketPayload = unit(packet_type: LongPacketType, from_client: bool) {
frame_type : uint8 &convert=cast<FrameType>($$);
# TODO: add other FrameTypes as well
switch ( self.frame_type ) {
FrameType::ACK1 -> a: ACKPayload;
FrameType::ACK2 -> b: ACKPayload;
FrameType::CRYPTO -> c: CRYPTOPayload(from_client);
FrameType::PADDING -> d: PADDINGPayload;
};
};
# TODO: investigate whether we can do something useful with this
public type EncryptedLongPacketPayload = unit {
payload: bytes &eod;
};
# Determines how to parse the long packet payload, depending on whether is was decrypted or not
public type LongPacketPayload = unit(packet_type: LongPacketType, from_client: bool, encrypted: bool) {
: DecryptedLongPacketPayload(packet_type, from_client) if (encrypted == False);
: EncryptedLongPacketPayload if (encrypted == True);
};
type CRYPTOPayload = unit(from_client: bool) {
var length_in_byte1: bytes;
var length_in_byte2: bytes;
offset: uint8;
length: VariableLengthInteger;
cryptodata: bytes &size=self.length.result;
on %done {
# As of 5 Sept. 2022 there is no function to convert a unsigned integer back to bytes.
# Therefore, the following (quite dirty) method is used. Should be fixed/improved whenever
# a better alternative is available.
# It converts a uint16 to its two-byte representation.
self.length_in_byte1 = ("%c" % cast<uint8>((self.length.result >> 8) & 0xff)).encode();
self.length_in_byte2 = ("%c" % cast<uint8>(self.length.result & 0xff)).encode();
# The data is passed to the SSL analyzer as part of a HANDSHAKE (0x16) message with TLS1.3 (\x03\x03).
# The 2 length bytes are also passed, followed by the actual CRYPTO blob which contains a CLIENT HELLO or SERVER HELLO
zeek::protocol_data_in(from_client, b"\x16\x03\x03" + self.length_in_byte1 + self.length_in_byte2 + self.cryptodata);
}
};
type ACKPayload = unit {
latest_ack: uint8;
ack_delay: uint8;
ack_range_count: uint8;
first_ack_range: uint8;
};
public type NullBytes = unit {
: (b"\x00")[];
x: InitialUint8 &try;
};
type PADDINGPayload = unit {
var padding_length: uint64 = 0;
# Simply consume all next nullbytes
: NullBytes;
};
##############
# Long packets
# Specific long packet type units
##############
type InitialLongPacketHeader = unit(type_specific_bits: uint8) {
var packet_number_length_full: uint8;
token_length: VariableLengthInteger;
token: bytes &size=self.token_length.result;
payload_length: VariableLengthInteger;
packet_number: bytes &size=self.packet_number_length_full &convert=$$.to_uint(spicy::ByteOrder::Big);
on %init {
# Calculate the packet number length while the initial byte is still encoded.
# Will result in 0, 1, 2 or 3. So we need to read n+1 bytes to properly parse the header.
self.packet_number_length_full = (type_specific_bits & 0x03) + 1;
}
};
# TODO: implement
type ZeroRTTLongPacketHeader = unit(type_specific_bits: uint8) {};
type HandshakeLongPacketHeader = unit(type_specific_bits: uint8) {};
type RetryLongPacketHeader = unit(type_specific_bits: uint8) {};
##############
# Short packets
##############
# TODO: implement
public type ShortHeader = unit(dest_conn_id_length: uint8) {
first_byte: bitfield(8) {
header_form: 7 &convert=cast<HeaderForm>(cast<uint8>($$));
fixed_bit: 6;
spin_bit: 5;
todo: 0..4;
};
dest_conn_id: bytes &size=dest_conn_id_length;
};
# TODO: investigate whether we can parse something useful out of this
public type ShortPacketPayload = unit {
payload: bytes &eod;
};
##############
# QUIC frame parsing
##############
type Frame = unit(from_client: bool, context: ConnectionIDInfo&) {
var hdr_form: HeaderForm;
var decrypted_data: bytes;
var full_packet: bytes;
# Peek into the header to check if it's a SHORT or LONG header
: InitialByte &try {
self.hdr_form = $$.initialbyte.header_form;
}
# Capture all the packet bytes if we're still have a chance of decrypting the INITIAL PACKETS
fpack: AllData &try if (context.initial_packets_exchanged == False);
# Depending on the header, parse it and update the src/dest ConnectionID's
switch ( self.hdr_form ) {
HeaderForm::SHORT -> short_header: ShortHeader(context.client_cid_len);
HeaderForm::LONG -> long_header: LongHeader {
# For now, only allow a change of src/dest ConnectionID's for INITIAL packets.
# TODO: allow this for Retry packets
if ( self.long_header.first_byte.packet_type == LongPacketType::INITIAL
&& context.initial_packets_exchanged == False ) {
if ( from_client ) {
context.server_cid_len = self.long_header.dest_conn_id_len;
context.client_cid_len = self.long_header.src_conn_id_len;
# This means that here, we can try to decrypt the initial packet!
# All data is accessible via the `long_header` unit
self.decrypted_data = decrypt_crypto_payload(self.fpack.data,
self.long_header.dest_conn_id,
self.long_header.encrypted_offset,
self.long_header.payload_length,
from_client);
# Set this to be the seed for the decryption
if ( ! context.initial_packets_exchanged ) {
context.initial_destination_conn_id = self.long_header.dest_conn_id;
}
} else {
context.server_cid_len = self.long_header.src_conn_id_len;
context.client_cid_len = self.long_header.dest_conn_id_len;
# Assuming that the client set up the connection, this can be considered the first
# received Initial from the client. So disable change of ConnectionID's afterwards
self.decrypted_data = decrypt_crypto_payload(self.fpack.data,
context.initial_destination_conn_id,
self.long_header.encrypted_offset,
self.long_header.payload_length,
from_client);
}
}
# If it's a reply from the server and it's not a REPLY, we assume the keys are restablished and decryption is no longer possible
# TODO: verify if this is actually correct per RFC
if (self.long_header.first_byte.packet_type != LongPacketType::RETRY && ! from_client) {
context.initial_packets_exchanged = True;
}
}
};
# Depending on the type of header, we parse the remaining payload.
switch ( self.hdr_form ) {
HeaderForm::SHORT -> remaining_short_payload: ShortPacketPayload;
HeaderForm::LONG -> remaining_long_payload : LongPacketPayload(self.long_header.first_byte.packet_type, from_client, context.initial_packets_exchanged)[] &parse-from=self.decrypted_data;
};
on %init {
# Make sure to only attach the SSL analyzer once per QUIC connection
if ( ! context.initialized ) {
context.initialized = True;
zeek::protocol_begin("SSL");
}
}
};
##############
# Entrypoints
##############
public type RequestFrame = unit {
%context = ConnectionIDInfo;
: Frame(True, self.context());
};
public type ResponseFrame = unit {
%context = ConnectionIDInfo;
: Frame(False, self.context());
};

View file

@ -0,0 +1,380 @@
// See the file "COPYING" in the main distribution directory for copyright.
/*
WORK-IN-PROGRESS
Initial working version of decrypting the INITIAL packets from
both client & server to be used by the Spicy parser. Might need a few more
refactors as C++ development is not our main profession.
*/
// Default imports
#include <stdlib.h>
#include <cstring>
#include <vector>
#include <iostream>
#include <string>
// OpenSSL imports
#include <openssl/kdf.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
// Import HILTI
#include <hilti/rt/libhilti.h>
// Struct to store decryption info for this specific connection
struct DecryptionInformation
{
std::vector<uint8_t> unprotected_header;
std::vector<uint8_t> protected_header;
uint64_t packet_number;
std::vector<uint8_t> nonce;
uint8_t packet_number_length;
};
/*
Constants used in the HKDF functions. HKDF-Expand-Label uses labels
such as 'quic key' and 'quic hp'. These labels can obviously be
calculated dynamically, but are incluced statically for now, as the
goal of this analyser is only to analyze the INITIAL packets.
*/
std::vector<uint8_t> INITIAL_SALT_V1 = {
0x38, 0x76, 0x2c, 0xf7, 0xf5,
0x59, 0x34, 0xb3, 0x4d, 0x17,
0x9a, 0xe6, 0xa4, 0xc8, 0x0c,
0xad, 0xcc, 0xbb, 0x7f, 0x0a};
std::vector<uint8_t> CLIENT_INITIAL_INFO = {
0x00, 0x20, 0x0f, 0x74, 0x6c,
0x73, 0x31, 0x33, 0x20, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74,
0x20, 0x69, 0x6e, 0x00};
std::vector<uint8_t> SERVER_INITIAL_INFO = {
0x00, 0x20, 0x0f, 0x74, 0x6c,
0x73, 0x31, 0x33, 0x20, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72,
0x20, 0x69, 0x6e, 0x00};
std::vector<uint8_t> KEY_INFO = {
0x00, 0x10, 0x0e, 0x74, 0x6c,
0x73, 0x31, 0x33, 0x20, 0x71,
0x75, 0x69, 0x63, 0x20, 0x6b,
0x65, 0x79, 0x00};
std::vector<uint8_t> IV_INFO = {
0x00, 0x0c, 0x0d, 0x74, 0x6c,
0x73, 0x31, 0x33, 0x20, 0x71,
0x75, 0x69, 0x63, 0x20, 0x69,
0x76, 0x00};
std::vector<uint8_t> HP_INFO = {
0x00, 0x10, 0x0d, 0x74, 0x6c,
0x73, 0x31, 0x33, 0x20, 0x71,
0x75, 0x69, 0x63, 0x20, 0x68,
0x70, 0x00};
/*
Constants used by the different functions
*/
const size_t INITIAL_SECRET_LEN = 32;
const size_t AEAD_KEY_LEN = 16;
const size_t AEAD_IV_LEN = 12;
const size_t AEAD_HP_LEN = 16;
const size_t AEAD_SAMPLE_LENGTH = 16;
const size_t AEAD_TAG_LENGTH = 16;
const size_t MAXIMUM_PACKET_LENGTH = 1500;
const size_t MAXIMUM_PACKET_NUMBER_LENGTH = 4;
/*
HKDF-Extract as decribed in https://www.rfc-editor.org/rfc/rfc8446.html#section-7.1
*/
std::vector<uint8_t> hkdf_extract(std::vector<uint8_t> connection_id)
{
std::vector<uint8_t> out_temp(INITIAL_SECRET_LEN);
size_t initial_secret_len = out_temp.size();
const EVP_MD *digest = EVP_sha256();
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
EVP_PKEY_derive_init(pctx);
EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY);
EVP_PKEY_CTX_set_hkdf_md(pctx, digest);
EVP_PKEY_CTX_set1_hkdf_key(pctx,
connection_id.data(),
connection_id.size());
EVP_PKEY_CTX_set1_hkdf_salt(pctx,
INITIAL_SALT_V1.data(),
INITIAL_SALT_V1.size());
EVP_PKEY_derive(pctx,
out_temp.data(),
reinterpret_cast<size_t *>(&initial_secret_len));
EVP_PKEY_CTX_free(pctx);
return out_temp;
}
/*
HKDF-Expand-Label as decribed in https://www.rfc-editor.org/rfc/rfc8446.html#section-7.1
that uses the global constant labels such as 'quic hp'.
*/
std::vector<uint8_t> hkdf_expand(size_t out_len,
std::vector<uint8_t> key,
std::vector<uint8_t> info)
{
std::vector<uint8_t> out_temp(out_len);
const EVP_MD *digest = EVP_sha256();
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
EVP_PKEY_derive_init(pctx);
EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
EVP_PKEY_CTX_set_hkdf_md(pctx, digest);
EVP_PKEY_CTX_set1_hkdf_key(pctx, key.data(), key.size());
EVP_PKEY_CTX_add1_hkdf_info(pctx, info.data(), info.size());
EVP_PKEY_derive(pctx, out_temp.data(), &out_len);
EVP_PKEY_CTX_free(pctx);
return out_temp;
}
/*
Removes the header protection from the INITIAL packet and returns a DecryptionInformation struct that is partially filled
*/
DecryptionInformation remove_header_protection(std::vector<uint8_t> client_hp, uint8_t encrypted_offset, std::vector<uint8_t> encrypted_packet)
{
DecryptionInformation decryptInfo;
int outlen;
auto cipher = EVP_aes_128_ecb();
auto ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, 1);
EVP_CIPHER_CTX_set_key_length(ctx, client_hp.size());
// Passing an 1 means ENCRYPT
EVP_CipherInit_ex(ctx, NULL, NULL, client_hp.data(), NULL, 1);
std::vector<uint8_t> sample(encrypted_packet.begin() +
encrypted_offset +
MAXIMUM_PACKET_NUMBER_LENGTH,
encrypted_packet.begin() +
encrypted_offset +
MAXIMUM_PACKET_NUMBER_LENGTH +
AEAD_SAMPLE_LENGTH);
std::vector<uint8_t> mask(sample.size());
EVP_CipherUpdate(ctx, mask.data(), &outlen, sample.data(), AEAD_SAMPLE_LENGTH);
// To determine the actual packet number length,
// we have to remove the mask from the first byte
uint8_t first_byte = encrypted_packet[0];
if (first_byte & 0x80)
{
first_byte ^= mask[0] & 0x0F;
}
else
{
first_byte ^= first_byte & 0x1F;
}
// And now we can fully recover the correct packet number length...
int recovered_packet_number_length = (first_byte & 0x03) + 1;
// .. and use this to reconstruct the (partially) unprotected header
std::vector<uint8_t> unprotected_header(
encrypted_packet.begin(),
encrypted_packet.begin() +
encrypted_offset +
recovered_packet_number_length);
uint32_t decoded_packet_number = 0;
unprotected_header[0] = first_byte;
for (int i = 0; i < recovered_packet_number_length; ++i)
{
unprotected_header[encrypted_offset + i] ^= mask[1 + i];
decoded_packet_number =
unprotected_header[encrypted_offset + i] |
(decoded_packet_number << 8);
}
std::vector<uint8_t> protected_header(encrypted_packet.begin(),
encrypted_packet.begin() +
encrypted_offset +
recovered_packet_number_length);
// Store the information back in the struct
decryptInfo.packet_number = decoded_packet_number;
decryptInfo.packet_number_length = recovered_packet_number_length;
decryptInfo.protected_header = protected_header;
decryptInfo.unprotected_header = unprotected_header;
return decryptInfo;
}
/*
Calculate the nonce for the AEAD by XOR'ing the CLIENT_IV and the
decoded packet number, and returns the nonce
*/
std::vector<uint8_t> calculate_nonce(std::vector<uint8_t> client_iv, uint64_t packet_number)
{
std::vector<uint8_t> nonce = client_iv;
for (int i = 0; i < 8; ++i)
{
nonce[AEAD_IV_LEN - 1 - i] ^=
(uint8_t)(packet_number >> 8 * i);
}
// Return the nonce
return nonce;
}
/*
Function that calls the AEAD decryption routine, and returns the
decrypted data
*/
std::vector<uint8_t> decrypt(std::vector<uint8_t> client_key,
std::vector<uint8_t> encrypted_packet,
uint64_t payload_offset,
DecryptionInformation decryptInfo)
{
int out, out2, res;
std::vector<uint8_t> encrypted_payload(
encrypted_packet.begin() +
decryptInfo.protected_header.size(),
encrypted_packet.begin() +
decryptInfo.protected_header.size() +
payload_offset -
decryptInfo.packet_number_length -
AEAD_TAG_LENGTH);
std::vector<uint8_t> tag_to_check(
encrypted_packet.begin() +
decryptInfo.protected_header.size() +
payload_offset -
decryptInfo.packet_number_length -
AEAD_TAG_LENGTH,
encrypted_packet.begin() +
decryptInfo.protected_header.size() +
payload_offset -
decryptInfo.packet_number_length);
unsigned char decrypt_buffer[MAXIMUM_PACKET_LENGTH];
// Setup context
auto cipher = EVP_aes_128_gcm();
auto ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx,
cipher,
NULL,
NULL,
NULL,
0);
// Set the sizes for the IV and KEY
EVP_CIPHER_CTX_ctrl(ctx,
EVP_CTRL_CCM_SET_IVLEN,
decryptInfo.nonce.size(),
NULL);
EVP_CIPHER_CTX_set_key_length(ctx,
client_key.size());
// Set the KEY and IV
EVP_CipherInit_ex(ctx,
NULL,
NULL,
client_key.data(),
decryptInfo.nonce.data(),
0);
// Set the tag to be validated after decryption
EVP_CIPHER_CTX_ctrl(ctx,
EVP_CTRL_CCM_SET_TAG,
tag_to_check.size(),
tag_to_check.data());
// Setting the second parameter to NULL will pass it as Associated Data
EVP_CipherUpdate(ctx,
NULL,
&out,
decryptInfo.unprotected_header.data(),
decryptInfo.unprotected_header.size());
// Set the actual data to decrypt data into the decrypt_buffer. The amount of
// byte decrypted is stored into `out`
EVP_CipherUpdate(ctx,
decrypt_buffer,
&out,
encrypted_payload.data(),
encrypted_payload.size());
// Validate whether the decryption was successful or not
EVP_CipherFinal_ex(ctx, NULL, &out2);
// Copy the decrypted data from the decrypted buffer into a new vector and return this
// Use the `out` variable to only include relevant bytes
std::vector<uint8_t> decrypted_data(decrypt_buffer, decrypt_buffer + out);
return decrypted_data;
}
/*
Function that is called from Spicy. It's a wrapper around `process_data`;
it stores all the passed data in a global struct and then calls `process_data`,
which will eventually return the decrypted data and pass it back to Spicy.
*/
hilti::rt::Bytes decrypt_crypto_payload(
const hilti::rt::Bytes &entire_packet,
const hilti::rt::Bytes &connection_id,
const hilti::rt::integer::safe<uint64_t> &encrypted_offset,
const hilti::rt::integer::safe<uint64_t> &payload_offset,
const hilti::rt::Bool &from_client)
{
// Fill in the entire packet bytes
std::vector<uint8_t> e_pkt;
for (const auto &singlebyte : entire_packet)
{
e_pkt.push_back(singlebyte);
}
std::vector<uint8_t> cnnid;
for (const auto &singlebyte : connection_id)
{
cnnid.push_back(singlebyte);
}
std::vector<uint8_t> initial_secret = hkdf_extract(cnnid);
std::vector<uint8_t> server_client_secret;
if (from_client)
{
server_client_secret = hkdf_expand(INITIAL_SECRET_LEN,
initial_secret,
CLIENT_INITIAL_INFO);
}
else
{
server_client_secret = hkdf_expand(INITIAL_SECRET_LEN,
initial_secret,
SERVER_INITIAL_INFO);
}
std::vector<uint8_t> key = hkdf_expand(AEAD_KEY_LEN,
server_client_secret,
KEY_INFO);
std::vector<uint8_t> iv = hkdf_expand(AEAD_IV_LEN,
server_client_secret,
IV_INFO);
std::vector<uint8_t> hp = hkdf_expand(AEAD_HP_LEN,
server_client_secret,
HP_INFO);
DecryptionInformation decryptInfo = remove_header_protection(hp, (uint8_t)encrypted_offset, e_pkt);
// Calculate the correct nonce for the decryption
decryptInfo.nonce = calculate_nonce(iv, decryptInfo.packet_number);
std::vector<uint8_t> decrypted_data = decrypt(key, e_pkt, payload_offset, decryptInfo);
// Return it as hilti Bytes again
hilti::rt::Bytes decr(decrypted_data.begin(), decrypted_data.end());
return decr;
}

View file

@ -0,0 +1,12 @@
module Zeek_QUIC;
import zeek;
import QUIC;
on QUIC::ResponseFrame::%done {
zeek::confirm_protocol();
}
on QUIC::ResponseFrame::%error {
zeek::reject_protocol("error while parsing QUIC message");
}

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path conn
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents
#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 1.2.3.4 49369 4.3.2.1 443 udp spicy_quic,ssl 18.071102 14371 394242 SF - - 0 Dd 96 17059 345 403902 -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path ssl
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert
#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 1.2.3.4 49369 4.3.2.1 443 - - - www.google.com F - - F C - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,4 @@
# @TEST-DOC: Test that runs the pcap
# @TEST-EXEC: zeek -Cr $TRACES/quic/quic_win11_firefox_google.pcap base/protocols/quic >output
# @TEST-EXEC: btest-diff conn.log
# @TEST-EXEC: btest-diff ssl.log