ptlshs

package
v0.4.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 14, 2021 License: Apache-2.0 Imports: 18 Imported by: 2

Documentation

Overview

Package ptlshs implements proxied TLS handshakes. When a client dials a ptlshs listener, the initial handshake is proxied to another TLS server. When this proxied handshake is complete, the dialer signals to the listener and the connection is established. From here, another, "true" handshake may be performed, but this is not the purvue of the ptlshs package.

Example

Example demonstrates proxied TLS handshakes.

package main

import (
	"context"
	"crypto/rand"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strings"
	"time"
)

// Example demonstrates proxied TLS handshakes.
func main() {
	var (
		clientMsg, serverMsg = "hello from the client", "hello from the server"
		successfulHandshakes = make(chan struct{}, 1)
	)

	// Start a TLS server to which we will proxy the handshake.
	tlsServerAddr, err := startTLSServer(successfulHandshakes)
	if err != nil {
		panic(err)
	}

	secret := new(Secret)
	if _, err := rand.Read(secret[:]); err != nil {
		panic(err)
	}

	// Start a TCP server which begins with our proxied TLS handshake.
	dialOrigin := func(_ context.Context) (net.Conn, error) { return net.Dial("tcp", tlsServerAddr) }
	listenerCfg := ListenerConfig{DialOrigin: dialOrigin, Secret: *secret}
	l, err := Listen("tcp", "localhost:0", listenerCfg)
	defer l.Close()

	go func() {
		for {
			conn, err := l.Accept()
			if err != nil && strings.Contains(err.Error(), "use of closed network connection") {
				// This is an unexported error indicating that the connection is closed.
				// See https://golang.org/pkg/internal/poll/#pkg-variables
				return
			}
			if err != nil {
				panic(err)
			}
			go func(c net.Conn) {
				defer c.Close()

				b := make([]byte, len(clientMsg))
				if _, err := c.Read(b); err != nil {
					panic(err)
				}
				fmt.Println("received message from the client:", string(b))

				if _, err := c.Write([]byte(serverMsg)); err != nil {
					panic(err)
				}
			}(conn)
		}
	}()

	// Dial with a ptlshs client.
	dialerCfg := DialerConfig{
		Handshaker: StdLibHandshaker{
			Config: &tls.Config{InsecureSkipVerify: true},
		},
		Secret: *secret,
	}
	conn, err := Dial("tcp", l.Addr().String(), dialerCfg)
	if err != nil {
		panic(err)
	}

	// We can use the connection like any other now.
	if _, err := conn.Write([]byte(clientMsg)); err != nil {
		panic(err)
	}
	b := make([]byte, len(serverMsg))
	if _, err := conn.Read(b); err != nil {
		panic(err)
	}
	fmt.Println("received message from the server:", string(b))

	// Make sure there was actually a handshake with the TLS server. This is not necessary, we're
	// just demonstrating that the handshake actually occurred.
	select {
	case <-successfulHandshakes:
	case <-time.After(100 * time.Millisecond):
		panic("no handshake with TLS server")
	}

	// Try connecting to the server with an unsuspecting HTTPS client. The connection will simply be
	// proxied to the TLS server.
	httpClient := http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
	resp, err := httpClient.Get("https://" + l.Addr().String())
	if err != nil {
		panic(err)
	}
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println("HTTPS client got:", string(respBody))

}

// Each successful handshake will result in a signal sent on the input channel.
func startTLSServer(successfulHandshakes chan<- struct{}) (addr string, err error) {
	tlsListener, err := tls.Listen("tcp", "localhost:0", &tls.Config{Certificates: []tls.Certificate{cert}})
	if err != nil {
		return "", err
	}
	loggingListener := loggingTLSListener{tlsListener, successfulHandshakes}
	go func() {
		log.Fatal(http.Serve(loggingListener, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			fmt.Fprint(w, "Hello from the TLS server")
		})))
	}()
	return tlsListener.Addr().String(), nil
}

type loggingTLSListener struct {
	net.Listener
	handshakes chan<- struct{}
}

func (l loggingTLSListener) Accept() (net.Conn, error) {
	conn, err := l.Listener.Accept()
	if err != nil {
		return nil, err
	}
	if err := conn.(*tls.Conn).Handshake(); err != nil {
		return nil, fmt.Errorf("handshake failed: %w", err)
	}
	l.handshakes <- struct{}{}
	return conn, nil
}

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)
	}
}
Output:

received message from the client: hello from the client
received message from the server: hello from the server
HTTPS client got: Hello from the TLS server

Index

Examples

Constants

View Source
const (
	// DefaultNonceTTL is used when DialerConfig.NonceTTL is not specified.
	DefaultNonceTTL = 30 * time.Minute

	// DefaultNonceSweepInterval is used when ListenerConfig.NonceSweepInterval is not specified.
	DefaultNonceSweepInterval = time.Minute
)

Variables

This section is empty.

Functions

func Dial

func Dial(network, address string, cfg DialerConfig) (net.Conn, error)

Dial a ptlshs listener.

func DialTimeout

func DialTimeout(network, address string, cfg DialerConfig, timeout time.Duration) (net.Conn, error)

DialTimeout acts like Dial but takes a timeout.

func Listen

func Listen(network, address string, cfg ListenerConfig) (net.Listener, error)

Listen for ptlshs dialers.

func WrapListener

func WrapListener(l net.Listener, cfg ListenerConfig) net.Listener

WrapListener wraps the input listener with one which speaks the ptlshs protocol.

Types

type Conn

type Conn interface {
	net.Conn

	// Handshake executes the ptlshs handshake protocol, if it has not yet been performed. Note
	// that, per the protocol, the connection will proxy all data until the completion signal. Thus,
	// if this connection comes from an active probe, this handshake function may not return until
	// the probe closes the connection on its end. As a result, this function should be treated as
	// one which may be long-running or never return.
	Handshake() error

	// TLSVersion is the TLS version negotiated during the proxied handshake.
	TLSVersion() uint16

	// CipherSuite is the cipher suite negotiated during the proxied handshake.
	CipherSuite() uint16

	// NextSeq increments and returns the connection's sequence number. The starting sequence number
	// is derived from the server random in the proxied handshake. Clients and servers will have the
	// same derived sequence numbers, so this can be used in cipher suites which use the sequence
	// number as a nonce.
	NextSeq() [8]byte

	// IV is an initialization vector. This is derived from the server random in the proxied handshake.
	// Dialers and listeners will have the same IV, so this can be used when needed in ciphers.
	IV() [16]byte
}

Conn is a network connection between two peers speaking the ptlshs protocol. Methods returning connection state data (TLSVersion, CipherSuite, etc.) block until the handshake is complete.

Connections returned by listeners and dialers in this package will implement this interface. However, most users of this package can ignore this type.

func Client

func Client(toServer net.Conn, cfg DialerConfig) Conn

Client initializes a client-side connection.

func Server

func Server(toClient net.Conn, cfg ListenerConfig) Conn

Server initializes a server-side connection.

type Dialer

type Dialer interface {
	Dial(network, address string) (net.Conn, error)
	DialContext(ctx context.Context, network, address string) (net.Conn, error)
}

Dialer is the interface implemented by network dialers.

func WrapDialer

func WrapDialer(d Dialer, cfg DialerConfig) Dialer

WrapDialer wraps the input dialer with a network dialer which will perform the ptlshs protocol.

type DialerConfig

type DialerConfig struct {
	// A Secret pre-shared between listeners and dialers. This value must be set.
	Secret Secret

	// Handshaker allows for customization of the handshake with the origin server. This can be
	// important as the handshake performed by the Go standard library can be fingerprinted. May not
	// be nil.
	Handshaker Handshaker

	// NonceTTL specifies the time-to-live for nonces used in completion signals. DefaultNonceTTL is
	// used if NonceTTL is unspecified.
	NonceTTL time.Duration
}

DialerConfig specifies configuration for dialing.

type HandshakeResult

type HandshakeResult struct {
	Version, CipherSuite uint16
}

HandshakeResult is the result of a TLS handshake.

type Handshaker

type Handshaker interface {
	Handshake(net.Conn) (*HandshakeResult, error)
}

Handshaker executes a TLS handshake using the input connection as a transport. Handshakers must unblock and return an error if the connection is closed. Usually this does not require special handling as the connection's I/O functions should do the same.

type ListenerConfig

type ListenerConfig struct {
	// DialOrigin is used to create TCP connections to the origin server. Must not be nil.
	DialOrigin func(context.Context) (net.Conn, error)

	// A Secret pre-shared between listeners and dialers.
	Secret Secret

	// NonceSweepInterval determines how often the nonce cache is swept for expired entries. If not
	// specified, DefaultNonceSweepInterval will be used.
	NonceSweepInterval time.Duration

	// NonFatalErrors will be used to log non-fatal errors. These will likely be due to probes.
	NonFatalErrors chan<- error
}

ListenerConfig specifies configuration for listening.

type Secret

type Secret [52]byte

A Secret pre-shared between listeners and dialers. This is used to secure the completion signal sent by the dialer.

type StdLibHandshaker

type StdLibHandshaker struct {
	// Config for the handshake. May not be nil. ServerName should be set according to the origin
	// server.
	Config *tls.Config
}

StdLibHandshaker execute a TLS handshakes using the Go standard library.

func (StdLibHandshaker) Handshake

func (h StdLibHandshaker) Handshake(conn net.Conn) (*HandshakeResult, error)

Handshake executes a TLS handshake using conn as a transport.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL