-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathyves.go
More file actions
289 lines (248 loc) · 7.86 KB
/
yves.go
File metadata and controls
289 lines (248 loc) · 7.86 KB
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package yves
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"sync"
"time"
)
var okHeader = "HTTP/1.1 200 OK\r\n\r\n"
type Proxy struct {
// HttpClient is a working HTTP Client
HttpClient *http.Client
// Tr is the transport used by the HttpClient
Tr *http.Transport
// HandleRequest is a function that is executed upon receving a request
HandleRequest func(int64, *http.Request) *http.Response
// HandleResponse is a function that is executed when a response is being sent back
HandleResponse func(int64, *http.Request, *http.Response)
// Session is used to count the number of requests received
// so that it is possible to correlate requests and responses from the handlers.
session int64
sessionMutex sync.Mutex
// CaKey and CaCert are, respectively the proxy TLS private
// key and certificate in PEM format.
CaKey []byte
CaCert []byte
HandleWebSocRequest func(websoc *WebsocketFragment) *WebsocketFragment
HandleWebSocResponse func(websoc *WebsocketFragment) *WebsocketFragment
}
func (p *Proxy) ServeHTTP(wrt http.ResponseWriter, req *http.Request) {
p.sessionMutex.Lock()
ctx := context.WithValue(context.Background(), "session", p.session)
p.session = p.session + 1
p.sessionMutex.Unlock()
// hijack the connection with the client
hijacker, ok := wrt.(http.Hijacker)
if !ok {
HttpError(wrt, "Hijacking not supported", http.StatusInternalServerError)
return
}
//Bleah: Needed for HTTPS
//And what about HTTP2?
// this is the connection with the client
clientConn, _, err := hijacker.Hijack()
defer clientConn.Close()
if err != nil {
HttpError(wrt, err.Error(), http.StatusInternalServerError)
return
}
if req.Method != http.MethodConnect {
// this is a plaintext HTTP connection
reqClone := req.Clone(context.TODO())
if err != nil {
HttpError(wrt, err.Error(), http.StatusInternalServerError)
return
}
// Forward the request to the remote host
// RequestURI will contain the Request Target
// https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2
resp, err := p.forwardReq(ctx, req, req.RequestURI)
if err != nil {
HttpError(wrt, err.Error(), http.StatusInternalServerError)
return
}
// forward the response back to the client
error := p.forwardResp(ctx, resp, clientConn, reqClone)
if error != nil {
HttpError(clientConn, error.Error(), http.StatusInternalServerError)
return
}
} else {
// Some usefull documentation
// https://datatracker.ietf.org/doc/html/rfc2817#section-5.2
// For being compliant with the RFC, the proxy should first
// perform a dial to the remote destination host and *then* send
// a 200OK to the client. However, I have no idea how to do this
// while leveraging the convinience of Transport provided by Go.
// So for know, I will knowingly violate the RFC.
// Save the destinationHost along with the scheme.
destinationHost := fmt.Sprintf("https://%s", req.RequestURI)
// Answer with a 200OK to the client.
clientConn.Write([]byte(okHeader))
// check if destination speaks TLS
conf := &tls.Config{
InsecureSkipVerify: true,
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
d := tls.Dialer{
Config: conf,
}
_, err := d.DialContext(ctx, "tcp", req.RequestURI)
cancel() // why am I calling the cancel function?
if err != nil {
//defer conn.Close()
// not a TLS connection, go with raw tcp for now
// I'm assuming that if I cannot establish a TLS connection with
// the remote server maybe this is a plaintext websocket connection
clientTlsReader := bufio.NewReader(clientConn)
req, err := http.ReadRequest(clientTlsReader)
if err != nil {
log.Println("Not an HTTP request")
return
}
if isWebSocketRequest(req) {
p.serveWebsocket(wrt, req, clientConn, false)
}
} else {
// a TLS connection
// Start a TLS connection with the client.
clientConn = p.startTlsWithClient(clientConn)
defer clientConn.Close()
clientTlsReader := bufio.NewReader(clientConn)
for !isEob(clientTlsReader) {
reqClone := req.Clone(context.TODO())
if err != nil {
HttpError(wrt, err.Error(), http.StatusInternalServerError)
return
}
req, err := http.ReadRequest(clientTlsReader)
if err != nil {
// Assume this is a HTTPS connection
//clientConnTls = p.startTlsWithClient(clientConn)
log.Println("Not an HTTP request")
return
} else {
if isWebSocketRequest(req) {
p.serveWebsocket(wrt, req, clientConn, true)
}
}
resp, err := p.forwardReq(ctx, req, destinationHost)
if err != nil {
HttpError(clientConn, err.Error(), http.StatusInternalServerError)
return
}
// Do I need to have a write buffer for the connection with the client??
error := p.forwardResp(ctx, resp, clientConn, reqClone)
if error != nil {
HttpError(clientConn, error.Error(), http.StatusInternalServerError)
return
}
return
}
return
}
}
}
// Takes the client request, eventually modifies it and sends it to the intended destination host
func (p *Proxy) forwardReq(ctx context.Context, clientRequest *http.Request, destinationHost string) (*http.Response, error) {
if p.HandleRequest != nil {
// call to HandleRequest
hResp := p.HandleRequest(ctx.Value("session").(int64), clientRequest)
if hResp != nil {
return hResp, nil
}
}
clientRequest.RequestURI = ""
u, err := url.Parse(destinationHost)
if err != nil {
return nil, err
}
clientRequest.URL.Scheme = u.Scheme
clientRequest.URL.Host = u.Host
return p.HttpClient.Do(clientRequest)
}
func (p *Proxy) forwardResp(ctx context.Context, resp *http.Response, down io.Writer, req *http.Request) error {
if p.HandleResponse != nil {
p.HandleResponse(ctx.Value("session").(int64), req, resp)
}
return resp.Write(down)
}
func HttpError(conn io.Writer, er string, code int) {
rsp := &http.Response{
ProtoMajor: 1,
ProtoMinor: 1,
StatusCode: code,
Header: make(map[string][]string),
Body: io.NopCloser(bytes.NewBufferString(er)),
}
rsp.Header.Add("Content-Type", "text/plain; charset=utf-8")
rsp.Header.Add("X-Content-Type-Options", "nosniff")
rsp.Write(conn)
}
func NewProxy() *Proxy {
p := &Proxy{}
certs = make(map[string]*tls.Certificate)
// By default skip TLS verification
p.Tr = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
// By default:
// - do not follow redirection;
// - set a 10 seconds timeout
cl := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Transport: p.Tr,
Timeout: time.Second * 10}
p.HttpClient = cl
if p.CaCert == nil || p.CaKey == nil {
p.CaCert = caCert
p.CaKey = caKey
}
return p
}
// startTlsWithClient starts a TLS connection with the client.
func (p *Proxy) startTlsWithClient(down net.Conn) net.Conn {
tlfConf := new(tls.Config)
// https://pkg.go.dev/crypto/tls#Config
// GetCertificate returns a Certificate based on the given
// ClientHelloInfo. It will only be called if the client supplies SNI
// information or if Certificates is empty.
tlfConf.GetCertificate = func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
// get CA key pair
CA, err := tls.X509KeyPair(p.CaCert, p.CaKey)
if err != nil {
log.Fatalf("Cannot parse provided CA key pair %s\n", err)
}
// get CA certificate
CA.Leaf, err = x509.ParseCertificate(CA.Certificate[0])
if err != nil {
log.Fatalf("Cannot parse CA certificate: %s\n", err)
}
return getCert(CA, hello.ServerName)
}
// perform a TLS connection with the client.
c := tls.Server(down, tlfConf)
if err := c.Handshake(); err != nil {
log.Printf("Server Handshake error: %v\n", err)
}
return c
}
// isEob check is there's something else to read from the buffer.
func isEob(r *bufio.Reader) bool {
_, err := r.Peek(1)
if err != nil {
return true
}
return false
}