-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from getlantern/init
Initial Work
- Loading branch information
Showing
4 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |