Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support PSK after receiving HelloRetryRequest (HRR) #305

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions examples/tls-resumption/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
}
22 changes: 17 additions & 5 deletions handshake_client_tls13.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"errors"
"fmt"
"hash"
"log"
"time"

"github.com/cloudflare/circl/kem"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
Expand Down
1 change: 1 addition & 0 deletions u_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
88 changes: 81 additions & 7 deletions u_pre_shared_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"io"
"time"

"golang.org/x/crypto/cryptobyte"
)
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand All @@ -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")
}

Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -265,15 +299,25 @@ func (e *UtlsPreSharedKeyExtension) PatchBuiltHello(hello *PubClientHelloMsg) er
if e.Len() == 0 {
return nil
}

private := hello.getCachedPrivatePtr()
if private == nil {
private = hello.getPrivatePtr()
}
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
Expand All @@ -284,19 +328,43 @@ 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

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
}

Expand All @@ -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")
}

Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion u_session_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading