transport.go 2.58 KB
Newer Older
Marten Seemann's avatar
Marten Seemann committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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
package libp2ptls

import (
	"context"
	"crypto/tls"
	"net"

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

// 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)
46
	return t.handshake(ctx, insecure, serv)
Marten Seemann's avatar
Marten Seemann committed
47 48 49 50 51
}

// SecureOutbound runs the TLS handshake as a client.
func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (cs.Conn, error) {
	cl := tls.Client(insecure, t.identity.ConfigForPeer(p))
52 53
	return t.handshake(ctx, insecure, cl)
}
54

55 56 57 58 59 60 61 62
func (t *Transport) handshake(
	ctx context.Context,
	// in Go 1.10, we need to close the underlying net.Conn
	// in Go 1.11 this was fixed, and tls.Conn.Close() works as well
	insecure net.Conn,
	tlsConn *tls.Conn,
) (cs.Conn, error) {
	errChan := make(chan error, 2)
63 64 65 66 67 68 69 70 71
	// There's no way to pass a context to tls.Conn.Handshake().
	// See https://github.com/golang/go/issues/18482.
	// Close the connection instead.
	done := make(chan struct{})
	defer close(done)
	go func() {
		select {
		case <-done:
		case <-ctx.Done():
72
			errChan <- ctx.Err()
73 74 75 76
			insecure.Close()
		}
	}()

77 78 79 80 81 82 83 84 85 86
	if err := tlsConn.Handshake(); err != nil {
		// if the context was canceled, return the context error
		errChan <- err
		return nil, <-errChan
	}
	conn, err := t.setupConn(tlsConn)
	if err != nil {
		// if the context was canceled, return the context error
		errChan <- err
		return nil, <-errChan
Marten Seemann's avatar
Marten Seemann committed
87
	}
88
	return conn, nil
Marten Seemann's avatar
Marten Seemann committed
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
}

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
}