-
Notifications
You must be signed in to change notification settings - Fork 0
/
tls_config.go
168 lines (149 loc) · 6.44 KB
/
tls_config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package chained
import (
"bytes"
"context"
"crypto/x509"
"encoding/pem"
"io"
"os"
"runtime"
tls "github.com/refraction-networking/utls"
"github.com/getlantern/common/config"
"github.com/getlantern/errors"
"github.com/getlantern/flashlight/v7/browsers/simbrowser"
"github.com/getlantern/flashlight/v7/common"
"github.com/getlantern/flashlight/v7/ops"
"github.com/getlantern/tlsresumption"
)
// Generates TLS configuration for connecting to proxy specified by the config.ProxyConfig. This
// function may block while determining things like how to mimic the default browser's client hello.
//
// Returns a slice of ClientHellos to be used for dialing. These hellos are in priority order: the
// first hello is the "ideal" one and the remaining hellos serve as backup in case something is
// wrong with the previous hellos. There will always be at least one hello. For each hello, the
// ClientHelloSpec will be non-nil if and only if the ClientHelloID is tls.HelloCustom.
func tlsConfigForProxy(ctx context.Context, configDir, proxyName string, pc *config.ProxyConfig, uc common.UserConfig) (
*tls.Config, []helloSpec, error) {
configuredHelloID := clientHelloID(pc)
var ss *tls.ClientSessionState
var err error
if pc.TLSClientSessionState != "" {
ss, err = tlsresumption.ParseClientSessionState(pc.TLSClientSessionState)
if err != nil {
log.Errorf("Unable to parse serialized client session state, continuing with normal handshake: %v", err)
} else {
log.Debug("Using serialized client session state")
if configuredHelloID.Client == "Golang" {
log.Debug("Need to mimic browser hello for session resumption, defaulting to HelloChrome_Auto")
configuredHelloID = tls.HelloChrome_Auto
}
}
}
configuredHelloSpec := helloSpec{configuredHelloID, nil}
if configuredHelloID == helloBrowser {
configuredHelloSpec = getBrowserHello(ctx, configDir, uc)
}
sessionTTL := simbrowser.ChooseForUser(ctx, uc).SessionTicketLifetime
sessionCache := newExpiringSessionCache(proxyName, sessionTTL, ss)
// We configure cipher suites here, then specify later which ClientHello type to mimic. As the
// ClientHello type itself specifies cipher suites, it's not immediately obvious who wins.
//
// When we specify HelloGolang (a fallback at the time of this writing), the cipher suites in
// the config are used. For our purposes, this is preferable, particularly as we can configure
// these remotely.
//
// For all other hello types, the cipher suite in the ClientHelloSpec are used:
// 1. With HelloCustom, we call ApplyPreset manually; skip to step 4.
// 2. The handshake is built by BuildHandshakeState, which calls applyPresetByID:
// https://github.com/getlantern/utls/blob/1abdc4b1acab98e8776ae9a5201f67968ffa01dc/u_conn.go#L82
// 3. For HelloRandomized*, random suites are generated by generateRandomSpec. For other,
// non-custom hellos, ApplyPreset is called:
// https://github.com/getlantern/utls/blob/1abdc4b1acab98e8776ae9a5201f67968ffa01dc/u_parrots.go#L977
// 4. ApplyPreset configures the cipher suites according to the ClientHelloSpec:
// https://github.com/getlantern/utls/blob/1abdc4b1acab98e8776ae9a5201f67968ffa01dc/u_parrots.go#L1034-L1040
cipherSuites := orderedCipherSuitesFromConfig(pc)
// Proxy certs are self-signed. We will just verify that the peer (the proxy) provided exactly
// the expected certificate.
if pc.Cert == "" {
return nil, nil, errors.New("no proxy certificate configured")
}
block, rest := pem.Decode([]byte(pc.Cert))
if block == nil {
return nil, nil, errors.New("failed to decode proxy certificate as PEM block")
}
if len(rest) > 0 {
return nil, nil, errors.New("unexpected extra data in proxy certificate PEM")
}
if block.Type != "CERTIFICATE" {
return nil, nil, errors.New("expected certificate in PEM block")
}
proxyCertDER := block.Bytes
// Byte-wise comparsion, verifying that the proxy cert is the expected one.
// n.b. Not invoked when resuming a session (as there are no peer certificates to inspect).
verifyPeerCert := func(peerCerts [][]byte, _ [][]*x509.Certificate) error {
if len(peerCerts) == 0 {
return errors.New("no peer certificate")
}
if !bytes.Equal(peerCerts[0], proxyCertDER) {
return errors.New("peer certificate does not match expected")
}
return nil
}
cfg := &tls.Config{
ClientSessionCache: sessionCache,
CipherSuites: cipherSuites,
ServerName: pc.TLSServerNameIndicator,
KeyLogWriter: getTLSKeyLogWriter(),
// We have to disable standard verification because we want to provide an alternative SNI.
// We provide our own verification function, which ensures that verification still occurs
// as part of the handshake.
InsecureSkipVerify: true,
VerifyPeerCertificate: verifyPeerCert,
}
hellos := []helloSpec{
configuredHelloSpec,
{tls.HelloChrome_Auto, nil},
{tls.HelloGolang, nil},
}
return cfg, hellos, nil
}
// getBrowserHello determines the best way to mimic the system's default web browser. There are a
// few possible failure points in making this determination, e.g. a failure to obtain the default
// browser or a failure to capture a hello from the browser. However, this function will always find
// something reasonable to fall back on.
func getBrowserHello(ctx context.Context, configDir string, uc common.UserConfig) helloSpec {
// We have a number of ways to approximate the browser's ClientHello format. We begin with the
// most desirable, progressively falling back to less desirable options on failure.
op := ops.Begin("get_browser_hello")
op.Set("platform", runtime.GOOS)
defer op.End()
hello, err := cachedHello(configDir)
if err == nil {
return *hello
}
op.FailIf(err)
log.Debugf("failed to actively obtain browser hello: %v", err)
// Our last option is to simulate a browser choice for the user based on market share.
return helloSpec{simbrowser.ChooseForUser(ctx, uc).ClientHelloID, nil}
}
func orderedCipherSuitesFromConfig(pc *config.ProxyConfig) []uint16 {
if common.Platform == "android" {
return mobileOrderedCipherSuites(pc)
}
return desktopOrderedCipherSuites(pc)
}
// Write the session keys to file if SSLKEYLOGFILE is set, same as browsers.
func getTLSKeyLogWriter() io.Writer {
createKeyLogWriterOnce.Do(func() {
path := os.Getenv("SSLKEYLOGFILE")
if path == "" {
return
}
var err error
tlsKeyLogWriter, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
log.Debugf("Error creating keylog file at %v: %s", path, err)
}
})
return tlsKeyLogWriter
}