fix: add tests and fix bugs

This commit is contained in:
Mingye Chen 2025-03-27 15:54:58 -06:00
parent ed46e3def4
commit 6f87c69b6a
6 changed files with 178 additions and 30 deletions

View file

@ -214,9 +214,9 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli
var ech *echClientContext
if c.config.EncryptedClientHelloConfigList != nil {
// if c.config.MinVersion != 0 && c.config.MinVersion < VersionTLS13 {
// return nil, nil, nil, errors.New("tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
// }
if c.config.MinVersion != 0 && c.config.MinVersion < VersionTLS13 {
return nil, nil, nil, errors.New("tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
}
if c.config.MaxVersion != 0 && c.config.MaxVersion <= VersionTLS12 {
return nil, nil, nil, errors.New("tls: MaxVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
}

View file

@ -476,11 +476,32 @@ func runMain(m *testing.M) int {
}
func testHandshake(t *testing.T, clientConfig, serverConfig *Config) (serverState, clientState ConnectionState, err error) {
// [uTLS SECTION BEGIN]
return testUtlsHandshake(t, clientConfig, serverConfig, nil)
}
func testUtlsHandshake(t *testing.T, clientConfig, serverConfig *Config, spec *ClientHelloSpec) (serverState, clientState ConnectionState, err error) {
// [uTLS SECTION END]
const sentinel = "SENTINEL\n"
c, s := localPipe(t)
errChan := make(chan error, 1)
go func() {
cli := Client(c, clientConfig)
// [uTLS SECTION BEGIN]
var cli interface {
Handshake() error
ConnectionState() ConnectionState
Close() error
io.Reader
}
if spec != nil {
ucli := UClient(c, clientConfig, HelloCustom)
if err = ucli.ApplyPreset(spec); err != nil {
return
}
cli = ucli
} else {
cli = Client(c, clientConfig)
}
// [uTLS SECTION END]
err := cli.Handshake()
if err != nil {
errChan <- fmt.Errorf("client: %v", err)

View file

@ -2068,6 +2068,10 @@ func TestLargeCertMsg(t *testing.T) {
}
func TestECH(t *testing.T) {
testECHSpec(t, nil, true)
}
func testECHSpec(t *testing.T, spec *ClientHelloSpec, expectSuccess bool) {
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
@ -2157,26 +2161,34 @@ func TestECH(t *testing.T) {
{Config: echConfig, PrivateKey: echKey.Bytes(), SendAsRetry: true},
}
ss, cs, err := testHandshake(t, clientConfig, serverConfig)
if err != nil {
t.Fatalf("unexpected failure: %s", err)
}
if !ss.ECHAccepted {
t.Fatal("server ConnectionState shows ECH not accepted")
}
if !cs.ECHAccepted {
t.Fatal("client ConnectionState shows ECH not accepted")
}
if cs.ServerName != "secret.example" || ss.ServerName != "secret.example" {
t.Fatalf("unexpected ConnectionState.ServerName, want %q, got server:%q, client: %q", "secret.example", ss.ServerName, cs.ServerName)
}
if len(cs.VerifiedChains) != 1 {
t.Fatal("unexpect number of certificate chains")
}
if len(cs.VerifiedChains[0]) != 1 {
t.Fatal("unexpect number of certificates")
}
if !cs.VerifiedChains[0][0].Equal(secretCert) {
t.Fatal("unexpected certificate")
// [uTLS SECTION BEGIN]
ss, cs, err := testUtlsHandshake(t, clientConfig, serverConfig, spec)
if expectSuccess {
if err != nil {
t.Fatalf("unexpected failure: %s", err)
}
if !ss.ECHAccepted {
t.Fatal("server ConnectionState shows ECH not accepted")
}
if !cs.ECHAccepted {
t.Fatal("client ConnectionState shows ECH not accepted")
}
if cs.ServerName != "secret.example" || ss.ServerName != "secret.example" {
t.Fatalf("unexpected ConnectionState.ServerName, want %q, got server:%q, client: %q", "secret.example", ss.ServerName, cs.ServerName)
}
if len(cs.VerifiedChains) != 1 {
t.Fatal("unexpect number of certificate chains")
}
if len(cs.VerifiedChains[0]) != 1 {
t.Fatal("unexpect number of certificates")
}
if !cs.VerifiedChains[0][0].Equal(secretCert) {
t.Fatal("unexpected certificate")
}
} else {
if err == nil {
t.Fatalf("unexpected handshake success, expected failure")
}
}
// [uTLS SECTION END]
}

View file

@ -528,6 +528,9 @@ func (uconn *UConn) computeAndUpdateOuterECHExtension(inner *clientHelloMsg, ech
_, ok := ext.(EncryptedClientHelloExtension)
return ok
})
if echExtIdx < 0 {
return fmt.Errorf("extension satisfying EncryptedClientHelloExtension not present")
}
oldExt := uconn.Extensions[echExtIdx]
uconn.Extensions[echExtIdx] = &GenericExtension{
@ -582,12 +585,18 @@ func (uconn *UConn) MarshalClientHello() error {
_, ok := ext.(*SNIExtension)
return ok
})
if sniExtIdex < 0 {
return fmt.Errorf("sni extension missing while attempting ECH")
}
oldSNI := uconn.Extensions[sniExtIdex]
uconn.Extensions[sniExtIdex] = &SNIExtension{
ServerName: string(ech.config.PublicName),
}
uconn.computeAndUpdateOuterECHExtension(inner, ech, true)
uconn.Extensions[sniExtIdex] = oldSNI
uconn.echCtx = ech
return nil
}
@ -753,8 +762,6 @@ func (uconn *UConn) SetTLSVers(minTLSVers, maxTLSVers uint16, specExtensions []T
}
uconn.HandshakeState.Hello.SupportedVersions = makeSupportedVersions(minTLSVers, maxTLSVers)
uconn.config.MinVersion = minTLSVers
uconn.config.MaxVersion = maxTLSVers
return nil
}

View file

@ -743,3 +743,111 @@ func TestUTLSMakeConnWithCompleteHandshake(t *testing.T) {
serverTls.Write(serverMsg)
}
func TestUTLSECH(t *testing.T) {
chromeLatest, err := utlsIdToSpec(HelloChrome_Auto)
if err != nil {
t.Fatal(err)
}
firefoxLatest, err := utlsIdToSpec(HelloFirefox_Auto)
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
name string
spec *ClientHelloSpec
expectSuccess bool
}{
{
name: "latest chrome",
spec: &chromeLatest,
expectSuccess: true,
},
{
name: "latest firefox",
spec: &firefoxLatest,
expectSuccess: true,
},
{
name: "ech extension missing",
spec: &ClientHelloSpec{
CipherSuites: []uint16{
GREASE_PLACEHOLDER,
TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384,
TLS_CHACHA20_POLY1305_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
TLS_RSA_WITH_AES_128_GCM_SHA256,
TLS_RSA_WITH_AES_256_GCM_SHA384,
TLS_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_256_CBC_SHA,
},
CompressionMethods: []byte{
0x00, // compressionNone
},
Extensions: ShuffleChromeTLSExtensions([]TLSExtension{
&UtlsGREASEExtension{},
&SNIExtension{},
&ExtendedMasterSecretExtension{},
&RenegotiationInfoExtension{Renegotiation: RenegotiateOnceAsClient},
&SupportedCurvesExtension{[]CurveID{
GREASE_PLACEHOLDER,
X25519Kyber768Draft00,
X25519,
CurveP256,
CurveP384,
}},
&SupportedPointsExtension{SupportedPoints: []byte{
0x00, // pointFormatUncompressed
}},
&SessionTicketExtension{},
&ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}},
&StatusRequestExtension{},
&SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{
ECDSAWithP256AndSHA256,
PSSWithSHA256,
PKCS1WithSHA256,
ECDSAWithP384AndSHA384,
PSSWithSHA384,
PKCS1WithSHA384,
PSSWithSHA512,
PKCS1WithSHA512,
}},
&SCTExtension{},
&KeyShareExtension{[]KeyShare{
{Group: CurveID(GREASE_PLACEHOLDER), Data: []byte{0}},
{Group: X25519Kyber768Draft00},
{Group: X25519},
}},
&PSKKeyExchangeModesExtension{[]uint8{
PskModeDHE,
}},
&SupportedVersionsExtension{[]uint16{
GREASE_PLACEHOLDER,
VersionTLS13,
VersionTLS12,
}},
&UtlsCompressCertExtension{[]CertCompressionAlgo{
CertCompressionBrotli,
}},
&ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}},
&UtlsGREASEExtension{},
}),
},
expectSuccess: false,
},
} {
t.Run(test.name, func(t *testing.T) {
testECHSpec(t, test.spec, test.expectSuccess)
})
}
}

View file

@ -353,9 +353,9 @@ func (c *Conn) makeClientHelloForApplyPreset() (*clientHelloMsg, *keySharePrivat
var ech *echClientContext
if c.config.EncryptedClientHelloConfigList != nil {
// if c.config.MinVersion != 0 && c.config.MinVersion < VersionTLS13 {
// return nil, nil, nil, errors.New("tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
// }
if c.config.MinVersion != 0 && c.config.MinVersion < VersionTLS13 {
return nil, nil, nil, errors.New("tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
}
if c.config.MaxVersion != 0 && c.config.MaxVersion <= VersionTLS12 {
return nil, nil, nil, errors.New("tls: MaxVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
}