transport.go 2.51 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)
Marten Seemann's avatar
Marten Seemann committed
46
	return t.handshake(ctx, 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))
Marten Seemann's avatar
Marten Seemann committed
52
	return t.handshake(ctx, cl)
53
}
54

Marten Seemann's avatar
Marten Seemann committed
55
func (t *Transport) handshake(ctx context.Context, tlsConn *tls.Conn,
56
) (cs.Conn, error) {
57 58 59
	// There's no way to pass a context to tls.Conn.Handshake().
	// See https://github.com/golang/go/issues/18482.
	// Close the connection instead.
60 61 62 63 64
	select {
	case <-ctx.Done():
		tlsConn.Close()
	default:
	}
65 66 67 68 69 70
	done := make(chan struct{})
	defer close(done)
	go func() {
		select {
		case <-done:
		case <-ctx.Done():
Marten Seemann's avatar
Marten Seemann committed
71
			tlsConn.Close()
72 73 74
		}
	}()

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

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
}