diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index 01c2756c4c..947b7aa5c3 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -389,11 +389,6 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { // and utlsExtensionPadding are supposed to change if hs.uconn != nil { if hs.uconn.ClientHelloID != HelloGolang { - if len(hs.hello.pskIdentities) > 0 { - // TODO: wait for someone who cares about PSK to implement - return errors.New("uTLS does not support reprocessing of PSK key triggered by HelloRetryRequest") - } - keyShareExtFound := false for _, ext := range hs.uconn.Extensions { // new ks seems to be generated either way @@ -438,6 +433,22 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { if err := hs.uconn.MarshalClientHelloNoECH(); err != nil { return err } + + if len(hs.hello.pskIdentities) > 0 { + for _, ext := range hs.uconn.Extensions { + if psk, ok := ext.(PreSharedKeyExtension); ok { + if err := psk.UpdateOnHRR(chHash, hs, c.config.time()); err != nil { + // Graceful degradation: clear PSK and continue without resumption. + hs.uconn.HandshakeState.Hello.PskIdentities = nil + hs.uconn.HandshakeState.Hello.PskBinders = nil + } else { + psk.PatchBuiltHello(hs.uconn.HandshakeState.Hello) + } + break + } + } + } + hs.hello.original = hs.uconn.HandshakeState.Hello.Raw } } diff --git a/u_pre_shared_key.go b/u_pre_shared_key.go index f1e4995ee5..6a6628843b 100644 --- a/u_pre_shared_key.go +++ b/u_pre_shared_key.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "io" + "time" "golang.org/x/crypto/cryptobyte" ) @@ -88,6 +89,12 @@ type PreSharedKeyExtension interface { // Its purpose is to update the binders of PSK (Pre-Shared Key) identities. PatchBuiltHello(hello *PubClientHelloMsg) error + // UpdateOnHRR is called when a HelloRetryRequest is received and PSK identities + // need to be reprocessed. It updates the session, cipher suite, obfuscated ticket + // age, and stores the previous ClientHello hash for correct transcript reconstruction + // in PatchBuiltHello. + UpdateOnHRR(prevClientHelloHash []byte, hs *clientHandshakeStateTLS13, timeNow time.Time) error + mustEmbedUnimplementedPreSharedKeyExtension() // this works like a type guard } @@ -127,6 +134,10 @@ func (*UnimplementedPreSharedKeyExtension) SetOmitEmptyPsk(val bool) { panic("tls: SetOmitEmptyPsk is not implemented for the PreSharedKeyExtension") } +func (*UnimplementedPreSharedKeyExtension) UpdateOnHRR([]byte, *clientHandshakeStateTLS13, time.Time) error { + panic("tls: UpdateOnHRR is not implemented for the PreSharedKeyExtension") +} + // UtlsPreSharedKeyExtension is an extension used to set the PSK extension in the // ClientHello. type UtlsPreSharedKeyExtension struct { @@ -136,6 +147,10 @@ type UtlsPreSharedKeyExtension struct { cachedLength *int // Deprecated: Set OmitEmptyPsk in Config instead. OmitEmptyPsk bool + + // used only for HRR-based recalculation of binders + prevClientHelloHash []byte + serverHello *serverHelloMsg } func (e *UtlsPreSharedKeyExtension) IsInitialized() bool { @@ -272,8 +287,17 @@ func (e *UtlsPreSharedKeyExtension) PatchBuiltHello(hello *PubClientHelloMsg) er private.original = hello.Raw private.pskBinders = e.Binders // set the placeholder to the private Hello - //--- mirror loadSession() begin ---// + //--- mirror loadSession() and processHelloRetryRequest() begin ---// transcript := e.cipherSuite.hash.New() + + if len(e.prevClientHelloHash) > 0 { // HRR will set this field + transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(e.prevClientHelloHash))}) + transcript.Write(e.prevClientHelloHash) + if err := transcriptMsg(e.serverHello, transcript); err != nil { + return err + } + } + helloBytes, err := private.marshalWithoutBinders() // no marshal() will be actually called, as we have set the field `raw` if err != nil { return err @@ -307,6 +331,29 @@ func (e *UtlsPreSharedKeyExtension) PatchBuiltHello(hello *PubClientHelloMsg) er return io.EOF } +func (e *UtlsPreSharedKeyExtension) UpdateOnHRR(prevClientHelloHash []byte, hs *clientHandshakeStateTLS13, timeNow time.Time) error { + if len(e.Identities) > 0 { + e.Session = hs.session + e.cipherSuite = cipherSuiteTLS13ByID(e.Session.cipherSuite) + if e.cipherSuite.hash != hs.suite.hash { + // disable PatchBuiltHello + e.Session = nil + e.cachedLength = new(int) + return errors.New("tls: cipher suite hash mismatch, PSK will not be used") + } + + // update the obfuscated ticket age + ticketAge := timeNow.Sub(time.Unix(int64(hs.session.createdAt), 0)) + e.Identities[0].ObfuscatedTicketAge = uint32(ticketAge/time.Millisecond) + hs.session.ageAdd + + e.cachedLength = nil // clear the cached length so re-serialization recalculates + + e.prevClientHelloHash = prevClientHelloHash + e.serverHello = hs.serverHello + } + return nil +} + func (e *UtlsPreSharedKeyExtension) Write(b []byte) (int, error) { return len(b), nil // ignore the data } @@ -388,6 +435,10 @@ func (*FakePreSharedKeyExtension) PatchBuiltHello(*PubClientHelloMsg) error { return nil // no need to patch the hello since we don't need to update binders } +func (*FakePreSharedKeyExtension) UpdateOnHRR([]byte, *clientHandshakeStateTLS13, time.Time) error { + return nil // no need to update on HRR since we don't recalculate binders +} + func (e *FakePreSharedKeyExtension) Write(b []byte) (n int, err error) { fullLen := len(b) s := cryptobyte.String(b)