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  }