Skip to content

Commit b3f1f23

Browse files
committed
quic: initiate key updates
For golang/go#58547 Change-Id: If27c0745fc49cb9e8cb9906733ce2f453926b893 Reviewed-on: https://go-review.googlesource.com/c/net/+/529595 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
1 parent 18f2095 commit b3f1f23

File tree

5 files changed

+102
-2
lines changed

5 files changed

+102
-2
lines changed

internal/quic/conn.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ func newConn(now time.Time, side connSide, initialConnID []byte, peerAddr netip.
100100
// The smallest allowed maximum QUIC datagram size is 1200 bytes.
101101
// TODO: PMTU discovery.
102102
const maxDatagramSize = 1200
103+
c.keysAppData.init()
103104
c.loss.init(c.side, maxDatagramSize, now)
104105
c.streamsInit()
105106
c.lifetimeInit()

internal/quic/conn_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ func newTestConn(t *testing.T, side connSide, opts ...any) *testConn {
242242
}
243243
tc.conn = conn
244244

245+
conn.keysAppData.updateAfter = maxPacketNumber // disable key updates
245246
tc.keysInitial.r = conn.keysInitial.w
246247
tc.keysInitial.w = conn.keysInitial.r
247248

@@ -687,6 +688,7 @@ func (tc *testConn) encodeTestPacket(p *testPacket, pad int) []byte {
687688
tc.wkeyAppData.pkt[p.keyNumber],
688689
},
689690
},
691+
updateAfter: maxPacketNumber,
690692
}
691693
if p.keyPhaseBit {
692694
k.phase |= keyPhaseBit

internal/quic/key_update_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,74 @@ func TestKeyUpdateRejectPacketFromPriorPhase(t *testing.T) {
161161
},
162162
})
163163
}
164+
165+
func TestKeyUpdateLocallyInitiated(t *testing.T) {
166+
const updateAfter = 4 // initiate key update after 1-RTT packet 4
167+
tc := newTestConn(t, serverSide)
168+
tc.conn.keysAppData.updateAfter = updateAfter
169+
tc.handshake()
170+
171+
for {
172+
tc.writeFrames(packetType1RTT, debugFramePing{})
173+
tc.advanceToTimer()
174+
tc.wantFrameType("conn ACKs last packet",
175+
packetType1RTT, debugFrameAck{})
176+
if tc.lastPacket.num > updateAfter {
177+
break
178+
}
179+
if got, want := tc.lastPacket.keyNumber, 0; got != want {
180+
t.Errorf("before key update, conn sent packet with key %v, want %v", got, want)
181+
}
182+
if tc.lastPacket.keyPhaseBit {
183+
t.Errorf("before key update, keyPhaseBit is set, want unset")
184+
}
185+
}
186+
if got, want := tc.lastPacket.keyNumber, 1; got != want {
187+
t.Errorf("after key update, conn sent packet with key %v, want %v", got, want)
188+
}
189+
if !tc.lastPacket.keyPhaseBit {
190+
t.Errorf("after key update, keyPhaseBit is unset, want set")
191+
}
192+
tc.wantFrame("first packet after a key update is always ack-eliciting",
193+
packetType1RTT, debugFramePing{})
194+
tc.wantIdle("no more frames")
195+
196+
// Peer sends another packet using the prior phase keys.
197+
tc.writeFrames(packetType1RTT, debugFramePing{})
198+
tc.advanceToTimer()
199+
tc.wantFrameType("conn ACKs packet in prior phase",
200+
packetType1RTT, debugFrameAck{})
201+
tc.wantIdle("packet is not ack-eliciting")
202+
if got, want := tc.lastPacket.keyNumber, 1; got != want {
203+
t.Errorf("after key update, conn sent packet with key %v, want %v", got, want)
204+
}
205+
206+
// Peer updates to the next phase.
207+
tc.sendKeyNumber = 1
208+
tc.sendKeyPhaseBit = true
209+
tc.writeAckForAll()
210+
tc.writeFrames(packetType1RTT, debugFramePing{})
211+
tc.advanceToTimer()
212+
tc.wantFrameType("conn ACKs packet in current phase",
213+
packetType1RTT, debugFrameAck{})
214+
tc.wantIdle("packet is not ack-eliciting")
215+
if got, want := tc.lastPacket.keyNumber, 1; got != want {
216+
t.Errorf("after key update, conn sent packet with key %v, want %v", got, want)
217+
}
218+
219+
// Peer initiates its own update.
220+
tc.sendKeyNumber = 2
221+
tc.sendKeyPhaseBit = false
222+
tc.writeFrames(packetType1RTT, debugFramePing{})
223+
tc.advanceToTimer()
224+
tc.wantFrameType("conn ACKs packet in current phase",
225+
packetType1RTT, debugFrameAck{})
226+
tc.wantFrame("first packet after a key update is always ack-eliciting",
227+
packetType1RTT, debugFramePing{})
228+
if got, want := tc.lastPacket.keyNumber, 2; got != want {
229+
t.Errorf("after peer key update, conn sent packet with key %v, want %v", got, want)
230+
}
231+
if tc.lastPacket.keyPhaseBit {
232+
t.Errorf("after peer key update, keyPhaseBit is unset, want set")
233+
}
234+
}

internal/quic/packet_codec_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ func TestRoundtripEncodeShortPacket(t *testing.T) {
153153
aes128Keys.w = aes128Keys.r
154154
aes256Keys.w = aes256Keys.r
155155
chachaKeys.w = chachaKeys.r
156+
aes128Keys.updateAfter = maxPacketNumber
157+
aes256Keys.updateAfter = maxPacketNumber
158+
chachaKeys.updateAfter = maxPacketNumber
156159
connID := make([]byte, connIDLen)
157160
for i := range connID {
158161
connID[i] = byte(i)

internal/quic/packet_protection.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,9 +340,19 @@ type updatingKeyPair struct {
340340
updating bool
341341
minSent packetNumber // min packet number sent since entering the updating state
342342
minReceived packetNumber // min packet number received in the next phase
343+
updateAfter packetNumber // packet number after which to initiate key update
343344
r, w updatingKeys
344345
}
345346

347+
func (k *updatingKeyPair) init() {
348+
// 1-RTT packets until the first key update.
349+
//
350+
// We perform the first key update early in the connection so a peer
351+
// which does not support key updates will fail rapidly,
352+
// rather than after the connection has been long established.
353+
k.updateAfter = 1000
354+
}
355+
346356
func (k *updatingKeyPair) canRead() bool {
347357
return k.r.hdr.hp != nil
348358
}
@@ -371,8 +381,6 @@ func (k *updatingKeyPair) needAckEliciting() bool {
371381
// protect applies packet protection to a packet.
372382
// Parameters and returns are as for fixedKeyPair.protect.
373383
func (k *updatingKeyPair) protect(hdr, pay []byte, pnumOff int, pnum packetNumber) []byte {
374-
// TODO: Initiate key updates as required to avoid the AEAD usage limit.
375-
// https://www.rfc-editor.org/rfc/rfc9001#section-6.6
376384
var pkt []byte
377385
if k.updating {
378386
hdr[0] |= k.phase ^ keyPhaseBit
@@ -381,6 +389,21 @@ func (k *updatingKeyPair) protect(hdr, pay []byte, pnumOff int, pnum packetNumbe
381389
} else {
382390
hdr[0] |= k.phase
383391
pkt = k.w.pkt[0].protect(hdr, pay, pnum)
392+
if pnum >= k.updateAfter {
393+
// Initiate a key update, starting with the next packet we send.
394+
//
395+
// We do this after protecting the current packet
396+
// to allow Conn.appendFrames to ensure that the first packet sent
397+
// in the new phase is ack-eliciting.
398+
k.updating = true
399+
k.minSent = maxPacketNumber
400+
k.minReceived = maxPacketNumber
401+
// The lowest confidentiality limit for a supported AEAD is 2^23 packets.
402+
// https://www.rfc-editor.org/rfc/rfc9001#section-6.6-5
403+
//
404+
// Schedule our next update for half that.
405+
k.updateAfter += (1 << 22)
406+
}
384407
}
385408
k.w.hdr.protect(pkt, pnumOff)
386409
return pkt

0 commit comments

Comments
 (0)