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

import (
	"context"
	"crypto/tls"
6
	"errors"
Marten Seemann's avatar
Marten Seemann committed
7
	"net"
Marten Seemann's avatar
Marten Seemann committed
8
	"os"
9
	"sync"
Marten Seemann's avatar
Marten Seemann committed
10

11 12 13
	ci "github.com/libp2p/go-libp2p-core/crypto"
	"github.com/libp2p/go-libp2p-core/peer"
	"github.com/libp2p/go-libp2p-core/sec"
Marten Seemann's avatar
Marten Seemann committed
14 15
)

Marten Seemann's avatar
Marten Seemann committed
16 17 18 19 20 21
// 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
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
// 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
	}
39 40 41 42
	t := &Transport{
		localPeer: id,
		privKey:   key,
	}
43

44
	identity, err := NewIdentity(key)
Marten Seemann's avatar
Marten Seemann committed
45 46 47
	if err != nil {
		return nil, err
	}
48 49
	t.identity = identity
	return t, nil
Marten Seemann's avatar
Marten Seemann committed
50 51
}

52
var _ sec.SecureTransport = &Transport{}
Marten Seemann's avatar
Marten Seemann committed
53 54

// SecureInbound runs the TLS handshake as a server.
55
func (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn) (sec.SecureConn, error) {
56
	config, keyCh := t.identity.ConfigForAny()
57 58 59 60 61
	cs, err := t.handshake(ctx, tls.Server(insecure, config), keyCh)
	if err != nil {
		insecure.Close()
	}
	return cs, err
Marten Seemann's avatar
Marten Seemann committed
62 63 64
}

// SecureOutbound runs the TLS handshake as a client.
Marten Seemann's avatar
Marten Seemann committed
65 66 67 68 69 70
// 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.
71
func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
72
	config, keyCh := t.identity.ConfigForPeer(p)
73 74 75 76 77
	cs, err := t.handshake(ctx, tls.Client(insecure, config), keyCh)
	if err != nil {
		insecure.Close()
	}
	return cs, err
78
}
79

80 81 82
func (t *Transport) handshake(
	ctx context.Context,
	tlsConn *tls.Conn,
83
	keyCh <-chan ci.PubKey,
84
) (sec.SecureConn, error) {
85 86 87
	// There's no way to pass a context to tls.Conn.Handshake().
	// See https://github.com/golang/go/issues/18482.
	// Close the connection instead.
88 89 90 91 92
	select {
	case <-ctx.Done():
		tlsConn.Close()
	default:
	}
93

94
	done := make(chan struct{})
95 96 97 98 99 100
	var wg sync.WaitGroup

	// Ensure that we do not return before
	// either being done or having a context
	// cancellation.
	defer wg.Wait()
101
	defer close(done)
102 103

	wg.Add(1)
104
	go func() {
105
		defer wg.Done()
106 107 108
		select {
		case <-done:
		case <-ctx.Done():
Marten Seemann's avatar
Marten Seemann committed
109
			tlsConn.Close()
110 111 112
		}
	}()

113 114
	if err := tlsConn.Handshake(); err != nil {
		// if the context was canceled, return the context error
115 116 117 118
		if ctxErr := ctx.Err(); ctxErr != nil {
			return nil, ctxErr
		}
		return nil, err
119
	}
120 121

	// Should be ready by this point, don't block.
Marten Seemann's avatar
Marten Seemann committed
122
	var remotePubKey ci.PubKey
123 124 125 126
	select {
	case remotePubKey = <-keyCh:
	default:
	}
127 128 129
	if remotePubKey == nil {
		return nil, errors.New("go-libp2p-tls BUG: expected remote pub key to be set")
	}
130 131

	conn, err := t.setupConn(tlsConn, remotePubKey)
132 133
	if err != nil {
		// if the context was canceled, return the context error
134 135 136 137
		if ctxErr := ctx.Err(); ctxErr != nil {
			return nil, ctxErr
		}
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
138
	}
139
	return conn, nil
Marten Seemann's avatar
Marten Seemann committed
140 141
}

142
func (t *Transport) setupConn(tlsConn *tls.Conn, remotePubKey ci.PubKey) (sec.SecureConn, error) {
Marten Seemann's avatar
Marten Seemann committed
143 144 145 146 147 148 149 150 151 152 153 154
	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
}