QUIC is a new transport protocol meant to be the replacement of the TCP + TLS, which is on top of UDP. Other the security mechanisms provided by TLS, QUIC itself has offered an extension for an endpoint to do an identity verification to the remote endpoint, to prevent from traffic amplification attack. It’s called address validation, and it would be done along side with initial handshakes between client and server. This post will briefly talked about how this is done within QUIC.
Address Validation
In short, address validation is mainly done by the field Token
in the initial packet. The receiver (server) would require the sender (client) of the initial packet to set Token
to the value the receiver previously generated.
If the sender keeps using a token that the receiver cannot recognize, receiver need to close the connection stateless by sending a CONNECTION_CLOSE frame with the error code INVALID_TOKEN (0x0b)
and ignore any subsequent requests.
Initial Packet {
Header Form (1) = 1,
Fixed Bit (1) = 1,
Long Packet Type (2) = 0,
Reserved Bits (2),
Packet Number Length (2),
Version (32),
Destination Connection ID Length (8),
Destination Connection ID (0..160),
Source Connection ID Length (8),
Source Connection ID (0..160),
Token Length (i),
Token (..),
Length (i),
Packet Number (8..32),
Packet Payload (8..),
}
Initial Packet Layout RFC 9000 Section 17.2.2
This would bring us to the first question. Since the client is the always the endpoint that opens a new connection, how would it know what the Token
value is the server wants if it has never reached to that server in the past? (Usually the client knows the server supports the QUIC&HTTP/3 by header field alt-svc
in the server’s response header, whose value would contain if HTTP/3 is available and on which port it is available.)
Validation by Retry Packet
sequenceDiagram participant Client participant Server Client->>Server: Initial Packet (payload contains Client Hello) Server->>Client: Retry Packet (contains new token) Client->>Server: Initial Packet + Client Hello(with new token) Server->>Client: Initial Packet (payload contains Server Hello) Server->>Client: Do TLS handshake Client-->Server: Start to exchange stream data
As shown in the sequence diagram above, upon receiving the client’s first initial packet and successfully decoded the Token
field in the packet header, server would compare the token value with its existing the valid token set. If no match, it would generate a new token and pass it to the client via. the Retry Packet’s Retry Token
field.
Retry Packet {
Header Form (1) = 1,
Fixed Bit (1) = 1,
Long Packet Type (2) = 3,
Unused (4),
Version (32),
Destination Connection ID Length (8),
Destination Connection ID (0..160),
Source Connection ID Length (8),
Source Connection ID (0..160),
Retry Token (..),
Retry Integrity Tag (128),
}
Retry Packet Layout RFC 9000 Section 17.2.5
A legitimate client would then resend its initial packets with the token value offered in the retry packet. Otherwise, server would turn down this connection.
One important thing to note though is that RFC didn’t explain if the token value generated during this stage is only valid for this connection or can be used in the future connections. However, it mentioned in the subsequent section that token in the retry packet is used immediately, and must not be carried over to the future connections.
For Future Connections
To avoid this extra step of sending/receiving retry packets for connection establishment in the future, server is allowed to advertise the token values it wishes the client to use in the future to the client via. NEW_TOKEN
frames.
NEW_TOKEN Frame {
Type (i) = 0x07,
Token Length (i),
Token (..),
}
NEW_TOKEN Frames Layout RFC 9000 Section 17.2.5
Multiple different token values can be sent to the client via. multiple NEW_TOKEN
frame, client could use any of these in the future connection, however since each token in acquired from the NEW_TOKEN
frame are likely to have an expiration time, it’s recommend for the client to always use the latest one when starting the new connection.
Server may need regularly retire old tokens and send to the clients with ones.
Purpose of Adding This
In TCP, the TLS handshake won’t happen until TCP handshake is done. So there is no way malicious actors could spoof the server with the victims' IP address, as the victim would only receive a TCP ACK for TCP handshake but no other DATA.
While in the QUIC, TLS handshake is done alone with the QUIC ACK for initial packets from the server, after the server received an initial packet from the client. If there is no address validation, server would directly reply a much larger QUIC packet containing the Server Hello and the server’s certificate to the spoofed IP address. The response would be even larger if the server has a relatively long chain of certificates. By doing so, the victim’s machine would be flooded with these kind of unwanted big UDP packets. This is also called as traffic amplification attack.
Now if the server enables the address validation, server then would reply with a much smaller retry packet. And if the victim won’t reply with the new token, server don’t have to send the TLS handshake back and just shuts down the connection.
However, since the fact that the initial packet and retry packet would always be sent in the plaint text, if the malicious actor has the capability of eavesdropping the traffic from the server, it can simply use such token in its initial packet and server would send Server Hello this time.