package namesys import ( "context" "fmt" "testing" "time" opts "github.com/ipfs/go-ipfs/namesys/opts" path "github.com/ipfs/go-ipfs/path" 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" mockrouting "gx/ipfs/QmZRcGYvxdauCd7hHnMYLYqcZRaDjv24c7eUNyJojAcdBb/go-ipfs-routing/mock" peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer" ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" ) 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()) 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() p := path.Path("/ipfs/QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG") entry, err := CreateRoutingEntryData(priv, p, 1, ts.Add(time.Hour)) if err != nil { t.Fatal(err) } // Make peer's public key available in peer store err = peerstore.AddPubKey(id, priv.GetPublic()) if err != nil { t.Fatal(err) } // Publish entry err = PublishEntry(ctx, vstore, ipnsDHTPath, entry) if err != nil { t.Fatal(err) } // Resolve entry resp, err := resolver.resolveOnce(ctx, id.Pretty(), opts.DefaultResolveOpts()) if err != nil { t.Fatal(err) } if resp != p { t.Fatalf("Mismatch between published path %s and resolved path %s", p, resp) } // Create expired entry expiredEntry, err := CreateRoutingEntryData(priv, p, 1, ts.Add(-1*time.Hour)) if err != nil { t.Fatal(err) } // Publish entry err = PublishEntry(ctx, vstore, ipnsDHTPath, expiredEntry) if err != nil { t.Fatal(err) } // Record should fail validation because entry is expired _, err = resolver.resolveOnce(ctx, id.Pretty(), opts.DefaultResolveOpts()) if err == nil { t.Fatal("ValidateIpnsRecord should have returned error") } // 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 { 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(), opts.DefaultResolveOpts()) if err == nil { t.Fatal("ValidateIpnsRecord should have failed signature verification") } // 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 public key is not available // in peer store or on network _, err = resolver.resolveOnce(ctx, id3.Pretty(), opts.DefaultResolveOpts()) if err == nil { t.Fatal("ValidateIpnsRecord should have failed because public key was not found") } // Publish public key to the network err = PublishPublicKey(ctx, vstore, pubkDHTPath3, priv3.GetPublic()) if err != nil { t.Fatal(err) } // 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(), opts.DefaultResolveOpts()) if err != nil { t.Fatal(err) } } 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) } // Create entry with expiry in one hour pid, err := peer.IDFromPrivateKey(priv) if err != nil { t.Fatal(err) } pubkDHTPath, ipnsDHTPath := IpnsKeysForID(pid) return priv, pid, pubkDHTPath, ipnsDHTPath } 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 (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 { return nil, err } pk, err = ci.UnmarshalPublicKey(val) if err != nil { return nil, err } return pk, m.kbook.AddPubKey(p, pk) } func (m *mockValueStore) GetValues(ctx context.Context, k string, count int) ([]routing.RecvdVal, error) { vals, err := m.r.GetValues(ctx, k, count) if err != nil { return nil, err } valid := make([]routing.RecvdVal, 0, len(vals)) for _, v := range vals { rec := new(recordpb.Record) rec.Key = proto.String(k) rec.Value = v.Val if err = m.Validator.VerifyRecord(rec); err == nil { valid = append(valid, v) } } return valid, nil } func (m *mockValueStore) PutValue(ctx context.Context, k string, d []byte) error { return m.r.PutValue(ctx, k, d) }