quic: update Initial keys when handling Retry

A Retry packet specifies a new connection ID for the client to use as a
destination address, in what the server will consider to be the client's
"first" Initial packet. Re-derive the Initial space's packet protection
keys, since that address is an input to their derivation function.

"Changing the Destination Connection ID field also results in a change
to the keys used to protect the Initial packet."
https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-4

For golang/go#58547

Change-Id: Id8acf5788a05d367f952dce33ef4b06f7e8b66e2
Reviewed-on: https://go-review.googlesource.com/c/net/+/712341
Auto-Submit: Rhys Hiltner <rhys.hiltner@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Rhys Hiltner
2025-10-16 10:19:11 -07:00
committed by Gopher Robot
parent 98daa2e33a
commit 6e243da531
5 changed files with 17 additions and 5 deletions

View File

@@ -114,7 +114,11 @@ func main() {
case "resumption":
// TODO
case "retry":
// TODO
if *listen != "" && len(urls) == 0 {
config.RequireAddressValidation = true
}
basicTest(ctx, config, urls)
return
case "versionnegotiation":
// "The client should start a connection using
// an unsupported version number [...]"

View File

@@ -67,7 +67,7 @@ type Conn struct {
// connTestHooks override conn behavior in tests.
type connTestHooks interface {
// init is called after a conn is created.
init()
init(first bool)
// nextMessage is called to request the next event from msgc.
// Used to give tests control of the connection event loop.
@@ -177,7 +177,7 @@ func newConn(now time.Time, side connSide, cids newServerConnIDs, peerHostname s
}
if c.testHooks != nil {
c.testHooks.init()
c.testHooks.init(true)
}
go c.loop(now)
return c, nil

View File

@@ -208,10 +208,14 @@ func (c *Conn) handleRetry(now time.Time, pkt []byte) {
}
c.retryToken = cloneBytes(p.token)
c.connIDState.handleRetryPacket(p.srcConnID)
c.keysInitial = initialKeys(p.srcConnID, c.side)
// We need to resend any data we've already sent in Initial packets.
// We must not reuse already sent packet numbers.
c.loss.discardPackets(initialSpace, c.log, c.handleAckOrLoss)
// TODO: Discard 0-RTT packets as well, once we support 0-RTT.
if c.testHooks != nil {
c.testHooks.init(false)
}
}
var errVersionNegotiation = errors.New("server does not support QUIC version 1")

View File

@@ -1001,11 +1001,11 @@ func spaceForPacketType(ptype packetType) numberSpace {
// testConnHooks implements connTestHooks.
type testConnHooks testConn
func (tc *testConnHooks) init() {
func (tc *testConnHooks) init(first bool) {
tc.conn.keysAppData.updateAfter = maxPacketNumber // disable key updates
tc.keysInitial.r = tc.conn.keysInitial.w
tc.keysInitial.w = tc.conn.keysInitial.r
if tc.conn.side == serverSide {
if first && tc.conn.side == serverSide {
tc.endpoint.acceptQueue = append(tc.endpoint.acceptQueue, (*testConn)(tc))
}
}

View File

@@ -22,6 +22,10 @@ func TestConnect(t *testing.T) {
newLocalConnPair(t, &Config{}, &Config{})
}
func TestConnectRetry(t *testing.T) {
newLocalConnPair(t, &Config{RequireAddressValidation: true}, &Config{})
}
func TestConnectDefaultTLSConfig(t *testing.T) {
serverConfig := newTestTLSConfigWithMoreDefaults(serverSide)
clientConfig := newTestTLSConfigWithMoreDefaults(clientSide)