mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-27 17:15:18 +00:00
183 lines
5.4 KiB
Go
183 lines
5.4 KiB
Go
|
package libp2ptls
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"crypto/tls"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"os"
|
||
|
"runtime/debug"
|
||
|
|
||
|
"github.com/libp2p/go-libp2p/core/canonicallog"
|
||
|
ci "github.com/libp2p/go-libp2p/core/crypto"
|
||
|
"github.com/libp2p/go-libp2p/core/network"
|
||
|
"github.com/libp2p/go-libp2p/core/peer"
|
||
|
"github.com/libp2p/go-libp2p/core/protocol"
|
||
|
"github.com/libp2p/go-libp2p/core/sec"
|
||
|
tptu "github.com/libp2p/go-libp2p/p2p/net/upgrader"
|
||
|
|
||
|
manet "github.com/multiformats/go-multiaddr/net"
|
||
|
)
|
||
|
|
||
|
// 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
|
||
|
muxers []protocol.ID
|
||
|
protocolID protocol.ID
|
||
|
}
|
||
|
|
||
|
var _ sec.SecureTransport = &Transport{}
|
||
|
|
||
|
// New creates a TLS encrypted transport
|
||
|
func New(id protocol.ID, key ci.PrivKey, muxers []tptu.StreamMuxer) (*Transport, error) {
|
||
|
localPeer, err := peer.IDFromPrivateKey(key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
muxerIDs := make([]protocol.ID, 0, len(muxers))
|
||
|
for _, m := range muxers {
|
||
|
muxerIDs = append(muxerIDs, m.ID)
|
||
|
}
|
||
|
t := &Transport{
|
||
|
protocolID: id,
|
||
|
localPeer: localPeer,
|
||
|
privKey: key,
|
||
|
muxers: muxerIDs,
|
||
|
}
|
||
|
|
||
|
identity, err := NewIdentity(key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
t.identity = identity
|
||
|
return t, nil
|
||
|
}
|
||
|
|
||
|
// SecureInbound runs the TLS handshake as a server.
|
||
|
// If p is empty, connections from any peer are accepted.
|
||
|
func (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
|
||
|
config, keyCh := t.identity.ConfigForPeer(p)
|
||
|
muxers := make([]string, 0, len(t.muxers))
|
||
|
for _, muxer := range t.muxers {
|
||
|
muxers = append(muxers, string(muxer))
|
||
|
}
|
||
|
// TLS' ALPN selection lets the server select the protocol, preferring the server's preferences.
|
||
|
// We want to prefer the client's preference though.
|
||
|
getConfigForClient := config.GetConfigForClient
|
||
|
config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||
|
alpnLoop:
|
||
|
for _, proto := range info.SupportedProtos {
|
||
|
for _, m := range muxers {
|
||
|
if m == proto {
|
||
|
// Match found. Select this muxer, as it's the client's preference.
|
||
|
// There's no need to add the "libp2p" entry here.
|
||
|
config.NextProtos = []string{proto}
|
||
|
break alpnLoop
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if getConfigForClient != nil {
|
||
|
return getConfigForClient(info)
|
||
|
}
|
||
|
return config, nil
|
||
|
}
|
||
|
config.NextProtos = append(muxers, config.NextProtos...)
|
||
|
cs, err := t.handshake(ctx, tls.Server(insecure, config), keyCh)
|
||
|
if err != nil {
|
||
|
addr, maErr := manet.FromNetAddr(insecure.RemoteAddr())
|
||
|
if maErr == nil {
|
||
|
canonicallog.LogPeerStatus(100, p, addr, "handshake_failure", "tls", "err", err.Error())
|
||
|
}
|
||
|
insecure.Close()
|
||
|
}
|
||
|
return cs, err
|
||
|
}
|
||
|
|
||
|
// SecureOutbound runs the TLS handshake as a client.
|
||
|
// 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.
|
||
|
func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
|
||
|
config, keyCh := t.identity.ConfigForPeer(p)
|
||
|
muxers := make([]string, 0, len(t.muxers))
|
||
|
for _, muxer := range t.muxers {
|
||
|
muxers = append(muxers, (string)(muxer))
|
||
|
}
|
||
|
// Prepend the prefered muxers list to TLS config.
|
||
|
config.NextProtos = append(muxers, config.NextProtos...)
|
||
|
cs, err := t.handshake(ctx, tls.Client(insecure, config), keyCh)
|
||
|
if err != nil {
|
||
|
insecure.Close()
|
||
|
}
|
||
|
return cs, err
|
||
|
}
|
||
|
|
||
|
func (t *Transport) handshake(ctx context.Context, tlsConn *tls.Conn, keyCh <-chan ci.PubKey) (_sconn sec.SecureConn, err error) {
|
||
|
defer func() {
|
||
|
if rerr := recover(); rerr != nil {
|
||
|
fmt.Fprintf(os.Stderr, "panic in TLS handshake: %s\n%s\n", rerr, debug.Stack())
|
||
|
err = fmt.Errorf("panic in TLS handshake: %s", rerr)
|
||
|
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
// handshaking...
|
||
|
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Should be ready by this point, don't block.
|
||
|
var remotePubKey ci.PubKey
|
||
|
select {
|
||
|
case remotePubKey = <-keyCh:
|
||
|
default:
|
||
|
}
|
||
|
if remotePubKey == nil {
|
||
|
return nil, errors.New("go-libp2p tls BUG: expected remote pub key to be set")
|
||
|
}
|
||
|
|
||
|
return t.setupConn(tlsConn, remotePubKey)
|
||
|
}
|
||
|
|
||
|
func (t *Transport) setupConn(tlsConn *tls.Conn, remotePubKey ci.PubKey) (sec.SecureConn, error) {
|
||
|
remotePeerID, err := peer.IDFromPublicKey(remotePubKey)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
nextProto := tlsConn.ConnectionState().NegotiatedProtocol
|
||
|
// The special ALPN extension value "libp2p" is used by libp2p versions
|
||
|
// that don't support early muxer negotiation. If we see this sepcial
|
||
|
// value selected, that means we are handshaking with a version that does
|
||
|
// not support early muxer negotiation. In this case return empty nextProto
|
||
|
// to indicate no muxer is selected.
|
||
|
if nextProto == "libp2p" {
|
||
|
nextProto = ""
|
||
|
}
|
||
|
|
||
|
return &conn{
|
||
|
Conn: tlsConn,
|
||
|
localPeer: t.localPeer,
|
||
|
remotePeer: remotePeerID,
|
||
|
remotePubKey: remotePubKey,
|
||
|
connectionState: network.ConnectionState{
|
||
|
StreamMultiplexer: protocol.ID(nextProto),
|
||
|
UsedEarlyMuxerNegotiation: nextProto != "",
|
||
|
},
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (t *Transport) ID() protocol.ID {
|
||
|
return t.protocolID
|
||
|
}
|