transport.go 3.8 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

	cs "github.com/libp2p/go-conn-security"
	ci "github.com/libp2p/go-libp2p-crypto"
13
	ic "github.com/libp2p/go-libp2p-crypto"
Marten Seemann's avatar
Marten Seemann committed
14 15 16
	peer "github.com/libp2p/go-libp2p-peer"
)

Marten Seemann's avatar
Marten Seemann committed
17 18 19 20 21 22
// 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
23 24 25 26 27 28 29 30 31
// 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
32

33 34
	activeMutex sync.Mutex
	active      map[net.Conn]ic.PubKey
Marten Seemann's avatar
Marten Seemann committed
35 36 37 38 39 40 41 42
}

// New creates a TLS encrypted transport
func New(key ci.PrivKey) (*Transport, error) {
	id, err := peer.IDFromPrivateKey(key)
	if err != nil {
		return nil, err
	}
43 44 45
	t := &Transport{
		localPeer: id,
		privKey:   key,
46
		active:    make(map[net.Conn]ic.PubKey),
47
	}
48 49 50 51 52 53

	identity, err := NewIdentity(key, func(conn net.Conn, pubKey ic.PubKey) {
		t.activeMutex.Lock()
		t.active[conn] = pubKey
		t.activeMutex.Unlock()
	})
Marten Seemann's avatar
Marten Seemann committed
54 55 56
	if err != nil {
		return nil, err
	}
57 58
	t.identity = identity
	return t, nil
Marten Seemann's avatar
Marten Seemann committed
59 60 61 62 63 64
}

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) {
65
	defer func() {
66
		t.activeMutex.Lock()
67
		// only contains this connection if we successfully derived the client's key
68 69
		delete(t.active, insecure)
		t.activeMutex.Unlock()
70 71
	}()

Marten Seemann's avatar
Marten Seemann committed
72
	serv := tls.Server(insecure, t.identity.Config)
73
	return t.handshake(ctx, insecure, serv)
Marten Seemann's avatar
Marten Seemann committed
74 75 76
}

// SecureOutbound runs the TLS handshake as a client.
Marten Seemann's avatar
Marten Seemann committed
77 78 79 80 81 82
// 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
83
func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (cs.Conn, error) {
84 85 86 87 88 89
	verifiedCallback := func(pubKey ic.PubKey) {
		t.activeMutex.Lock()
		t.active[insecure] = pubKey
		t.activeMutex.Unlock()
	}
	cl := tls.Client(insecure, t.identity.ConfigForPeer(p, verifiedCallback))
90
	return t.handshake(ctx, insecure, cl)
91
}
92

93 94 95 96
func (t *Transport) handshake(
	ctx context.Context,
	insecure net.Conn,
	tlsConn *tls.Conn,
97
) (cs.Conn, error) {
98 99 100
	// There's no way to pass a context to tls.Conn.Handshake().
	// See https://github.com/golang/go/issues/18482.
	// Close the connection instead.
101 102 103 104 105
	select {
	case <-ctx.Done():
		tlsConn.Close()
	default:
	}
106 107 108 109 110 111
	done := make(chan struct{})
	defer close(done)
	go func() {
		select {
		case <-done:
		case <-ctx.Done():
Marten Seemann's avatar
Marten Seemann committed
112
			tlsConn.Close()
113 114 115
		}
	}()

116 117
	if err := tlsConn.Handshake(); err != nil {
		// if the context was canceled, return the context error
118 119 120 121
		if ctxErr := ctx.Err(); ctxErr != nil {
			return nil, ctxErr
		}
		return nil, err
122
	}
123
	conn, err := t.setupConn(insecure, tlsConn)
124 125
	if err != nil {
		// if the context was canceled, return the context error
126 127 128 129
		if ctxErr := ctx.Err(); ctxErr != nil {
			return nil, ctxErr
		}
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
130
	}
131
	return conn, nil
Marten Seemann's avatar
Marten Seemann committed
132 133
}

134
func (t *Transport) setupConn(insecure net.Conn, tlsConn *tls.Conn) (cs.Conn, error) {
135 136 137
	t.activeMutex.Lock()
	remotePubKey := t.active[insecure]
	t.activeMutex.Unlock()
138 139

	if remotePubKey == nil {
140
		return nil, errors.New("go-libp2p-tls BUG: expected remote pub key to be set")
Marten Seemann's avatar
Marten Seemann committed
141
	}
142

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
}