mirror of
https://github.com/golang/net.git
synced 2026-03-31 10:27:08 +09:00
Per RFC 9000, a Destination Connection ID in a Retry packet is of valid length as long as it is 20 bytes or shorter. However, when validating tokens for Retry packets, our server currently makes an assumption that it will only deal with a Destination Connection ID that is exactly 20 bytes long. As a result, a misbehaving or malicious client can cause our server to panic. When our server validates a token for a Retry packet, the nonce is partially constructed from the Destination Connection ID. Due to our server's bad assumption, a shorter-than-expected Destination Connection ID will result in cipher.AEAD.Open being given a nonce of bad length. We currently use XChaCha20-Poly1305 from x/crypto for our AEAD implementation, which will panic when given a nonce of bad length. Fixes golang/go#78292 Change-Id: Ieb65d8b84bcb5c531bdb46d5c75b13dad9c76eb2 Reviewed-on: https://go-review.googlesource.com/c/net/+/758360 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Nicholas Husin <husin@google.com> Reviewed-by: Damien Neil <dneil@google.com>
631 lines
18 KiB
Go
631 lines
18 KiB
Go
// Copyright 2023 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package quic
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"net/netip"
|
|
"testing"
|
|
"testing/synctest"
|
|
"time"
|
|
)
|
|
|
|
type retryServerTest struct {
|
|
te *testEndpoint
|
|
originalSrcConnID []byte
|
|
originalDstConnID []byte
|
|
retry retryPacket
|
|
initialCrypto []byte
|
|
}
|
|
|
|
// newRetryServerTest creates a test server connection,
|
|
// sends the connection an Initial packet,
|
|
// and expects a Retry in response.
|
|
func newRetryServerTest(t *testing.T) *retryServerTest {
|
|
t.Helper()
|
|
config := &Config{
|
|
TLSConfig: newTestTLSConfig(serverSide),
|
|
RequireAddressValidation: true,
|
|
}
|
|
te := newTestEndpoint(t, config)
|
|
srcID := testPeerConnID(0)
|
|
dstID := testLocalConnID(-1)
|
|
params := defaultTransportParameters()
|
|
params.initialSrcConnID = srcID
|
|
initialCrypto := initialClientCrypto(t, te, params)
|
|
|
|
// Initial packet with no Token.
|
|
// Server responds with a Retry containing a token.
|
|
te.writeDatagram(&testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeInitial,
|
|
num: 0,
|
|
version: quicVersion1,
|
|
srcConnID: srcID,
|
|
dstConnID: dstID,
|
|
frames: []debugFrame{
|
|
debugFrameCrypto{
|
|
data: initialCrypto,
|
|
},
|
|
},
|
|
}},
|
|
paddedSize: 1200,
|
|
})
|
|
got := te.readDatagram()
|
|
if len(got.packets) != 1 || got.packets[0].ptype != packetTypeRetry {
|
|
t.Fatalf("got datagram: %v\nwant Retry", got)
|
|
}
|
|
p := got.packets[0]
|
|
if got, want := p.dstConnID, srcID; !bytes.Equal(got, want) {
|
|
t.Fatalf("Retry destination = {%x}, want {%x}", got, want)
|
|
}
|
|
|
|
return &retryServerTest{
|
|
te: te,
|
|
originalSrcConnID: srcID,
|
|
originalDstConnID: dstID,
|
|
retry: retryPacket{
|
|
dstConnID: p.dstConnID,
|
|
srcConnID: p.srcConnID,
|
|
token: p.token,
|
|
},
|
|
initialCrypto: initialCrypto,
|
|
}
|
|
}
|
|
|
|
func TestRetryServerSucceeds(t *testing.T) {
|
|
synctest.Test(t, testRetryServerSucceeds)
|
|
}
|
|
func testRetryServerSucceeds(t *testing.T) {
|
|
rt := newRetryServerTest(t)
|
|
te := rt.te
|
|
time.Sleep(retryTokenValidityPeriod)
|
|
te.writeDatagram(&testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeInitial,
|
|
num: 1,
|
|
version: quicVersion1,
|
|
srcConnID: rt.originalSrcConnID,
|
|
dstConnID: rt.retry.srcConnID,
|
|
token: rt.retry.token,
|
|
frames: []debugFrame{
|
|
debugFrameCrypto{
|
|
data: rt.initialCrypto,
|
|
},
|
|
},
|
|
}},
|
|
paddedSize: 1200,
|
|
})
|
|
tc := te.accept()
|
|
initial := tc.readPacket()
|
|
if initial == nil || initial.ptype != packetTypeInitial {
|
|
t.Fatalf("got packet:\n%v\nwant: Initial", initial)
|
|
}
|
|
handshake := tc.readPacket()
|
|
if handshake == nil || handshake.ptype != packetTypeHandshake {
|
|
t.Fatalf("got packet:\n%v\nwant: Handshake", initial)
|
|
}
|
|
if got, want := tc.sentTransportParameters.retrySrcConnID, rt.retry.srcConnID; !bytes.Equal(got, want) {
|
|
t.Errorf("retry_source_connection_id = {%x}, want {%x}", got, want)
|
|
}
|
|
if got, want := tc.sentTransportParameters.initialSrcConnID, initial.srcConnID; !bytes.Equal(got, want) {
|
|
t.Errorf("initial_source_connection_id = {%x}, want {%x}", got, want)
|
|
}
|
|
if got, want := tc.sentTransportParameters.originalDstConnID, rt.originalDstConnID; !bytes.Equal(got, want) {
|
|
t.Errorf("original_destination_connection_id = {%x}, want {%x}", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRetryServerTokenInvalid(t *testing.T) {
|
|
synctest.Test(t, testRetryServerTokenInvalid)
|
|
}
|
|
func testRetryServerTokenInvalid(t *testing.T) {
|
|
// "If a server receives a client Initial that contains an invalid Retry token [...]
|
|
// the server SHOULD immediately close [...] the connection with an
|
|
// INVALID_TOKEN error."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-8.1.2-5
|
|
rt := newRetryServerTest(t)
|
|
te := rt.te
|
|
te.writeDatagram(&testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeInitial,
|
|
num: 1,
|
|
version: quicVersion1,
|
|
srcConnID: rt.originalSrcConnID,
|
|
dstConnID: rt.retry.srcConnID,
|
|
token: append(rt.retry.token, 0),
|
|
frames: []debugFrame{
|
|
debugFrameCrypto{
|
|
data: rt.initialCrypto,
|
|
},
|
|
},
|
|
}},
|
|
paddedSize: 1200,
|
|
})
|
|
te.wantDatagram("server closes connection after Initial with invalid Retry token",
|
|
initialConnectionCloseDatagram(
|
|
rt.retry.srcConnID,
|
|
rt.originalSrcConnID,
|
|
errInvalidToken))
|
|
}
|
|
|
|
func TestRetryServerTokenTooOld(t *testing.T) {
|
|
synctest.Test(t, testRetryServerTokenTooOld)
|
|
}
|
|
func testRetryServerTokenTooOld(t *testing.T) {
|
|
// "[...] a token SHOULD have an expiration time [...]"
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-8.1.3-3
|
|
rt := newRetryServerTest(t)
|
|
te := rt.te
|
|
time.Sleep(retryTokenValidityPeriod + time.Second)
|
|
te.writeDatagram(&testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeInitial,
|
|
num: 1,
|
|
version: quicVersion1,
|
|
srcConnID: rt.originalSrcConnID,
|
|
dstConnID: rt.retry.srcConnID,
|
|
token: rt.retry.token,
|
|
frames: []debugFrame{
|
|
debugFrameCrypto{
|
|
data: rt.initialCrypto,
|
|
},
|
|
},
|
|
}},
|
|
paddedSize: 1200,
|
|
})
|
|
te.wantDatagram("server closes connection after Initial with expired token",
|
|
initialConnectionCloseDatagram(
|
|
rt.retry.srcConnID,
|
|
rt.originalSrcConnID,
|
|
errInvalidToken))
|
|
}
|
|
|
|
func TestRetryServerTokenWrongIP(t *testing.T) {
|
|
synctest.Test(t, testRetryServerTokenWrongIP)
|
|
}
|
|
func testRetryServerTokenWrongIP(t *testing.T) {
|
|
// "Tokens sent in Retry packets SHOULD include information that allows the server
|
|
// to verify that the source IP address and port in client packets remain constant."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-8.1.4-3
|
|
rt := newRetryServerTest(t)
|
|
te := rt.te
|
|
te.writeDatagram(&testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeInitial,
|
|
num: 1,
|
|
version: quicVersion1,
|
|
srcConnID: rt.originalSrcConnID,
|
|
dstConnID: rt.retry.srcConnID,
|
|
token: rt.retry.token,
|
|
frames: []debugFrame{
|
|
debugFrameCrypto{
|
|
data: rt.initialCrypto,
|
|
},
|
|
},
|
|
}},
|
|
paddedSize: 1200,
|
|
addr: netip.MustParseAddrPort("10.0.0.2:8000"),
|
|
})
|
|
te.wantDatagram("server closes connection after Initial from wrong address",
|
|
initialConnectionCloseDatagram(
|
|
rt.retry.srcConnID,
|
|
rt.originalSrcConnID,
|
|
errInvalidToken))
|
|
}
|
|
|
|
func TestRetryServerShortDstConnID(t *testing.T) {
|
|
synctest.Test(t, testRetryServerShortDstConnID)
|
|
}
|
|
func testRetryServerShortDstConnID(t *testing.T) {
|
|
// Verify that a shorter-than-expected Destination Connection ID does not
|
|
// cause a panic due to bad nonce length. https://go.dev/issue/78292.
|
|
rt := newRetryServerTest(t)
|
|
te := rt.te
|
|
te.writeDatagram(&testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeInitial,
|
|
num: 1,
|
|
version: quicVersion1,
|
|
srcConnID: rt.originalSrcConnID,
|
|
dstConnID: []byte("short id"),
|
|
token: rt.retry.token,
|
|
frames: []debugFrame{
|
|
debugFrameCrypto{
|
|
data: rt.initialCrypto,
|
|
},
|
|
},
|
|
}},
|
|
paddedSize: 1200,
|
|
})
|
|
te.wantDatagram("server closes connection after Initial from wrong address",
|
|
initialConnectionCloseDatagram(
|
|
[]byte("short id"),
|
|
rt.originalSrcConnID,
|
|
errInvalidToken))
|
|
}
|
|
|
|
func TestRetryServerIgnoresRetry(t *testing.T) {
|
|
synctest.Test(t, testRetryServerIgnoresRetry)
|
|
}
|
|
func testRetryServerIgnoresRetry(t *testing.T) {
|
|
tc := newTestConn(t, serverSide)
|
|
tc.handshake()
|
|
tc.write(&testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeRetry,
|
|
originalDstConnID: testLocalConnID(-1),
|
|
srcConnID: testPeerConnID(0),
|
|
dstConnID: testLocalConnID(0),
|
|
token: []byte{1, 2, 3, 4},
|
|
}},
|
|
})
|
|
// Send two packets, to trigger an immediate ACK.
|
|
tc.writeFrames(packetType1RTT, debugFramePing{})
|
|
tc.writeFrames(packetType1RTT, debugFramePing{})
|
|
tc.wantFrameType("server connection ignores spurious Retry packet",
|
|
packetType1RTT, debugFrameAck{})
|
|
}
|
|
|
|
func TestRetryClientSuccess(t *testing.T) {
|
|
synctest.Test(t, testRetryClientSuccess)
|
|
}
|
|
func testRetryClientSuccess(t *testing.T) {
|
|
// "This token MUST be repeated by the client in all Initial packets it sends
|
|
// for that connection after it receives the Retry packet."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-8.1.2-1
|
|
tc := newTestConn(t, clientSide)
|
|
tc.wantFrame("client Initial CRYPTO data",
|
|
packetTypeInitial, debugFrameCrypto{
|
|
data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial],
|
|
})
|
|
newServerConnID := []byte("new_conn_id")
|
|
token := []byte("token")
|
|
tc.write(&testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeRetry,
|
|
originalDstConnID: testLocalConnID(-1),
|
|
srcConnID: newServerConnID,
|
|
dstConnID: testLocalConnID(0),
|
|
token: token,
|
|
}},
|
|
})
|
|
tc.wantPacket("client sends a new Initial packet with a token",
|
|
&testPacket{
|
|
ptype: packetTypeInitial,
|
|
num: 1,
|
|
version: quicVersion1,
|
|
srcConnID: testLocalConnID(0),
|
|
dstConnID: newServerConnID,
|
|
token: token,
|
|
frames: []debugFrame{
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial],
|
|
},
|
|
},
|
|
},
|
|
)
|
|
tc.advanceToTimer()
|
|
tc.wantPacket("after PTO client sends another Initial packet with a token",
|
|
&testPacket{
|
|
ptype: packetTypeInitial,
|
|
num: 2,
|
|
version: quicVersion1,
|
|
srcConnID: testLocalConnID(0),
|
|
dstConnID: newServerConnID,
|
|
token: token,
|
|
frames: []debugFrame{
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial],
|
|
},
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
func TestRetryClientInvalidServerTransportParameters(t *testing.T) {
|
|
// Various permutations of missing or invalid values for transport parameters
|
|
// after a Retry.
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-7.3
|
|
initialSrcConnID := testPeerConnID(0)
|
|
originalDstConnID := testLocalConnID(-1)
|
|
retrySrcConnID := testPeerConnID(100)
|
|
for _, test := range []struct {
|
|
name string
|
|
f func(*transportParameters)
|
|
ok bool
|
|
}{{
|
|
name: "valid",
|
|
f: func(p *transportParameters) {},
|
|
ok: true,
|
|
}, {
|
|
name: "missing initial_source_connection_id",
|
|
f: func(p *transportParameters) {
|
|
p.initialSrcConnID = nil
|
|
},
|
|
}, {
|
|
name: "invalid initial_source_connection_id",
|
|
f: func(p *transportParameters) {
|
|
p.initialSrcConnID = []byte("invalid")
|
|
},
|
|
}, {
|
|
name: "missing original_destination_connection_id",
|
|
f: func(p *transportParameters) {
|
|
p.originalDstConnID = nil
|
|
},
|
|
}, {
|
|
name: "invalid original_destination_connection_id",
|
|
f: func(p *transportParameters) {
|
|
p.originalDstConnID = []byte("invalid")
|
|
},
|
|
}, {
|
|
name: "missing retry_source_connection_id",
|
|
f: func(p *transportParameters) {
|
|
p.retrySrcConnID = nil
|
|
},
|
|
}, {
|
|
name: "invalid retry_source_connection_id",
|
|
f: func(p *transportParameters) {
|
|
p.retrySrcConnID = []byte("invalid")
|
|
},
|
|
}} {
|
|
synctestSubtest(t, test.name, func(t *testing.T) {
|
|
tc := newTestConn(t, clientSide,
|
|
func(p *transportParameters) {
|
|
p.initialSrcConnID = initialSrcConnID
|
|
p.originalDstConnID = originalDstConnID
|
|
p.retrySrcConnID = retrySrcConnID
|
|
},
|
|
test.f)
|
|
tc.ignoreFrame(frameTypeAck)
|
|
tc.wantFrameType("client Initial CRYPTO data",
|
|
packetTypeInitial, debugFrameCrypto{})
|
|
tc.write(&testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeRetry,
|
|
originalDstConnID: originalDstConnID,
|
|
srcConnID: retrySrcConnID,
|
|
dstConnID: testLocalConnID(0),
|
|
token: []byte{1, 2, 3, 4},
|
|
}},
|
|
})
|
|
tc.wantFrameType("client resends Initial CRYPTO data",
|
|
packetTypeInitial, debugFrameCrypto{})
|
|
tc.writeFrames(packetTypeInitial,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
|
|
})
|
|
tc.writeFrames(packetTypeHandshake,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake],
|
|
})
|
|
if test.ok {
|
|
tc.wantFrameType("valid params, client sends Handshake",
|
|
packetTypeHandshake, debugFrameCrypto{})
|
|
} else {
|
|
tc.wantFrame("invalid transport parameters",
|
|
packetTypeInitial, debugFrameConnectionCloseTransport{
|
|
code: errTransportParameter,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRetryClientIgnoresRetryAfterReceivingPacket(t *testing.T) {
|
|
synctest.Test(t, testRetryClientIgnoresRetryAfterReceivingPacket)
|
|
}
|
|
func testRetryClientIgnoresRetryAfterReceivingPacket(t *testing.T) {
|
|
// "After the client has received and processed an Initial or Retry packet
|
|
// from the server, it MUST discard any subsequent Retry packets that it receives."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-1
|
|
tc := newTestConn(t, clientSide)
|
|
tc.ignoreFrame(frameTypeAck)
|
|
tc.ignoreFrame(frameTypeNewConnectionID)
|
|
tc.wantFrameType("client Initial CRYPTO data",
|
|
packetTypeInitial, debugFrameCrypto{})
|
|
tc.writeFrames(packetTypeInitial,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
|
|
})
|
|
retry := &testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeRetry,
|
|
originalDstConnID: testLocalConnID(-1),
|
|
srcConnID: testPeerConnID(100),
|
|
dstConnID: testLocalConnID(0),
|
|
token: []byte{1, 2, 3, 4},
|
|
}},
|
|
}
|
|
tc.write(retry)
|
|
tc.wantIdle("client ignores Retry after receiving Initial packet")
|
|
tc.writeFrames(packetTypeHandshake,
|
|
debugFrameCrypto{
|
|
data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake],
|
|
})
|
|
tc.wantFrameType("client Handshake CRYPTO data",
|
|
packetTypeHandshake, debugFrameCrypto{})
|
|
tc.write(retry)
|
|
tc.wantIdle("client ignores Retry after discarding Initial keys")
|
|
}
|
|
|
|
func TestRetryClientIgnoresRetryAfterReceivingRetry(t *testing.T) {
|
|
synctest.Test(t, testRetryClientIgnoresRetryAfterReceivingRetry)
|
|
}
|
|
func testRetryClientIgnoresRetryAfterReceivingRetry(t *testing.T) {
|
|
// "After the client has received and processed an Initial or Retry packet
|
|
// from the server, it MUST discard any subsequent Retry packets that it receives."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-1
|
|
tc := newTestConn(t, clientSide)
|
|
tc.wantFrameType("client Initial CRYPTO data",
|
|
packetTypeInitial, debugFrameCrypto{})
|
|
retry := &testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeRetry,
|
|
originalDstConnID: testLocalConnID(-1),
|
|
srcConnID: testPeerConnID(100),
|
|
dstConnID: testLocalConnID(0),
|
|
token: []byte{1, 2, 3, 4},
|
|
}},
|
|
}
|
|
tc.write(retry)
|
|
tc.wantFrameType("client resends Initial CRYPTO data",
|
|
packetTypeInitial, debugFrameCrypto{})
|
|
tc.write(retry)
|
|
tc.wantIdle("client ignores second Retry")
|
|
}
|
|
|
|
func TestRetryClientIgnoresRetryWithInvalidIntegrityTag(t *testing.T) {
|
|
synctest.Test(t, testRetryClientIgnoresRetryWithInvalidIntegrityTag)
|
|
}
|
|
func testRetryClientIgnoresRetryWithInvalidIntegrityTag(t *testing.T) {
|
|
tc := newTestConn(t, clientSide)
|
|
tc.wantFrameType("client Initial CRYPTO data",
|
|
packetTypeInitial, debugFrameCrypto{})
|
|
pkt := encodeRetryPacket(testLocalConnID(-1), retryPacket{
|
|
srcConnID: testPeerConnID(100),
|
|
dstConnID: testLocalConnID(0),
|
|
token: []byte{1, 2, 3, 4},
|
|
})
|
|
pkt[len(pkt)-1] ^= 1 // invalidate the integrity tag
|
|
tc.endpoint.write(&datagram{
|
|
b: pkt,
|
|
peerAddr: testClientAddr,
|
|
})
|
|
tc.wantIdle("client ignores Retry with invalid integrity tag")
|
|
}
|
|
|
|
func TestRetryClientIgnoresRetryWithZeroLengthToken(t *testing.T) {
|
|
synctest.Test(t, testRetryClientIgnoresRetryWithZeroLengthToken)
|
|
}
|
|
func testRetryClientIgnoresRetryWithZeroLengthToken(t *testing.T) {
|
|
// "A client MUST discard a Retry packet with a zero-length Retry Token field."
|
|
// https://www.rfc-editor.org/rfc/rfc9000#section-17.2.5.2-2
|
|
tc := newTestConn(t, clientSide)
|
|
tc.wantFrameType("client Initial CRYPTO data",
|
|
packetTypeInitial, debugFrameCrypto{})
|
|
tc.write(&testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeRetry,
|
|
originalDstConnID: testLocalConnID(-1),
|
|
srcConnID: testPeerConnID(100),
|
|
dstConnID: testLocalConnID(0),
|
|
token: []byte{},
|
|
}},
|
|
})
|
|
tc.wantIdle("client ignores Retry with zero-length token")
|
|
}
|
|
|
|
func TestRetryStateValidateInvalidToken(t *testing.T) {
|
|
// Test handling of tokens that may have a valid signature,
|
|
// but unexpected contents.
|
|
var rs retryState
|
|
if err := rs.init(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nonce := make([]byte, rs.aead.NonceSize())
|
|
now := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
srcConnID := []byte{1, 2, 3, 4}
|
|
dstConnID := nonce[:20]
|
|
addr := testClientAddr
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
token []byte
|
|
}{{
|
|
name: "token too short",
|
|
token: []byte{1, 2, 3},
|
|
}, {
|
|
name: "token plaintext too short",
|
|
token: func() []byte {
|
|
plaintext := make([]byte, 7) // not enough bytes of content
|
|
token := append([]byte{}, nonce[20:]...)
|
|
return rs.aead.Seal(token, nonce, plaintext, rs.additionalData(srcConnID, addr))
|
|
}(),
|
|
}} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if _, ok := rs.validateToken(now, test.token, srcConnID, dstConnID, addr); ok {
|
|
t.Errorf("validateToken succeeded, want failure")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseInvalidRetryPackets(t *testing.T) {
|
|
originalDstConnID := []byte{1, 2, 3, 4}
|
|
goodPkt := encodeRetryPacket(originalDstConnID, retryPacket{
|
|
dstConnID: []byte{1},
|
|
srcConnID: []byte{2},
|
|
token: []byte{3},
|
|
})
|
|
for _, test := range []struct {
|
|
name string
|
|
pkt []byte
|
|
}{{
|
|
name: "packet too short",
|
|
pkt: goodPkt[:len(goodPkt)-4],
|
|
}, {
|
|
name: "packet header invalid",
|
|
pkt: goodPkt[:5],
|
|
}, {
|
|
name: "integrity tag invalid",
|
|
pkt: func() []byte {
|
|
pkt := cloneBytes(goodPkt)
|
|
pkt[len(pkt)-1] ^= 1
|
|
return pkt
|
|
}(),
|
|
}} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if _, ok := parseRetryPacket(test.pkt, originalDstConnID); ok {
|
|
t.Errorf("parseRetryPacket succeeded, want failure")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func initialClientCrypto(t *testing.T, e *testEndpoint, p transportParameters) []byte {
|
|
t.Helper()
|
|
config := &tls.QUICConfig{TLSConfig: newTestTLSConfig(clientSide)}
|
|
tlsClient := tls.QUICClient(config)
|
|
tlsClient.SetTransportParameters(marshalTransportParameters(p))
|
|
tlsClient.Start(context.Background())
|
|
t.Cleanup(func() {
|
|
tlsClient.Close()
|
|
})
|
|
e.peerTLSConn = tlsClient
|
|
var data []byte
|
|
for {
|
|
e := tlsClient.NextEvent()
|
|
switch e.Kind {
|
|
case tls.QUICNoEvent:
|
|
return data
|
|
case tls.QUICWriteData:
|
|
if e.Level != tls.QUICEncryptionLevelInitial {
|
|
t.Fatal("initial data at unexpected level")
|
|
}
|
|
data = append(data, e.Data...)
|
|
}
|
|
}
|
|
}
|
|
|
|
func initialConnectionCloseDatagram(srcConnID, dstConnID []byte, code transportError) *testDatagram {
|
|
return &testDatagram{
|
|
packets: []*testPacket{{
|
|
ptype: packetTypeInitial,
|
|
num: 0,
|
|
version: quicVersion1,
|
|
srcConnID: srcConnID,
|
|
dstConnID: dstConnID,
|
|
frames: []debugFrame{
|
|
debugFrameConnectionCloseTransport{
|
|
code: code,
|
|
},
|
|
},
|
|
}},
|
|
}
|
|
}
|