diff --git a/namesys/ipns_validate_test.go b/namesys/ipns_validate_test.go index b5df33a00..262b0711d 100644 --- a/namesys/ipns_validate_test.go +++ b/namesys/ipns_validate_test.go @@ -2,6 +2,7 @@ package namesys import ( "context" + "fmt" "testing" "time" @@ -21,7 +22,62 @@ import ( ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" ) -func TestValidation(t *testing.T) { +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() + + 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()) diff --git a/namesys/routing.go b/namesys/routing.go index 0c17ca4ff..120fabd66 100644 --- a/namesys/routing.go +++ b/namesys/routing.go @@ -147,7 +147,9 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa return "", err } - // use the routing system to get the name. + // 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 { diff --git a/namesys/selector.go b/namesys/selector.go index 296d34830..53c712d1c 100644 --- a/namesys/selector.go +++ b/namesys/selector.go @@ -42,11 +42,13 @@ func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) { } else if r.GetSequence() == bestSeq { rt, err := u.ParseRFC3339(string(r.GetValidity())) if err != nil { + log.Errorf("failed to parse ipns record EOL %s", r.GetValidity()) continue } bestt, err := u.ParseRFC3339(string(recs[besti].GetValidity())) if err != nil { + log.Errorf("failed to parse ipns record EOL %s", recs[besti].GetValidity()) continue } diff --git a/namesys/validator.go b/namesys/validator.go index 1466eac87..e9cefc562 100644 --- a/namesys/validator.go +++ b/namesys/validator.go @@ -29,6 +29,18 @@ var ErrInvalidPath = errors.New("record path invalid") // 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 @@ -44,19 +56,19 @@ func NewIpnsRecordValidator(kbook pstore.KeyBook) *record.ValidChecker { entry := new(pb.IpnsEntry) err := proto.Unmarshal(r.Value, entry) if err != nil { - return err + 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 public key hash", r.Key) - return ErrSignature + 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 ErrSignature + return ErrPublicKeyNotFound } // Check the ipns record signature with the public key