~/home/study/quic-protocol-overview-handshake

QUIC Protocol Overview & Handshake Fundamentals - Introductory Guide

Learn the core architecture of QUIC, its handshake flow, version negotiation, transport parameters, connection IDs, 0-RTT handling, and the tooling needed to capture and decode QUIC traffic.

Introduction

QUIC (Quick UDP Internet Connections) is a transport protocol that runs over UDP and integrates TLS 1.3, multiplexing, and connection migration into a single, low-latency stack. Originally designed by Google and later standardized by IETF (RFC 9000), QUIC aims to solve many of TCP’s shortcomings-head-of-line blocking, slow start, and inefficient connection setup-while providing built-in security.

Understanding QUIC is essential for modern security professionals because the protocol is rapidly adopted by major browsers, CDNs, and cloud services. Attack surfaces such as version downgrade, 0-RTT replay, and connection-ID spoofing are unique to QUIC and require a solid grasp of the handshake fundamentals.

Real-world relevance includes traffic analysis of high-performance APIs, detecting malicious QUIC-based DoS attacks, and auditing TLS-1.3 configurations embedded in QUIC implementations.

Prerequisites

  • Solid knowledge of the TCP/IP stack (IP, UDP, TCP state machine).
  • Familiarity with TLS 1.3 handshake messages and cipher suites.
  • Comfort with Linux command line, tcpdump/tshark, and basic packet-capture analysis.
  • Basic programming experience (Go, Python, or C) is helpful for code examples.

Core Concepts

At a high level, QUIC merges three traditionally separate layers:

  1. Transport layer - reliability, congestion control, loss recovery, and multiplexed streams.
  2. Security layer - TLS 1.3 provides encryption, authentication, and forward secrecy.
  3. Connection management - connection IDs, stateless resets, and migration support.

Because QUIC runs over UDP, it bypasses kernel TCP stacks, allowing user-space implementations to evolve quickly. The protocol is packet-oriented; every QUIC packet contains a header (type, version, DCID, SCID, length, token) followed by encrypted payload.

Diagram (described in text): Imagine a flow where the client sends an Initial packet containing a random Destination Connection ID (DCID) and a Source Connection ID (SCID). The server replies with its own Initial packet, swapping IDs, optionally sending a Retry, and then proceeds to the Handshake phase where TLS 1.3 messages are encrypted under early keys.

QUIC architecture and design goals

QUIC was built around three primary goals:

  • Reduced latency: Combine TCP’s three-way handshake and TLS 1.3’s handshake into a single round-trip (or zero-RTT when possible).
  • Improved reliability: Stream multiplexing eliminates head-of-line blocking; lost packets only affect the streams they belong to.
  • Connection migration: By using opaque Connection IDs, a client can change IP addresses (e.g., move from Wi-Fi to cellular) without breaking the session.

Other design decisions include mandatory encryption of all frames, explicit version negotiation, and extensibility via transport parameters.

Initial handshake flow (Client Initial, Server Initial)

The handshake begins with two Initial packets:

  1. Client Initial
    • Header: Long header, QUIC version, random DCID (chosen by client), SCID (optional), token length = 0.
    • Payload: TLS 1.3 ClientHello (CH) encrypted with a temporary key derived from the client’s initial secret.
    • Transport parameters are sent inside a transport_parameters extension within the CH.
  2. Server Initial
    • Header: Long header, same version, DCID = client’s DCID, SCID = server-chosen connection ID.
    • If the server does not like the version, it may send a Version Negotiation packet (see next subtopic).
    • Payload: TLS 1.3 ServerHello, encrypted under the server’s initial secret, followed by EncryptedExtensions, Certificate, CertificateVerify, and Finished messages.

After the Server Initial, the client sends a Handshake packet containing its Finished message, completing the TLS handshake. Subsequent packets use 1-RTT keys.

Version negotiation and supported versions

If the server receives a version it does not support, it replies with a Version Negotiation packet:

  • Long header, type = 0x1 (Version Negotiation).
  • DCID = client’s DCID, SCID = server’s SCID (echoed back unchanged).
  • Payload: List of versions the server does support, encoded as 32-bit integers.

The client then restarts the handshake using the first version from the list (or its own fallback list). QUIC implementations must maintain a supported_versions list; the IETF standard currently mandates version 1 (0x00000001).

Security note: Version Negotiation packets are not encrypted, making them a potential vector for downgrade attacks. Implementations should verify that the server’s version list is not empty and must discard any version that is not advertised in the client’s own list.

Transport parameters exchange

Transport parameters are key/value pairs that negotiate capabilities such as maximum stream counts, flow-control windows, and idle timeout. They are exchanged inside the TLS handshake:

  • client_transport_parameters - sent in the ClientHello extension.
  • server_transport_parameters - sent in EncryptedExtensions.

Important parameters include:

NameTypeDefault
initial_max_stream_data_bidi_localvarint65536
initial_max_stream_data_bidi_remotevarint65536
initial_max_streams_bidivarint100
max_idle_timeoutvarint (ms)30000
disable_active_migrationflagnot set

Mis-configuring these values can lead to resource exhaustion (e.g., setting an extremely high initial_max_streams_bidi invites a client to open many streams, consuming server buffers).

Connection IDs and stateless reset tokens

Because QUIC runs over UDP, the server cannot rely on the 5-tuple to identify a connection. Instead, it uses opaque Connection IDs (CIDs):

  • Destination CID (DCID) - tells the receiver which connection the packet belongs to.
  • Source CID (SCID) - the sender’s own identifier that the peer will echo back as DCID in the next direction.

CIDs are 1-20 bytes long and can be rotated at any time, enabling seamless migration.

A Stateless Reset Token (16-byte random value) is bound to a CID and can be used by a server to abruptly terminate a connection without keeping state. When the server receives a packet with an unknown CID, it may send a short packet containing the reset token; the client, upon verifying the token, discards the connection.

Security implication: An attacker who can guess a reset token can force a denial-of-service. Therefore tokens must be generated with high entropy and tied to a per-CID secret.

0-RTT data handling

0-RTT enables a client to send encrypted data in the first flight, re-using credentials from a previous connection. The flow is:

  1. Client stores the early_data secret from a prior successful handshake.
  2. During a new handshake, the client includes an EarlyData extension in the ClientHello and immediately sends application data encrypted with the early secret.
  3. Server validates the early_data acceptance flag; if acceptable, it processes the data, otherwise it discards it.

Key points for security professionals:

  • 0-RTT data is not forward-secure; replay attacks are possible.
  • Servers must implement replay detection (e.g., using a cache of early_data identifiers).
  • Sensitive operations (payments, state-changing APIs) should be prohibited over 0-RTT.

Tools for capturing and decoding QUIC (Wireshark, qlog, quic-go)

Analyzing QUIC traffic requires specialized tooling because most of the payload is encrypted. Below are the most common utilities:

  • Wireshark - recent versions (>=3.4) understand QUIC long-header packets and can decode the TLS handshake if the appropriate keys are supplied via SSLKEYLOGFILE.
  • qlog - a JSON-based logging format used by many QUIC implementations (quic-go, Chromium). It provides a timeline of packet events, frames, and loss recovery.
  • quic-go - a Go library that includes a built-in logger and a quic-client/quic-server binary useful for testing handshakes.

Typical workflow: capture with tcpdump -i eth0 -w quic.pcap udp port 443, export TLS keys, and open the pcap in Wireshark with File → Export → SSL Session Keys.

Practical Examples

Example 1 - Capturing a QUIC handshake with Wireshark

# Export TLS keys from Chrome (Linux)
export SSLKEYLOGFILE=$HOME/.sslkeylogfile
chrome --user-data-dir=$HOME/chromeprofile &

# Capture traffic on interface eth0 for 10 seconds
timeout 10 tcpdump -i eth0 -w quic_handshake.pcap 'udp port 443'

# Open in Wireshark and point to the keylog file
# Wireshark → Preferences → Protocols → TLS → (Pre)-Master-Secret log filename

The capture will show the Initial, Handshake, and 1-RTT packets with their Connection IDs and decrypted TLS frames.

Example 2 - Using quic-go to send 0-RTT data

package main

import ( "crypto/tls" "log" "net" "github.com/lucas-clemente/quic-go"
)

func main() { tlsConf := &tls.Config{InsecureSkipVerify: true, NextProtos: []string{"hq-29"}} // Enable 0-RTT by providing a session cache tlsConf.ClientSessionCache = tls.NewLRUClientSessionCache(32) session, err := quic.DialAddr("example.com:443", tlsConf, &quic.Config{Enable0RTT: true}) if err != nil { log.Fatal(err) } stream, err := session.OpenStreamSync(context.Background()) if err != nil { log.Fatal(err) } _, err = stream.Write([]byte("GET / HTTP/3")) if err != nil { log.Fatal(err) } // Read response buf := make([]byte, 1024) n, _ := stream.Read(buf) log.Printf("Response: %s", string(buf[:n]))
}

This program establishes a QUIC session, re-uses a cached TLS session, and sends an HTTP/3 request in the first flight (0-RTT). If the server rejects 0-RTT, the library automatically falls back to a normal 1-RTT handshake.

Tools & Commands

  • tcpdump -i any -nn -s 0 -w quic.pcap udp port 443 - raw capture.
  • tshark -r quic.pcap -Y "quic" -V - command-line inspection of QUIC packets.
  • openssl s_client -connect example.com:443 -nextprotoneg hq-29 -quiet - not a QUIC client but useful for TLS 1.3 debugging.
  • quic-go -client -addr example.com:443 -keylog $HOME/.sslkeylogfile - runs a simple client with key logging.
  • jq . quic_log.json | less - pretty-print qlog files.

Defense & Mitigation

While QUIC improves performance, it also introduces new attack surfaces. Defensive measures include:

  • Version validation: Reject unknown or downgraded versions; enforce a minimum supported version.
  • Replay protection for 0-RTT: Deploy server-side anti-replay caches (e.g., using early_data identifiers + timestamps).
  • Connection-ID rate limiting: Limit the rate at which new CIDs are accepted to mitigate CID exhaustion attacks.
  • Stateless reset token entropy: Use a cryptographically strong PRNG per CID; rotate tokens periodically.
  • Network monitoring: Deploy IDS signatures that look for abnormal QUIC Initial packet sizes or repeated Version Negotiation loops.

Common Mistakes

  • Assuming QUIC is always encrypted: Initial packets are encrypted but the header fields (including version and CIDs) are visible. Attackers can still fingerprint services.
  • Neglecting 0-RTT replay risk: Deploying 0-RTT for idempotent operations only; avoid using it for authentication or financial actions.
  • Hard-coding Connection IDs: Leads to inability to migrate; also makes the service vulnerable to targeted DoS.
  • Improper key-log handling: Leaving SSLKEYLOGFILE on production hosts can expose all traffic to local users.

Real-World Impact

Enterprises that have migrated to HTTP/3 (which runs on QUIC) report latency reductions of 20-30% for mobile users. However, incident reports from 2024 show a rise in QUIC-based amplification attacks where attackers spoof source IPs and flood victims with large Initial packets. Mitigation required firewall rules that limit UDP payload size and enforce rate-limiting on port 443.

In the cloud arena, services like AWS CloudFront and Google Cloud CDN now expose QUIC endpoints. Security teams must adapt their TLS-certificate lifecycle processes to include QUIC-specific transport parameter reviews, otherwise mis-configurations can lead to connection-ID collisions and unexpected session drops.

Expert opinion: As QUIC becomes the default for web traffic, visibility will shift from traditional NetFlow to encrypted-aware telemetry. Investing in qlog aggregation platforms and integrating TLS key-logging into SIEM pipelines will be a competitive advantage for threat hunting.

Practice Exercises

  1. Capture and decode a QUIC handshake
    • Enable SSLKEYLOGFILE in Chrome.
    • Capture traffic with tcpdump while loading a website that supports HTTP/3.
    • Open the pcap in Wireshark and verify the Initial, Handshake, and 1-RTT packets.
  2. Implement a custom QUIC client using quic-go
    • Write a Go program that sends a GET request over QUIC with 0-RTT enabled.
    • Log the Connection IDs exchanged and compare them to the server’s logs.
  3. Simulate a Version Negotiation attack
    • Craft a UDP packet with a bogus version number using scapy.
    • Observe the server’s Version Negotiation response and verify that your client properly restarts with a supported version.
  4. Test stateless reset handling
    • Force the server to send a stateless reset by sending a packet with an unknown CID.
    • Confirm that the client discards the connection after receiving the reset token.

Further Reading

  • RFC 9000 - QUIC: A UDP-Based Multiplexed Transport Protocol
  • RFC 9001 - Using TLS to Secure QUIC
  • RFC 9002 - QUIC Loss Detection and Congestion Control
  • Google’s QUIC Transport Drafts (for upcoming extensions).
  • “HTTP/3 Explained” - IETF Working Group Slides (2023).
  • “QUIC Security Analysis” - 2022 USENIX Security paper.

Summary

  • QUIC merges transport, security, and connection management into a single UDP-based protocol.
  • The handshake consists of Client Initial → Server Initial → Handshake → 1-RTT, with TLS 1.3 providing encryption.
  • Version negotiation, transport parameters, Connection IDs, and stateless reset tokens enable flexibility and migration but introduce new attack vectors.
  • 0-RTT offers latency gains but must be protected against replay.
  • Capture and analysis require tools like Wireshark (with key logs), qlog, and libraries such as quic-go.
  • Defensive best practices include strict version handling, replay protection, CID rate limiting, and robust monitoring.