Unverified 提交 9014c64f 作者: Whyrusleeping 提交者: GitHub

Merge pull request #4628 from dirkmc/fix/namesys-verify-pubk

namesys: verify signature in ipns validator
......@@ -949,14 +949,14 @@ func startListening(ctx context.Context, host p2phost.Host, cfg *config.Config)
func constructDHTRouting(ctx context.Context, host p2phost.Host, dstore repo.Datastore) (routing.IpfsRouting, error) {
dhtRouting := dht.NewDHT(ctx, host, dstore)
dhtRouting.Validator[IpnsValidatorTag] = namesys.IpnsRecordValidator
dhtRouting.Validator[IpnsValidatorTag] = namesys.NewIpnsRecordValidator(host.Peerstore())
dhtRouting.Selector[IpnsValidatorTag] = namesys.IpnsSelectorFunc
return dhtRouting, nil
}
func constructClientDHTRouting(ctx context.Context, host p2phost.Host, dstore repo.Datastore) (routing.IpfsRouting, error) {
dhtRouting := dht.NewDHTClient(ctx, host, dstore)
dhtRouting.Validator[IpnsValidatorTag] = namesys.IpnsRecordValidator
dhtRouting.Validator[IpnsValidatorTag] = namesys.NewIpnsRecordValidator(host.Peerstore())
dhtRouting.Selector[IpnsValidatorTag] = namesys.IpnsSelectorFunc
return dhtRouting, nil
}
......
package namesys
import (
"io"
"context"
"fmt"
"testing"
"time"
path "github.com/ipfs/go-ipfs/path"
mockrouting "github.com/ipfs/go-ipfs/routing/mock"
u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
ds "gx/ipfs/QmPpegoMqhAEqjncrzArm7KVWAkCm78rqL2DPuNjhPrshg/go-datastore"
dssync "gx/ipfs/QmPpegoMqhAEqjncrzArm7KVWAkCm78rqL2DPuNjhPrshg/go-datastore/sync"
routing "gx/ipfs/QmTiWLZ6Fo5j4KcTVutZJ5KWRRJrbxzmxA4td8NfEdrPh7/go-libp2p-routing"
record "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record"
recordpb "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record/pb"
testutil "gx/ipfs/QmVvkK7s5imCiq3JVbL3pGfnhcCnf3LrFJPF4GE2sAoGZf/go-testutil"
pstore "gx/ipfs/QmXauCuJzmzapetmC6W4TuDJLL1yFFrVzSHoWv8YdbmnxH/go-libp2p-peerstore"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
)
func TestValidation(t *testing.T) {
// Create a record validator
validator := make(record.Validator)
validator["ipns"] = &record.ValidChecker{Func: ValidateIpnsRecord, Sign: true}
func testValidatorCase(t *testing.T, priv ci.PrivKey, kbook pstore.KeyBook, ns string, key string, val []byte, eol time.Time, exp error) {
validChecker := NewIpnsRecordValidator(kbook)
p := path.Path("/ipfs/QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG")
entry, err := CreateRoutingEntryData(priv, p, 1, eol)
if err != nil {
t.Fatal(err)
}
data := val
if data == nil {
data, err = proto.Marshal(entry)
if err != nil {
t.Fatal(err)
}
}
rec := &record.ValidationRecord{
Namespace: ns,
Key: key,
Value: data,
}
err = validChecker.Func(rec)
if err != exp {
params := fmt.Sprintf("namespace: %s\nkey: %s\neol: %s\n", ns, key, eol)
if exp == nil {
t.Fatalf("Unexpected error %s for params %s", err, params)
} else if err == nil {
t.Fatalf("Expected error %s but there was no error for params %s", exp, params)
} else {
t.Fatalf("Expected error %s but got %s for params %s", exp, err, params)
}
}
}
func TestValidator(t *testing.T) {
ts := time.Now()
// Generate a key for signing the records
r := u.NewSeededRand(15) // generate deterministic keypair
priv, ipnsPath := genKeys(t, r)
priv, id, _, _ := genKeys(t)
priv2, id2, _, _ := genKeys(t)
kbook := pstore.NewPeerstore()
kbook.AddPubKey(id, priv.GetPublic())
emptyKbook := pstore.NewPeerstore()
testValidatorCase(t, priv, kbook, "ipns", string(id), nil, ts.Add(time.Hour), nil)
testValidatorCase(t, priv, kbook, "ipns", string(id), nil, ts.Add(time.Hour*-1), ErrExpiredRecord)
testValidatorCase(t, priv, kbook, "ipns", string(id), []byte("bad data"), ts.Add(time.Hour), ErrBadRecord)
testValidatorCase(t, priv, kbook, "ipns", "bad key", nil, ts.Add(time.Hour), ErrKeyFormat)
testValidatorCase(t, priv, emptyKbook, "ipns", string(id), nil, ts.Add(time.Hour), ErrPublicKeyNotFound)
testValidatorCase(t, priv2, kbook, "ipns", string(id2), nil, ts.Add(time.Hour), ErrPublicKeyNotFound)
testValidatorCase(t, priv2, kbook, "ipns", string(id), nil, ts.Add(time.Hour), ErrSignature)
testValidatorCase(t, priv, kbook, "", string(id), nil, ts.Add(time.Hour), ErrInvalidPath)
testValidatorCase(t, priv, kbook, "wrong", string(id), nil, ts.Add(time.Hour), ErrInvalidPath)
}
func TestResolverValidation(t *testing.T) {
ctx := context.Background()
rid := testutil.RandIdentityOrFatal(t)
dstore := dssync.MutexWrap(ds.NewMapDatastore())
peerstore := pstore.NewPeerstore()
vstore := newMockValueStore(rid, dstore, peerstore)
vstore.Validator["ipns"] = NewIpnsRecordValidator(peerstore)
vstore.Validator["pk"] = &record.ValidChecker{
Func: func(r *record.ValidationRecord) error {
return nil
},
Sign: false,
}
resolver := NewRoutingResolver(vstore, 0)
// Create entry with expiry in one hour
priv, id, _, ipnsDHTPath := genKeys(t)
ts := time.Now()
entry, err := CreateRoutingEntryData(priv, path.Path("foo"), 1, ts.Add(time.Hour))
p := path.Path("/ipfs/QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG")
entry, err := CreateRoutingEntryData(priv, p, 1, ts.Add(time.Hour))
if err != nil {
t.Fatal(err)
}
val, err := proto.Marshal(entry)
// Make peer's public key available in peer store
err = peerstore.AddPubKey(id, priv.GetPublic())
if err != nil {
t.Fatal(err)
}
// Create the record
rec, err := record.MakePutRecord(priv, ipnsPath, val, true)
// Publish entry
err = PublishEntry(ctx, vstore, ipnsDHTPath, entry)
if err != nil {
t.Fatal(err)
}
// Validate the record
err = validator.VerifyRecord(rec)
// Resolve entry
resp, err := resolver.resolveOnce(ctx, id.Pretty())
if err != nil {
t.Fatal(err)
}
if resp != p {
t.Fatalf("Mismatch between published path %s and resolved path %s", p, resp)
}
/* TODO(#4613)
// Create IPNS record path with a different private key
_, ipnsWrongAuthor := genKeys(t, r)
wrongAuthorRec, err := record.MakePutRecord(priv, ipnsWrongAuthor, val, true)
// Create expired entry
expiredEntry, err := CreateRoutingEntryData(priv, p, 1, ts.Add(-1*time.Hour))
if err != nil {
t.Fatal(err)
}
// Record should fail validation because path doesn't match author
err = validator.VerifyRecord(wrongAuthorRec)
if err != ErrInvalidAuthor {
t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor")
// Publish entry
err = PublishEntry(ctx, vstore, ipnsDHTPath, expiredEntry)
if err != nil {
t.Fatal(err)
}
// Create IPNS record path with extra path components after author
extraPath := ipnsPath + "/some/path"
extraPathRec, err := record.MakePutRecord(priv, extraPath, val, true)
// Record should fail validation because entry is expired
_, err = resolver.resolveOnce(ctx, id.Pretty())
if err != ErrExpiredRecord {
t.Fatal("ValidateIpnsRecord should have returned ErrExpiredRecord")
}
// Create IPNS record path with a different private key
priv2, id2, _, ipnsDHTPath2 := genKeys(t)
// Make peer's public key available in peer store
err = peerstore.AddPubKey(id2, priv2.GetPublic())
if err != nil {
t.Fatal(err)
}
// Record should fail validation because path has extra components after author
err = validator.VerifyRecord(extraPathRec)
if err != ErrInvalidAuthor {
t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor")
// Publish entry
err = PublishEntry(ctx, vstore, ipnsDHTPath2, entry)
if err != nil {
t.Fatal(err)
}
// Record should fail validation because public key defined by
// ipns path doesn't match record signature
_, err = resolver.resolveOnce(ctx, id2.Pretty())
if err != ErrSignature {
t.Fatal("ValidateIpnsRecord should have failed signature verification")
}
// Create unsigned IPNS record
unsignedRec, err := record.MakePutRecord(priv, ipnsPath, val, false)
// Publish entry without making public key available in peer store
priv3, id3, pubkDHTPath3, ipnsDHTPath3 := genKeys(t)
entry3, err := CreateRoutingEntryData(priv3, p, 1, ts.Add(time.Hour))
if err != nil {
t.Fatal(err)
}
err = PublishEntry(ctx, vstore, ipnsDHTPath3, entry3)
if err != nil {
t.Fatal(err)
}
// Record should fail validation because IPNS records require signature
err = validator.VerifyRecord(unsignedRec)
if err != ErrInvalidAuthor {
t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor")
// Record should fail validation because public key is not available
// in peer store or on network
_, err = resolver.resolveOnce(ctx, id3.Pretty())
if err == nil {
t.Fatal("ValidateIpnsRecord should have failed because public key was not found")
}
// Create unsigned IPNS record with no author
unsignedRecNoAuthor, err := record.MakePutRecord(priv, ipnsPath, val, false)
// Publish public key to the network
err = PublishPublicKey(ctx, vstore, pubkDHTPath3, priv3.GetPublic())
if err != nil {
t.Fatal(err)
}
noAuth := ""
unsignedRecNoAuthor.Author = &noAuth
// Record should fail validation because IPNS records require author
err = validator.VerifyRecord(unsignedRecNoAuthor)
if err != ErrInvalidAuthor {
t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor")
// Record should now pass validation because resolver will ensure
// public key is available in the peer store by looking it up in
// the DHT, which causes the DHT to fetch it and cache it in the
// peer store
_, err = resolver.resolveOnce(ctx, id3.Pretty())
if err != nil {
t.Fatal(err)
}
*/
}
// Create expired entry
expiredEntry, err := CreateRoutingEntryData(priv, path.Path("foo"), 1, ts.Add(-1*time.Hour))
func genKeys(t *testing.T) (ci.PrivKey, peer.ID, string, string) {
sr := u.NewTimeSeededRand()
priv, _, err := ci.GenerateKeyPairWithReader(ci.RSA, 1024, sr)
if err != nil {
t.Fatal(err)
}
valExp, err := proto.Marshal(expiredEntry)
// Create entry with expiry in one hour
pid, err := peer.IDFromPrivateKey(priv)
if err != nil {
t.Fatal(err)
}
pubkDHTPath, ipnsDHTPath := IpnsKeysForID(pid)
// Create record with the expired entry
expiredRec, err := record.MakePutRecord(priv, ipnsPath, valExp, true)
return priv, pid, pubkDHTPath, ipnsDHTPath
}
// Record should fail validation because entry is expired
err = validator.VerifyRecord(expiredRec)
if err != ErrExpiredRecord {
t.Fatal("ValidateIpnsRecord should have returned ErrExpiredRecord")
type mockValueStore struct {
r routing.ValueStore
kbook pstore.KeyBook
Validator record.Validator
}
func newMockValueStore(id testutil.Identity, dstore ds.Datastore, kbook pstore.KeyBook) *mockValueStore {
serv := mockrouting.NewServer()
r := serv.ClientWithDatastore(context.Background(), id, dstore)
return &mockValueStore{r, kbook, make(record.Validator)}
}
func (m *mockValueStore) GetValue(ctx context.Context, k string) ([]byte, error) {
data, err := m.r.GetValue(ctx, k)
if err != nil {
return data, err
}
rec := new(recordpb.Record)
rec.Key = proto.String(k)
rec.Value = data
if err = m.Validator.VerifyRecord(rec); err != nil {
return nil, err
}
return data, err
}
func genKeys(t *testing.T, r io.Reader) (ci.PrivKey, string) {
priv, _, err := ci.GenerateKeyPairWithReader(ci.RSA, 1024, r)
func (m *mockValueStore) GetPublicKey(ctx context.Context, p peer.ID) (ci.PubKey, error) {
pk := m.kbook.PubKey(p)
if pk != nil {
return pk, nil
}
pkkey := routing.KeyForPublicKey(p)
val, err := m.GetValue(ctx, pkkey)
if err != nil {
t.Fatal(err)
return nil, err
}
id, err := peer.IDFromPrivateKey(priv)
pk, err = ci.UnmarshalPublicKey(val)
if err != nil {
t.Fatal(err)
return nil, err
}
_, ipnsKey := IpnsKeysForID(id)
return priv, ipnsKey
return pk, m.kbook.AddPubKey(p, pk)
}
func (m *mockValueStore) GetValues(ctx context.Context, k string, count int) ([]routing.RecvdVal, error) {
return m.r.GetValues(ctx, k, count)
}
func (m *mockValueStore) PutValue(ctx context.Context, k string, d []byte) error {
return m.r.PutValue(ctx, k, d)
}
......@@ -3,7 +3,6 @@ package namesys
import (
"bytes"
"context"
"errors"
"fmt"
"time"
......@@ -16,25 +15,12 @@ import (
u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
ds "gx/ipfs/QmPpegoMqhAEqjncrzArm7KVWAkCm78rqL2DPuNjhPrshg/go-datastore"
routing "gx/ipfs/QmTiWLZ6Fo5j4KcTVutZJ5KWRRJrbxzmxA4td8NfEdrPh7/go-libp2p-routing"
record "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record"
dhtpb "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record/pb"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
)
// ErrExpiredRecord should be returned when an ipns record is
// invalid due to being too old
var ErrExpiredRecord = errors.New("expired record")
// ErrUnrecognizedValidity is returned when an IpnsRecord has an
// unknown validity type.
var ErrUnrecognizedValidity = errors.New("unrecognized validity type")
// ErrInvalidPath should be returned when an ipns record path
// is not in a valid format
var ErrInvalidPath = errors.New("record path invalid")
const PublishPutValTimeout = time.Minute
const DefaultRecordTTL = 24 * time.Hour
......@@ -208,7 +194,7 @@ func PublishEntry(ctx context.Context, r routing.ValueStore, ipnskey string, rec
}
log.Debugf("Storing ipns entry at: %s", ipnskey)
// Store ipns entry at "/ipns/"+b58(h(pubkey))
// Store ipns entry at "/ipns/"+h(pubkey)
return r.PutValue(timectx, ipnskey, data)
}
......@@ -238,114 +224,6 @@ func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
[]byte{})
}
var IpnsRecordValidator = &record.ValidChecker{
Func: ValidateIpnsRecord,
Sign: true,
}
func IpnsSelectorFunc(k string, vals [][]byte) (int, error) {
var recs []*pb.IpnsEntry
for _, v := range vals {
e := new(pb.IpnsEntry)
err := proto.Unmarshal(v, e)
if err == nil {
recs = append(recs, e)
} else {
recs = append(recs, nil)
}
}
return selectRecord(recs, vals)
}
func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) {
var best_seq uint64
best_i := -1
for i, r := range recs {
if r == nil || r.GetSequence() < best_seq {
continue
}
if best_i == -1 || r.GetSequence() > best_seq {
best_seq = r.GetSequence()
best_i = i
} else if r.GetSequence() == best_seq {
rt, err := u.ParseRFC3339(string(r.GetValidity()))
if err != nil {
continue
}
bestt, err := u.ParseRFC3339(string(recs[best_i].GetValidity()))
if err != nil {
continue
}
if rt.After(bestt) {
best_i = i
} else if rt == bestt {
if bytes.Compare(vals[i], vals[best_i]) > 0 {
best_i = i
}
}
}
}
if best_i == -1 {
return 0, errors.New("no usable records in given set")
}
return best_i, nil
}
// ValidateIpnsRecord implements ValidatorFunc and verifies that the
// given 'val' is an IpnsEntry and that that entry is valid.
func ValidateIpnsRecord(r *record.ValidationRecord) error {
if r.Namespace != "ipns" {
return ErrInvalidPath
}
entry := new(pb.IpnsEntry)
err := proto.Unmarshal(r.Value, entry)
if err != nil {
return err
}
// NOTE/FIXME(#4613): We're not checking the DHT signature/author here.
// We're going to remove them in a followup commit and then check the
// *IPNS* signature. However, to do that, we need to ensure we *have*
// the public key and:
//
// 1. Don't want to fetch it from the network when handling PUTs.
// 2. Do want to fetch it from the network when handling GETs.
//
// Therefore, we'll need to either:
//
// 1. Pass some for of offline hint to the validator (e.g., using a context).
// 2. Ensure we pre-fetch the key when performing gets.
//
// This PR is already *way* too large so we're punting that fix to a new
// PR.
//
// This is not a regression, it just restores the current (bad)
// behavior.
// Check that record has not expired
switch entry.GetValidityType() {
case pb.IpnsEntry_EOL:
t, err := u.ParseRFC3339(string(entry.GetValidity()))
if err != nil {
log.Debug("failed parsing time for ipns record EOL")
return err
}
if time.Now().After(t) {
return ErrExpiredRecord
}
default:
return ErrUnrecognizedValidity
}
return nil
}
// InitializeKeyspace sets the ipns record for the given key to
// point to an empty directory.
// TODO: this doesnt feel like it belongs here
......
......@@ -2,7 +2,6 @@ package namesys
import (
"context"
"fmt"
"strings"
"time"
......@@ -14,8 +13,8 @@ import (
routing "gx/ipfs/QmTiWLZ6Fo5j4KcTVutZJ5KWRRJrbxzmxA4td8NfEdrPh7/go-libp2p-routing"
lru "gx/ipfs/QmVYxfoJQiZijTgPNHCHgHELvQpbsJNTg6Crmc3dQkj3yy/golang-lru"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
)
......@@ -131,58 +130,39 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa
return "", err
}
// use the routing system to get the name.
// /ipns/<name>
h := []byte("/ipns/" + string(hash))
var entry *pb.IpnsEntry
var pubkey ci.PubKey
resp := make(chan error, 2)
go func() {
ipnsKey := string(h)
val, err := r.routing.GetValue(ctx, ipnsKey)
// Name should be the hash of a public key retrievable from ipfs.
// We retrieve the public key here to make certain that it's in the peer
// store before calling GetValue() on the DHT - the DHT will call the
// ipns validator, which in turn will get the public key from the peer
// store to verify the record signature
_, err = routing.GetPublicKey(r.routing, ctx, hash)
if err != nil {
log.Debugf("RoutingResolver: dht get failed: %s", err)
resp <- err
return
log.Debugf("RoutingResolver: could not retrieve public key %s: %s\n", name, err)
return "", err
}
entry = new(pb.IpnsEntry)
err = proto.Unmarshal(val, entry)
pid, err := peer.IDFromBytes(hash)
if err != nil {
resp <- err
return
log.Debugf("RoutingResolver: could not convert public key hash %s to peer ID: %s\n", name, err)
return "", err
}
resp <- nil
}()
go func() {
// name should be a public key retrievable from ipfs
pubk, err := routing.GetPublicKey(r.routing, ctx, hash)
// Use the routing system to get the name.
// Note that the DHT will call the ipns validator when retrieving
// the value, which in turn verifies the ipns record signature
_, ipnsKey := IpnsKeysForID(pid)
val, err := r.routing.GetValue(ctx, ipnsKey)
if err != nil {
resp <- err
return
log.Debugf("RoutingResolver: dht get for name %s failed: %s", name, err)
return "", err
}
pubkey = pubk
resp <- nil
}()
for i := 0; i < 2; i++ {
err = <-resp
entry := new(pb.IpnsEntry)
err = proto.Unmarshal(val, entry)
if err != nil {
log.Debugf("RoutingResolver: could not unmarshal value for name %s: %s", name, err)
return "", err
}
}
// check sig with pk
if ok, err := pubkey.Verify(ipnsEntryDataForSig(entry), entry.GetSignature()); err != nil || !ok {
return "", fmt.Errorf("ipns entry for %s has invalid signature", h)
}
// ok sig checks out. this is a valid name.
// check for old style record:
valh, err := mh.Cast(entry.GetValue())
......@@ -197,7 +177,7 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa
return p, nil
} else {
// Its an old style multihash record
log.Debugf("encountered CIDv0 ipns entry: %s", h)
log.Debugf("encountered CIDv0 ipns entry: %s", valh)
p := path.FromCid(cid.NewCidV0(valh))
r.cacheSet(name, p, entry)
return p, nil
......
package namesys
import (
"bytes"
"errors"
pb "github.com/ipfs/go-ipfs/namesys/pb"
u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
)
// IpnsSelectorFunc selects the best record by checking which has the highest
// sequence number and latest EOL
func IpnsSelectorFunc(k string, vals [][]byte) (int, error) {
var recs []*pb.IpnsEntry
for _, v := range vals {
e := new(pb.IpnsEntry)
err := proto.Unmarshal(v, e)
if err == nil {
recs = append(recs, e)
} else {
recs = append(recs, nil)
}
}
return selectRecord(recs, vals)
}
func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) {
var bestSeq uint64
besti := -1
for i, r := range recs {
if r == nil || r.GetSequence() < bestSeq {
continue
}
rt, err := u.ParseRFC3339(string(r.GetValidity()))
if err != nil {
log.Errorf("failed to parse ipns record EOL %s", r.GetValidity())
continue
}
if besti == -1 || r.GetSequence() > bestSeq {
bestSeq = r.GetSequence()
besti = i
} else if r.GetSequence() == bestSeq {
bestt, _ := u.ParseRFC3339(string(recs[besti].GetValidity()))
if rt.After(bestt) {
besti = i
} else if rt == bestt {
if bytes.Compare(vals[i], vals[besti]) > 0 {
besti = i
}
}
}
}
if besti == -1 {
return 0, errors.New("no usable records in given set")
}
return besti, nil
}
package namesys
import (
"errors"
"time"
pb "github.com/ipfs/go-ipfs/namesys/pb"
pstore "gx/ipfs/QmXauCuJzmzapetmC6W4TuDJLL1yFFrVzSHoWv8YdbmnxH/go-libp2p-peerstore"
peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
record "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
)
// ErrExpiredRecord should be returned when an ipns record is
// invalid due to being too old
var ErrExpiredRecord = errors.New("expired record")
// ErrUnrecognizedValidity is returned when an IpnsRecord has an
// unknown validity type.
var ErrUnrecognizedValidity = errors.New("unrecognized validity type")
// ErrInvalidPath should be returned when an ipns record path
// is not in a valid format
var ErrInvalidPath = errors.New("record path invalid")
// ErrSignature should be returned when an ipns record fails
// signature verification
var ErrSignature = errors.New("record signature verification failed")
// ErrBadRecord should be returned when an ipns record cannot be unmarshalled
var ErrBadRecord = errors.New("record could not be unmarshalled")
// ErrKeyFormat should be returned when an ipns record key is
// incorrectly formatted (not a peer ID)
var ErrKeyFormat = errors.New("record key could not be parsed into peer ID")
// ErrPublicKeyNotFound should be returned when the public key
// corresponding to the ipns record path cannot be retrieved
// from the peer store
var ErrPublicKeyNotFound = errors.New("public key not found in peer store")
// NewIpnsRecordValidator returns a ValidChecker for IPNS records.
// The validator function will get a public key from the KeyBook
// to verify the record's signature. Note that the public key must
// already have been fetched from the network and put into the KeyBook
// by the caller.
func NewIpnsRecordValidator(kbook pstore.KeyBook) *record.ValidChecker {
// ValidateIpnsRecord implements ValidatorFunc and verifies that the
// given record's value is an IpnsEntry, that the entry has been correctly
// signed, and that the entry has not expired
ValidateIpnsRecord := func(r *record.ValidationRecord) error {
if r.Namespace != "ipns" {
return ErrInvalidPath
}
// Parse the value into an IpnsEntry
entry := new(pb.IpnsEntry)
err := proto.Unmarshal(r.Value, entry)
if err != nil {
return ErrBadRecord
}
// Get the public key defined by the ipns path
pid, err := peer.IDFromString(r.Key)
if err != nil {
log.Debugf("failed to parse ipns record key %s into peer ID", r.Key)
return ErrKeyFormat
}
pubk := kbook.PubKey(pid)
if pubk == nil {
log.Debugf("public key with hash %s not found in peer store", pid)
return ErrPublicKeyNotFound
}
// Check the ipns record signature with the public key
if ok, err := pubk.Verify(ipnsEntryDataForSig(entry), entry.GetSignature()); err != nil || !ok {
log.Debugf("failed to verify signature for ipns record %s", r.Key)
return ErrSignature
}
// Check that record has not expired
switch entry.GetValidityType() {
case pb.IpnsEntry_EOL:
t, err := u.ParseRFC3339(string(entry.GetValidity()))
if err != nil {
log.Debugf("failed parsing time for ipns record EOL in record %s", r.Key)
return err
}
if time.Now().After(t) {
return ErrExpiredRecord
}
default:
return ErrUnrecognizedValidity
}
return nil
}
return &record.ValidChecker{
Func: ValidateIpnsRecord,
Sign: false,
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论