transport.go 3.06 KB
Newer Older
Marten Seemann's avatar
Marten Seemann committed
1 2 3 4 5 6
package libp2ptls

import (
	"context"
	"crypto/tls"
	"net"
Marten Seemann's avatar
Marten Seemann committed
7
	"os"
Marten Seemann's avatar
Marten Seemann committed
8 9 10 11 12 13

	cs "github.com/libp2p/go-conn-security"
	ci "github.com/libp2p/go-libp2p-crypto"
	peer "github.com/libp2p/go-libp2p-peer"
)

Marten Seemann's avatar
Marten Seemann committed
14 15 16 17 18 19
// TLS 1.3 is opt-in in Go 1.12
// Activate it by setting the tls13 GODEBUG flag.
func init() {
	os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1")
}

Marten Seemann's avatar
Marten Seemann committed
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
// ID is the protocol ID (used when negotiating with multistream)
const ID = "/tls/1.0.0"

// Transport constructs secure communication sessions for a peer.
type Transport struct {
	identity *Identity

	localPeer peer.ID
	privKey   ci.PrivKey
}

// New creates a TLS encrypted transport
func New(key ci.PrivKey) (*Transport, error) {
	id, err := peer.IDFromPrivateKey(key)
	if err != nil {
		return nil, err
	}
	identity, err := NewIdentity(key)
	if err != nil {
		return nil, err
	}
	return &Transport{
		identity:  identity,
		localPeer: id,
		privKey:   key,
	}, nil
}

var _ cs.Transport = &Transport{}

// SecureInbound runs the TLS handshake as a server.
func (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn) (cs.Conn, error) {
	serv := tls.Server(insecure, t.identity.Config)
Marten Seemann's avatar
Marten Seemann committed
53
	return t.handshake(ctx, serv)
Marten Seemann's avatar
Marten Seemann committed
54 55 56
}

// SecureOutbound runs the TLS handshake as a client.
Marten Seemann's avatar
Marten Seemann committed
57 58 59 60 61 62
// Note that SecureOutbound will not return an error if the server doesn't
// accept the certificate. This is due to the fact that in TLS 1.3, the client
// sends its certificate and the ClientFinished in the same flight, and can send
// application data immediately afterwards.
// If the handshake fails, the server will close the connection. The client will
// notice this after 1 RTT when calling Read.
Marten Seemann's avatar
Marten Seemann committed
63 64
func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (cs.Conn, error) {
	cl := tls.Client(insecure, t.identity.ConfigForPeer(p))
Marten Seemann's avatar
Marten Seemann committed
65
	return t.handshake(ctx, cl)
66
}
67

Marten Seemann's avatar
Marten Seemann committed
68
func (t *Transport) handshake(ctx context.Context, tlsConn *tls.Conn,
69
) (cs.Conn, error) {
70 71 72
	// There's no way to pass a context to tls.Conn.Handshake().
	// See https://github.com/golang/go/issues/18482.
	// Close the connection instead.
73 74 75 76 77
	select {
	case <-ctx.Done():
		tlsConn.Close()
	default:
	}
78 79 80 81 82 83
	done := make(chan struct{})
	defer close(done)
	go func() {
		select {
		case <-done:
		case <-ctx.Done():
Marten Seemann's avatar
Marten Seemann committed
84
			tlsConn.Close()
85 86 87
		}
	}()

88 89
	if err := tlsConn.Handshake(); err != nil {
		// if the context was canceled, return the context error
90 91 92 93
		if ctxErr := ctx.Err(); ctxErr != nil {
			return nil, ctxErr
		}
		return nil, err
94 95 96 97
	}
	conn, err := t.setupConn(tlsConn)
	if err != nil {
		// if the context was canceled, return the context error
98 99 100 101
		if ctxErr := ctx.Err(); ctxErr != nil {
			return nil, ctxErr
		}
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
102
	}
103
	return conn, nil
Marten Seemann's avatar
Marten Seemann committed
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
}

func (t *Transport) setupConn(tlsConn *tls.Conn) (cs.Conn, error) {
	remotePubKey, err := KeyFromChain(tlsConn.ConnectionState().PeerCertificates)
	if err != nil {
		return nil, err
	}
	remotePeerID, err := peer.IDFromPublicKey(remotePubKey)
	if err != nil {
		return nil, err
	}
	return &conn{
		Conn:         tlsConn,
		localPeer:    t.localPeer,
		privKey:      t.privKey,
		remotePeer:   remotePeerID,
		remotePubKey: remotePubKey,
	}, nil
}