Skip to content

Commit

Permalink
noise: use Noise Extension to negotiate the muxer during the handshake (
Browse files Browse the repository at this point in the history
#1813)

* Muxer early selection over Noise protocol

* Address review points round 2

* Muxer selection according responder's preference

* Address some review points again.

* noise: regenerate the protobuf

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
  • Loading branch information
julian88110 and marten-seemann authored Oct 25, 2022
1 parent c33f910 commit 7465a50
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 20 deletions.
2 changes: 1 addition & 1 deletion p2p/security/noise/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func makeTransport(b *testing.B) *Transport {
if err != nil {
b.Fatal(err)
}
tpt, err := New(priv)
tpt, err := New(priv, nil)
if err != nil {
b.Fatalf("error constructing transport: %v", err)
}
Expand Down
79 changes: 68 additions & 11 deletions p2p/security/noise/pb/payload.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions p2p/security/noise/pb/payload.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pb;

message NoiseExtensions {
repeated bytes webtransport_certhashes = 1;
repeated string stream_muxers = 2;
}

message NoiseHandshakePayload {
Expand Down
12 changes: 11 additions & 1 deletion p2p/security/noise/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ type secureSession struct {
prologue []byte

initiatorEarlyDataHandler, responderEarlyDataHandler EarlyDataHandler

// ConnectionState holds state information releated to the secureSession entity.
connectionState network.ConnectionState
}

// newSecureSession creates a Noise session over the given insecureConn Conn, using
Expand Down Expand Up @@ -110,7 +113,7 @@ func (s *secureSession) RemotePublicKey() crypto.PubKey {
}

func (s *secureSession) ConnState() network.ConnectionState {
return network.ConnectionState{}
return s.connectionState
}

func (s *secureSession) SetDeadline(t time.Time) error {
Expand All @@ -128,3 +131,10 @@ func (s *secureSession) SetWriteDeadline(t time.Time) error {
func (s *secureSession) Close() error {
return s.insecureConn.Close()
}

func SessionWithConnState(s *secureSession, muxer string) *secureSession {
if s != nil {
s.connectionState.NextProto = muxer
}
return s
}
71 changes: 66 additions & 5 deletions p2p/security/noise/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ import (
"github.com/libp2p/go-libp2p/core/canonicallog"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/libp2p/go-libp2p/core/sec"
"github.com/libp2p/go-libp2p/p2p/security/noise/pb"

manet "github.com/multiformats/go-multiaddr/net"
)

// ID is the protocol ID for noise
const ID = "/noise"
const (
ID = "/noise"
maxProtoNum = 100
)

var _ sec.SecureTransport = &Transport{}

Expand All @@ -22,38 +27,51 @@ var _ sec.SecureTransport = &Transport{}
type Transport struct {
localID peer.ID
privateKey crypto.PrivKey
muxers []string
}

// New creates a new Noise transport using the given private key as its
// libp2p identity key.
func New(privkey crypto.PrivKey) (*Transport, error) {
func New(privkey crypto.PrivKey, muxers []protocol.ID) (*Transport, error) {
localID, err := peer.IDFromPrivateKey(privkey)
if err != nil {
return nil, err
}

smuxers := make([]string, 0, len(muxers))
for _, muxer := range muxers {
smuxers = append(smuxers, string(muxer))
}

return &Transport{
localID: localID,
privateKey: privkey,
muxers: smuxers,
}, nil
}

// SecureInbound runs the Noise handshake as the responder.
// 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) {
c, err := newSecureSession(t, ctx, insecure, p, nil, nil, nil, false)
responderEDH := newTransportEDH(t)
c, err := newSecureSession(t, ctx, insecure, p, nil, nil, responderEDH, false)
if err != nil {
addr, maErr := manet.FromNetAddr(insecure.RemoteAddr())
if maErr == nil {
canonicallog.LogPeerStatus(100, p, addr, "handshake_failure", "noise", "err", err.Error())
}
}
return c, err
return SessionWithConnState(c, responderEDH.MatchMuxers(false)), err
}

// SecureOutbound runs the Noise handshake as the initiator.
func (t *Transport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
return newSecureSession(t, ctx, insecure, p, nil, nil, nil, true)
initiatorEDH := newTransportEDH(t)
c, err := newSecureSession(t, ctx, insecure, p, nil, initiatorEDH, nil, true)
if err != nil {
return c, err
}
return SessionWithConnState(c, initiatorEDH.MatchMuxers(true)), err
}

func (t *Transport) WithSessionOptions(opts ...SessionOption) (sec.SecureTransport, error) {
Expand All @@ -65,3 +83,46 @@ func (t *Transport) WithSessionOptions(opts ...SessionOption) (sec.SecureTranspo
}
return st, nil
}

func matchMuxers(initiatorMuxers, responderMuxers []string) string {
for _, muxer := range responderMuxers {
for _, initMuxer := range initiatorMuxers {
if initMuxer == muxer {
return muxer
}
}
}
return ""
}

type transportEarlyDataHandler struct {
transport *Transport
receivedMuxers []string
}

var _ EarlyDataHandler = &transportEarlyDataHandler{}

func newTransportEDH(t *Transport) *transportEarlyDataHandler {
return &transportEarlyDataHandler{transport: t}
}

func (i *transportEarlyDataHandler) Send(context.Context, net.Conn, peer.ID) *pb.NoiseExtensions {
return &pb.NoiseExtensions{
StreamMuxers: i.transport.muxers,
}
}

func (i *transportEarlyDataHandler) Received(_ context.Context, _ net.Conn, extension *pb.NoiseExtensions) error {
// Discard messages with size or the number of protocols exceeding extension limit for security.
if extension != nil && len(extension.StreamMuxers) <= maxProtoNum {
i.receivedMuxers = extension.GetStreamMuxers()
}
return nil
}

func (i *transportEarlyDataHandler) MatchMuxers(isInitiator bool) string {
if isInitiator {
return matchMuxers(i.transport.muxers, i.receivedMuxers)
}
return matchMuxers(i.receivedMuxers, i.transport.muxers)
}
59 changes: 59 additions & 0 deletions p2p/security/noise/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ func newTestTransport(t *testing.T, typ, bits int) *Transport {
}
}

func newTestTransportWithMuxers(t *testing.T, typ, bits int, muxers []string) *Transport {
transport := newTestTransport(t, typ, bits)
transport.muxers = muxers
return transport
}

// Create a new pair of connected TCP sockets.
func newConnPair(t *testing.T) (net.Conn, net.Conn) {
lstnr, err := net.Listen("tcp", "localhost:0")
Expand Down Expand Up @@ -586,3 +592,56 @@ func TestEarlyfffDataAcceptedWithNoHandler(t *testing.T) {
require.NoError(t, err)
}
}

type noiseEarlyDataTestCase struct {
initProtos []string
respProtos []string
expectedResult string
}

func TestHandshakeWithTransportEarlyData(t *testing.T) {
tests := []noiseEarlyDataTestCase{
{initProtos: nil, respProtos: nil, expectedResult: ""},
{[]string{"muxer1"}, []string{"muxer1"}, "muxer1"},
{[]string{"muxer1"}, []string{}, ""},
{[]string{}, []string{"muxer2"}, ""},
{[]string{"muxer2"}, []string{"muxer1"}, ""},
{[]string{"muxer1/1.0.0", "muxer2/1.0.1"}, []string{"muxer2/1.0.1", "muxer1/1.0.0"}, "muxer2/1.0.1"},
{[]string{"muxer1/1.0.0", "muxer2/1.0.1", "muxer3/1.0.0"}, []string{"muxer2/1.0.1", "muxer1/1.0.1", "muxer3/1.0.0"}, "muxer2/1.0.1"},
{[]string{"muxer1/1.0.0", "muxer2/1.0.0"}, []string{"muxer3/1.0.0"}, ""},
}

noiseHandshake := func(t *testing.T, initProtos, respProtos []string, expectedProto string) {
initTransport := newTestTransportWithMuxers(t, crypto.Ed25519, 2048, initProtos)
respTransport := newTestTransportWithMuxers(t, crypto.Ed25519, 2048, respProtos)

initConn, respConn := connect(t, initTransport, respTransport)
defer initConn.Close()
defer respConn.Close()

require.Equal(t, expectedProto, initConn.connectionState.NextProto)
require.Equal(t, expectedProto, respConn.connectionState.NextProto)

initData := []byte("Test data for noise transport")
_, err := initConn.Write(initData)
if err != nil {
t.Fatal(err)
}

respData := make([]byte, len(initData))
_, err = respConn.Read(respData)
if err != nil {
t.Fatal(err)
}

if !bytes.Equal(initData, respData) {
t.Errorf("Data transmitted mismatch over noise session. %v != %v", initData, respData)
}
}

for _, test := range tests {
t.Run("Transport EarlyData Test", func(t *testing.T) {
noiseHandshake(t, test.initProtos, test.respProtos, test.expectedResult)
})
}
}
2 changes: 1 addition & 1 deletion p2p/transport/websocket/websocket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func newSecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) {
t.Fatal(err)
}
var secMuxer csms.SSMuxer
noiseTpt, err := noise.New(priv)
noiseTpt, err := noise.New(priv, nil)
require.NoError(t, err)
secMuxer.AddTransport(noise.ID, noiseTpt)
return id, &secMuxer
Expand Down
2 changes: 1 addition & 1 deletion p2p/transport/webtransport/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func New(key ic.PrivKey, gater connmgr.ConnectionGater, rcmgr network.ResourceMa
return nil, err
}
}
n, err := noise.New(key)
n, err := noise.New(key, nil)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit 7465a50

Please sign in to comment.