Skip to content

Commit

Permalink
Merge pull request #1323 from getlantern/ox/issue340
Browse files Browse the repository at this point in the history
  • Loading branch information
oxtoacart authored Aug 31, 2023
2 parents 5ce2352 + 90b16c9 commit 342b926
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ xcuserdata
.gomobilecache
vendor
build
hellocap/hellocap/hellocap
1 change: 1 addition & 0 deletions chained/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ var availableClientHelloIDs = map[string]tls.ClientHelloID{
"HelloChrome_58": tls.HelloChrome_58,
"HelloChrome_62": tls.HelloChrome_62,
"HelloChrome_102": tls.HelloChrome_102,
"HelloChrome_106": tls.HelloChrome_106_Shuffle,
"HelloEdge_Auto": tls.HelloEdge_Auto,
"Hello360_Auto": tls.Hello360_Auto,
"HelloQQ_Auto": tls.HelloQQ_Auto,
Expand Down
28 changes: 25 additions & 3 deletions chained/hello_roller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ type helloSpec struct {
}

// This function is guaranteed to return one of the following:
// - A non-custom client hello ID (i.e. not utls.ClientHelloCustom)
// - utls.ClientHelloCustom and a non-nil utls.ClientHelloSpec.
// - A non-custom client hello ID (i.e. not utls.HelloCustom)
// - utls.HelloCustom and a non-nil utls.ClientHelloSpec.
// - An error (if the above is not possible)
func (hs helloSpec) utlsSpec() (tls.ClientHelloID, *tls.ClientHelloSpec, error) {
const tlsRecordHeaderLen = 5
Expand All @@ -42,7 +42,7 @@ func (hs helloSpec) utlsSpec() (tls.ClientHelloID, *tls.ClientHelloSpec, error)
AllowBluntMimicry: false,
AlwaysAddPadding: false,
}
spec, err := fp.FingerprintClientHello(hs.sample[tlsRecordHeaderLen:])
spec, err := fp.FingerprintClientHello(hs.sample)
if err != nil {
return hs.id, nil, errors.New("failed to fingerprint sample hello: %v", err)
}
Expand All @@ -65,6 +65,28 @@ func (hs helloSpec) uconn(transport net.Conn, cfg *tls.Config) (*tls.UConn, erro
return uconn, nil
}

func (hs helloSpec) supportsSessionTickets() (bool, error) {
id, spec, err := hs.utlsSpec()
if err != nil {
return false, errors.New("failed to get hello spec: %v", err)
}
if spec == nil {
// this can happen if we're not using a custom spec
_spec, err := tls.UTLSIdToSpec(id)
if err != nil {
return false, errors.New("failed to get spec for hello %v: %v", id, err)
}
spec = &_spec
}
for _, extension := range spec.Extensions {
_, isSessionTicket := extension.(*tls.SessionTicketExtension)
if isSessionTicket {
return true, nil
}
}
return false, nil
}

type helloRoller struct {
hellos []helloSpec
index, advances int
Expand Down
101 changes: 101 additions & 0 deletions chained/hello_roller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package chained

import (
"encoding/hex"
"testing"

tls "github.com/refraction-networking/utls"
"github.com/stretchr/testify/require"
)

var (
firefoxSampleHello = "1603010200010001fc03033b6aa797e7119cb350c645bef5fee28a7a2c85b9d2027e0cc751d42642f000962095ffa63d2435d184d49adf9d929072fc76a227e0b2b4323e05562a50928282180022130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f0035010001910000000e000c0000096c6f63616c686f737400170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000022000a000804030503060302030033006b0069001d0020d01ad620e95e47149d6c22f54c4904e5533a3f828ed16cb6122436dcdc5c3e1d0017004104235e144fbbc20753ccfcd2c4fec975a117b0a17ad721ea94fb943bcf2b71b0a7e7d63e2cfb648134686e07d3b894f0274cf5e0ac211f51f1ef269c8ddf6ba47d002b00050403040303000d0018001604030503060308040805080604010501060102030201002d00020101001c000240010015008d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

goodSample []byte
badSample []byte
)

func init() {
var err error
goodSample, err = hex.DecodeString(firefoxSampleHello)
if err != nil {
panic(err)
}

badSample = make([]byte, len(goodSample))
copy(badSample, goodSample)
badSample[0] = 0
}

func TestUTLSSpec(t *testing.T) {
cases := []struct {
name string
id tls.ClientHelloID
sample []byte
expectSpec bool
expectedError string
}{
{name: "successful custom hello", id: tls.HelloCustom, sample: goodSample, expectSpec: true},
{name: "custom hello with empty sample", id: tls.HelloCustom, expectedError: "illegal combination of HelloCustom and nil sample hello"},
{name: "custom hello with short sample", id: tls.HelloCustom, sample: []byte("hi"), expectedError: "sample hello is too small"},
{name: "custom hello with bad sample", id: tls.HelloCustom, sample: badSample, expectedError: "failed to fingerprint"},
{name: "non-custom hello", id: tls.HelloChrome_106_Shuffle},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
hs := helloSpec{
id: tc.id,
sample: tc.sample,
}

id, spec, err := hs.utlsSpec()
if tc.expectedError != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedError)
} else {
require.NoError(t, err)
require.Equal(t, tc.id, id)
if tc.expectSpec {
require.NotNil(t, spec)
} else {
require.Nil(t, spec)
}
}
})
}
}

func TestSupportsSessionTickets(t *testing.T) {
cases := []struct {
name string
id tls.ClientHelloID
sample []byte
supportsSessionTickets bool
expectedError string
}{
{name: "successful custom hello", id: tls.HelloCustom, sample: goodSample, supportsSessionTickets: true},
{name: "custom hello with empty sample", id: tls.HelloCustom, expectedError: "failed to get hello spec"},
{name: "unknown hello", id: tls.ClientHelloID{Client: "unknown"}, expectedError: "failed to get spec for hello"},
{name: "Chrome", id: tls.HelloChrome_106_Shuffle, supportsSessionTickets: true},
{name: "Safari", id: tls.HelloSafari_16_0, supportsSessionTickets: false},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
hs := helloSpec{
id: tc.id,
sample: tc.sample,
}

supportsSessionTickets, err := hs.supportsSessionTickets()
if tc.expectedError != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedError)
} else {
require.NoError(t, err)
require.Equal(t, tc.supportsSessionTickets, supportsSessionTickets)
}
})
}
}
18 changes: 16 additions & 2 deletions chained/https_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type httpsImpl struct {
tlsConfig *tls.Config
roller *helloRoller
tlsClientHelloSplitting bool
requiresSessionTickets bool
sync.Mutex
}

Expand All @@ -45,6 +46,7 @@ func newHTTPSImpl(configDir, name, addr string, pc *config.ProxyConfig, uc commo
tlsConfig: tlsConfig,
roller: &helloRoller{hellos: hellos},
tlsClientHelloSplitting: pc.TLSClientHelloSplitting,
requiresSessionTickets: pc.TLSClientSessionState != "",
}, nil
}

Expand All @@ -55,10 +57,22 @@ func (impl *httpsImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, er
currentHello := r.current()
helloID, helloSpec, err := currentHello.utlsSpec()
if err != nil {
log.Debugf("failed to generate valid utls hello spec; advancing roller: %v", err)
r.advance()
return nil, errors.New("failed to generate valid utls hello spec: %v", err)
return nil, errors.New("failed to generate valid utls hello spec, advancing roller: %v", err)
}

if impl.requiresSessionTickets && helloID != tls.HelloGolang {
supportsSessionTickets, err := currentHello.supportsSessionTickets()
if err != nil {
r.advance()
return nil, errors.New("unable to determine if hello %v supports session tickets, advancing roller: %v", helloID, err)
}
if !supportsSessionTickets {
r.advance()
return nil, errors.New("session ticket is required, but hello %v does not support them; advancing roller", helloID)
}
}

d := tlsdialer.Dialer{
DoDial: func(network, addr string, timeout time.Duration) (net.Conn, error) {
tcpConn, err := impl.dialCore(op, ctx, impl.addr)
Expand Down
14 changes: 13 additions & 1 deletion hellocap/hellocap.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"os/exec"
"sync"

utls "github.com/refraction-networking/utls"

"github.com/getlantern/tlsutil"
)

Expand Down Expand Up @@ -127,7 +129,17 @@ func (c *capturingConn) checkHello(newBytes []byte) {
c.helloBuf.Write(newBytes)
nHello, parseErr := tlsutil.ValidateClientHello(c.helloBuf.Bytes())
if parseErr == nil {
c.onHello(c.helloBuf.Bytes()[:nHello], nil)
f := &utls.Fingerprinter{
AllowBluntMimicry: false,
AlwaysAddPadding: false,
}
_, err := f.FingerprintClientHello(c.helloBuf.Bytes()[:nHello])
if err != nil {
c.onHello(nil, err)
} else {
fmt.Println("fingerprinting okay")
c.onHello(c.helloBuf.Bytes()[:nHello], nil)
}
c.helloRead = true
} else if !errors.Is(parseErr, io.ErrUnexpectedEOF) {
c.onHello(nil, fmt.Errorf("could not parse captured bytes as a ClientHello: %w", parseErr))
Expand Down

0 comments on commit 342b926

Please sign in to comment.