Skip to content

Commit

Permalink
Merge pull request #1 from getlantern/init
Browse files Browse the repository at this point in the history
Initial Work
  • Loading branch information
hwh33 authored May 28, 2020
2 parents 355012d + 377a314 commit c5b8482
Show file tree
Hide file tree
Showing 4 changed files with 310 additions and 0 deletions.
92 changes: 92 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package hellosplitter

import (
"crypto/tls"
"fmt"
"net"
"os"
)

func Example() {
s, err := startTLSHandshakeServer()
if err != nil {
panic(err)
}
tcpConn, err := net.Dial("tcp", s.Addr())
if err != nil {
panic(err)
}
tcpConn = Wrap(tcpConn, func(b []byte) [][]byte {
splits := make([][]byte, 2)
splits[0], splits[1] = b[:len(b)/2], b[len(b)/2:]
return splits
})
tlsConn := tls.Client(tcpConn, &tls.Config{InsecureSkipVerify: true})
defer tlsConn.Close()
if err := tlsConn.Handshake(); err != nil {
panic(err)
}
}

// Conducts a handshake with any incoming TLS client connections.
type tlsHandshakeServer struct {
l net.Listener
}

func startTLSHandshakeServer() (*tlsHandshakeServer, error) {
l, err := tls.Listen("tcp", "", &tls.Config{Certificates: []tls.Certificate{cert}})
if err != nil {
return nil, fmt.Errorf("failed to start TLS listener: %w", err)
}
go func() {
for {
conn, err := l.Accept()
if err != nil {
fmt.Fprintln(os.Stderr, "server: accept error:", err)
return
}
if err := conn.(*tls.Conn).Handshake(); err != nil {
fmt.Fprintln(os.Stderr, "server: handshake error:", err)
}
conn.Close()
}
}()
return &tlsHandshakeServer{l}, nil
}

func (ths tlsHandshakeServer) Addr() string {
return ths.l.Addr().String()
}

func (ths tlsHandshakeServer) Close() error {
return ths.l.Close()
}

var (
certPem = []byte(`-----BEGIN CERTIFICATE-----
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
6MF9+Yw1Yy0t
-----END CERTIFICATE-----`)
keyPem = []byte(`-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49
AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q
EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
-----END EC PRIVATE KEY-----`)

cert tls.Certificate
)

func init() {
var err error
cert, err = tls.X509KeyPair(certPem, keyPem)
if err != nil {
panic(err)
}
}
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/getlantern/hellosplitter

go 1.13

require (
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7
github.com/getlantern/tlsutil v0.2.0
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
)
33 changes: 33 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/tlsutil v0.2.0 h1:i1X/r3w6FLaceoNBkTsRXWcE3Ujz7rbWmtipS28vvQo=
github.com/getlantern/tlsutil v0.2.0/go.mod h1:Vxsyr9DVnYwsqHaEzMYkg9fT8aBrnO2eI+gdICMQbQU=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
176 changes: 176 additions & 0 deletions hellosplitter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Package hellosplitter is used to split TLS ClientHello messages across multiple TCP packets. To
// achieve this, use hellosplitter's Conn as a transport in a TLS client connection.
package hellosplitter

import (
"bytes"
"errors"
"fmt"
"io"
"net"
"sync"

"github.com/getlantern/golog"
"github.com/getlantern/tlsutil"
)

var log = golog.LoggerFor("hellosplitter")

// A BufferedWriteError occurs when a Conn attempts to write buffered data and fails.
type BufferedWriteError struct {
BufferedData []byte

// Written is the number of bytes from BufferedData which were successfully written to the
// underlying transport.
Written int

cause error
}

func (err BufferedWriteError) Error() string {
msg := "failed to write all buffered data to transport"
if err.cause != nil {
return fmt.Sprintf("%s: %v", msg, err.cause)
}
return msg
}

// Unwrap supports Go 1.13-style error unwrapping.
func (err BufferedWriteError) Unwrap() error {
return err.cause
}

// A HelloParsingError occurs when a Conn fails to parse buffered data as a ClientHello.
type HelloParsingError struct {
BufferedData []byte
cause error
}

func (err HelloParsingError) Error() string {
return fmt.Sprintf("failed to parse buffered data as client hello: %v", err.cause)
}

// Unwrap supports Go 1.13-style error unwrapping.
func (err HelloParsingError) Unwrap() error {
return err.cause
}

// A SplitFunc defines how a ClientHello is split.
type SplitFunc func([]byte) [][]byte

// Conn is intended for use as a transport in a TLS client connection. When Conn is used in this
// manner, the ClientHello will be split across multiple TCP packets. The ClientHello should be the
// first record written to this connection.
type Conn struct {
net.Conn

splitFunc SplitFunc

// Everything passed to Write gets written to helloBuf until (a) we have processed a full
// ClientHello or (b) we are sure that what we have processed could not constitute be part of a
// ClientHello.
helloBuf *bytes.Buffer

wroteHello bool
wroteHelloLock sync.Mutex

// True iff SetNoDelay(false) was called before the ClientHello was sent. Also protected by
// wroteHelloLock.
queuedNoDelayFalse bool
}

// Wrap the input connection with a hello-splitting connection. The ClientHello should be the first
// record written to the returned connection.
//
// If conn is a *net.TCPConn, then TCP_NODELAY will be configured on the connection. This is a
// requirement for splitting the ClientHello. To override this behavior for transmissions after the
// ClientHello, use Conn.SetNoDelay.
//
// If conn is not a *net.TCPConn, it must mimic TCP_NODELAY (sending packets as soon as they are
// ready). If the host OS does not support TCP_NODELAY, hello-splitting may not function as desired.
func Wrap(conn net.Conn, f SplitFunc) *Conn {
return &Conn{conn, f, new(bytes.Buffer), false, sync.Mutex{}, false}
}

func (c *Conn) Write(b []byte) (n int, err error) {
n, err = c.checkHello(b)
if err != nil || n == len(b) {
return
}
var m int
m, err = c.Conn.Write(b[n:])
return n + m, err
}

// SetNoDelay behaves like net.TCPConn.SetNoDelay except that the choice only applies after the
// ClientHello. Until the ClientHello is set, TCP_NODELAY will always be used.
//
// This is a no-op if the underlying transport is not a *net.TCPConn.
func (c *Conn) SetNoDelay(noDelay bool) error {
c.wroteHelloLock.Lock()
defer c.wroteHelloLock.Unlock()
if c.wroteHello {
if tcpConn, ok := c.Conn.(*net.TCPConn); ok {
return tcpConn.SetNoDelay(noDelay)
}
} else if !noDelay {
c.queuedNoDelayFalse = true
}
return nil
}

// Returns the number of bytes written *from b*. Note that more bytes may be written to the
// underlying transport if part of the hello was previously buffered.
func (c *Conn) checkHello(b []byte) (int, error) {
c.wroteHelloLock.Lock()
defer c.wroteHelloLock.Unlock()
if !c.wroteHello {
c.helloBuf.Write(b) // Writes to bytes.Buffers do not return errors.
nHello, parseErr := tlsutil.ValidateClientHello(c.helloBuf.Bytes())
tcpConn, isTCPConn := c.Conn.(*net.TCPConn)
if isTCPConn {
// No delay is the default, but we set it for good measure. If the type check fails and
// the transport is not a net.TCPConn, we just assume the user knows what they're doing.
if err := tcpConn.SetNoDelay(true); err != nil {
log.Errorf("failed to set TCP_NODELAY: %v", err)
}
}
if parseErr == nil {
var writeN int
splits := c.splitFunc(c.helloBuf.Bytes()[:nHello])
for _, split := range splits {
splitN, splitErr := c.Conn.Write(split)
writeN += splitN
if splitErr != nil {
writtenFromB := max(len(b)-(c.helloBuf.Len()-writeN), 0)
return writtenFromB, BufferedWriteError{contents(c.helloBuf), writeN, nil}
}
}
writtenFromB := len(b) - (c.helloBuf.Len() - nHello)
c.wroteHello = true
if c.queuedNoDelayFalse && isTCPConn {
if err := tcpConn.SetNoDelay(false); err != nil {
log.Errorf("failed to disable TCP_NODELAY after hello: %v", err)
}
}
c.helloBuf = nil
return writtenFromB, nil
} else if !errors.Is(parseErr, io.EOF) {
return 0, HelloParsingError{contents(c.helloBuf), parseErr}
}
}
return 0, nil
}

func contents(buf *bytes.Buffer) []byte {
b := make([]byte, buf.Len())
copy(b, buf.Bytes())
return b
}

func max(a, b int) int {
if a > b {
return a
}
return b
}

0 comments on commit c5b8482

Please sign in to comment.