Skip to content

Commit 8d77135

Browse files
identify: reject signed peer records on peer ID mismatch
and make ConsumeEnvelope harder to misuse --------- Co-authored-by: Marco Munizaga <[email protected]>
1 parent 305282b commit 8d77135

File tree

3 files changed

+101
-11
lines changed

3 files changed

+101
-11
lines changed

core/record/envelope.go

+2-7
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,6 @@ func Seal(rec Record, privateKey crypto.PrivKey) (*Envelope, error) {
106106
// doSomethingWithPeerRecord(peerRec)
107107
// }
108108
//
109-
// Important: you MUST check the error value before using the returned Envelope. In some error
110-
// cases, including when the envelope signature is invalid, both the Envelope and an error will
111-
// be returned. This allows you to inspect the unmarshalled but invalid Envelope. As a result,
112-
// you must not assume that any non-nil Envelope returned from this function is valid.
113-
//
114109
// If the Envelope signature is valid, but no Record type is registered for the Envelope's
115110
// PayloadType, ErrPayloadTypeNotRegistered will be returned, along with the Envelope and
116111
// a nil Record.
@@ -122,12 +117,12 @@ func ConsumeEnvelope(data []byte, domain string) (envelope *Envelope, rec Record
122117

123118
err = e.validate(domain)
124119
if err != nil {
125-
return e, nil, fmt.Errorf("failed to validate envelope: %w", err)
120+
return nil, nil, fmt.Errorf("failed to validate envelope: %w", err)
126121
}
127122

128123
rec, err = e.Record()
129124
if err != nil {
130-
return e, nil, fmt.Errorf("failed to unmarshal envelope payload: %w", err)
125+
return nil, nil, fmt.Errorf("failed to unmarshal envelope payload: %w", err)
131126
}
132127
return e, rec, nil
133128
}

p2p/protocol/identify/id.go

+8-4
Original file line numberDiff line numberDiff line change
@@ -759,10 +759,14 @@ func (ids *idService) consumeMessage(mes *pb.Identify, c network.Conn, isPush bo
759759

760760
// add signed addrs if we have them and the peerstore supports them
761761
cab, ok := peerstore.GetCertifiedAddrBook(ids.Host.Peerstore())
762-
if ok && signedPeerRecord != nil {
763-
_, addErr := cab.ConsumePeerRecord(signedPeerRecord, ttl)
764-
if addErr != nil {
765-
log.Debugf("error adding signed addrs to peerstore: %v", addErr)
762+
if ok && signedPeerRecord != nil && signedPeerRecord.PublicKey != nil {
763+
id, err := peer.IDFromPublicKey(signedPeerRecord.PublicKey)
764+
if err != nil {
765+
log.Debugf("failed to derive peer ID from peer record: %s", err)
766+
} else if id != c.RemotePeer() {
767+
log.Debugf("received signed peer record for unexpected peer ID. expected %s, got %s", c.RemotePeer(), id)
768+
} else if _, err := cab.ConsumePeerRecord(signedPeerRecord, ttl); err != nil {
769+
log.Debugf("error adding signed addrs to peerstore: %v", err)
766770
}
767771
} else {
768772
ids.Host.Peerstore().AddAddrs(p, lmaddrs, ttl)

p2p/protocol/identify/id_glass_test.go

+91
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ package identify
22

33
import (
44
"context"
5+
"fmt"
56
"testing"
67
"time"
78

89
"github.com/libp2p/go-libp2p/core/network"
910
"github.com/libp2p/go-libp2p/core/peer"
11+
"github.com/libp2p/go-libp2p/core/peerstore"
12+
recordPb "github.com/libp2p/go-libp2p/core/record/pb"
1013
blhost "github.com/libp2p/go-libp2p/p2p/host/blank"
1114
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
15+
"google.golang.org/protobuf/proto"
1216

1317
"github.com/stretchr/testify/assert"
1418
"github.com/stretchr/testify/require"
@@ -82,3 +86,90 @@ func TestFastDisconnect(t *testing.T) {
8286
// double-check to make sure we didn't actually timeout somewhere.
8387
require.NoError(t, ctx.Err())
8488
}
89+
90+
func TestWrongSignedPeerRecord(t *testing.T) {
91+
h1 := blhost.NewBlankHost(swarmt.GenSwarm(t))
92+
defer h1.Close()
93+
ids, err := NewIDService(h1)
94+
require.NoError(t, err)
95+
ids.Start()
96+
defer ids.Close()
97+
98+
h2 := blhost.NewBlankHost(swarmt.GenSwarm(t))
99+
defer h2.Close()
100+
ids2, err := NewIDService(h2)
101+
require.NoError(t, err)
102+
ids2.Start()
103+
defer ids2.Close()
104+
105+
h3 := blhost.NewBlankHost(swarmt.GenSwarm(t))
106+
defer h2.Close()
107+
ids3, err := NewIDService(h3)
108+
require.NoError(t, err)
109+
ids3.Start()
110+
defer ids3.Close()
111+
112+
h2.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()})
113+
s, err := h2.NewStream(context.Background(), h1.ID(), IDPush)
114+
require.NoError(t, err)
115+
116+
err = ids3.sendIdentifyResp(s, true)
117+
// This should fail because the peer record is signed by h3, not h2
118+
require.NoError(t, err)
119+
time.Sleep(time.Second)
120+
121+
require.Empty(t, h1.Peerstore().Addrs(h3.ID()), "h1 should not know about h3 since it was relayed over h2")
122+
}
123+
124+
func TestInvalidSignedPeerRecord(t *testing.T) {
125+
h1 := blhost.NewBlankHost(swarmt.GenSwarm(t))
126+
defer h1.Close()
127+
ids, err := NewIDService(h1)
128+
require.NoError(t, err)
129+
ids.Start()
130+
defer ids.Close()
131+
132+
h2 := blhost.NewBlankHost(swarmt.GenSwarm(t))
133+
defer h2.Close()
134+
ids2, err := NewIDService(h2)
135+
require.NoError(t, err)
136+
// We don't want to start the identify service, we'll manage the messages h2
137+
// sends manually so we can tweak it
138+
// ids2.Start()
139+
140+
h2.Connect(context.Background(), peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()})
141+
require.Empty(t, h1.Peerstore().Addrs(h2.ID()))
142+
143+
s, err := h2.NewStream(context.Background(), h1.ID(), IDPush)
144+
require.NoError(t, err)
145+
146+
ids2.updateSnapshot()
147+
ids2.currentSnapshot.Lock()
148+
snapshot := ids2.currentSnapshot.snapshot
149+
ids2.currentSnapshot.Unlock()
150+
mes := ids2.createBaseIdentifyResponse(s.Conn(), &snapshot)
151+
fmt.Println("Signed record is", snapshot.record)
152+
marshalled, err := snapshot.record.Marshal()
153+
require.NoError(t, err)
154+
155+
var envPb recordPb.Envelope
156+
err = proto.Unmarshal(marshalled, &envPb)
157+
require.NoError(t, err)
158+
159+
envPb.Signature = []byte("invalid")
160+
161+
mes.SignedPeerRecord, err = proto.Marshal(&envPb)
162+
require.NoError(t, err)
163+
164+
err = ids2.writeChunkedIdentifyMsg(s, mes)
165+
require.NoError(t, err)
166+
fmt.Println("Done sending msg")
167+
s.Close()
168+
169+
// Wait a bit for h1 to process the message
170+
time.Sleep(1 * time.Second)
171+
172+
cab, ok := h1.Peerstore().(peerstore.CertifiedAddrBook)
173+
require.True(t, ok)
174+
require.Nil(t, cab.GetPeerRecord(h2.ID()))
175+
}

0 commit comments

Comments
 (0)