From a4c00db1ef44a7ccf7a106e2c2ec729b9ed6aa65 Mon Sep 17 00:00:00 2001 From: sinku Date: Mon, 2 Mar 2026 13:19:04 -0500 Subject: [PATCH] Add AlwaysIncludePSK config option to force PSK extension inclusion Add a Config.AlwaysIncludePSK option that, when enabled, ensures the PreSharedKey extension is always present in the ClientHello for TLS 1.3 connections, even if the selected ClientHelloSpec does not include it. This enables session resumption with browser fingerprint specs that omit the PSK extension. When no cached session exists, OmitEmptyPsk controls whether the empty extension is omitted. The PSK extension is appended as the last extension per RFC 8446, Section 4.2.11. Changes: - Add AlwaysIncludePSK field to Config in common.go - Include AlwaysIncludePSK in Config.Clone() - Add PSK auto-injection logic in ApplyPreset() in u_parrots.go --- common.go | 7 +++++++ tls_test.go | 2 +- u_parrots.go | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/common.go b/common.go index 73b6dad51c..a5372e3449 100644 --- a/common.go +++ b/common.go @@ -711,6 +711,12 @@ type Config struct { // this behavior at their own discretion. OmitEmptyPsk bool // [uTLS] + // AlwaysIncludePSK controls whether the PreSharedKey extension is always + // included in the ClientHello if there is a cached session, even if not specified + // in the selected ClientHelloSpec. If there are no cached sessions, OmitEmptyPsk + // controls whether the extension is omitted. + AlwaysIncludePSK bool // [uTLS] + // InsecureServerNameToVerify is used to verify the hostname on the returned // certificates. It is intended to use with spoofed ServerName. // If InsecureServerNameToVerify is "*", crypto/tls will do normal @@ -999,6 +1005,7 @@ func (c *Config) Clone() *Config { InsecureSkipTimeVerify: c.InsecureSkipTimeVerify, InsecureServerNameToVerify: c.InsecureServerNameToVerify, OmitEmptyPsk: c.OmitEmptyPsk, + AlwaysIncludePSK: c.AlwaysIncludePSK, CipherSuites: c.CipherSuites, PreferServerCipherSuites: c.PreferServerCipherSuites, SessionTicketsDisabled: c.SessionTicketsDisabled, diff --git a/tls_test.go b/tls_test.go index c91263b1c1..2df707a422 100644 --- a/tls_test.go +++ b/tls_test.go @@ -877,7 +877,7 @@ func TestCloneNonFuncFields(t *testing.T) { f.Set(reflect.ValueOf("b")) case "ClientAuth": f.Set(reflect.ValueOf(VerifyClientCertIfGiven)) - case "InsecureSkipVerify", "InsecureSkipTimeVerify", "SessionTicketsDisabled", "DynamicRecordSizingDisabled", "PreferServerCipherSuites", "OmitEmptyPsk", "PreferSkipResumptionOnNilExtension": + case "InsecureSkipVerify", "InsecureSkipTimeVerify", "SessionTicketsDisabled", "DynamicRecordSizingDisabled", "PreferServerCipherSuites", "OmitEmptyPsk", "PreferSkipResumptionOnNilExtension", "AlwaysIncludePSK": f.Set(reflect.ValueOf(true)) case "InsecureServerNameToVerify": f.Set(reflect.ValueOf("c")) diff --git a/u_parrots.go b/u_parrots.go index 8416fa3363..cef77308ab 100644 --- a/u_parrots.go +++ b/u_parrots.go @@ -3015,6 +3015,23 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error { return err } + // Add PSK extension if not specified in the spec. + if uconn.config.AlwaysIncludePSK { + supportsPSK := uconn.config.MaxVersion >= VersionTLS13 + if supportsPSK { + hasPskExt := false + for _, ext := range p.Extensions { + if _, ok := ext.(PreSharedKeyExtension); ok { + hasPskExt = true + } + } + if !hasPskExt { + // pre_shared_key must be the last extension (RFC 8446, Section 4.2.11). + p.Extensions = append(p.Extensions, &UtlsPreSharedKeyExtension{}) + } + } + } + privateHello, clientKeySharePrivate, ech, err := uconn.makeClientHelloForApplyPreset() if err != nil { return err