Skip to content

Commit b87b416

Browse files
committed
Workaround TLS problems
1 parent 69b4758 commit b87b416

File tree

4 files changed

+228
-25
lines changed

4 files changed

+228
-25
lines changed

config.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const (
1717
actionProxy configActions = 2
1818
actionDirect configActions = 4
1919
actionForceHTTPS configActions = 8
20+
actionFragment configActions = 16
2021
)
2122

2223
type configCase struct {
@@ -38,10 +39,11 @@ func compileMask(mask string) *regexp.Regexp {
3839
var actionSeparator = regexp.MustCompile(`[ \t]+`)
3940

4041
var actionFromString = map[string]configActions{
41-
"block": actionBlock,
42-
"proxy": actionProxy,
43-
"direct": actionDirect,
44-
"https": actionForceHTTPS,
42+
"block": actionBlock,
43+
"proxy": actionProxy,
44+
"direct": actionDirect,
45+
"https": actionForceHTTPS,
46+
"fragment": actionFragment,
4547
}
4648

4749
func loadConfigReader(reader io.Reader) (*config, error) {

go.mod

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
module github.com/snaury/ssh-proxy
22

3-
go 1.15
3+
go 1.22
44

5-
require (
6-
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
7-
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 // indirect
8-
)
5+
require golang.org/x/crypto v0.25.0
6+
7+
require golang.org/x/sys v0.22.0 // indirect

go.sum

+6-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
2-
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
3-
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
4-
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
5-
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
6-
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
7-
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
8-
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE=
9-
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
10-
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
11-
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
12-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1+
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
2+
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
3+
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
4+
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
5+
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
6+
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=

main.go

+212-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"errors"
45
"flag"
56
"fmt"
67
"io"
@@ -199,6 +200,203 @@ func isNormalError(err error) bool {
199200
return false
200201
}
201202

203+
type fragmentedReader struct {
204+
io.Reader
205+
header [5]byte
206+
buffer []byte
207+
handshake []byte
208+
handshakeErr error
209+
processed bool
210+
}
211+
212+
var errNotHandshakeRecord = errors.New("not a handshake record")
213+
214+
func (c *fragmentedReader) readHandshakeRecord() ([]byte, error) {
215+
n, err := io.ReadFull(c.Reader, c.header[:5])
216+
c.buffer = append(c.buffer, c.header[:n]...)
217+
if err != nil {
218+
if err == io.ErrUnexpectedEOF {
219+
err = io.EOF
220+
}
221+
return nil, err
222+
}
223+
// log.Printf("readHandshakeRecord: header %q", c.header[:5])
224+
225+
// TLS 1.0 handshake record
226+
if c.header[0] == 0x16 && c.header[1] == 0x03 && c.header[2] == 0x01 {
227+
n = int(c.header[3])<<8 | int(c.header[4])
228+
b := make([]byte, n)
229+
n, err = io.ReadFull(c.Reader, b)
230+
c.buffer = append(c.buffer, b[:n]...)
231+
if err != nil {
232+
if err == io.ErrUnexpectedEOF {
233+
err = io.EOF
234+
}
235+
return nil, err
236+
}
237+
238+
// log.Printf("readHandshakeRecord: data %q", b)
239+
return b, nil
240+
}
241+
242+
// not a handshake record
243+
return nil, errNotHandshakeRecord
244+
}
245+
246+
func (c *fragmentedReader) readHandshakeBytes(n int) error {
247+
for len(c.handshake) < n {
248+
b, err := c.readHandshakeRecord()
249+
if err != nil {
250+
return err
251+
}
252+
c.handshake = append(c.handshake, b...)
253+
}
254+
return nil
255+
}
256+
257+
func (c *fragmentedReader) appendHandshakeRecord(b []byte) {
258+
for len(b) > 0 {
259+
n := len(b)
260+
if n > 65535 {
261+
n = 65535
262+
}
263+
c.buffer = append(c.buffer, 0x16, 0x03, 0x01, byte(n>>8), byte(n))
264+
c.buffer = append(c.buffer, b[:n]...)
265+
b = b[n:]
266+
}
267+
}
268+
269+
func findSnameExt(b []byte) (int, int) {
270+
pos := 0
271+
for len(b) >= 4 {
272+
n := int(b[2])<<8 | int(b[3])
273+
if !(4+n <= len(b)) {
274+
break
275+
}
276+
if b[0] == 0 && b[1] == 0 {
277+
return pos + 4, n
278+
}
279+
b = b[4+n:]
280+
pos += 4 + n
281+
}
282+
return -1, -1
283+
}
284+
285+
func (c *fragmentedReader) processClientHello() error {
286+
err := c.readHandshakeBytes(4)
287+
if err != nil {
288+
log.Printf("failed to read 4 bytes (client hello header): %s", err)
289+
if err == errNotHandshakeRecord {
290+
err = nil
291+
}
292+
return err
293+
}
294+
if c.handshake[0] != 0x01 {
295+
// expected client hello message
296+
return nil
297+
}
298+
n := int(c.handshake[1])<<16 | int(c.handshake[2])<<8 | int(c.handshake[3])
299+
err = c.readHandshakeBytes(4 + n)
300+
if err != nil {
301+
log.Printf("failed to read %d bytes (client hello data): %s", n, err)
302+
if err == errNotHandshakeRecord {
303+
err = nil
304+
}
305+
return err
306+
}
307+
pos := 4
308+
end := 4 + n
309+
if !(pos+2 <= end) || c.handshake[pos] != 0x03 || c.handshake[pos+1] != 0x03 {
310+
// expected TLS 1.2 outer layer
311+
return nil
312+
}
313+
pos += 2 + 32
314+
// skip session id
315+
if !(pos+1 <= end) {
316+
return nil
317+
}
318+
k := int(c.handshake[pos])
319+
pos += 1 + k
320+
// skip cipher suites
321+
if !(pos+2 <= end) {
322+
return nil
323+
}
324+
k = int(c.handshake[pos])<<8 | int(c.handshake[pos+1])
325+
pos += 2 + k
326+
// skip compression methods
327+
if !(pos+1 <= end) {
328+
return nil
329+
}
330+
k = int(c.handshake[pos])
331+
pos += 1 + k
332+
// extensions
333+
if !(pos+2 <= end) {
334+
return nil
335+
}
336+
extSize := int(c.handshake[pos])<<8 | int(c.handshake[pos+1])
337+
if extSize < 4 {
338+
return nil
339+
}
340+
pos += 2
341+
extStart := pos
342+
extEnd := extStart + extSize
343+
if !(extEnd <= end) {
344+
return nil
345+
}
346+
ext := c.handshake[extStart:extEnd]
347+
348+
// log.Printf("Full handshake: %x", c.handshake)
349+
// log.Printf("Found extensions: %q", ext)
350+
351+
snameStart, snameSize := findSnameExt(ext)
352+
if snameStart >= 0 {
353+
// we found a server name extension
354+
// let's repackage it into small fragmented records
355+
snameStart += extStart
356+
snameEnd := snameStart + snameSize
357+
log.Printf("Fragmenting sname: %q", c.handshake[snameStart:snameEnd])
358+
pos = snameStart - 3 // we want to fragment the 00 00 tag as well
359+
// log.Printf("Original buffer: %q", c.buffer)
360+
c.buffer = c.buffer[:0]
361+
c.appendHandshakeRecord(c.handshake[:pos])
362+
for pos+2 < snameEnd {
363+
c.appendHandshakeRecord(c.handshake[pos : pos+2])
364+
pos += 2
365+
}
366+
c.appendHandshakeRecord(c.handshake[pos:])
367+
// log.Printf("Final buffer: %q", c.buffer)
368+
}
369+
370+
return nil
371+
}
372+
373+
func (c *fragmentedReader) Read(b []byte) (int, error) {
374+
if !c.processed {
375+
err := c.processClientHello()
376+
if err != nil {
377+
c.handshakeErr = err
378+
}
379+
c.processed = true
380+
}
381+
if len(c.buffer) > 0 {
382+
n := len(c.buffer)
383+
if n > len(b) {
384+
n = len(b)
385+
}
386+
copy(b[:n], c.buffer[:n])
387+
c.buffer = c.buffer[n:]
388+
if len(c.buffer) == 0 {
389+
c.buffer = nil
390+
}
391+
return n, nil
392+
}
393+
if c.handshakeErr != nil {
394+
return 0, c.handshakeErr
395+
}
396+
n, err := c.Reader.Read(b)
397+
return n, err
398+
}
399+
202400
func (p *SecureReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
203401
if req.Host == "localhost" || strings.HasPrefix(req.Host, "localhost:") {
204402
rw.WriteHeader(200)
@@ -228,6 +426,9 @@ func (p *SecureReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request
228426
if actions&actionDirect != 0 {
229427
prefix = "(direct) "
230428
}
429+
if actions&actionFragment != 0 {
430+
prefix += "(fragmented) "
431+
}
231432
suffix := ""
232433
if actions&actionBlock != 0 {
233434
suffix = " (blocked)"
@@ -281,11 +482,11 @@ func (p *SecureReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request
281482
io.WriteString(b, "HTTP/1.1 200 OK\r\n\r\n")
282483
b.Flush() // this is the last write into b
283484

284-
// write side runs it its own goroutine
485+
// write side runs in its own goroutine
285486
go func() {
286487
defer local.Close()
287488
defer remote.Close()
288-
var buffer [65536]byte
489+
var buffer [1024]byte
289490
done := false
290491
for !done {
291492
n, err := remote.Read(buffer[:])
@@ -307,11 +508,18 @@ func (p *SecureReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request
307508
}
308509
}()
309510

511+
var r io.Reader = b
512+
if actions&actionFragment != 0 {
513+
r = &fragmentedReader{
514+
Reader: r,
515+
}
516+
}
517+
310518
// read side runs here, first we grab what we have in in b
311-
var buffer [65536]byte
519+
var buffer [1024]byte
312520
done := false
313521
for !done {
314-
n, err := b.Read(buffer[:])
522+
n, err := r.Read(buffer[:])
315523
if n > 0 {
316524
_, werr := remote.Write(buffer[:n])
317525
if werr != nil {

0 commit comments

Comments
 (0)