Skip to content

Commit

Permalink
Allow adding prologue to noise connections (#1663)
Browse files Browse the repository at this point in the history
* Allow adding prologue to noise connections

* Expose session transport

* Expose session transport

* cleanup noise prologue test

* fix staticheck error
  • Loading branch information
ckousik authored Aug 9, 2022
1 parent 68722aa commit b845164
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 4 deletions.
2 changes: 1 addition & 1 deletion p2p/security/noise/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func TestCryptoFailsIfHandshakeIncomplete(t *testing.T) {
init, resp := net.Pipe()
_ = resp.Close()

session, _ := newSecureSession(initTransport, context.TODO(), init, "remote-peer", true)
session, _ := newSecureSession(initTransport, context.TODO(), init, "remote-peer", nil, true)
_, err := session.encrypt(nil, []byte("hi"))
if err == nil {
t.Error("expected encryption error when handshake incomplete")
Expand Down
1 change: 1 addition & 0 deletions p2p/security/noise/handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func (s *secureSession) runHandshake(ctx context.Context) (err error) {
Pattern: noise.HandshakeXX,
Initiator: s.initiator,
StaticKeypair: kp,
Prologue: s.prologue,
}

hs, err := noise.NewHandshakeState(cfg)
Expand Down
6 changes: 5 additions & 1 deletion p2p/security/noise/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,22 @@ type secureSession struct {

enc *noise.CipherState
dec *noise.CipherState

// noise prologue
prologue []byte
}

// newSecureSession creates a Noise session over the given insecureConn Conn, using
// the libp2p identity keypair from the given Transport.
func newSecureSession(tpt *Transport, ctx context.Context, insecure net.Conn, remote peer.ID, initiator bool) (*secureSession, error) {
func newSecureSession(tpt *Transport, ctx context.Context, insecure net.Conn, remote peer.ID, prologue []byte, initiator bool) (*secureSession, error) {
s := &secureSession{
insecureConn: insecure,
insecureReader: bufio.NewReader(insecure),
initiator: initiator,
localID: tpt.localID,
localKey: tpt.privateKey,
remoteID: remote,
prologue: prologue,
}

// the go-routine we create to run the handshake will
Expand Down
58 changes: 58 additions & 0 deletions p2p/security/noise/session_transport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package noise

import (
"context"
"net"

"github.com/libp2p/go-libp2p-core/canonicallog"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/sec"
manet "github.com/multiformats/go-multiaddr/net"
)

type SessionOption = func(*SessionTransport) error

var _ sec.SecureTransport = &SessionTransport{}

// SessionTransport can be used
// to provide per-connection options
type SessionTransport struct {
t *Transport
// options
prologue []byte
}

// SecureInbound runs the Noise handshake as the responder.
// If p is empty, connections from any peer are accepted.
func (i *SessionTransport) SecureInbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
c, err := newSecureSession(i.t, ctx, insecure, p, i.prologue, 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
}

// SecureOutbound runs the Noise handshake as the initiator.
func (i *SessionTransport) SecureOutbound(ctx context.Context, insecure net.Conn, p peer.ID) (sec.SecureConn, error) {
return newSecureSession(i.t, ctx, insecure, p, i.prologue, true)
}

func (t *Transport) WithSessionOptions(opts ...SessionOption) (sec.SecureTransport, error) {
st := &SessionTransport{t: t}
for _, opt := range opts {
if err := opt(st); err != nil {
return nil, err
}
}
return st, nil
}

func Prologue(prologue []byte) SessionOption {
return func(s *SessionTransport) error {
s.prologue = prologue
return nil
}
}
4 changes: 2 additions & 2 deletions p2p/security/noise/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func New(privkey crypto.PrivKey) (*Transport, error) {
// 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, false)
c, err := newSecureSession(t, ctx, insecure, p, nil, false)
if err != nil {
addr, maErr := manet.FromNetAddr(insecure.RemoteAddr())
if maErr == nil {
Expand All @@ -52,5 +52,5 @@ func (t *Transport) SecureInbound(ctx context.Context, insecure net.Conn, p peer

// 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, true)
return newSecureSession(t, ctx, insecure, p, nil, true)
}
54 changes: 54 additions & 0 deletions p2p/security/noise/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,57 @@ func TestReadUnencryptedFails(t *testing.T) {
require.Error(t, err)
require.Equal(t, 0, afterLen)
}

func TestPrologueMatches(t *testing.T) {
commonPrologue := []byte("test")
initTransport := newTestTransport(t, crypto.Ed25519, 2048)
respTransport := newTestTransport(t, crypto.Ed25519, 2048)

initConn, respConn := newConnPair(t)

done := make(chan struct{})

go func() {
defer close(done)
tpt, err := initTransport.
WithSessionOptions(Prologue(commonPrologue))
require.NoError(t, err)
conn, err := tpt.SecureOutbound(context.TODO(), initConn, respTransport.localID)
require.NoError(t, err)
defer conn.Close()
}()

tpt, err := respTransport.
WithSessionOptions(Prologue(commonPrologue))
require.NoError(t, err)
conn, err := tpt.SecureInbound(context.TODO(), respConn, "")
require.NoError(t, err)
defer conn.Close()
<-done
}

func TestPrologueDoesNotMatchFailsHandshake(t *testing.T) {
initPrologue, respPrologue := []byte("initPrologue"), []byte("respPrologue")
initTransport := newTestTransport(t, crypto.Ed25519, 2048)
respTransport := newTestTransport(t, crypto.Ed25519, 2048)

initConn, respConn := newConnPair(t)

done := make(chan struct{})

go func() {
defer close(done)
tpt, err := initTransport.
WithSessionOptions(Prologue(initPrologue))
require.NoError(t, err)
_, err = tpt.SecureOutbound(context.TODO(), initConn, respTransport.localID)
require.Error(t, err)
}()

tpt, err := respTransport.WithSessionOptions(Prologue(respPrologue))
require.NoError(t, err)

_, err = tpt.SecureInbound(context.TODO(), respConn, "")
require.Error(t, err)
<-done
}

0 comments on commit b845164

Please sign in to comment.