diff --git a/config/security.go b/config/security.go index 0f6c78871a..bac3eb2185 100644 --- a/config/security.go +++ b/config/security.go @@ -3,13 +3,13 @@ package config import ( "fmt" + csms "github.com/libp2p/go-libp2p/p2p/net/conn-security-multistream" + "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/sec" "github.com/libp2p/go-libp2p-core/sec/insecure" - - csms "github.com/libp2p/go-conn-security-multistream" ) // SecC is a security transport constructor. diff --git a/go.mod b/go.mod index fed40f9e17..a3539c7331 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/ipfs/go-log/v2 v2.5.1 github.com/klauspost/compress v1.15.1 github.com/libp2p/go-buffer-pool v0.0.2 - github.com/libp2p/go-conn-security-multistream v0.3.0 github.com/libp2p/go-eventbus v0.2.1 github.com/libp2p/go-libp2p-asn-util v0.1.0 github.com/libp2p/go-libp2p-circuit v0.6.0 diff --git a/p2p/net/conn-security-multistream/ssms.go b/p2p/net/conn-security-multistream/ssms.go new file mode 100644 index 0000000000..46bb334a88 --- /dev/null +++ b/p2p/net/conn-security-multistream/ssms.go @@ -0,0 +1,109 @@ +package csms + +import ( + "context" + "fmt" + "log" + "net" + + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/sec" + mss "github.com/multiformats/go-multistream" +) + +// SSMuxer is a multistream stream security transport multiplexer. +// +// SSMuxer is safe to use without initialization. However, it's not safe to move +// after use. +type SSMuxer struct { + mux mss.MultistreamMuxer + tpts map[string]sec.SecureTransport + OrderPreference []string +} + +var _ sec.SecureMuxer = (*SSMuxer)(nil) + +// AddTransport adds a stream security transport to this multistream muxer. +// +// This method is *not* thread-safe. It should be called only when initializing +// the SSMuxer. +func (sm *SSMuxer) AddTransport(path string, transport sec.SecureTransport) { + if sm.tpts == nil { + sm.tpts = make(map[string]sec.SecureTransport, 1) + } + + sm.mux.AddHandler(path, nil) + sm.tpts[path] = transport + sm.OrderPreference = append(sm.OrderPreference, path) +} + +// SecureInbound secures an inbound connection using this multistream +// multiplexed stream security transport. +func (sm *SSMuxer) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, bool, error) { + tpt, _, err := sm.selectProto(ctx, insecure, true) + if err != nil { + return nil, false, err + } + sconn, err := tpt.SecureInbound(ctx, insecure, p) + return sconn, true, err +} + +// SecureOutbound secures an outbound connection using this multistream +// multiplexed stream security transport. +func (sm *SSMuxer) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, bool, error) { + tpt, server, err := sm.selectProto(ctx, insecure, false) + if err != nil { + return nil, false, err + } + + var sconn sec.SecureConn + if server { + sconn, err = tpt.SecureInbound(ctx, insecure, p) + if err != nil { + return nil, false, fmt.Errorf("failed to secure inbound connection: %s", err) + } + // ensure the correct peer connected to us + if sconn.RemotePeer() != p { + sconn.Close() + log.Printf("Handshake failed to properly authenticate peer. Authenticated %s, expected %s.", sconn.RemotePeer(), p) + return nil, false, fmt.Errorf("unexpected peer") + } + } else { + sconn, err = tpt.SecureOutbound(ctx, insecure, p) + } + + return sconn, server, err +} + +func (sm *SSMuxer) selectProto(ctx context.Context, insecure net.Conn, server bool) (sec.SecureTransport, bool, error) { + var proto string + var err error + var iamserver bool + done := make(chan struct{}) + go func() { + defer close(done) + if server { + iamserver = true + proto, _, err = sm.mux.Negotiate(insecure) + } else { + proto, iamserver, err = mss.SelectWithSimopenOrFail(sm.OrderPreference, insecure) + } + }() + + select { + case <-done: + if err != nil { + return nil, false, err + } + if tpt, ok := sm.tpts[proto]; ok { + return tpt, iamserver, nil + } + return nil, false, fmt.Errorf("selected unknown security transport") + case <-ctx.Done(): + // We *must* do this. We have outstanding work on the connection + // and it's no longer safe to use. + insecure.Close() + <-done // wait to stop using the connection. + return nil, false, ctx.Err() + } +} diff --git a/p2p/net/conn-security-multistream/ssms_test.go b/p2p/net/conn-security-multistream/ssms_test.go new file mode 100644 index 0000000000..cee925a36a --- /dev/null +++ b/p2p/net/conn-security-multistream/ssms_test.go @@ -0,0 +1,79 @@ +package csms + +import ( + "context" + "net" + "sync" + "testing" + + "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p-core/sec" + "github.com/libp2p/go-libp2p-core/sec/insecure" + tnet "github.com/libp2p/go-libp2p-testing/net" + sst "github.com/libp2p/go-libp2p-testing/suites/sec" +) + +type TransportAdapter struct { + mux *SSMuxer +} + +func (sm *TransportAdapter) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) { + sconn, _, err := sm.mux.SecureInbound(ctx, insecure, p) + return sconn, err +} + +func (sm *TransportAdapter) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) { + sconn, _, err := sm.mux.SecureOutbound(ctx, insecure, p) + return sconn, err +} + +func TestCommonProto(t *testing.T) { + idA := tnet.RandIdentityOrFatal(t) + idB := tnet.RandIdentityOrFatal(t) + + var at, bt SSMuxer + + atInsecure := insecure.NewWithIdentity(idA.ID(), idA.PrivateKey()) + btInsecure := insecure.NewWithIdentity(idB.ID(), idB.PrivateKey()) + at.AddTransport("/plaintext/1.0.0", atInsecure) + bt.AddTransport("/plaintext/1.1.0", btInsecure) + bt.AddTransport("/plaintext/1.0.0", btInsecure) + sst.SubtestRW(t, &TransportAdapter{mux: &at}, &TransportAdapter{mux: &bt}, idA.ID(), idB.ID()) +} + +func TestNoCommonProto(t *testing.T) { + idA := tnet.RandIdentityOrFatal(t) + idB := tnet.RandIdentityOrFatal(t) + + var at, bt SSMuxer + atInsecure := insecure.NewWithIdentity(idA.ID(), idA.PrivateKey()) + btInsecure := insecure.NewWithIdentity(idB.ID(), idB.PrivateKey()) + + at.AddTransport("/plaintext/1.0.0", atInsecure) + bt.AddTransport("/plaintext/1.1.0", btInsecure) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + a, b := net.Pipe() + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + defer a.Close() + _, _, err := at.SecureInbound(ctx, a, "") + if err == nil { + t.Error("connection should have failed") + } + }() + + go func() { + defer wg.Done() + defer b.Close() + _, _, err := bt.SecureOutbound(ctx, b, "peerA") + if err == nil { + t.Error("connection should have failed") + } + }() + wg.Wait() +} diff --git a/p2p/net/swarm/dial_worker_test.go b/p2p/net/swarm/dial_worker_test.go index a7cc1e8d8f..0219a63a85 100644 --- a/p2p/net/swarm/dial_worker_test.go +++ b/p2p/net/swarm/dial_worker_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/libp2p/go-libp2p/p2p/muxer/yamux" + csms "github.com/libp2p/go-libp2p/p2p/net/conn-security-multistream" quic "github.com/libp2p/go-libp2p/p2p/transport/quic" "github.com/libp2p/go-libp2p/p2p/transport/tcp" @@ -16,7 +17,6 @@ import ( "github.com/libp2p/go-libp2p-core/sec/insecure" "github.com/libp2p/go-libp2p-core/transport" - csms "github.com/libp2p/go-conn-security-multistream" "github.com/libp2p/go-libp2p-peerstore/pstoremem" tnet "github.com/libp2p/go-libp2p-testing/net" tptu "github.com/libp2p/go-libp2p-transport-upgrader" diff --git a/p2p/net/swarm/testing/testing.go b/p2p/net/swarm/testing/testing.go index 0b75205e4e..6e56112f97 100644 --- a/p2p/net/swarm/testing/testing.go +++ b/p2p/net/swarm/testing/testing.go @@ -5,6 +5,7 @@ import ( "time" "github.com/libp2p/go-libp2p/p2p/muxer/yamux" + csms "github.com/libp2p/go-libp2p/p2p/net/conn-security-multistream" "github.com/libp2p/go-libp2p/p2p/net/swarm" quic "github.com/libp2p/go-libp2p/p2p/transport/quic" "github.com/libp2p/go-libp2p/p2p/transport/tcp" @@ -19,7 +20,6 @@ import ( "github.com/libp2p/go-libp2p-core/sec/insecure" "github.com/libp2p/go-libp2p-core/transport" - csms "github.com/libp2p/go-conn-security-multistream" "github.com/libp2p/go-libp2p-peerstore/pstoremem" tnet "github.com/libp2p/go-libp2p-testing/net" tptu "github.com/libp2p/go-libp2p-transport-upgrader" diff --git a/p2p/transport/tcp/tcp_test.go b/p2p/transport/tcp/tcp_test.go index 8e48a99136..3976d76fdc 100644 --- a/p2p/transport/tcp/tcp_test.go +++ b/p2p/transport/tcp/tcp_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/libp2p/go-libp2p/p2p/muxer/yamux" + csms "github.com/libp2p/go-libp2p/p2p/net/conn-security-multistream" "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/network" @@ -14,7 +15,6 @@ import ( "github.com/libp2p/go-libp2p-core/sec/insecure" "github.com/libp2p/go-libp2p-core/transport" - csms "github.com/libp2p/go-conn-security-multistream" mocknetwork "github.com/libp2p/go-libp2p-testing/mocks/network" ttransport "github.com/libp2p/go-libp2p-testing/suites/transport" tptu "github.com/libp2p/go-libp2p-transport-upgrader" diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index 01ced160c3..db80277adb 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -15,6 +15,8 @@ import ( "testing" "time" + csms "github.com/libp2p/go-libp2p/p2p/net/conn-security-multistream" + "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" @@ -24,7 +26,6 @@ import ( "github.com/libp2p/go-libp2p-core/transport" "github.com/libp2p/go-libp2p/p2p/muxer/yamux" - csms "github.com/libp2p/go-conn-security-multistream" ttransport "github.com/libp2p/go-libp2p-testing/suites/transport" tptu "github.com/libp2p/go-libp2p-transport-upgrader"