-
Notifications
You must be signed in to change notification settings - Fork 13
/
utlsx.go
172 lines (153 loc) · 5.26 KB
/
utlsx.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
169
170
171
172
// Package utlsx contains UTLS extensions.
package utlsx
import (
"context"
"crypto/tls"
"log"
"net"
"net/url"
"time"
oohttp "github.com/ooni/oohttp"
"github.com/ooni/oohttp/example/internal/runtimex"
utls "github.com/refraction-networking/utls"
)
// connAdapter adapts utls.UConn to the oohttp.TLSConn interface.
type connAdapter struct {
*utls.UConn
}
var _ oohttp.TLSConn = &connAdapter{}
// ConnectionState implements oohttp.TLSConn.
func (c *connAdapter) ConnectionState() tls.ConnectionState {
cs := c.UConn.ConnectionState()
return tls.ConnectionState{
Version: cs.Version,
HandshakeComplete: cs.HandshakeComplete,
DidResume: cs.DidResume,
CipherSuite: cs.CipherSuite,
NegotiatedProtocol: cs.NegotiatedProtocol,
NegotiatedProtocolIsMutual: cs.NegotiatedProtocolIsMutual,
ServerName: cs.ServerName,
PeerCertificates: cs.PeerCertificates,
VerifiedChains: cs.VerifiedChains,
SignedCertificateTimestamps: cs.SignedCertificateTimestamps,
OCSPResponse: cs.OCSPResponse,
TLSUnique: cs.TLSUnique,
}
}
// HandshakeContext implements oohttp.TLSConn.
func (c *connAdapter) HandshakeContext(ctx context.Context) error {
errch := make(chan error, 1)
go func() {
errch <- c.UConn.Handshake()
}()
select {
case err := <-errch:
return err
case <-ctx.Done():
return ctx.Err()
}
}
// DefaultClientHelloID is the default [utls.ClientHelloID].
var DefaultClientHelloID = &utls.HelloFirefox_55
// ConnFactory is a factory for creating UTLS connections.
type ConnFactory interface {
// NewUTLSConn creates a new UTLS connection. The conn and config arguments MUST
// NOT be nil. We will honour the following fields of config:
//
// - RootCAs
//
// - NextProtos
//
// - ServerName
//
// - InsecureSkipVerify
//
// - DynamicRecordSizingDisabled
//
// However, some of these fields values MAY be ignored depending on the parrot we use.
NewUTLSConn(conn net.Conn, config *tls.Config) oohttp.TLSConn
}
// FactoryWithParrot implements ConnFactory.
type FactoryWithParrot struct {
// Parrot is the OPTIONAL parrot.
Parrot *utls.ClientHelloID
}
// NewUTLSConn implements ConnFactory.
func (f *FactoryWithParrot) NewUTLSConn(conn net.Conn, config *tls.Config) oohttp.TLSConn {
parrot := f.Parrot
if parrot == nil {
parrot = DefaultClientHelloID
}
uConfig := &utls.Config{
RootCAs: config.RootCAs,
NextProtos: config.NextProtos,
ServerName: config.ServerName,
InsecureSkipVerify: config.InsecureSkipVerify,
DynamicRecordSizingDisabled: config.DynamicRecordSizingDisabled,
}
return &connAdapter{utls.UClient(conn, uConfig, *parrot)}
}
// DefaultNetDialer is the default [net.Dialer].
var DefaultNetDialer = &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
// TLSDialer is a dialer that uses UTLS
type TLSDialer struct {
// Config is the OPTIONAL Config. In case it's not nil, we will
// pass this config to [Factory] rather than a default one.
Config *tls.Config
// Parrot is the OPTIONAL parrot to use.
Parrot *utls.ClientHelloID
// beforeHandshakeFunc is a function called before the
// TLS handshake, which is only useful for testing.
beforeHandshakeFunc func()
}
// DialTLSContext dials a TLS connection using UTLS.
func (d *TLSDialer) DialTLSContext(ctx context.Context, network string, addr string) (net.Conn, error) {
conn, err := DefaultNetDialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
sni, _, err := net.SplitHostPort(addr)
runtimex.PanicOnError(err, "net.SplitHostPort failed") // cannot fail after successful dial
config := &tls.Config{ServerName: sni}
if d.Config != nil {
config = d.Config // as documented
}
if d.beforeHandshakeFunc != nil {
d.beforeHandshakeFunc() // useful for testing
}
uadapter := (&FactoryWithParrot{d.Parrot}).NewUTLSConn(conn, config)
if err := uadapter.HandshakeContext(ctx); err != nil {
conn.Close()
return nil, err
}
proto := uadapter.ConnectionState().NegotiatedProtocol
log.Printf("negotiated protocol: %s", proto)
return uadapter, nil
}
// TLSFactoryFunc is the type of the [ConnFactory.NewUTLSConn] func.
type TLSFactoryFunc func(conn net.Conn, config *tls.Config) oohttp.TLSConn
// ProxyFunc is the the type of the [http.ProxyFromEnvironment] func.
type ProxyFunc func(*oohttp.Request) (*url.URL, error)
// TLSDialFunc is the type of the [TLSDialer.DialTLSContext] func.
type TLSDialFunc func(ctx context.Context, network string, addr string) (net.Conn, error)
// NewOOHTTPTransport creates a new OOHTTP transport using the given funcs. Each
// function MAY be nil. In such a case, we'll use a reasonable default.
func NewOOHTTPTransport(
proxy ProxyFunc, tlsFactory TLSFactoryFunc, tlsDialer TLSDialFunc) *oohttp.StdlibTransport {
return &oohttp.StdlibTransport{
Transport: &oohttp.Transport{
Proxy: proxy,
DialContext: DefaultNetDialer.DialContext,
DialTLSContext: tlsDialer,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientFactory: tlsFactory,
},
}
}