golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/quic/retry_test.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build go1.21 6 7 package quic 8 9 import ( 10 "bytes" 11 "context" 12 "crypto/tls" 13 "net/netip" 14 "testing" 15 "time" 16 ) 17 18 type retryServerTest struct { 19 te *testEndpoint 20 originalSrcConnID []byte 21 originalDstConnID []byte 22 retry retryPacket 23 initialCrypto []byte 24 } 25 26 // newRetryServerTest creates a test server connection, 27 // sends the connection an Initial packet, 28 // and expects a Retry in response. 29 func newRetryServerTest(t *testing.T) *retryServerTest { 30 t.Helper() 31 config := &Config{ 32 TLSConfig: newTestTLSConfig(serverSide), 33 RequireAddressValidation: true, 34 } 35 te := newTestEndpoint(t, config) 36 srcID := testPeerConnID(0) 37 dstID := testLocalConnID(-1) 38 params := defaultTransportParameters() 39 params.initialSrcConnID = srcID 40 initialCrypto := initialClientCrypto(t, te, params) 41 42 // Initial packet with no Token. 43 // Server responds with a Retry containing a token. 44 te.writeDatagram(&testDatagram{ 45 packets: []*testPacket{{ 46 ptype: packetTypeInitial, 47 num: 0, 48 version: quicVersion1, 49 srcConnID: srcID, 50 dstConnID: dstID, 51 frames: []debugFrame{ 52 debugFrameCrypto{ 53 data: initialCrypto, 54 }, 55 }, 56 }}, 57 paddedSize: 1200, 58 }) 59 got := te.readDatagram() 60 if len(got.packets) != 1 || got.packets[0].ptype != packetTypeRetry { 61 t.Fatalf("got datagram: %v\nwant Retry", got) 62 } 63 p := got.packets[0] 64 if got, want := p.dstConnID, srcID; !bytes.Equal(got, want) { 65 t.Fatalf("Retry destination = {%x}, want {%x}", got, want) 66 } 67 68 return &retryServerTest{ 69 te: te, 70 originalSrcConnID: srcID, 71 originalDstConnID: dstID, 72 retry: retryPacket{ 73 dstConnID: p.dstConnID, 74 srcConnID: p.srcConnID, 75 token: p.token, 76 }, 77 initialCrypto: initialCrypto, 78 } 79 } 80 81 func TestRetryServerSucceeds(t *testing.T) { 82 rt := newRetryServerTest(t) 83 te := rt.te 84 te.advance(retryTokenValidityPeriod) 85 te.writeDatagram(&testDatagram{ 86 packets: []*testPacket{{ 87 ptype: packetTypeInitial, 88 num: 1, 89 version: quicVersion1, 90 srcConnID: rt.originalSrcConnID, 91 dstConnID: rt.retry.srcConnID, 92 token: rt.retry.token, 93 frames: []debugFrame{ 94 debugFrameCrypto{ 95 data: rt.initialCrypto, 96 }, 97 }, 98 }}, 99 paddedSize: 1200, 100 }) 101 tc := te.accept() 102 initial := tc.readPacket() 103 if initial == nil || initial.ptype != packetTypeInitial { 104 t.Fatalf("got packet:\n%v\nwant: Initial", initial) 105 } 106 handshake := tc.readPacket() 107 if handshake == nil || handshake.ptype != packetTypeHandshake { 108 t.Fatalf("got packet:\n%v\nwant: Handshake", initial) 109 } 110 if got, want := tc.sentTransportParameters.retrySrcConnID, rt.retry.srcConnID; !bytes.Equal(got, want) { 111 t.Errorf("retry_source_connection_id = {%x}, want {%x}", got, want) 112 } 113 if got, want := tc.sentTransportParameters.initialSrcConnID, initial.srcConnID; !bytes.Equal(got, want) { 114 t.Errorf("initial_source_connection_id = {%x}, want {%x}", got, want) 115 } 116 if got, want := tc.sentTransportParameters.originalDstConnID, rt.originalDstConnID; !bytes.Equal(got, want) { 117 t.Errorf("original_destination_connection_id = {%x}, want {%x}", got, want) 118 } 119 } 120 121 func TestRetryServerTokenInvalid(t *testing.T) { 122 // "If a server receives a client Initial that contains an invalid Retry token [...] 123 // the server SHOULD immediately close [...] the connection with an 124 // INVALID_TOKEN error." 125 // https://www.rfc-editor.org/rfc/rfc9000#section-8.1.2-5 126 rt := newRetryServerTest(t) 127 te := rt.te 128 te.writeDatagram(&testDatagram{ 129 packets: []*testPacket{{ 130 ptype: packetTypeInitial, 131 num: 1, 132 version: quicVersion1, 133 srcConnID: rt.originalSrcConnID, 134 dstConnID: rt.retry.srcConnID, 135 token: append(rt.retry.token, 0), 136 frames: []debugFrame{ 137 debugFrameCrypto{ 138 data: rt.initialCrypto, 139 }, 140 }, 141 }}, 142 paddedSize: 1200, 143 }) 144 te.wantDatagram("server closes connection after Initial with invalid Retry token", 145 initialConnectionCloseDatagram( 146 rt.retry.srcConnID, 147 rt.originalSrcConnID, 148 errInvalidToken)) 149 } 150 151 func TestRetryServerTokenTooOld(t *testing.T) { 152 // "[...] a token SHOULD have an expiration time [...]" 153 // https://www.rfc-editor.org/rfc/rfc9000#section-8.1.3-3 154 rt := newRetryServerTest(t) 155 te := rt.te 156 te.advance(retryTokenValidityPeriod + time.Second) 157 te.writeDatagram(&testDatagram{ 158 packets: []*testPacket{{ 159 ptype: packetTypeInitial, 160 num: 1, 161 version: quicVersion1, 162 srcConnID: rt.originalSrcConnID, 163 dstConnID: rt.retry.srcConnID, 164 token: rt.retry.token, 165 frames: []debugFrame{ 166 debugFrameCrypto{ 167 data: rt.initialCrypto, 168 }, 169 }, 170 }}, 171 paddedSize: 1200, 172 }) 173 te.wantDatagram("server closes connection after Initial with expired token", 174 initialConnectionCloseDatagram( 175 rt.retry.srcConnID, 176 rt.originalSrcConnID, 177 errInvalidToken)) 178 } 179 180 func TestRetryServerTokenWrongIP(t *testing.T) { 181 // "Tokens sent in Retry packets SHOULD include information that allows the server 182 // to verify that the source IP address and port in client packets remain constant." 183 // https://www.rfc-editor.org/rfc/rfc9000#section-8.1.4-3 184 rt := newRetryServerTest(t) 185 te := rt.te 186 te.writeDatagram(&testDatagram{ 187 packets: []*testPacket{{ 188 ptype: packetTypeInitial, 189 num: 1, 190 version: quicVersion1, 191 srcConnID: rt.originalSrcConnID, 192 dstConnID: rt.retry.srcConnID, 193 token: rt.retry.token, 194 frames: []debugFrame{ 195 debugFrameCrypto{ 196 data: rt.initialCrypto, 197 }, 198 }, 199 }}, 200 paddedSize: 1200, 201 addr: netip.MustParseAddrPort("10.0.0.2:8000"), 202 }) 203 te.wantDatagram("server closes connection after Initial from wrong address", 204 initialConnectionCloseDatagram( 205 rt.retry.srcConnID, 206 rt.originalSrcConnID, 207 errInvalidToken)) 208 } 209 210 func TestRetryServerIgnoresRetry(t *testing.T) { 211 tc := newTestConn(t, serverSide) 212 tc.handshake() 213 tc.write(&testDatagram{ 214 packets: []*testPacket{{ 215 ptype: packetTypeRetry, 216 originalDstConnID: testLocalConnID(-1), 217 srcConnID: testPeerConnID(0), 218 dstConnID: testLocalConnID(0), 219 token: []byte{1, 2, 3, 4}, 220 }}, 221 }) 222 // Send two packets, to trigger an immediate ACK. 223 tc.writeFrames(packetType1RTT, debugFramePing{}) 224 tc.writeFrames(packetType1RTT, debugFramePing{}) 225 tc.wantFrameType("server connection ignores spurious Retry packet", 226 packetType1RTT, debugFrameAck{}) 227 } 228 229 func TestRetryClientSuccess(t *testing.T) { 230 // "This token MUST be repeated by the client in all Initial packets it sends 231 // for that connection after it receives the Retry packet." 232 // https://www.rfc-editor.org/rfc/rfc9000#section-8.1.2-1 233 tc := newTestConn(t, clientSide) 234 tc.wantFrame("client Initial CRYPTO data", 235 packetTypeInitial, debugFrameCrypto{ 236 data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], 237 }) 238 newServerConnID := []byte("new_conn_id") 239 token := []byte("token") 240 tc.write(&testDatagram{ 241 packets: []*testPacket{{ 242 ptype: packetTypeRetry, 243 originalDstConnID: testLocalConnID(-1), 244 srcConnID: newServerConnID, 245 dstConnID: testLocalConnID(0), 246 token: token, 247 }}, 248 }) 249 tc.wantPacket("client sends a new Initial packet with a token", 250 &testPacket{ 251 ptype: packetTypeInitial, 252 num: 1, 253 version: quicVersion1, 254 srcConnID: testLocalConnID(0), 255 dstConnID: newServerConnID, 256 token: token, 257 frames: []debugFrame{ 258 debugFrameCrypto{ 259 data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], 260 }, 261 }, 262 }, 263 ) 264 tc.advanceToTimer() 265 tc.wantPacket("after PTO client sends another Initial packet with a token", 266 &testPacket{ 267 ptype: packetTypeInitial, 268 num: 2, 269 version: quicVersion1, 270 srcConnID: testLocalConnID(0), 271 dstConnID: newServerConnID, 272 token: token, 273 frames: []debugFrame{ 274 debugFrameCrypto{ 275 data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], 276 }, 277 }, 278 }, 279 ) 280 } 281 282 func TestRetryClientInvalidServerTransportParameters(t *testing.T) { 283 // Various permutations of missing or invalid values for transport parameters 284 // after a Retry. 285 // https://www.rfc-editor.org/rfc/rfc9000#section-7.3 286 initialSrcConnID := testPeerConnID(0) 287 originalDstConnID := testLocalConnID(-1) 288 retrySrcConnID := testPeerConnID(100) 289 for _, test := range []struct { 290 name string 291 f func(*transportParameters) 292 ok bool 293 }{{ 294 name: "valid", 295 f: func(p *transportParameters) {}, 296 ok: true, 297 }, { 298 name: "missing initial_source_connection_id", 299 f: func(p *transportParameters) { 300 p.initialSrcConnID = nil 301 }, 302 }, { 303 name: "invalid initial_source_connection_id", 304 f: func(p *transportParameters) { 305 p.initialSrcConnID = []byte("invalid") 306 }, 307 }, { 308 name: "missing original_destination_connection_id", 309 f: func(p *transportParameters) { 310 p.originalDstConnID = nil 311 }, 312 }, { 313 name: "invalid original_destination_connection_id", 314 f: func(p *transportParameters) { 315 p.originalDstConnID = []byte("invalid") 316 }, 317 }, { 318 name: "missing retry_source_connection_id", 319 f: func(p *transportParameters) { 320 p.retrySrcConnID = nil 321 }, 322 }, { 323 name: "invalid retry_source_connection_id", 324 f: func(p *transportParameters) { 325 p.retrySrcConnID = []byte("invalid") 326 }, 327 }} { 328 t.Run(test.name, func(t *testing.T) { 329 tc := newTestConn(t, clientSide, 330 func(p *transportParameters) { 331 p.initialSrcConnID = initialSrcConnID 332 p.originalDstConnID = originalDstConnID 333 p.retrySrcConnID = retrySrcConnID 334 }, 335 test.f) 336 tc.ignoreFrame(frameTypeAck) 337 tc.wantFrameType("client Initial CRYPTO data", 338 packetTypeInitial, debugFrameCrypto{}) 339 tc.write(&testDatagram{ 340 packets: []*testPacket{{ 341 ptype: packetTypeRetry, 342 originalDstConnID: originalDstConnID, 343 srcConnID: retrySrcConnID, 344 dstConnID: testLocalConnID(0), 345 token: []byte{1, 2, 3, 4}, 346 }}, 347 }) 348 tc.wantFrameType("client resends Initial CRYPTO data", 349 packetTypeInitial, debugFrameCrypto{}) 350 tc.writeFrames(packetTypeInitial, 351 debugFrameCrypto{ 352 data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], 353 }) 354 tc.writeFrames(packetTypeHandshake, 355 debugFrameCrypto{ 356 data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], 357 }) 358 if test.ok { 359 tc.wantFrameType("valid params, client sends Handshake", 360 packetTypeHandshake, debugFrameCrypto{}) 361 } else { 362 tc.wantFrame("invalid transport parameters", 363 packetTypeInitial, debugFrameConnectionCloseTransport{ 364 code: errTransportParameter, 365 }) 366 } 367 }) 368 } 369 } 370 371 func TestRetryClientIgnoresRetryAfterReceivingPacket(t *testing.T) { 372 // "After the client has received and processed an Initial or Retry packet 373 // from the server, it MUST discard any subsequent Retry packets that it receives." 374 // https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-1 375 tc := newTestConn(t, clientSide) 376 tc.ignoreFrame(frameTypeAck) 377 tc.ignoreFrame(frameTypeNewConnectionID) 378 tc.wantFrameType("client Initial CRYPTO data", 379 packetTypeInitial, debugFrameCrypto{}) 380 tc.writeFrames(packetTypeInitial, 381 debugFrameCrypto{ 382 data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], 383 }) 384 retry := &testDatagram{ 385 packets: []*testPacket{{ 386 ptype: packetTypeRetry, 387 originalDstConnID: testLocalConnID(-1), 388 srcConnID: testPeerConnID(100), 389 dstConnID: testLocalConnID(0), 390 token: []byte{1, 2, 3, 4}, 391 }}, 392 } 393 tc.write(retry) 394 tc.wantIdle("client ignores Retry after receiving Initial packet") 395 tc.writeFrames(packetTypeHandshake, 396 debugFrameCrypto{ 397 data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], 398 }) 399 tc.wantFrameType("client Handshake CRYPTO data", 400 packetTypeHandshake, debugFrameCrypto{}) 401 tc.write(retry) 402 tc.wantIdle("client ignores Retry after discarding Initial keys") 403 } 404 405 func TestRetryClientIgnoresRetryAfterReceivingRetry(t *testing.T) { 406 // "After the client has received and processed an Initial or Retry packet 407 // from the server, it MUST discard any subsequent Retry packets that it receives." 408 // https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-1 409 tc := newTestConn(t, clientSide) 410 tc.wantFrameType("client Initial CRYPTO data", 411 packetTypeInitial, debugFrameCrypto{}) 412 retry := &testDatagram{ 413 packets: []*testPacket{{ 414 ptype: packetTypeRetry, 415 originalDstConnID: testLocalConnID(-1), 416 srcConnID: testPeerConnID(100), 417 dstConnID: testLocalConnID(0), 418 token: []byte{1, 2, 3, 4}, 419 }}, 420 } 421 tc.write(retry) 422 tc.wantFrameType("client resends Initial CRYPTO data", 423 packetTypeInitial, debugFrameCrypto{}) 424 tc.write(retry) 425 tc.wantIdle("client ignores second Retry") 426 } 427 428 func TestRetryClientIgnoresRetryWithInvalidIntegrityTag(t *testing.T) { 429 tc := newTestConn(t, clientSide) 430 tc.wantFrameType("client Initial CRYPTO data", 431 packetTypeInitial, debugFrameCrypto{}) 432 pkt := encodeRetryPacket(testLocalConnID(-1), retryPacket{ 433 srcConnID: testPeerConnID(100), 434 dstConnID: testLocalConnID(0), 435 token: []byte{1, 2, 3, 4}, 436 }) 437 pkt[len(pkt)-1] ^= 1 // invalidate the integrity tag 438 tc.endpoint.write(&datagram{ 439 b: pkt, 440 peerAddr: testClientAddr, 441 }) 442 tc.wantIdle("client ignores Retry with invalid integrity tag") 443 } 444 445 func TestRetryClientIgnoresRetryWithZeroLengthToken(t *testing.T) { 446 // "A client MUST discard a Retry packet with a zero-length Retry Token field." 447 // https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-2 448 tc := newTestConn(t, clientSide) 449 tc.wantFrameType("client Initial CRYPTO data", 450 packetTypeInitial, debugFrameCrypto{}) 451 tc.write(&testDatagram{ 452 packets: []*testPacket{{ 453 ptype: packetTypeRetry, 454 originalDstConnID: testLocalConnID(-1), 455 srcConnID: testPeerConnID(100), 456 dstConnID: testLocalConnID(0), 457 token: []byte{}, 458 }}, 459 }) 460 tc.wantIdle("client ignores Retry with zero-length token") 461 } 462 463 func TestRetryStateValidateInvalidToken(t *testing.T) { 464 // Test handling of tokens that may have a valid signature, 465 // but unexpected contents. 466 var rs retryState 467 if err := rs.init(); err != nil { 468 t.Fatal(err) 469 } 470 nonce := make([]byte, rs.aead.NonceSize()) 471 now := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) 472 srcConnID := []byte{1, 2, 3, 4} 473 dstConnID := nonce[:20] 474 addr := testClientAddr 475 476 for _, test := range []struct { 477 name string 478 token []byte 479 }{{ 480 name: "token too short", 481 token: []byte{1, 2, 3}, 482 }, { 483 name: "token plaintext too short", 484 token: func() []byte { 485 plaintext := make([]byte, 7) // not enough bytes of content 486 token := append([]byte{}, nonce[20:]...) 487 return rs.aead.Seal(token, nonce, plaintext, rs.additionalData(srcConnID, addr)) 488 }(), 489 }} { 490 t.Run(test.name, func(t *testing.T) { 491 if _, ok := rs.validateToken(now, test.token, srcConnID, dstConnID, addr); ok { 492 t.Errorf("validateToken succeeded, want failure") 493 } 494 }) 495 } 496 } 497 498 func TestParseInvalidRetryPackets(t *testing.T) { 499 originalDstConnID := []byte{1, 2, 3, 4} 500 goodPkt := encodeRetryPacket(originalDstConnID, retryPacket{ 501 dstConnID: []byte{1}, 502 srcConnID: []byte{2}, 503 token: []byte{3}, 504 }) 505 for _, test := range []struct { 506 name string 507 pkt []byte 508 }{{ 509 name: "packet too short", 510 pkt: goodPkt[:len(goodPkt)-4], 511 }, { 512 name: "packet header invalid", 513 pkt: goodPkt[:5], 514 }, { 515 name: "integrity tag invalid", 516 pkt: func() []byte { 517 pkt := cloneBytes(goodPkt) 518 pkt[len(pkt)-1] ^= 1 519 return pkt 520 }(), 521 }} { 522 t.Run(test.name, func(t *testing.T) { 523 if _, ok := parseRetryPacket(test.pkt, originalDstConnID); ok { 524 t.Errorf("parseRetryPacket succeeded, want failure") 525 } 526 }) 527 } 528 } 529 530 func initialClientCrypto(t *testing.T, e *testEndpoint, p transportParameters) []byte { 531 t.Helper() 532 config := &tls.QUICConfig{TLSConfig: newTestTLSConfig(clientSide)} 533 tlsClient := tls.QUICClient(config) 534 tlsClient.SetTransportParameters(marshalTransportParameters(p)) 535 tlsClient.Start(context.Background()) 536 t.Cleanup(func() { 537 tlsClient.Close() 538 }) 539 e.peerTLSConn = tlsClient 540 var data []byte 541 for { 542 e := tlsClient.NextEvent() 543 switch e.Kind { 544 case tls.QUICNoEvent: 545 return data 546 case tls.QUICWriteData: 547 if e.Level != tls.QUICEncryptionLevelInitial { 548 t.Fatal("initial data at unexpected level") 549 } 550 data = append(data, e.Data...) 551 } 552 } 553 } 554 555 func initialConnectionCloseDatagram(srcConnID, dstConnID []byte, code transportError) *testDatagram { 556 return &testDatagram{ 557 packets: []*testPacket{{ 558 ptype: packetTypeInitial, 559 num: 0, 560 version: quicVersion1, 561 srcConnID: srcConnID, 562 dstConnID: dstConnID, 563 frames: []debugFrame{ 564 debugFrameConnectionCloseTransport{ 565 code: code, 566 }, 567 }, 568 }}, 569 } 570 }