Unverified Commit 6627ef88 authored by Whyrusleeping's avatar Whyrusleeping Committed by GitHub

Merge pull request #5124 from ipfs/feat/extract-ipns

extract ipns record logic to go-ipns
parents d643c045 dafe4953
......@@ -47,9 +47,6 @@ ifneq ($(filter coverage% clean distclean,$(MAKECMDGOALS)),)
include $(dir)/Rules.mk
endif
dir := namesys/pb
include $(dir)/Rules.mk
dir := unixfs/pb
include $(dir)/Rules.mk
......
......@@ -4,12 +4,15 @@ import (
"testing"
"github.com/ipfs/go-ipfs/namesys"
tu "gx/ipfs/QmPdxCaVp4jZ9RbxqZADvKH6kiCR5jHvdR5f2ycjAY6T2a/go-testutil"
ipns "gx/ipfs/QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt/go-ipns"
)
func TestKeyTranslation(t *testing.T) {
pid := tu.RandPeerIDFatal(t)
a, b := namesys.IpnsKeysForID(pid)
pkname := namesys.PkKeyForID(pid)
ipnsname := ipns.RecordKey(pid)
pkk, err := escapeDhtKey("/pk/" + pid.Pretty())
if err != nil {
......@@ -21,11 +24,11 @@ func TestKeyTranslation(t *testing.T) {
t.Fatal(err)
}
if pkk != a {
if pkk != pkname {
t.Fatal("keys didnt match!")
}
if ipnsk != b {
if ipnsk != ipnsname {
t.Fatal("keys didnt match!")
}
}
......@@ -42,6 +42,7 @@ import (
circuit "gx/ipfs/QmPavh4h3Edx5cv8GJQPC35AQAws7faXmzEZyru7k9b9Mn/go-libp2p-circuit"
u "gx/ipfs/QmPdKqUcHGFdeSpvjVoaTRPPstGif9GBZb5Q56RVw9o69A/go-ipfs-util"
p2phost "gx/ipfs/QmQQGtcp6nVUrQjNsnU53YWV1q8fK1Kd9S7FEkYbRZzxry/go-libp2p-host"
ipns "gx/ipfs/QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt/go-ipns"
floodsub "gx/ipfs/QmRFEBGcNjtWPupwHA7zGHeGVLuUyE4ZRFi2MgtrPM6pfb/go-libp2p-floodsub"
pnet "gx/ipfs/QmRGvSwDpN4eunxgDNfmQhayZ6Z9F5a2v31V2D7y77osLg/go-libp2p-pnet"
goprocess "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess"
......@@ -459,7 +460,7 @@ func (n *IpfsNode) startOnlineServicesWithHost(ctx context.Context, host p2phost
validator := record.NamespacedValidator{
"pk": record.PublicKeyValidator{},
"ipns": namesys.IpnsValidator{KeyBook: host.Peerstore()},
"ipns": ipns.Validator{KeyBook: host.Peerstore()},
}
// setup routing service
......
......@@ -2,23 +2,19 @@ package namesys
import (
"context"
"fmt"
"math/rand"
"strings"
"testing"
"time"
opts "github.com/ipfs/go-ipfs/namesys/opts"
pb "github.com/ipfs/go-ipfs/namesys/pb"
path "github.com/ipfs/go-ipfs/path"
record "gx/ipfs/QmPWjVzxHeJdrjp4Jr2R2sPxBrMbBgGPWQtKwCKHHCBF7x/go-libp2p-record"
u "gx/ipfs/QmPdKqUcHGFdeSpvjVoaTRPPstGif9GBZb5Q56RVw9o69A/go-ipfs-util"
testutil "gx/ipfs/QmPdxCaVp4jZ9RbxqZADvKH6kiCR5jHvdR5f2ycjAY6T2a/go-testutil"
ipns "gx/ipfs/QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt/go-ipns"
routing "gx/ipfs/QmUV9hDAAyjeGbxbXkJ2sYqZ6dTd1DXJ2REhYEkRm178Tg/go-libp2p-routing"
ropts "gx/ipfs/QmUV9hDAAyjeGbxbXkJ2sYqZ6dTd1DXJ2REhYEkRm178Tg/go-libp2p-routing/options"
peer "gx/ipfs/QmVf8hTAsLLFtn4WPCRNdnaF2Eag2qTBS6uR8AiHPZARXy/go-libp2p-peer"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
pstore "gx/ipfs/QmZhsmorLpD9kmQ4ynbAu4vbKv2goMUnXazwGA4gnWHDjB/go-libp2p-peerstore"
mockrouting "gx/ipfs/Qmb1N7zdjG2FexpzWNj8T289u9QnQLEiSsTRadDGQxX32D/go-ipfs-routing/mock"
ci "gx/ipfs/Qme1knMqwt1hKZbc1BmQFmnm9f36nyQGwXxPGVpVJ9rMK5/go-libp2p-crypto"
......@@ -26,147 +22,6 @@ import (
dssync "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore/sync"
)
func testValidatorCase(t *testing.T, priv ci.PrivKey, kbook pstore.KeyBook, key string, val []byte, eol time.Time, exp error) {
t.Helper()
match := func(t *testing.T, err error) {
t.Helper()
if err != exp {
params := fmt.Sprintf("key: %s\neol: %s\n", 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)
}
}
}
testValidatorCaseMatchFunc(t, priv, kbook, key, val, eol, match)
}
func testValidatorCaseMatchFunc(t *testing.T, priv ci.PrivKey, kbook pstore.KeyBook, key string, val []byte, eol time.Time, matchf func(*testing.T, error)) {
t.Helper()
validator := IpnsValidator{kbook}
data := val
if data == nil {
p := path.Path("/ipfs/QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG")
entry, err := CreateRoutingEntryData(priv, p, 1, eol)
if err != nil {
t.Fatal(err)
}
data, err = proto.Marshal(entry)
if err != nil {
t.Fatal(err)
}
}
matchf(t, validator.Validate(key, data))
}
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 mustMarshal(t *testing.T, entry *pb.IpnsEntry) []byte {
t.Helper()
data, err := proto.Marshal(entry)
if err != nil {
t.Fatal(err)
}
return data
}
func TestEmbeddedPubKeyValidate(t *testing.T) {
goodeol := time.Now().Add(time.Hour)
kbook := pstore.NewPeerstore()
pth := path.Path("/ipfs/QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG")
priv, _, _, ipnsk := genKeys(t)
entry, err := CreateRoutingEntryData(priv, pth, 1, goodeol)
if err != nil {
t.Fatal(err)
}
testValidatorCase(t, priv, kbook, ipnsk, mustMarshal(t, entry), goodeol, ErrPublicKeyNotFound)
pubkb, err := priv.GetPublic().Bytes()
if err != nil {
t.Fatal(err)
}
entry.PubKey = pubkb
testValidatorCase(t, priv, kbook, ipnsk, mustMarshal(t, entry), goodeol, nil)
entry.PubKey = []byte("probably not a public key")
testValidatorCaseMatchFunc(t, priv, kbook, ipnsk, mustMarshal(t, entry), goodeol, func(t *testing.T, err error) {
if !strings.Contains(err.Error(), "unmarshaling pubkey in record:") {
t.Fatal("expected pubkey unmarshaling to fail")
}
})
opriv, _, _, _ := genKeys(t)
wrongkeydata, err := opriv.GetPublic().Bytes()
if err != nil {
t.Fatal(err)
}
entry.PubKey = wrongkeydata
testValidatorCase(t, priv, kbook, ipnsk, mustMarshal(t, entry), goodeol, ErrPublicKeyMismatch)
}
func TestPeerIDPubKeyValidate(t *testing.T) {
goodeol := time.Now().Add(time.Hour)
kbook := pstore.NewPeerstore()
pth := path.Path("/ipfs/QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG")
sk, pk, err := ci.GenerateEd25519Key(rand.New(rand.NewSource(42)))
if err != nil {
t.Fatal(err)
}
pid, err := peer.IDFromPublicKey(pk)
if err != nil {
t.Fatal(err)
}
ipnsk := "/ipns/" + string(pid)
entry, err := CreateRoutingEntryData(sk, pth, 1, goodeol)
if err != nil {
t.Fatal(err)
}
dataNoKey, err := proto.Marshal(entry)
if err != nil {
t.Fatal(err)
}
testValidatorCase(t, sk, kbook, ipnsk, dataNoKey, goodeol, nil)
}
func TestResolverValidation(t *testing.T) {
ctx := context.Background()
rid := testutil.RandIdentityOrFatal(t)
......@@ -179,8 +34,8 @@ func TestResolverValidation(t *testing.T) {
// 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))
p := []byte("/ipfs/QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG")
entry, err := ipns.Create(priv, p, 1, ts.Add(time.Hour))
if err != nil {
t.Fatal(err)
}
......@@ -202,12 +57,12 @@ func TestResolverValidation(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if resp != p {
if resp != path.Path(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))
expiredEntry, err := ipns.Create(priv, p, 1, ts.Add(-1*time.Hour))
if err != nil {
t.Fatal(err)
}
......@@ -248,7 +103,7 @@ func TestResolverValidation(t *testing.T) {
// 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))
entry3, err := ipns.Create(priv3, p, 1, ts.Add(time.Hour))
if err != nil {
t.Fatal(err)
}
......@@ -292,9 +147,8 @@ func genKeys(t *testing.T) (ci.PrivKey, peer.ID, string, string) {
if err != nil {
t.Fatal(err)
}
pubkDHTPath, ipnsDHTPath := IpnsKeysForID(pid)
return priv, pid, pubkDHTPath, ipnsDHTPath
return priv, pid, PkKeyForID(pid), ipns.RecordKey(pid)
}
type mockValueStore struct {
......@@ -307,7 +161,7 @@ func newMockValueStore(id testutil.Identity, dstore ds.Datastore, kbook pstore.K
serv := mockrouting.NewServer()
r := serv.ClientWithDatastore(context.Background(), id, dstore)
return &mockValueStore{r, kbook, record.NamespacedValidator{
"ipns": IpnsValidator{kbook},
"ipns": ipns.Validator{KeyBook: kbook},
"pk": record.PublicKeyValidator{},
}}
}
......
package namesys
import (
"fmt"
"math/rand"
"testing"
"time"
pb "github.com/ipfs/go-ipfs/namesys/pb"
path "github.com/ipfs/go-ipfs/path"
u "gx/ipfs/QmPdKqUcHGFdeSpvjVoaTRPPstGif9GBZb5Q56RVw9o69A/go-ipfs-util"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
ci "gx/ipfs/Qme1knMqwt1hKZbc1BmQFmnm9f36nyQGwXxPGVpVJ9rMK5/go-libp2p-crypto"
)
func shuffle(a []*pb.IpnsEntry) {
for n := 0; n < 5; n++ {
for i, _ := range a {
j := rand.Intn(len(a))
a[i], a[j] = a[j], a[i]
}
}
}
func AssertSelected(r *pb.IpnsEntry, from ...*pb.IpnsEntry) error {
shuffle(from)
var vals [][]byte
for _, r := range from {
data, err := proto.Marshal(r)
if err != nil {
return err
}
vals = append(vals, data)
}
i, err := selectRecord(from, vals)
if err != nil {
return err
}
if from[i] != r {
return fmt.Errorf("selected incorrect record %d", i)
}
return nil
}
func TestOrdering(t *testing.T) {
// select timestamp so selection is deterministic
ts := time.Unix(1000000, 0)
// generate a key for signing the records
r := u.NewSeededRand(15) // generate deterministic keypair
priv, _, err := ci.GenerateKeyPairWithReader(ci.RSA, 1024, r)
if err != nil {
t.Fatal(err)
}
e1, err := CreateRoutingEntryData(priv, path.Path("foo"), 1, ts.Add(time.Hour))
if err != nil {
t.Fatal(err)
}
e2, err := CreateRoutingEntryData(priv, path.Path("bar"), 2, ts.Add(time.Hour))
if err != nil {
t.Fatal(err)
}
e3, err := CreateRoutingEntryData(priv, path.Path("baz"), 3, ts.Add(time.Hour))
if err != nil {
t.Fatal(err)
}
e4, err := CreateRoutingEntryData(priv, path.Path("cat"), 3, ts.Add(time.Hour*2))
if err != nil {
t.Fatal(err)
}
e5, err := CreateRoutingEntryData(priv, path.Path("dog"), 4, ts.Add(time.Hour*3))
if err != nil {
t.Fatal(err)
}
e6, err := CreateRoutingEntryData(priv, path.Path("fish"), 4, ts.Add(time.Hour*3))
if err != nil {
t.Fatal(err)
}
// e1 is the only record, i hope it gets this right
err = AssertSelected(e1, e1)
if err != nil {
t.Fatal(err)
}
// e2 has the highest sequence number
err = AssertSelected(e2, e1, e2)
if err != nil {
t.Fatal(err)
}
// e3 has the highest sequence number
err = AssertSelected(e3, e1, e2, e3)
if err != nil {
t.Fatal(err)
}
// e4 has a higher timeout
err = AssertSelected(e4, e1, e2, e3, e4)
if err != nil {
t.Fatal(err)
}
// e5 has the highest sequence number
err = AssertSelected(e5, e1, e2, e3, e4, e5)
if err != nil {
t.Fatal(err)
}
// e6 should be selected as its signauture will win in the comparison
err = AssertSelected(e6, e1, e2, e3, e4, e5, e6)
if err != nil {
t.Fatal(err)
}
_ = []interface{}{e1, e2, e3, e4, e5, e6}
}
include mk/header.mk
PB_$(d) = $(wildcard $(d)/*.proto)
TGTS_$(d) = $(PB_$(d):.proto=.pb.go)
#DEPS_GO += $(TGTS_$(d))
include mk/footer.mk
// Code generated by protoc-gen-gogo.
// source: namesys/pb/namesys.proto
// DO NOT EDIT!
/*
Package namesys_pb is a generated protocol buffer package.
It is generated from these files:
namesys/pb/namesys.proto
It has these top-level messages:
IpnsEntry
*/
package namesys_pb
import proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
type IpnsEntry_ValidityType int32
const (
// setting an EOL says "this record is valid until..."
IpnsEntry_EOL IpnsEntry_ValidityType = 0
)
var IpnsEntry_ValidityType_name = map[int32]string{
0: "EOL",
}
var IpnsEntry_ValidityType_value = map[string]int32{
"EOL": 0,
}
func (x IpnsEntry_ValidityType) Enum() *IpnsEntry_ValidityType {
p := new(IpnsEntry_ValidityType)
*p = x
return p
}
func (x IpnsEntry_ValidityType) String() string {
return proto.EnumName(IpnsEntry_ValidityType_name, int32(x))
}
func (x *IpnsEntry_ValidityType) UnmarshalJSON(data []byte) error {
value, err := proto.UnmarshalJSONEnum(IpnsEntry_ValidityType_value, data, "IpnsEntry_ValidityType")
if err != nil {
return err
}
*x = IpnsEntry_ValidityType(value)
return nil
}
type IpnsEntry struct {
Value []byte `protobuf:"bytes,1,req,name=value" json:"value,omitempty"`
Signature []byte `protobuf:"bytes,2,req,name=signature" json:"signature,omitempty"`
ValidityType *IpnsEntry_ValidityType `protobuf:"varint,3,opt,name=validityType,enum=namesys.pb.IpnsEntry_ValidityType" json:"validityType,omitempty"`
Validity []byte `protobuf:"bytes,4,opt,name=validity" json:"validity,omitempty"`
Sequence *uint64 `protobuf:"varint,5,opt,name=sequence" json:"sequence,omitempty"`
Ttl *uint64 `protobuf:"varint,6,opt,name=ttl" json:"ttl,omitempty"`
// in order for nodes to properly validate a record upon receipt, they need the public
// key associated with it. For old RSA keys, its easiest if we just send this as part of
// the record itself. For newer ed25519 keys, the public key can be embedded in the
// peerID, making this field unnecessary.
PubKey []byte `protobuf:"bytes,7,opt,name=pubKey" json:"pubKey,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *IpnsEntry) Reset() { *m = IpnsEntry{} }
func (m *IpnsEntry) String() string { return proto.CompactTextString(m) }
func (*IpnsEntry) ProtoMessage() {}
func (m *IpnsEntry) GetValue() []byte {
if m != nil {
return m.Value
}
return nil
}
func (m *IpnsEntry) GetSignature() []byte {
if m != nil {
return m.Signature
}
return nil
}
func (m *IpnsEntry) GetValidityType() IpnsEntry_ValidityType {
if m != nil && m.ValidityType != nil {
return *m.ValidityType
}
return IpnsEntry_EOL
}
func (m *IpnsEntry) GetValidity() []byte {
if m != nil {
return m.Validity
}
return nil
}
func (m *IpnsEntry) GetSequence() uint64 {
if m != nil && m.Sequence != nil {
return *m.Sequence
}
return 0
}
func (m *IpnsEntry) GetTtl() uint64 {
if m != nil && m.Ttl != nil {
return *m.Ttl
}
return 0
}
func (m *IpnsEntry) GetPubKey() []byte {
if m != nil {
return m.PubKey
}
return nil
}
func init() {
proto.RegisterType((*IpnsEntry)(nil), "namesys.pb.IpnsEntry")
proto.RegisterEnum("namesys.pb.IpnsEntry_ValidityType", IpnsEntry_ValidityType_name, IpnsEntry_ValidityType_value)
}
package namesys.pb;
message IpnsEntry {
enum ValidityType {
// setting an EOL says "this record is valid until..."
EOL = 0;
}
required bytes value = 1;
required bytes signature = 2;
optional ValidityType validityType = 3;
optional bytes validity = 4;
optional uint64 sequence = 5;
optional uint64 ttl = 6;
// in order for nodes to properly validate a record upon receipt, they need the public
// key associated with it. For old RSA keys, its easiest if we just send this as part of
// the record itself. For newer ed25519 keys, the public key can be embedded in the
// peerID, making this field unnecessary.
optional bytes pubKey = 7;
}
package namesys
import (
"bytes"
"context"
"fmt"
"strings"
"sync"
"time"
pb "github.com/ipfs/go-ipfs/namesys/pb"
path "github.com/ipfs/go-ipfs/path"
pin "github.com/ipfs/go-ipfs/pin"
ft "github.com/ipfs/go-ipfs/unixfs"
u "gx/ipfs/QmPdKqUcHGFdeSpvjVoaTRPPstGif9GBZb5Q56RVw9o69A/go-ipfs-util"
ipns "gx/ipfs/QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt/go-ipns"
pb "gx/ipfs/QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt/go-ipns/pb"
routing "gx/ipfs/QmUV9hDAAyjeGbxbXkJ2sYqZ6dTd1DXJ2REhYEkRm178Tg/go-libp2p-routing"
peer "gx/ipfs/QmVf8hTAsLLFtn4WPCRNdnaF2Eag2qTBS6uR8AiHPZARXy/go-libp2p-peer"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
......@@ -131,7 +130,7 @@ func (p *IpnsPublisher) GetPublished(ctx context.Context, id peer.ID, checkRouti
if !checkRouting {
return nil, nil
}
_, ipnskey := IpnsKeysForID(id)
ipnskey := ipns.RecordKey(id)
value, err = p.routing.GetValue(ctx, ipnskey)
if err != nil {
// Not found or other network issue. Can't really do
......@@ -175,7 +174,7 @@ func (p *IpnsPublisher) updateRecord(ctx context.Context, k ci.PrivKey, value pa
}
// Create record
entry, err := CreateRoutingEntryData(k, value, seqno, eol)
entry, err := ipns.Create(k, []byte(value), seqno, eol)
if err != nil {
return nil, err
}
......@@ -229,40 +228,29 @@ func PutRecordToRouting(ctx context.Context, r routing.ValueStore, k ci.PubKey,
errs := make(chan error, 2) // At most two errors (IPNS, and public key)
id, err := peer.IDFromPublicKey(k)
if err != nil {
if err := ipns.EmbedPublicKey(k, entry); err != nil {
return err
}
// Attempt to extract the public key from the ID
extractedPublicKey, err := id.ExtractPublicKey()
id, err := peer.IDFromPublicKey(k)
if err != nil {
return err
}
// if we can't derive the public key from the peerID, embed the entire pubkey in
// the record to make the verifiers job easier
if extractedPublicKey == nil {
pubkeyBytes, err := k.Bytes()
if err != nil {
return err
}
entry.PubKey = pubkeyBytes
}
namekey, ipnskey := IpnsKeysForID(id)
go func() {
errs <- PublishEntry(ctx, r, ipnskey, entry)
errs <- PublishEntry(ctx, r, ipns.RecordKey(id), entry)
}()
// Publish the public key if a public key cannot be extracted from the ID
// TODO: once v0.4.16 is widespread enough, we can stop doing this
// and at that point we can even deprecate the /pk/ namespace in the dht
if extractedPublicKey == nil {
//
// NOTE: This check actually checks if the public key has been embedded
// in the IPNS entry. This check is sufficient because we embed the
// public key in the IPNS entry if it can't be extracted from the ID.
if entry.PubKey != nil {
go func() {
errs <- PublishPublicKey(ctx, r, namekey, k)
errs <- PublishPublicKey(ctx, r, PkKeyForID(id), k)
}()
if err := waitOnErrChan(ctx, errs); err != nil {
......@@ -309,32 +297,6 @@ func PublishEntry(ctx context.Context, r routing.ValueStore, ipnskey string, rec
return r.PutValue(timectx, ipnskey, data)
}
func CreateRoutingEntryData(pk ci.PrivKey, val path.Path, seq uint64, eol time.Time) (*pb.IpnsEntry, error) {
entry := new(pb.IpnsEntry)
entry.Value = []byte(val)
typ := pb.IpnsEntry_EOL
entry.ValidityType = &typ
entry.Sequence = proto.Uint64(seq)
entry.Validity = []byte(u.FormatRFC3339(eol))
sig, err := pk.Sign(ipnsEntryDataForSig(entry))
if err != nil {
return nil, err
}
entry.Signature = sig
return entry, nil
}
func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
return bytes.Join([][]byte{
e.Value,
e.Validity,
[]byte(fmt.Sprint(e.GetValidityType())),
},
[]byte{})
}
// InitializeKeyspace sets the ipns record for the given key to
// point to an empty directory.
// TODO: this doesnt feel like it belongs here
......@@ -356,9 +318,7 @@ func InitializeKeyspace(ctx context.Context, pub Publisher, pins pin.Pinner, key
return pub.Publish(ctx, key, path.FromCid(emptyDir.Cid()))
}
func IpnsKeysForID(id peer.ID) (name, ipns string) {
namekey := "/pk/" + string(id)
ipnskey := "/ipns/" + string(id)
return namekey, ipnskey
// PkKeyForID returns the public key routing key for the given peer ID.
func PkKeyForID(id peer.ID) string {
return "/pk/" + string(id)
}
......@@ -6,10 +6,9 @@ import (
"testing"
"time"
path "github.com/ipfs/go-ipfs/path"
dshelp "gx/ipfs/QmNP2u7bofwUQptHQGPfabGWtTCbxhNLSZKqbf1uzsup9V/go-ipfs-ds-help"
testutil "gx/ipfs/QmPdxCaVp4jZ9RbxqZADvKH6kiCR5jHvdR5f2ycjAY6T2a/go-testutil"
ipns "gx/ipfs/QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt/go-ipns"
ma "gx/ipfs/QmUxSEGbv2nmYNnfXi7839wwQqTN3kwQeUxe8dTjZWZs7J/go-multiaddr"
peer "gx/ipfs/QmVf8hTAsLLFtn4WPCRNdnaF2Eag2qTBS6uR8AiHPZARXy/go-libp2p-peer"
mockrouting "gx/ipfs/Qmb1N7zdjG2FexpzWNj8T289u9QnQLEiSsTRadDGQxX32D/go-ipfs-routing/mock"
......@@ -55,7 +54,7 @@ func testNamekeyPublisher(t *testing.T, keyType int, expectedErr error, expected
}
// Value
value := path.Path("ipfs/TESTING")
value := []byte("ipfs/TESTING")
// Seqnum
seqnum := uint64(0)
......@@ -75,7 +74,7 @@ func testNamekeyPublisher(t *testing.T, keyType int, expectedErr error, expected
serv := mockrouting.NewServer()
r := serv.ClientWithDatastore(context.Background(), &identity{p}, dstore)
entry, err := CreateRoutingEntryData(privKey, value, seqnum, eol)
entry, err := ipns.Create(privKey, value, seqnum, eol)
if err != nil {
t.Fatal(err)
}
......@@ -86,7 +85,7 @@ func testNamekeyPublisher(t *testing.T, keyType int, expectedErr error, expected
}
// Check for namekey existence in value store
namekey, _ := IpnsKeysForID(id)
namekey := PkKeyForID(id)
_, err = r.GetValue(ctx, namekey)
if err != expectedErr {
t.Fatal(err)
......
......@@ -7,9 +7,9 @@ import (
keystore "github.com/ipfs/go-ipfs/keystore"
namesys "github.com/ipfs/go-ipfs/namesys"
pb "github.com/ipfs/go-ipfs/namesys/pb"
path "github.com/ipfs/go-ipfs/path"
pb "gx/ipfs/QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt/go-ipns/pb"
goprocess "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess"
gpctx "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess/context"
peer "gx/ipfs/QmVf8hTAsLLFtn4WPCRNdnaF2Eag2qTBS6uR8AiHPZARXy/go-libp2p-peer"
......
......@@ -9,6 +9,7 @@ import (
path "github.com/ipfs/go-ipfs/path"
testutil "gx/ipfs/QmPdxCaVp4jZ9RbxqZADvKH6kiCR5jHvdR5f2ycjAY6T2a/go-testutil"
ipns "gx/ipfs/QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt/go-ipns"
peer "gx/ipfs/QmVf8hTAsLLFtn4WPCRNdnaF2Eag2qTBS6uR8AiHPZARXy/go-libp2p-peer"
mockrouting "gx/ipfs/Qmb1N7zdjG2FexpzWNj8T289u9QnQLEiSsTRadDGQxX32D/go-ipfs-routing/mock"
ds "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore"
......@@ -71,7 +72,7 @@ func TestPrexistingExpiredRecord(t *testing.T) {
h := path.FromString("/ipfs/QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN")
eol := time.Now().Add(time.Hour * -1)
entry, err := CreateRoutingEntryData(privk, h, 0, eol)
entry, err := ipns.Create(privk, []byte(h), 0, eol)
if err != nil {
t.Fatal(err)
}
......@@ -112,7 +113,7 @@ func TestPrexistingRecord(t *testing.T) {
// Make a good record and put it in the datastore
h := path.FromString("/ipfs/QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN")
eol := time.Now().Add(time.Hour)
entry, err := CreateRoutingEntryData(privk, h, 0, eol)
entry, err := ipns.Create(privk, []byte(h), 0, eol)
if err != nil {
t.Fatal(err)
}
......
......@@ -6,11 +6,11 @@ import (
"time"
opts "github.com/ipfs/go-ipfs/namesys/opts"
pb "github.com/ipfs/go-ipfs/namesys/pb"
path "github.com/ipfs/go-ipfs/path"
u "gx/ipfs/QmPdKqUcHGFdeSpvjVoaTRPPstGif9GBZb5Q56RVw9o69A/go-ipfs-util"
mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
ipns "gx/ipfs/QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt/go-ipns"
pb "gx/ipfs/QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt/go-ipns/pb"
routing "gx/ipfs/QmUV9hDAAyjeGbxbXkJ2sYqZ6dTd1DXJ2REhYEkRm178Tg/go-libp2p-routing"
peer "gx/ipfs/QmVf8hTAsLLFtn4WPCRNdnaF2Eag2qTBS6uR8AiHPZARXy/go-libp2p-peer"
proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
......@@ -82,7 +82,7 @@ func (r *IpnsResolver) resolveOnce(ctx context.Context, name string, options *op
// 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)
ipnsKey := ipns.RecordKey(pid)
val, err := r.routing.GetValue(ctx, ipnsKey, dht.Quorum(int(options.DhtRecordCount)))
if err != nil {
log.Debugf("RoutingResolver: dht get for name %s failed: %s", name, err)
......@@ -114,7 +114,10 @@ func (r *IpnsResolver) resolveOnce(ctx context.Context, name string, options *op
if entry.Ttl != nil {
ttl = time.Duration(*entry.Ttl)
}
if eol, ok := checkEOL(entry); ok {
switch eol, err := ipns.GetEOL(entry); err {
case ipns.ErrUnrecognizedValidity:
// No EOL.
case nil:
ttEol := eol.Sub(time.Now())
if ttEol < 0 {
// It *was* valid when we first resolved it.
......@@ -122,18 +125,10 @@ func (r *IpnsResolver) resolveOnce(ctx context.Context, name string, options *op
} else if ttEol < ttl {
ttl = ttEol
}
default:
log.Errorf("encountered error when parsing EOL: %s", err)
return "", 0, err
}
return p, ttl, nil
}
func checkEOL(e *pb.IpnsEntry) (time.Time, bool) {
if e.GetValidityType() == pb.IpnsEntry_EOL {
eol, err := u.ParseRFC3339(string(e.GetValidity()))
if err != nil {
return time.Time{}, false
}
return eol, true
}
return time.Time{}, false
}
package namesys
import (
"bytes"
"errors"
"fmt"
"time"
pb "github.com/ipfs/go-ipfs/namesys/pb"
peer "gx/ipfs/QmVf8hTAsLLFtn4WPCRNdnaF2Eag2qTBS6uR8AiHPZARXy/go-libp2p-peer"
pstore "gx/ipfs/QmZhsmorLpD9kmQ4ynbAu4vbKv2goMUnXazwGA4gnWHDjB/go-libp2p-peerstore"
ic "gx/ipfs/Qme1knMqwt1hKZbc1BmQFmnm9f36nyQGwXxPGVpVJ9rMK5/go-libp2p-crypto"
record "gx/ipfs/QmPWjVzxHeJdrjp4Jr2R2sPxBrMbBgGPWQtKwCKHHCBF7x/go-libp2p-record"
u "gx/ipfs/QmPdKqUcHGFdeSpvjVoaTRPPstGif9GBZb5Q56RVw9o69A/go-ipfs-util"
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")
var ErrPublicKeyMismatch = errors.New("public key in record did not match expected pubkey")
type IpnsValidator struct {
KeyBook pstore.KeyBook
}
func (v IpnsValidator) Validate(key string, value []byte) error {
ns, pidString, err := record.SplitKey(key)
if err != nil || ns != "ipns" {
return ErrInvalidPath
}
// Parse the value into an IpnsEntry
entry := new(pb.IpnsEntry)
err = proto.Unmarshal(value, entry)
if err != nil {
return ErrBadRecord
}
// Get the public key defined by the ipns path
pid, err := peer.IDFromString(pidString)
if err != nil {
log.Debugf("failed to parse ipns record key %s into peer ID", pidString)
return ErrKeyFormat
}
pubk, err := v.getPublicKey(pid, entry)
if err != nil {
return err
}
// 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", pidString)
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", pidString)
return err
}
if time.Now().After(t) {
return ErrExpiredRecord
}
default:
return ErrUnrecognizedValidity
}
return nil
}
func (v IpnsValidator) getPublicKey(pid peer.ID, entry *pb.IpnsEntry) (ic.PubKey, error) {
if entry.PubKey != nil {
pk, err := ic.UnmarshalPublicKey(entry.PubKey)
if err != nil {
log.Debugf("public key in ipns record failed to parse: ", err)
return nil, fmt.Errorf("unmarshaling pubkey in record: %s", err)
}
expPid, err := peer.IDFromPublicKey(pk)
if err != nil {
return nil, fmt.Errorf("could not regenerate peerID from pubkey: %s", err)
}
if pid != expPid {
return nil, ErrPublicKeyMismatch
}
return pk, nil
}
pubk := v.KeyBook.PubKey(pid)
if pubk == nil {
log.Debugf("public key with hash %s not found in peer store", pid)
return nil, ErrPublicKeyNotFound
}
return pubk, nil
}
// IpnsSelectorFunc selects the best record by checking which has the highest
// sequence number and latest EOL
func (v IpnsValidator) Select(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
}
......@@ -539,6 +539,12 @@
"hash": "QmfNjggF4Pt6erqg3NDafD3MdvDHk1qqCVr8pL5hnPucS8",
"name": "fsnotify",
"version": "0.1.1"
},
{
"author": "stebalien",
"hash": "QmRAPFFaF7nrezCZQaLihyp2qbAXqSJU5WpvSpwroMv1Xt",
"name": "go-ipns",
"version": "0.1.1"
}
],
"gxVersion": "0.10.0",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment