diff --git a/u_common.go b/u_common.go index a9f90f75e3..12f0bfc8b8 100644 --- a/u_common.go +++ b/u_common.go @@ -41,6 +41,7 @@ const ( utlsFakeExtensionCustom uint16 = 1234 // not IANA assigned, for ALPS utlsExtensionECH uint16 = 0xfe0d // draft-ietf-tls-esni-17 utlsExtensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-17 + utlsExtensionTrustAnchors uint16 = 0xca34 // draft-ietf-tls-trust-anchor-ids // extensions with 'fake' prefix break connection, if server echoes them back fakeExtensionEncryptThenMAC uint16 = 22 @@ -109,6 +110,11 @@ const ( var ( FakePKCS1WithSHA224 SignatureScheme = 0x0301 FakeECDSAWithSHA224 SignatureScheme = 0x0303 + // FakeMLDSA* are advertised for ClientHello fingerprint parity only. uTLS + // does not implement ML-DSA certificate signature verification. + FakeMLDSA44 SignatureScheme = 0x0904 + FakeMLDSA65 SignatureScheme = 0x0905 + FakeMLDSA87 SignatureScheme = 0x0906 FakeSHA1WithDSA SignatureScheme = 0x0202 FakeSHA256WithDSA SignatureScheme = 0x0402 @@ -613,7 +619,7 @@ var ( HelloFirefox_120 = ClientHelloID{helloFirefox, "120", nil, nil} HelloFirefox_148 = ClientHelloID{helloFirefox, "148", nil, nil} - HelloChrome_Auto = HelloChrome_133 + HelloChrome_Auto = HelloChrome_153 HelloChrome_58 = ClientHelloID{helloChrome, "58", nil, nil} HelloChrome_62 = ClientHelloID{helloChrome, "62", nil, nil} HelloChrome_70 = ClientHelloID{helloChrome, "70", nil, nil} @@ -645,6 +651,9 @@ var ( HelloChrome_131 = ClientHelloID{helloChrome, "131", nil, nil} // Chrome w/ New ALPS codepoint HelloChrome_133 = ClientHelloID{helloChrome, "133", nil, nil} + // Chrome w/ Trust Anchor IDs and post-quantum signature algorithms. The + // trust_anchors extension is feature- and platform-dependent in Chrome. + HelloChrome_153 = ClientHelloID{helloChrome, "153", nil, nil} HelloIOS_Auto = HelloIOS_14 HelloIOS_11_1 = ClientHelloID{helloIOS, "111", nil, nil} // legacy "111" means 11.1 diff --git a/u_parrots.go b/u_parrots.go index 9b6c5b61b8..b177fc1305 100644 --- a/u_parrots.go +++ b/u_parrots.go @@ -964,6 +964,86 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { &UtlsGREASEExtension{}, }), }, nil + case HelloChrome_153: + return 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, + X25519MLKEM768, + X25519, + CurveP256, + CurveP384, + }}, + &SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &SessionTicketExtension{}, + &ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &StatusRequestExtension{}, + &SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{ + GREASE_PLACEHOLDER, + FakeMLDSA44, + FakeMLDSA65, + FakeMLDSA87, + ECDSAWithP256AndSHA256, + PSSWithSHA256, + PKCS1WithSHA256, + ECDSAWithP384AndSHA384, + PSSWithSHA384, + PKCS1WithSHA384, + PSSWithSHA512, + PKCS1WithSHA512, + }}, + &SCTExtension{}, + &KeyShareExtension{[]KeyShare{ + {Group: CurveID(GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: X25519MLKEM768}, + {Group: X25519}, + }}, + &PSKKeyExchangeModesExtension{[]uint8{ + PskModeDHE, + }}, + &SupportedVersionsExtension{[]uint16{ + GREASE_PLACEHOLDER, + VersionTLS13, + VersionTLS12, + }}, + &UtlsCompressCertExtension{[]CertCompressionAlgo{ + CertCompressionBrotli, + }}, + &ApplicationSettingsExtensionNew{SupportedProtocols: []string{"h2"}}, + // Chrome sends an empty RequestedTrustAnchorList when the + // trust-anchor-IDs feature is enabled but no IDs are requested. + &GenericExtension{Id: utlsExtensionTrustAnchors, Data: []byte{0, 0}}, + BoringGREASEECH(), + &UtlsGREASEExtension{}, + }), + }, nil case HelloFirefox_55, HelloFirefox_56: return ClientHelloSpec{ TLSVersMax: VersionTLS12, @@ -3131,6 +3211,12 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error { ext.Curves[i] = CurveID(GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_group)) } } + case *SignatureAlgorithmsExtension: + for i := range ext.SupportedSignatureAlgorithms { + if isGREASEUint16(uint16(ext.SupportedSignatureAlgorithms[i])) { + ext.SupportedSignatureAlgorithms[i] = SignatureScheme(GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_signature_algorithm)) + } + } case *KeyShareExtension: preferredCurveIsSet := false reusableClassicalKeys := make(map[CurveID][]*ecdh.PrivateKey) diff --git a/u_parrots_test.go b/u_parrots_test.go index c600cd3ad2..3bd4f1c50d 100644 --- a/u_parrots_test.go +++ b/u_parrots_test.go @@ -3,7 +3,9 @@ package tls import ( "bytes" "crypto/sha256" + "io" "net" + "slices" "testing" ) @@ -54,6 +56,88 @@ func newTestUConnWithIncrementingRand() *UConn { }, HelloCustom) } +func TestChrome153ClientHelloSpec(t *testing.T) { + if HelloChrome_Auto != HelloChrome_153 { + t.Fatalf("HelloChrome_Auto = %s, want %s", HelloChrome_Auto.Str(), HelloChrome_153.Str()) + } + + spec, err := UTLSIdToSpec(HelloChrome_153) + if err != nil { + t.Fatalf("creating Chrome 153 spec: %v", err) + } + + wantSignatureAlgorithms := []SignatureScheme{ + GREASE_PLACEHOLDER, + FakeMLDSA44, + FakeMLDSA65, + FakeMLDSA87, + ECDSAWithP256AndSHA256, + PSSWithSHA256, + PKCS1WithSHA256, + ECDSAWithP384AndSHA384, + PSSWithSHA384, + PKCS1WithSHA384, + PSSWithSHA512, + PKCS1WithSHA512, + } + + var signatureAlgorithms *SignatureAlgorithmsExtension + var trustAnchors *GenericExtension + for _, ext := range spec.Extensions { + switch ext := ext.(type) { + case *SignatureAlgorithmsExtension: + signatureAlgorithms = ext + case *GenericExtension: + if ext.Id == utlsExtensionTrustAnchors { + trustAnchors = ext + } + } + } + + if signatureAlgorithms == nil { + t.Fatal("signature_algorithms extension not found") + } + if !slices.Equal(signatureAlgorithms.SupportedSignatureAlgorithms, wantSignatureAlgorithms) { + t.Fatalf("signature_algorithms = %#v, want %#v", signatureAlgorithms.SupportedSignatureAlgorithms, wantSignatureAlgorithms) + } + if trustAnchors == nil { + t.Fatal("trust_anchors extension not found") + } + if !bytes.Equal(trustAnchors.Data, []byte{0, 0}) { + t.Fatalf("trust_anchors data = %x, want 0000", trustAnchors.Data) + } + + wire := make([]byte, trustAnchors.Len()) + if _, err := trustAnchors.Read(wire); err != io.EOF { + t.Fatalf("trust_anchors Read error = %v, want io.EOF", err) + } + if want := []byte{0xca, 0x34, 0x00, 0x02, 0x00, 0x00}; !bytes.Equal(wire, want) { + t.Fatalf("trust_anchors wire encoding = %x, want %x", wire, want) + } +} + +func TestChrome153ReplacesSignatureAlgorithmGREASE(t *testing.T) { + uconn := UClient(&net.TCPConn{}, &Config{ + ServerName: "example.com", + Rand: &incrementingSource{next: 0x81}, + }, HelloChrome_153) + if err := uconn.BuildHandshakeState(); err != nil { + t.Fatalf("building Chrome 153 ClientHello: %v", err) + } + + got := uconn.HandshakeState.Hello.SupportedSignatureAlgorithms[0] + want := SignatureScheme(GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_signature_algorithm)) + if got != want { + t.Fatalf("signature algorithm GREASE = %#04x, want %#04x", got, want) + } + if !isGREASEUint16(uint16(got)) { + t.Fatalf("signature algorithm GREASE = %#04x, want a GREASE value", got) + } + if got == GREASE_PLACEHOLDER { + t.Fatalf("signature algorithm GREASE was not randomized: got placeholder %#04x", got) + } +} + func fingerprintsWithHybridClassicalKeyShareReuse() []ClientHelloID { return []ClientHelloID{ HelloFirefox_148, diff --git a/u_tls_extensions.go b/u_tls_extensions.go index 6b2ef4865d..e7ce06454a 100644 --- a/u_tls_extensions.go +++ b/u_tls_extensions.go @@ -966,7 +966,9 @@ const ( ssl_grease_extension2 ssl_grease_version ssl_grease_ticket_extension - ssl_grease_last_index = ssl_grease_ticket_extension + ssl_grease_ech_config_id + ssl_grease_signature_algorithm + ssl_grease_last_index ) // it is responsibility of user not to generate multiple grease extensions with same value