提交 5e8c9481 作者: Dirk McCormick

namesys: verify signature in ipns validator

License: MIT
Signed-off-by: 's avatarDirk McCormick <dirkmdev@gmail.com>
上级 3106135d
...@@ -949,14 +949,14 @@ func startListening(ctx context.Context, host p2phost.Host, cfg *config.Config) ...@@ -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) { func constructDHTRouting(ctx context.Context, host p2phost.Host, dstore repo.Datastore) (routing.IpfsRouting, error) {
dhtRouting := dht.NewDHT(ctx, host, dstore) dhtRouting := dht.NewDHT(ctx, host, dstore)
dhtRouting.Validator[IpnsValidatorTag] = namesys.IpnsRecordValidator dhtRouting.Validator[IpnsValidatorTag] = namesys.NewIpnsRecordValidator(host.Peerstore())
dhtRouting.Selector[IpnsValidatorTag] = namesys.IpnsSelectorFunc dhtRouting.Selector[IpnsValidatorTag] = namesys.IpnsSelectorFunc
return dhtRouting, nil return dhtRouting, nil
} }
func constructClientDHTRouting(ctx context.Context, host p2phost.Host, dstore repo.Datastore) (routing.IpfsRouting, error) { func constructClientDHTRouting(ctx context.Context, host p2phost.Host, dstore repo.Datastore) (routing.IpfsRouting, error) {
dhtRouting := dht.NewDHTClient(ctx, host, dstore) dhtRouting := dht.NewDHTClient(ctx, host, dstore)
dhtRouting.Validator[IpnsValidatorTag] = namesys.IpnsRecordValidator dhtRouting.Validator[IpnsValidatorTag] = namesys.NewIpnsRecordValidator(host.Peerstore())
dhtRouting.Selector[IpnsValidatorTag] = namesys.IpnsSelectorFunc dhtRouting.Selector[IpnsValidatorTag] = namesys.IpnsSelectorFunc
return dhtRouting, nil return dhtRouting, nil
} }
......
package namesys package namesys
import ( import (
"io" "context"
"testing" "testing"
"time" "time"
path "github.com/ipfs/go-ipfs/path" path "github.com/ipfs/go-ipfs/path"
mockrouting "github.com/ipfs/go-ipfs/routing/mock"
u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util" 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" 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" proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer" peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
) )
func TestValidation(t *testing.T) { func TestValidation(t *testing.T) {
// Create a record validator ctx := context.Background()
validator := make(record.Validator) rid := testutil.RandIdentityOrFatal(t)
validator["ipns"] = &record.ValidChecker{Func: ValidateIpnsRecord, Sign: true} dstore := dssync.MutexWrap(ds.NewMapDatastore())
peerstore := pstore.NewPeerstore()
// Generate a key for signing the records vstore := newMockValueStore(rid, dstore, peerstore)
r := u.NewSeededRand(15) // generate deterministic keypair vstore.Validator["ipns"] = NewIpnsRecordValidator(peerstore)
priv, ipnsPath := genKeys(t, r) 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 // Create entry with expiry in one hour
priv, id, _, ipnsDHTPath := genKeys(t)
ts := time.Now() 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 { if err != nil {
t.Fatal(err) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Create the record // Publish entry
rec, err := record.MakePutRecord(priv, ipnsPath, val, true) err = PublishEntry(ctx, vstore, ipnsDHTPath, entry)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Validate the record // Resolve entry
err = validator.VerifyRecord(rec) resp, err := resolver.resolveOnce(ctx, id.Pretty())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if resp != p {
t.Fatal("Mismatch between published path %s and resolved path %s", p, resp)
}
/* TODO(#4613) // Create expired entry
// Create IPNS record path with a different private key expiredEntry, err := CreateRoutingEntryData(priv, p, 1, ts.Add(-1*time.Hour))
_, ipnsWrongAuthor := genKeys(t, r)
wrongAuthorRec, err := record.MakePutRecord(priv, ipnsWrongAuthor, val, true)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Record should fail validation because path doesn't match author // Publish entry
err = validator.VerifyRecord(wrongAuthorRec) err = PublishEntry(ctx, vstore, ipnsDHTPath, expiredEntry)
if err != ErrInvalidAuthor { if err != nil {
t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor") t.Fatal(err)
} }
// Create IPNS record path with extra path components after author // Record should fail validation because entry is expired
extraPath := ipnsPath + "/some/path" _, err = resolver.resolveOnce(ctx, id.Pretty())
extraPathRec, err := record.MakePutRecord(priv, extraPath, val, true) 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)
}
// Publish entry
err = PublishEntry(ctx, vstore, ipnsDHTPath2, entry)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Record should fail validation because path has extra components after author // Record should fail validation because public key defined by
err = validator.VerifyRecord(extraPathRec) // ipns path doesn't match record signature
if err != ErrInvalidAuthor { _, err = resolver.resolveOnce(ctx, id2.Pretty())
t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor") if err != ErrSignature {
t.Fatal("ValidateIpnsRecord should have failed signature verification")
} }
// Create unsigned IPNS record // Publish entry without making public key available in peer store
unsignedRec, err := record.MakePutRecord(priv, ipnsPath, val, false) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Record should fail validation because IPNS records require signature // Record should fail validation because public key is not available
err = validator.VerifyRecord(unsignedRec) // in peer store or on network
if err != ErrInvalidAuthor { _, err = resolver.resolveOnce(ctx, id3.Pretty())
t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor") if err == nil {
t.Fatal("ValidateIpnsRecord should have failed because public key was not found")
} }
// Create unsigned IPNS record with no author // Publish public key to the network
unsignedRecNoAuthor, err := record.MakePutRecord(priv, ipnsPath, val, false) err = PublishPublicKey(ctx, vstore, pubkDHTPath3, priv3.GetPublic())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
noAuth := ""
unsignedRecNoAuthor.Author = &noAuth
// Record should fail validation because IPNS records require author // Record should now pass validation because resolver will ensure
err = validator.VerifyRecord(unsignedRecNoAuthor) // public key is available in the peer store by looking it up in
if err != ErrInvalidAuthor { // the DHT, which causes the DHT to fetch it and cache it in the
t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor") // peer store
_, err = resolver.resolveOnce(ctx, id3.Pretty())
if err != nil {
t.Fatal(err)
} }
*/ }
// Create expired entry func genKeys(t *testing.T) (ci.PrivKey, peer.ID, string, string) {
expiredEntry, err := CreateRoutingEntryData(priv, path.Path("foo"), 1, ts.Add(-1*time.Hour)) sr := u.NewTimeSeededRand()
priv, _, err := ci.GenerateKeyPairWithReader(ci.RSA, 1024, sr)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
valExp, err := proto.Marshal(expiredEntry)
// Create entry with expiry in one hour
pid, err := peer.IDFromPrivateKey(priv)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
pubkDHTPath, ipnsDHTPath := IpnsKeysForID(pid)
// Create record with the expired entry return priv, pid, pubkDHTPath, ipnsDHTPath
expiredRec, err := record.MakePutRecord(priv, ipnsPath, valExp, true) }
// Record should fail validation because entry is expired type mockValueStore struct {
err = validator.VerifyRecord(expiredRec) r routing.ValueStore
if err != ErrExpiredRecord { kbook pstore.KeyBook
t.Fatal("ValidateIpnsRecord should have returned ErrExpiredRecord") 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) { func (m *mockValueStore) GetPublicKey(ctx context.Context, p peer.ID) (ci.PubKey, error) {
priv, _, err := ci.GenerateKeyPairWithReader(ci.RSA, 1024, r) pk := m.kbook.PubKey(p)
if pk != nil {
return pk, nil
}
pkkey := routing.KeyForPublicKey(p)
val, err := m.GetValue(ctx, pkkey)
if err != nil { if err != nil {
t.Fatal(err) return nil, err
} }
id, err := peer.IDFromPrivateKey(priv)
pk, err = ci.UnmarshalPublicKey(val)
if err != nil { 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 ...@@ -3,7 +3,6 @@ package namesys
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"time" "time"
...@@ -16,25 +15,12 @@ import ( ...@@ -16,25 +15,12 @@ import (
u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util" u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
ds "gx/ipfs/QmPpegoMqhAEqjncrzArm7KVWAkCm78rqL2DPuNjhPrshg/go-datastore" ds "gx/ipfs/QmPpegoMqhAEqjncrzArm7KVWAkCm78rqL2DPuNjhPrshg/go-datastore"
routing "gx/ipfs/QmTiWLZ6Fo5j4KcTVutZJ5KWRRJrbxzmxA4td8NfEdrPh7/go-libp2p-routing" routing "gx/ipfs/QmTiWLZ6Fo5j4KcTVutZJ5KWRRJrbxzmxA4td8NfEdrPh7/go-libp2p-routing"
record "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record"
dhtpb "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record/pb" dhtpb "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record/pb"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto" proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer" peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" 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 PublishPutValTimeout = time.Minute
const DefaultRecordTTL = 24 * time.Hour const DefaultRecordTTL = 24 * time.Hour
...@@ -208,7 +194,7 @@ func PublishEntry(ctx context.Context, r routing.ValueStore, ipnskey string, rec ...@@ -208,7 +194,7 @@ func PublishEntry(ctx context.Context, r routing.ValueStore, ipnskey string, rec
} }
log.Debugf("Storing ipns entry at: %s", ipnskey) 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) return r.PutValue(timectx, ipnskey, data)
} }
...@@ -238,114 +224,6 @@ func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte { ...@@ -238,114 +224,6 @@ func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
[]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 // InitializeKeyspace sets the ipns record for the given key to
// point to an empty directory. // point to an empty directory.
// TODO: this doesnt feel like it belongs here // TODO: this doesnt feel like it belongs here
......
...@@ -2,7 +2,6 @@ package namesys ...@@ -2,7 +2,6 @@ package namesys
import ( import (
"context" "context"
"fmt"
"strings" "strings"
"time" "time"
...@@ -15,7 +14,7 @@ import ( ...@@ -15,7 +14,7 @@ import (
lru "gx/ipfs/QmVYxfoJQiZijTgPNHCHgHELvQpbsJNTg6Crmc3dQkj3yy/golang-lru" lru "gx/ipfs/QmVYxfoJQiZijTgPNHCHgHELvQpbsJNTg6Crmc3dQkj3yy/golang-lru"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto" proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash" mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid" cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
) )
...@@ -131,58 +130,37 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa ...@@ -131,58 +130,37 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa
return "", err return "", err
} }
// use the routing system to get the name. // Name should be the hash of a public key retrievable from ipfs.
// /ipns/<name> // We retrieve the public key here to make certain that it's in the peer
h := []byte("/ipns/" + string(hash)) // 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
var entry *pb.IpnsEntry // store to verify the record signature
var pubkey ci.PubKey _, err = routing.GetPublicKey(r.routing, ctx, hash)
resp := make(chan error, 2)
go func() {
ipnsKey := string(h)
val, err := r.routing.GetValue(ctx, ipnsKey)
if err != nil { if err != nil {
log.Debugf("RoutingResolver: dht get failed: %s", err) log.Debugf("RoutingResolver: could not retrieve public key %s: %s\n", name, err)
resp <- err return "", err
return
} }
entry = new(pb.IpnsEntry) pid, err := peer.IDFromBytes(hash)
err = proto.Unmarshal(val, entry)
if err != nil { if err != nil {
resp <- err log.Debugf("RoutingResolver: could not convert public key hash %s to peer ID: %s\n", name, err)
return return "", err
} }
resp <- nil // use the routing system to get the name.
}() _, ipnsKey := IpnsKeysForID(pid)
val, err := r.routing.GetValue(ctx, ipnsKey)
go func() {
// name should be a public key retrievable from ipfs
pubk, err := routing.GetPublicKey(r.routing, ctx, hash)
if err != nil { if err != nil {
resp <- err log.Debugf("RoutingResolver: dht get for name %s failed: %s", name, err)
return return "", err
} }
pubkey = pubk entry := new(pb.IpnsEntry)
resp <- nil err = proto.Unmarshal(val, entry)
}()
for i := 0; i < 2; i++ {
err = <-resp
if err != nil { if err != nil {
log.Debugf("RoutingResolver: could not unmarshal value for name %s: %s", name, err)
return "", 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: // check for old style record:
valh, err := mh.Cast(entry.GetValue()) valh, err := mh.Cast(entry.GetValue())
...@@ -197,7 +175,7 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa ...@@ -197,7 +175,7 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa
return p, nil return p, nil
} else { } else {
// Its an old style multihash record // 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)) p := path.FromCid(cid.NewCidV0(valh))
r.cacheSet(name, p, entry) r.cacheSet(name, p, entry)
return p, nil 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"
)
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
}
package namesys
import (
"errors"
"time"
pb "github.com/ipfs/go-ipfs/namesys/pb"
peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
pstore "gx/ipfs/QmXauCuJzmzapetmC6W4TuDJLL1yFFrVzSHoWv8YdbmnxH/go-libp2p-peerstore"
u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
record "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record"
)
// 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")
func NewIpnsRecordValidator(kbook pstore.KeyBook) *record.ValidChecker {
// ValidateIpnsRecord implements ValidatorFunc and verifies that the
// given 'val' is an IpnsEntry and that that entry is valid.
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 err
}
// 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 public key hash", r.Key)
return ErrSignature
}
pubk := kbook.PubKey(pid)
if pubk == nil {
log.Debugf("public key with hash %s not found in peer store", pid)
return ErrSignature
}
// 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论