Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider record encryption #3

Open
wants to merge 32 commits into
base: noot/prefix-lookup
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
242a3b5
implement AES encrypt/decrypt
noot Sep 21, 2022
677a86b
update AddProviders to take a peer.ID
noot Sep 21, 2022
0c57991
update GetProviders to also return []peer.ID, remove peerstore from p…
noot Sep 21, 2022
3596989
fix unit tests, cleanup
noot Sep 22, 2022
284f2b6
provider peer ID encryption working
noot Sep 22, 2022
c35602b
cleanup
noot Sep 22, 2022
06b08b7
cleanup
noot Sep 22, 2022
853acdf
fix tests by removing originator check in handleAddProvider
noot Sep 26, 2022
fa069ea
wip verification of signature
noot Sep 29, 2022
639d763
Merge branch 'noot/prefix-lookup' of github.com:ChainSafe/go-libp2p-k…
noot Sep 29, 2022
c1a6dc8
add public key to Message_Peer, verify sig in ADD_PROVIDER handler
noot Sep 30, 2022
8264e56
revert some test changes
noot Oct 3, 2022
704129f
remove todos for logs
noot Oct 3, 2022
3d9734f
cleanup
noot Oct 3, 2022
98c0812
fix put in handleAddProviders, add todos
noot Oct 5, 2022
40deff8
verify peer ID in handleAddProviders
noot Oct 5, 2022
657001d
Merge branch 'noot/prefix-lookup' of github.com:ChainSafe/go-libp2p-k…
noot Oct 18, 2022
147e2d9
merge w noot/prefix-lookup
noot Nov 1, 2022
b1c4871
merge
noot Nov 2, 2022
eabcfd0
merge
noot Nov 17, 2022
5485040
Merge branch 'noot/prefix-lookup' of github.com:ChainSafe/go-libp2p-k…
noot Nov 17, 2022
0a934e8
merge w prefix-lookup branch
noot Nov 30, 2022
e5ec16b
merge
noot Nov 30, 2022
c7922af
cleanup
noot Nov 30, 2022
7a4c6c9
merge
noot Dec 1, 2022
526fae6
Merge branch 'noot/prefix-lookup' of github.com:ChainSafe/go-libp2p-k…
noot Dec 1, 2022
2c251cf
Merge branch 'noot/prefix-lookup' of github.com:ChainSafe/go-libp2p-k…
noot Dec 1, 2022
2d001b9
fix add providers merge issue
noot Dec 1, 2022
74b7c51
Merge branch 'noot/prefix-lookup' of github.com:ChainSafe/go-libp2p-k…
noot Jan 4, 2023
dc84e3e
change log
noot Jan 17, 2023
6a5b7c9
merge
noot Jan 30, 2023
e8eca13
update encrypt functions to use AEAD.NonceSize()
noot Jan 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dht.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ func makeDHT(ctx context.Context, h host.Host, cfg dhtcfg.Config) (*IpfsDHT, err
if cfg.ProviderStore != nil {
dht.providerStore = cfg.ProviderStore
} else {
dht.providerStore, err = providers.NewProviderManager(dht.ctx, h.ID(), h.Peerstore(), cfg.Datastore)
dht.providerStore, err = providers.NewProviderManager(dht.ctx, h.ID(), cfg.Datastore)
if err != nil {
return nil, fmt.Errorf("initializing default provider manager (%v)", err)
}
Expand Down
13 changes: 7 additions & 6 deletions dht_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ func TestValueGetInvalid(t *testing.T) {
testSetGet("valid", "newer", nil)
}

func TestProvides(t *testing.T) {
func TestProvides_Small(t *testing.T) {
// t.Skip("skipping test to debug another")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand Down Expand Up @@ -919,7 +919,7 @@ func TestProvidesMany(t *testing.T) {
}

// what is this timeout for? was 60ms before.
time.Sleep(time.Millisecond * 6)
time.Sleep(time.Second)

errchan := make(chan error)

Expand Down Expand Up @@ -1037,13 +1037,14 @@ func TestProvides_PrefixLookup(t *testing.T) {
}
}

time.Sleep(time.Millisecond * 6)
time.Sleep(time.Second)

n := 0
for _, c := range testCaseCids {
for i, c := range testCaseCids {
t.Log("searching for cid", c, i)
n = (n + 1) % 3

logger.Debugf("getting providers for %s from %d", c, n)
t.Logf("getting providers for %s from %d", c, n)
ctxT, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
dhts[n].prefixLength = 16 // half the hashed CID for now
Expand Down Expand Up @@ -1349,7 +1350,7 @@ func TestClientModeConnect(t *testing.T) {
c := testCaseCids[0]
p := peer.ID("TestPeer")
mhHash, _ := internal.Sha256Multihash(c.Hash())
err := a.ProviderStore().AddProvider(ctx, mhHash[:], peer.AddrInfo{ID: p})
err := a.ProviderStore().AddProvider(ctx, mhHash[:], p)
if err != nil {
t.Fatal(err)
}
Expand Down
74 changes: 74 additions & 0 deletions encrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package dht

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"

"github.com/multiformats/go-multihash"
)

const (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to get these constants from somewhere where they are already defined instead of defining them again? This would help with code maintainability :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately the Go AES package doesn't define these :/ I'll try looking somewhere for them!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to get Nonce size from cipher.AEAD interface.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

keySize = 32
encryptedPeerIDLength = 66
)

var errInvalidKeySize = errors.New("key size must be 32 bytes")

func encryptAES(plaintext, key []byte) ([]byte, error) {
aesgcm, err := newAESGCM(key)
if err != nil {
return nil, err
}

nonce := make([]byte, aesgcm.NonceSize())
_, err = rand.Read(nonce)
if err != nil {
return nil, err
}

ct := aesgcm.Seal(nil, nonce, plaintext, nil)
return append(nonce, ct...), nil

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need to append encryption algorithm here too so that the encryption function rotation doesn't require coordinated client update. I.e. for example AESGCM || nonce || enc(payload). That would also allow to have different encryption functions in the same DHT.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, will add!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to add 6 bytes to every encrypted value. Can we use a varint as code for AESGSM, then update the spec to document it?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@masih do you have a pointer to the AESGCM varint?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we will have to define this mask ourselves and then document in the spec what that number means

Copy link

@masih masih Jan 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. We'd need to define those in the spec ourselves.

This can also be a new multicodec. I recommend picking a varint that's not reserved already to have the option of adding it to the multicodec table later on.

}

func decryptAES(nonceAndCT, key []byte) ([]byte, error) {
aesgcm, err := newAESGCM(key)
if err != nil {
return nil, err
}

nonce := nonceAndCT[:aesgcm.NonceSize()]
ciphertext := nonceAndCT[aesgcm.NonceSize():]
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}

return plaintext, nil
}

func newAESGCM(key []byte) (cipher.AEAD, error) {
if len(key) != keySize {
return nil, errInvalidKeySize
}

block, err := aes.NewCipher(key[:])
if err != nil {
return nil, err
}

aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}

return aesgcm, nil
}

func multihashToKey(mh multihash.Multihash) []byte {
const prefix = "AESGCM"
h := sha256.Sum256(append([]byte(prefix), mh...))
return h[:]
}
18 changes: 18 additions & 0 deletions encrypt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dht

import (
"testing"

"github.com/stretchr/testify/require"
)

var testKey = []byte("AES256Key-32Characters1234567890")

func TestAES(t *testing.T) {
plaintext := []byte("nootwashere")
ciphertext, err := encryptAES(plaintext, testKey)
require.NoError(t, err)
plaintextRes, err := decryptAES(ciphertext, testKey)
require.NoError(t, err)
require.Equal(t, plaintext, plaintextRes)
}
13 changes: 5 additions & 8 deletions ext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,13 @@ func TestNotFound(t *testing.T) {
case pb.Message_GET_VALUE:
resp := &pb.Message{Type: pmes.Type}

ps := []peer.AddrInfo{}
ps := []peer.ID{}
for i := 0; i < 7; i++ {
p := hosts[rand.Intn(len(hosts))].ID()
pi := host.Peerstore().PeerInfo(p)
ps = append(ps, pi)
ps = append(ps, p)
}

resp.CloserPeers = pb.PeerInfosToPBPeers(d.host.Network(), ps)
resp.CloserPeers = pb.PeerIDsToPBPeers(d.host.Network(), host.Peerstore(), ps)
if err := pbw.WriteMsg(resp); err != nil {
return
}
Expand Down Expand Up @@ -362,10 +361,9 @@ func TestLessThanKResponses(t *testing.T) {

switch pmes.GetType() {
case pb.Message_GET_VALUE:
pi := host.Peerstore().PeerInfo(hosts[1].ID())
resp := &pb.Message{
Type: pmes.Type,
CloserPeers: pb.PeerInfosToPBPeers(d.host.Network(), []peer.AddrInfo{pi}),
CloserPeers: pb.PeerIDsToPBPeers(d.host.Network(), host.Peerstore(), []peer.ID{hosts[1].ID()}),
}

if err := pbw.WriteMsg(resp); err != nil {
Expand Down Expand Up @@ -432,10 +430,9 @@ func TestMultipleQueries(t *testing.T) {

switch pmes.GetType() {
case pb.Message_GET_VALUE:
pi := hosts[1].Peerstore().PeerInfo(hosts[0].ID())
resp := &pb.Message{
Type: pmes.Type,
CloserPeers: pb.PeerInfosToPBPeers(d.host.Network(), []peer.AddrInfo{pi}),
CloserPeers: pb.PeerIDsToPBPeers(d.host.Network(), hosts[1].Peerstore(), []peer.ID{hosts[0].ID()}),
}

if err := pbw.WriteMsg(resp); err != nil {
Expand Down
11 changes: 6 additions & 5 deletions fullrt/dht.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func NewFullRT(h host.Host, protocolPrefix protocol.ID, options ...Option) (*Ful

ctx, cancel := context.WithCancel(context.Background())

pm, err := providers.NewProviderManager(ctx, h.ID(), h.Peerstore(), dhtcfg.Datastore)
pm, err := providers.NewProviderManager(ctx, h.ID(), dhtcfg.Datastore)
if err != nil {
cancel()
return nil, err
Expand Down Expand Up @@ -781,7 +781,7 @@ func (dht *FullRT) Provide(ctx context.Context, key cid.Cid, brdcst bool) (err e
logger.Debugw("providing", "cid", key, "mh", internal.LoggableProviderRecordBytes(keyMH), "mhHash", mhHash)

// add self locally
err = dht.ProviderManager.AddProvider(ctx, mhHash, peer.AddrInfo{ID: dht.h.ID()})
err = dht.ProviderManager.AddProvider(ctx, mhHash, dht.h.ID())
if err != nil {
return err
}
Expand Down Expand Up @@ -827,7 +827,7 @@ func (dht *FullRT) Provide(ctx context.Context, key cid.Cid, brdcst bool) (err e
}

successes := dht.execOnMany(ctx, func(ctx context.Context, p peer.ID) error {
err := dht.protoMessenger.PutProvider(ctx, p, keyMH, dht.h)
err := dht.protoMessenger.PutProvider(ctx, p, keyMH, dht.h, []byte(dht.h.ID()))
return err
}, peers, true)

Expand Down Expand Up @@ -1251,9 +1251,10 @@ func (dht *FullRT) findProvidersAsyncRoutine(ctx context.Context, key multihash.
}
for _, p := range provs {
// NOTE: Assuming that this list of peers is unique
if psTryAdd(p.ID) {
addrInfo := dht.h.Peerstore().PeerInfo(p)
if psTryAdd(p) {
select {
case peerOut <- p:
case peerOut <- addrInfo:
case <-ctx.Done():
return
}
Expand Down
81 changes: 45 additions & 36 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"fmt"
"time"

"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
pstore "github.com/libp2p/go-libp2p/p2p/host/peerstore"

"github.com/gogo/protobuf/proto"
ds "github.com/ipfs/go-datastore"
Expand Down Expand Up @@ -70,20 +70,7 @@ func (dht *IpfsDHT) handleGetValue(ctx context.Context, p peer.ID, pmes *pb.Mess
// Find closest peer on given cluster to desired key and reply with that info
closer := dht.betterPeersToQuery(pmes, p, dht.bucketSize)
if len(closer) > 0 {
// TODO: pstore.PeerInfos should move to core (=> peerstore.AddrInfos).
closerinfos := pstore.PeerInfos(dht.peerstore, closer)
for _, pi := range closerinfos {
logger.Debugf("handleGetValue returning closer peer: '%s'", pi.ID)
if len(pi.Addrs) < 1 {
logger.Warnw("no addresses on peer being sent",
"local", dht.self,
"to", p,
"sending", pi.ID,
)
}
}

resp.CloserPeers = pb.PeerInfosToPBPeers(dht.host.Network(), closerinfos)
resp.CloserPeers = pb.PeerIDsToPBPeers(dht.host.Network(), dht.peerstore, closer)
}

return resp, nil
Expand Down Expand Up @@ -294,16 +281,13 @@ func (dht *IpfsDHT) handleFindPeer(ctx context.Context, from peer.ID, pmes *pb.M
return resp, nil
}

// TODO: pstore.PeerInfos should move to core (=> peerstore.AddrInfos).
closestinfos := pstore.PeerInfos(dht.peerstore, closest)
// possibly an over-allocation but this array is temporary anyways.
withAddresses := make([]peer.AddrInfo, 0, len(closestinfos))
for _, pi := range closestinfos {
if len(pi.Addrs) > 0 {
withAddresses = append(withAddresses, pi)
withAddresses := make([]peer.AddrInfo, 0, len(closest))
for _, p := range closest {
addrInfo := dht.peerstore.PeerInfo(p)
if len(addrInfo.Addrs) > 0 {
withAddresses = append(withAddresses, addrInfo)
}
}

resp.CloserPeers = pb.PeerInfosToPBPeers(dht.host.Network(), withAddresses)
return resp, nil
}
Expand Down Expand Up @@ -335,15 +319,14 @@ func (dht *IpfsDHT) handleGetProviders(ctx context.Context, p peer.ID, pmes *pb.
if err != nil {
return nil, err
}
resp.ProviderPeers = pb.PeersToPeersWithKey(pb.PeerInfosToPBPeers(dht.host.Network(), providers))

resp.ProviderPeers = pb.PeersToPeersWithKey(pb.PeerIDsToPBPeers(dht.host.Network(), dht.peerstore, providers))
}

// Also send closer peers.
closer := dht.betterPeersToQuery(pmes, p, dht.bucketSize)
if closer != nil {
// TODO: pstore.PeerInfos should move to core (=> peerstore.AddrInfos).
infos := pstore.PeerInfos(dht.peerstore, closer)
resp.CloserPeers = pb.PeerInfosToPBPeers(dht.host.Network(), infos)
resp.CloserPeers = pb.PeerIDsToPBPeers(dht.host.Network(), dht.peerstore, closer)
}

return resp, nil
Expand All @@ -360,24 +343,50 @@ func (dht *IpfsDHT) handleAddProvider(ctx context.Context, p peer.ID, pmes *pb.M
logger.Debugw("adding provider", "from", p, "key", internal.LoggableProviderRecordBytes(key))

// add provider should use the address given in the message
pinfos := pb.PBPeersToAddrInfos(pmes.GetProviderPeers())
for _, pi := range pinfos {
if pi.ID != p {
// we should ignore this provider record! not from originator.
// (we should sign them and check signature later...)
logger.Debugw("received provider from wrong peer", "from", p, "peer", pi.ID)
provs := pmes.GetProviderPeers()
pinfos := pb.PBPeersToAddrInfos(provs)
for i, pi := range pinfos {
if len(pi.Addrs) < 1 {
logger.Debugw("no valid addresses for provider", "from", p)
continue
}

if len(pi.Addrs) < 1 {
logger.Debugw("no valid addresses for provider", "from", p)
sig := provs[i].Signature
pub, err := crypto.PublicKeyFromProto(provs[i].PublicKey)
if err != nil {
logger.Debugw("failed to unmarshal public key", "from", p, "peer", pi.ID, "error", err)
continue
}

err := dht.providerStore.AddProvider(ctx, key, peer.AddrInfo{ID: p})
// verify that public key corresponds to sender peer ID
id, err := peer.IDFromPublicKey(pub)
if err != nil {
logger.Debugw("failed to derive peer ID from public key", "from", p, "peer", pi.ID, "error", err)
continue
}

if id != p {
logger.Debugw("remote peer ID does not match public key's peer ID", "from", p, "peer", pi.ID, "error", err)
continue
}

ok, err := pub.Verify(append(key, pi.ID...), sig)
if err != nil {
logger.Debugw("failed to verify signature", "from", p, "peer", pi.ID, "error", err)
continue
}

if !ok {
logger.Debugw("failed to verify signature", "from", p, "peer", pi.ID)
continue
}

err = dht.providerStore.AddProvider(ctx, key, pi.ID)
if err != nil {
return nil, err
}

logger.Infof("added provider %x for %x", pi.ID, key)
}

return nil, nil
Expand Down
3 changes: 1 addition & 2 deletions pb/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ build:
protoc --proto_path=$(GOPATH)/src:. --gogofast_out=. $<

clean:
rm -f *.pb.go
rm -f *.go
rm -f *.pb.go
Loading