From 888b9cb09d7ddcfa929f4aa5829cc07742661e5c Mon Sep 17 00:00:00 2001 From: Gaukas Wang Date: Tue, 16 Jul 2024 02:15:04 -0600 Subject: [PATCH 1/3] feat: add post-HelloRetryRequest PSK support Add UpdateOnHRR to allow PSK to recalculate its state using previously transcribed Client Hello's hash and client handshake state. Signed-off-by: Gaukas Wang --- handshake_client_tls13.go | 22 +++++++--- u_common.go | 1 + u_pre_shared_key.go | 88 +++++++++++++++++++++++++++++++++++---- 3 files changed, 99 insertions(+), 12 deletions(-) diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go index e4f955e0..53941b59 100644 --- a/handshake_client_tls13.go +++ b/handshake_client_tls13.go @@ -14,6 +14,7 @@ import ( "errors" "fmt" "hash" + "log" "time" "github.com/cloudflare/circl/kem" @@ -410,11 +411,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 @@ -459,6 +455,22 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { if err := hs.uconn.MarshalClientHello(); 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 { + hs.uconn.HandshakeState.Hello.PskIdentities = nil + hs.uconn.HandshakeState.Hello.PskBinders = nil + log.Printf("[Error] PreSharedKeyExtension.UpdateOnHRR failed: %v", err) + } else { + psk.PatchBuiltHello(hs.uconn.HandshakeState.Hello) + } + break + } + } + } + hs.hello.raw = hs.uconn.HandshakeState.Hello.Raw } } diff --git a/u_common.go b/u_common.go index 59a5a2e3..9d6bc701 100644 --- a/u_common.go +++ b/u_common.go @@ -612,6 +612,7 @@ var ( // Chrome w/ PSK: Chrome start sending this ClientHello after doing TLS 1.3 handshake with the same server. // Beta: PSK extension added. However, uTLS doesn't ship with full PSK support. // Use at your own discretion. + HelloChrome_PSK_Auto = HelloChrome_114_Padding_PSK_Shuf HelloChrome_100_PSK = ClientHelloID{helloChrome, "100_PSK", nil, nil} HelloChrome_112_PSK_Shuf = ClientHelloID{helloChrome, "112_PSK", nil, nil} HelloChrome_114_Padding_PSK_Shuf = ClientHelloID{helloChrome, "114_PSK", nil, nil} diff --git a/u_pre_shared_key.go b/u_pre_shared_key.go index 65e0bb66..60f6428f 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" ) @@ -68,6 +69,26 @@ type PreSharedKeyCommon struct { // > - Implementations should gather and provide the final pre-shared key (PSK) related data. // // > - This data will be incorporated into both the clientHello and HandshakeState, ensuring that the PSK-related information is properly set and ready for the handshake process. +// +// HelloRetryRequest Phase (server selects a different curve supported but not selected by the client): +// +// - [UpdateOnHRR() called]: +// +// > - Implementations should update the extension's state accordingly and save the first Client Hello's hash. +// +// > - The binders should be recalculated based on the updated state LATER when PatchBuiltHello() and/or GetPreSharedKeyCommon() is called. +// +// - [PatchBuiltHello() called]: +// +// > - The client hello is already marshaled in the "hello.Raw" format. +// +// > - Implementations are expected to update the binders within the marshaled client hello. +// +// - [GetPreSharedKeyCommon() called]: +// +// > - Implementations should gather and provide the final pre-shared key (PSK) related data. +// +// > - This data will be incorporated into both the clientHello and HandshakeState, ensuring that the PSK-related information is properly set and ready for the handshake process. type PreSharedKeyExtension interface { // TLSExtension must be implemented by all PreSharedKeyExtension implementations. TLSExtension @@ -88,6 +109,11 @@ type PreSharedKeyExtension interface { // Its purpose is to update the binders of PSK (Pre-Shared Key) identities. PatchBuiltHello(hello *PubClientHelloMsg) error + // UpdateOnHRR is called when the server sends a HelloRetryRequest. + // Implementations should update the extension's state accordingly + // and recalculate the binders. + UpdateOnHRR(prevClientHelloHash []byte, hs *clientHandshakeStateTLS13, timeNow time.Time) error + mustEmbedUnimplementedPreSharedKeyExtension() // this works like a type guard } @@ -99,7 +125,7 @@ func (*UnimplementedPreSharedKeyExtension) IsInitialized() bool { panic("tls: IsInitialized is not implemented for the PreSharedKeyExtension") } -func (*UnimplementedPreSharedKeyExtension) InitializeByUtls(session *SessionState, earlySecret []byte, binderKey []byte, identities []PskIdentity) { +func (*UnimplementedPreSharedKeyExtension) InitializeByUtls(*SessionState, []byte, []byte, []PskIdentity) { panic("tls: Initialize is not implemented for the PreSharedKeyExtension") } @@ -119,14 +145,18 @@ func (*UnimplementedPreSharedKeyExtension) GetPreSharedKeyCommon() PreSharedKeyC panic("tls: GetPreSharedKeyCommon is not implemented for the PreSharedKeyExtension") } -func (*UnimplementedPreSharedKeyExtension) PatchBuiltHello(hello *PubClientHelloMsg) error { +func (*UnimplementedPreSharedKeyExtension) PatchBuiltHello(*PubClientHelloMsg) error { panic("tls: ReadWithRawHello is not implemented for the PreSharedKeyExtension") } -func (*UnimplementedPreSharedKeyExtension) SetOmitEmptyPsk(val bool) { +func (*UnimplementedPreSharedKeyExtension) SetOmitEmptyPsk(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 +166,10 @@ type UtlsPreSharedKeyExtension struct { cachedLength *int // Deprecated: Set OmitEmptyPsk in Config instead. OmitEmptyPsk bool + + // used only for HRR-based recalculation of binders purpose + prevClientHelloHash []byte // used for HRR-based recalculation of binders + serverHello *serverHelloMsg } func (e *UtlsPreSharedKeyExtension) IsInitialized() bool { @@ -265,6 +299,7 @@ func (e *UtlsPreSharedKeyExtension) PatchBuiltHello(hello *PubClientHelloMsg) er if e.Len() == 0 { return nil } + private := hello.getCachedPrivatePtr() if private == nil { private = hello.getPrivatePtr() @@ -272,8 +307,17 @@ func (e *UtlsPreSharedKeyExtension) PatchBuiltHello(hello *PubClientHelloMsg) er private.raw = hello.Raw private.pskBinders = e.Binders // set the placeholder to the private Hello - //--- mirror loadSession() begin ---// + // derived from 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 @@ -284,7 +328,7 @@ func (e *UtlsPreSharedKeyExtension) PatchBuiltHello(hello *PubClientHelloMsg) er if err := private.updateBinders(pskBinders); err != nil { return err } - //--- mirror loadSession() end ---// + // derived end // e.Binders = pskBinders // no need to care about other PSK related fields, they will be handled separately @@ -292,11 +336,35 @@ 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.Binders = nil + e.cachedLength = nil // clear the cached length + + e.prevClientHelloHash = prevClientHelloHash + e.serverHello = hs.serverHello + } + return nil +} + func (e *UtlsPreSharedKeyExtension) Write(b []byte) (int, error) { return len(b), nil // ignore the data } -func (e *UtlsPreSharedKeyExtension) UnmarshalJSON(_ []byte) error { +func (e *UtlsPreSharedKeyExtension) UnmarshalJSON([]byte) error { return nil // ignore the data } @@ -319,7 +387,7 @@ func (e *FakePreSharedKeyExtension) IsInitialized() bool { return e.Identities != nil && e.Binders != nil } -func (e *FakePreSharedKeyExtension) InitializeByUtls(session *SessionState, earlySecret []byte, binderKey []byte, identities []PskIdentity) { +func (e *FakePreSharedKeyExtension) InitializeByUtls(*SessionState, []byte, []byte, []PskIdentity) { panic("InitializeByUtls failed: don't let utls initialize FakePreSharedKeyExtension; provide your own identities and binders or use UtlsPreSharedKeyExtension") } @@ -459,13 +527,19 @@ func (e *FakePreSharedKeyExtension) UnmarshalJSON(data []byte) error { return nil } +func (e *FakePreSharedKeyExtension) UpdateOnHRR([]byte, *clientHandshakeStateTLS13, time.Time) error { + return nil +} + // type guard var ( _ PreSharedKeyExtension = (*UtlsPreSharedKeyExtension)(nil) _ TLSExtensionJSON = (*UtlsPreSharedKeyExtension)(nil) + _ TLSExtensionWriter = (*UtlsPreSharedKeyExtension)(nil) _ PreSharedKeyExtension = (*FakePreSharedKeyExtension)(nil) _ TLSExtensionJSON = (*FakePreSharedKeyExtension)(nil) _ TLSExtensionWriter = (*FakePreSharedKeyExtension)(nil) + _ PreSharedKeyExtension = (*UnimplementedPreSharedKeyExtension)(nil) ) // type ExternalPreSharedKeyExtension struct{} // TODO: wait for whoever cares about external PSK to implement it From 258eeafc949e2694a2d41c68916fce8ecc51d9d5 Mon Sep 17 00:00:00 2001 From: Gaukas Wang Date: Tue, 16 Jul 2024 02:35:00 -0600 Subject: [PATCH 2/3] fix: example using old url no longer valid Signed-off-by: Gaukas Wang --- examples/tls-resumption/main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/tls-resumption/main.go b/examples/tls-resumption/main.go index baaa213a..2e05778d 100644 --- a/examples/tls-resumption/main.go +++ b/examples/tls-resumption/main.go @@ -171,8 +171,8 @@ func runResumptionCheck(helloID tls.ClientHelloID, getCustomSpec func() *tls.Cli func main() { tls13Url := "www.microsoft.com:443" - tls12Url1 := "spocs.getpocket.com:443" - tls12Url2 := "marketplace.visualstudio.com:443" + tls13HRRUrl := "marketplace.visualstudio.com:443" // will send HRR for P384/P521 + tls12Url := "tls-v1-2.badssl.com:1012" runResumptionCheck(tls.HelloChrome_100, nil, noResumption, tls13Url, 3, false) // no-resumption + utls func() { defer func() { @@ -189,9 +189,9 @@ func main() { runResumptionCheck(tls.HelloChrome_100_PSK, nil, pskResumption, tls13Url, 1, false) // psk + utls runResumptionCheck(tls.HelloGolang, nil, pskResumption, tls13Url, 1, false) // psk + crypto/tls - runResumptionCheck(tls.HelloChrome_100_PSK, nil, ticketResumption, tls12Url1, 10, false) // session ticket + utls - runResumptionCheck(tls.HelloGolang, nil, ticketResumption, tls12Url1, 10, false) // session ticket + crypto/tls - runResumptionCheck(tls.HelloChrome_100_PSK, nil, ticketResumption, tls12Url2, 10, false) // session ticket + utls - runResumptionCheck(tls.HelloGolang, nil, ticketResumption, tls12Url2, 10, false) // session ticket + crypto/tls + runResumptionCheck(tls.HelloChrome_100_PSK, nil, pskResumption, tls13HRRUrl, 20, false) // psk (HRR) + utls + runResumptionCheck(tls.HelloGolang, nil, pskResumption, tls13HRRUrl, 20, false) // psk (HRR) + crypto/tls + runResumptionCheck(tls.HelloChrome_100_PSK, nil, ticketResumption, tls12Url, 10, false) // session ticket + utls + runResumptionCheck(tls.HelloGolang, nil, ticketResumption, tls12Url, 10, false) // session ticket + crypto/tls } From dd2ffe0d6e2992acb426513b1b30128115c291a1 Mon Sep 17 00:00:00 2001 From: Gaukas Wang Date: Tue, 16 Jul 2024 02:37:10 -0600 Subject: [PATCH 3/3] chore: note for bugrisk Signed-off-by: Gaukas Wang --- u_session_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/u_session_controller.go b/u_session_controller.go index 4c6a79cd..ce81dddc 100644 --- a/u_session_controller.go +++ b/u_session_controller.go @@ -223,7 +223,7 @@ func (s *sessionController) shouldUpdateBinders() bool { func (s *sessionController) updateBinders() { uAssert(s.shouldUpdateBinders(), "tls: updateBinders failed: shouldn't update binders") - s.pskExtension.PatchBuiltHello(s.uconnRef.HandshakeState.Hello) + s.pskExtension.PatchBuiltHello(s.uconnRef.HandshakeState.Hello) // bugrisk: retured error is ignored } func (s *sessionController) overrideExtension(extension Initializable, override func(), initializedState sessionControllerState) error {