@cryptotaxi247 / kubo / commits / 5e8c9481e

namesys: verify signature in ipns validator License: MIT Signed-off-by: Dirk McCormick <dirkmdev@gmail.com>

namesys: verify signature in ipns validator License: MIT Signed-off-by: Dirk McCormick <dirkmdev@gmail.com>

Dirk McCormick committed Jan 30, 2018 at 22:32 UTC 5e8c9481eea1c49e815e903824a6d31f32c8860e
6 files changed +322 -234
core/core.go
+2 -2
@@ -949,14 +949,14 @@ func startListening(ctx context.Context, host p2phost.Host, cfg *config.Config)
949
950 func constructDHTRouting(ctx context.Context, host p2phost.Host, dstore repo.Datastore) (routing.IpfsRouting, error) {
951 dhtRouting := dht.NewDHT(ctx, host, dstore)
952 - dhtRouting.Validator[IpnsValidatorTag] = namesys.IpnsRecordValidator
952 + dhtRouting.Validator[IpnsValidatorTag] = namesys.NewIpnsRecordValidator(host.Peerstore())
953 dhtRouting.Selector[IpnsValidatorTag] = namesys.IpnsSelectorFunc
954 return dhtRouting, nil
955 }
956
957 func constructClientDHTRouting(ctx context.Context, host p2phost.Host, dstore repo.Datastore) (routing.IpfsRouting, error) {
958 dhtRouting := dht.NewDHTClient(ctx, host, dstore)
959 - dhtRouting.Validator[IpnsValidatorTag] = namesys.IpnsRecordValidator
959 + dhtRouting.Validator[IpnsValidatorTag] = namesys.NewIpnsRecordValidator(host.Peerstore())
960 dhtRouting.Selector[IpnsValidatorTag] = namesys.IpnsSelectorFunc
961 return dhtRouting, nil
962 }
namesys/ipns_validate_test.go
+140 -59
@@ -1,134 +1,215 @@
1 package namesys
2
3 import (
4 - "io"
4 + "context"
5 "testing"
6 "time"
7
8 path "github.com/ipfs/go-ipfs/path"
9 + mockrouting "github.com/ipfs/go-ipfs/routing/mock"
10 +
11 u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
12 + ds "gx/ipfs/QmPpegoMqhAEqjncrzArm7KVWAkCm78rqL2DPuNjhPrshg/go-datastore"
13 + dssync "gx/ipfs/QmPpegoMqhAEqjncrzArm7KVWAkCm78rqL2DPuNjhPrshg/go-datastore/sync"
14 + routing "gx/ipfs/QmTiWLZ6Fo5j4KcTVutZJ5KWRRJrbxzmxA4td8NfEdrPh7/go-libp2p-routing"
15 record "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record"
16 + recordpb "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record/pb"
17 + testutil "gx/ipfs/QmVvkK7s5imCiq3JVbL3pGfnhcCnf3LrFJPF4GE2sAoGZf/go-testutil"
18 + pstore "gx/ipfs/QmXauCuJzmzapetmC6W4TuDJLL1yFFrVzSHoWv8YdbmnxH/go-libp2p-peerstore"
19 proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
20 peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
21 ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
22 )
23
24 func TestValidation(t *testing.T) {
17 - // Create a record validator
18 - validator := make(record.Validator)
19 - validator["ipns"] = &record.ValidChecker{Func: ValidateIpnsRecord, Sign: true}
25 + ctx := context.Background()
26 + rid := testutil.RandIdentityOrFatal(t)
27 + dstore := dssync.MutexWrap(ds.NewMapDatastore())
28 + peerstore := pstore.NewPeerstore()
29
21 - // Generate a key for signing the records
22 - r := u.NewSeededRand(15) // generate deterministic keypair
23 - priv, ipnsPath := genKeys(t, r)
30 + vstore := newMockValueStore(rid, dstore, peerstore)
31 + vstore.Validator["ipns"] = NewIpnsRecordValidator(peerstore)
32 + vstore.Validator["pk"] = &record.ValidChecker{
33 + Func: func(r *record.ValidationRecord) error {
34 + return nil
35 + },
36 + Sign: false,
37 + }
38 + resolver := NewRoutingResolver(vstore, 0)
39
40 // Create entry with expiry in one hour
41 + priv, id, _, ipnsDHTPath := genKeys(t)
42 ts := time.Now()
27 - entry, err := CreateRoutingEntryData(priv, path.Path("foo"), 1, ts.Add(time.Hour))
43 + p := path.Path("/ipfs/QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG")
44 + entry, err := CreateRoutingEntryData(priv, p, 1, ts.Add(time.Hour))
45 if err != nil {
46 t.Fatal(err)
47 }
48
32 - val, err := proto.Marshal(entry)
49 + // Make peer's public key available in peer store
50 + err = peerstore.AddPubKey(id, priv.GetPublic())
51 if err != nil {
52 t.Fatal(err)
53 }
54
37 - // Create the record
38 - rec, err := record.MakePutRecord(priv, ipnsPath, val, true)
55 + // Publish entry
56 + err = PublishEntry(ctx, vstore, ipnsDHTPath, entry)
57 if err != nil {
58 t.Fatal(err)
59 }
60
43 - // Validate the record
44 - err = validator.VerifyRecord(rec)
61 + // Resolve entry
62 + resp, err := resolver.resolveOnce(ctx, id.Pretty())
63 if err != nil {
64 t.Fatal(err)
65 }
66 + if resp != p {
67 + t.Fatal("Mismatch between published path %s and resolved path %s", p, resp)
68 + }
69
49 - /* TODO(#4613)
50 - // Create IPNS record path with a different private key
51 - _, ipnsWrongAuthor := genKeys(t, r)
52 - wrongAuthorRec, err := record.MakePutRecord(priv, ipnsWrongAuthor, val, true)
70 + // Create expired entry
71 + expiredEntry, err := CreateRoutingEntryData(priv, p, 1, ts.Add(-1*time.Hour))
72 if err != nil {
73 t.Fatal(err)
74 }
75
57 - // Record should fail validation because path doesn't match author
58 - err = validator.VerifyRecord(wrongAuthorRec)
59 - if err != ErrInvalidAuthor {
60 - t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor")
76 + // Publish entry
77 + err = PublishEntry(ctx, vstore, ipnsDHTPath, expiredEntry)
78 + if err != nil {
79 + t.Fatal(err)
80 }
81
63 - // Create IPNS record path with extra path components after author
64 - extraPath := ipnsPath + "/some/path"
65 - extraPathRec, err := record.MakePutRecord(priv, extraPath, val, true)
82 + // Record should fail validation because entry is expired
83 + _, err = resolver.resolveOnce(ctx, id.Pretty())
84 + if err != ErrExpiredRecord {
85 + t.Fatal("ValidateIpnsRecord should have returned ErrExpiredRecord")
86 + }
87 +
88 + // Create IPNS record path with a different private key
89 + priv2, id2, _, ipnsDHTPath2 := genKeys(t)
90 +
91 + // Make peer's public key available in peer store
92 + err = peerstore.AddPubKey(id2, priv2.GetPublic())
93 + if err != nil {
94 + t.Fatal(err)
95 + }
96 +
97 + // Publish entry
98 + err = PublishEntry(ctx, vstore, ipnsDHTPath2, entry)
99 if err != nil {
100 t.Fatal(err)
101 }
102
70 - // Record should fail validation because path has extra components after author
71 - err = validator.VerifyRecord(extraPathRec)
72 - if err != ErrInvalidAuthor {
73 - t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor")
103 + // Record should fail validation because public key defined by
104 + // ipns path doesn't match record signature
105 + _, err = resolver.resolveOnce(ctx, id2.Pretty())
106 + if err != ErrSignature {
107 + t.Fatal("ValidateIpnsRecord should have failed signature verification")
108 }
109
76 - // Create unsigned IPNS record
77 - unsignedRec, err := record.MakePutRecord(priv, ipnsPath, val, false)
110 + // Publish entry without making public key available in peer store
111 + priv3, id3, pubkDHTPath3, ipnsDHTPath3 := genKeys(t)
112 + entry3, err := CreateRoutingEntryData(priv3, p, 1, ts.Add(time.Hour))
113 + if err != nil {
114 + t.Fatal(err)
115 + }
116 + err = PublishEntry(ctx, vstore, ipnsDHTPath3, entry3)
117 if err != nil {
118 t.Fatal(err)
119 }
120
82 - // Record should fail validation because IPNS records require signature
83 - err = validator.VerifyRecord(unsignedRec)
84 - if err != ErrInvalidAuthor {
85 - t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor")
121 + // Record should fail validation because public key is not available
122 + // in peer store or on network
123 + _, err = resolver.resolveOnce(ctx, id3.Pretty())
124 + if err == nil {
125 + t.Fatal("ValidateIpnsRecord should have failed because public key was not found")
126 }
127
88 - // Create unsigned IPNS record with no author
89 - unsignedRecNoAuthor, err := record.MakePutRecord(priv, ipnsPath, val, false)
128 + // Publish public key to the network
129 + err = PublishPublicKey(ctx, vstore, pubkDHTPath3, priv3.GetPublic())
130 if err != nil {
131 t.Fatal(err)
132 }
93 - noAuth := ""
94 - unsignedRecNoAuthor.Author = &noAuth
133
96 - // Record should fail validation because IPNS records require author
97 - err = validator.VerifyRecord(unsignedRecNoAuthor)
98 - if err != ErrInvalidAuthor {
99 - t.Fatal("ValidateIpnsRecord should have returned ErrInvalidAuthor")
134 + // Record should now pass validation because resolver will ensure
135 + // public key is available in the peer store by looking it up in
136 + // the DHT, which causes the DHT to fetch it and cache it in the
137 + // peer store
138 + _, err = resolver.resolveOnce(ctx, id3.Pretty())
139 + if err != nil {
140 + t.Fatal(err)
141 }
101 - */
142 +}
143
103 - // Create expired entry
104 - expiredEntry, err := CreateRoutingEntryData(priv, path.Path("foo"), 1, ts.Add(-1*time.Hour))
144 +func genKeys(t *testing.T) (ci.PrivKey, peer.ID, string, string) {
145 + sr := u.NewTimeSeededRand()
146 + priv, _, err := ci.GenerateKeyPairWithReader(ci.RSA, 1024, sr)
147 if err != nil {
148 t.Fatal(err)
149 }
108 - valExp, err := proto.Marshal(expiredEntry)
150 +
151 + // Create entry with expiry in one hour
152 + pid, err := peer.IDFromPrivateKey(priv)
153 if err != nil {
154 t.Fatal(err)
155 }
156 + pubkDHTPath, ipnsDHTPath := IpnsKeysForID(pid)
157
113 - // Create record with the expired entry
114 - expiredRec, err := record.MakePutRecord(priv, ipnsPath, valExp, true)
158 + return priv, pid, pubkDHTPath, ipnsDHTPath
159 +}
160
116 - // Record should fail validation because entry is expired
117 - err = validator.VerifyRecord(expiredRec)
118 - if err != ErrExpiredRecord {
119 - t.Fatal("ValidateIpnsRecord should have returned ErrExpiredRecord")
161 +type mockValueStore struct {
162 + r routing.ValueStore
163 + kbook pstore.KeyBook
164 + Validator record.Validator
165 +}
166 +
167 +func newMockValueStore(id testutil.Identity, dstore ds.Datastore, kbook pstore.KeyBook) *mockValueStore {
168 + serv := mockrouting.NewServer()
169 + r := serv.ClientWithDatastore(context.Background(), id, dstore)
170 + return &mockValueStore{r, kbook, make(record.Validator)}
171 +}
172 +
173 +func (m *mockValueStore) GetValue(ctx context.Context, k string) ([]byte, error) {
174 + data, err := m.r.GetValue(ctx, k)
175 + if err != nil {
176 + return data, err
177 + }
178 +
179 + rec := new(recordpb.Record)
180 + rec.Key = proto.String(k)
181 + rec.Value = data
182 + if err = m.Validator.VerifyRecord(rec); err != nil {
183 + return nil, err
184 }
185 +
186 + return data, err
187 }
188
123 -func genKeys(t *testing.T, r io.Reader) (ci.PrivKey, string) {
124 - priv, _, err := ci.GenerateKeyPairWithReader(ci.RSA, 1024, r)
189 +func (m *mockValueStore) GetPublicKey(ctx context.Context, p peer.ID) (ci.PubKey, error) {
190 + pk := m.kbook.PubKey(p)
191 + if pk != nil {
192 + return pk, nil
193 + }
194 +
195 + pkkey := routing.KeyForPublicKey(p)
196 + val, err := m.GetValue(ctx, pkkey)
197 if err != nil {
126 - t.Fatal(err)
198 + return nil, err
199 }
128 - id, err := peer.IDFromPrivateKey(priv)
200 +
201 + pk, err = ci.UnmarshalPublicKey(val)
202 if err != nil {
130 - t.Fatal(err)
203 + return nil, err
204 }
132 - _, ipnsKey := IpnsKeysForID(id)
133 - return priv, ipnsKey
205 +
206 + return pk, m.kbook.AddPubKey(p, pk)
207 +}
208 +
209 +func (m *mockValueStore) GetValues(ctx context.Context, k string, count int) ([]routing.RecvdVal, error) {
210 + return m.r.GetValues(ctx, k, count)
211 +}
212 +
213 +func (m *mockValueStore) PutValue(ctx context.Context, k string, d []byte) error {
214 + return m.r.PutValue(ctx, k, d)
215 }
namesys/publisher.go
+1 -123
@@ -3,7 +3,6 @@ package namesys
3 import (
4 "bytes"
5 "context"
6 - "errors"
6 "fmt"
7 "time"
8
@@ -16,25 +15,12 @@ import (
15 u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
16 ds "gx/ipfs/QmPpegoMqhAEqjncrzArm7KVWAkCm78rqL2DPuNjhPrshg/go-datastore"
17 routing "gx/ipfs/QmTiWLZ6Fo5j4KcTVutZJ5KWRRJrbxzmxA4td8NfEdrPh7/go-libp2p-routing"
19 - record "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record"
18 dhtpb "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record/pb"
19 proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
20 peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
21 ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
22 )
23
26 -// ErrExpiredRecord should be returned when an ipns record is
27 -// invalid due to being too old
28 -var ErrExpiredRecord = errors.New("expired record")
29 -
30 -// ErrUnrecognizedValidity is returned when an IpnsRecord has an
31 -// unknown validity type.
32 -var ErrUnrecognizedValidity = errors.New("unrecognized validity type")
33 -
34 -// ErrInvalidPath should be returned when an ipns record path
35 -// is not in a valid format
36 -var ErrInvalidPath = errors.New("record path invalid")
37 -
24 const PublishPutValTimeout = time.Minute
25 const DefaultRecordTTL = 24 * time.Hour
26
@@ -208,7 +194,7 @@ func PublishEntry(ctx context.Context, r routing.ValueStore, ipnskey string, rec
194 }
195
196 log.Debugf("Storing ipns entry at: %s", ipnskey)
211 - // Store ipns entry at "/ipns/"+b58(h(pubkey))
197 + // Store ipns entry at "/ipns/"+h(pubkey)
198 return r.PutValue(timectx, ipnskey, data)
199 }
200
@@ -238,114 +224,6 @@ func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte {
224 []byte{})
225 }
226
241 -var IpnsRecordValidator = &record.ValidChecker{
242 - Func: ValidateIpnsRecord,
243 - Sign: true,
244 -}
245 -
246 -func IpnsSelectorFunc(k string, vals [][]byte) (int, error) {
247 - var recs []*pb.IpnsEntry
248 - for _, v := range vals {
249 - e := new(pb.IpnsEntry)
250 - err := proto.Unmarshal(v, e)
251 - if err == nil {
252 - recs = append(recs, e)
253 - } else {
254 - recs = append(recs, nil)
255 - }
256 - }
257 -
258 - return selectRecord(recs, vals)
259 -}
260 -
261 -func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) {
262 - var best_seq uint64
263 - best_i := -1
264 -
265 - for i, r := range recs {
266 - if r == nil || r.GetSequence() < best_seq {
267 - continue
268 - }
269 -
270 - if best_i == -1 || r.GetSequence() > best_seq {
271 - best_seq = r.GetSequence()
272 - best_i = i
273 - } else if r.GetSequence() == best_seq {
274 - rt, err := u.ParseRFC3339(string(r.GetValidity()))
275 - if err != nil {
276 - continue
277 - }
278 -
279 - bestt, err := u.ParseRFC3339(string(recs[best_i].GetValidity()))
280 - if err != nil {
281 - continue
282 - }
283 -
284 - if rt.After(bestt) {
285 - best_i = i
286 - } else if rt == bestt {
287 - if bytes.Compare(vals[i], vals[best_i]) > 0 {
288 - best_i = i
289 - }
290 - }
291 - }
292 - }
293 - if best_i == -1 {
294 - return 0, errors.New("no usable records in given set")
295 - }
296 -
297 - return best_i, nil
298 -}
299 -
300 -// ValidateIpnsRecord implements ValidatorFunc and verifies that the
301 -// given 'val' is an IpnsEntry and that that entry is valid.
302 -func ValidateIpnsRecord(r *record.ValidationRecord) error {
303 - if r.Namespace != "ipns" {
304 - return ErrInvalidPath
305 - }
306 -
307 - entry := new(pb.IpnsEntry)
308 - err := proto.Unmarshal(r.Value, entry)
309 - if err != nil {
310 - return err
311 - }
312 -
313 - // NOTE/FIXME(#4613): We're not checking the DHT signature/author here.
314 - // We're going to remove them in a followup commit and then check the
315 - // *IPNS* signature. However, to do that, we need to ensure we *have*
316 - // the public key and:
317 - //
318 - // 1. Don't want to fetch it from the network when handling PUTs.
319 - // 2. Do want to fetch it from the network when handling GETs.
320 - //
321 - // Therefore, we'll need to either:
322 - //
323 - // 1. Pass some for of offline hint to the validator (e.g., using a context).
324 - // 2. Ensure we pre-fetch the key when performing gets.
325 - //
326 - // This PR is already *way* too large so we're punting that fix to a new
327 - // PR.
328 - //
329 - // This is not a regression, it just restores the current (bad)
330 - // behavior.
331 -
332 - // Check that record has not expired
333 - switch entry.GetValidityType() {
334 - case pb.IpnsEntry_EOL:
335 - t, err := u.ParseRFC3339(string(entry.GetValidity()))
336 - if err != nil {
337 - log.Debug("failed parsing time for ipns record EOL")
338 - return err
339 - }
340 - if time.Now().After(t) {
341 - return ErrExpiredRecord
342 - }
343 - default:
344 - return ErrUnrecognizedValidity
345 - }
346 - return nil
347 -}
348 -
227 // InitializeKeyspace sets the ipns record for the given key to
228 // point to an empty directory.
229 // TODO: this doesnt feel like it belongs here
namesys/routing.go
+28 -50
@@ -2,7 +2,6 @@ package namesys
2
3 import (
4 "context"
5 - "fmt"
5 "strings"
6 "time"
7
@@ -15,7 +14,7 @@ import (
14 lru "gx/ipfs/QmVYxfoJQiZijTgPNHCHgHELvQpbsJNTg6Crmc3dQkj3yy/golang-lru"
15 proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
16 mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
18 - ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto"
17 + peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
18 cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
19 )
20
@@ -131,58 +130,37 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa
130 return "", err
131 }
132
134 - // use the routing system to get the name.
135 - // /ipns/<name>
136 - h := []byte("/ipns/" + string(hash))
137 -
138 - var entry *pb.IpnsEntry
139 - var pubkey ci.PubKey
140 -
141 - resp := make(chan error, 2)
142 - go func() {
143 - ipnsKey := string(h)
144 - val, err := r.routing.GetValue(ctx, ipnsKey)
145 - if err != nil {
146 - log.Debugf("RoutingResolver: dht get failed: %s", err)
147 - resp <- err
148 - return
149 - }
150 -
151 - entry = new(pb.IpnsEntry)
152 - err = proto.Unmarshal(val, entry)
153 - if err != nil {
154 - resp <- err
155 - return
156 - }
157 -
158 - resp <- nil
159 - }()
160 -
161 - go func() {
162 - // name should be a public key retrievable from ipfs
163 - pubk, err := routing.GetPublicKey(r.routing, ctx, hash)
164 - if err != nil {
165 - resp <- err
166 - return
167 - }
168 -
169 - pubkey = pubk
170 - resp <- nil
171 - }()
133 + // Name should be the hash of a public key retrievable from ipfs.
134 + // We retrieve the public key here to make certain that it's in the peer
135 + // store before calling GetValue() on the DHT - the DHT will call the
136 + // ipns validator, which in turn will get the public key from the peer
137 + // store to verify the record signature
138 + _, err = routing.GetPublicKey(r.routing, ctx, hash)
139 + if err != nil {
140 + log.Debugf("RoutingResolver: could not retrieve public key %s: %s\n", name, err)
141 + return "", err
142 + }
143
173 - for i := 0; i < 2; i++ {
174 - err = <-resp
175 - if err != nil {
176 - return "", err
177 - }
144 + pid, err := peer.IDFromBytes(hash)
145 + if err != nil {
146 + log.Debugf("RoutingResolver: could not convert public key hash %s to peer ID: %s\n", name, err)
147 + return "", err
148 }
149
180 - // check sig with pk
181 - if ok, err := pubkey.Verify(ipnsEntryDataForSig(entry), entry.GetSignature()); err != nil || !ok {
182 - return "", fmt.Errorf("ipns entry for %s has invalid signature", h)
150 + // use the routing system to get the name.
151 + _, ipnsKey := IpnsKeysForID(pid)
152 + val, err := r.routing.GetValue(ctx, ipnsKey)
153 + if err != nil {
154 + log.Debugf("RoutingResolver: dht get for name %s failed: %s", name, err)
155 + return "", err
156 }
157
185 - // ok sig checks out. this is a valid name.
158 + entry := new(pb.IpnsEntry)
159 + err = proto.Unmarshal(val, entry)
160 + if err != nil {
161 + log.Debugf("RoutingResolver: could not unmarshal value for name %s: %s", name, err)
162 + return "", err
163 + }
164
165 // check for old style record:
166 valh, err := mh.Cast(entry.GetValue())
@@ -197,7 +175,7 @@ func (r *routingResolver) resolveOnce(ctx context.Context, name string) (path.Pa
175 return p, nil
176 } else {
177 // Its an old style multihash record
200 - log.Debugf("encountered CIDv0 ipns entry: %s", h)
178 + log.Debugf("encountered CIDv0 ipns entry: %s", valh)
179 p := path.FromCid(cid.NewCidV0(valh))
180 r.cacheSet(name, p, entry)
181 return p, nil
namesys/selector.go new
+65
@@ -0,0 +1,65 @@
1 +package namesys
2 +
3 +import (
4 + "bytes"
5 + "errors"
6 +
7 + pb "github.com/ipfs/go-ipfs/namesys/pb"
8 +
9 + u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
10 + proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
11 +)
12 +
13 +func IpnsSelectorFunc(k string, vals [][]byte) (int, error) {
14 + var recs []*pb.IpnsEntry
15 + for _, v := range vals {
16 + e := new(pb.IpnsEntry)
17 + err := proto.Unmarshal(v, e)
18 + if err == nil {
19 + recs = append(recs, e)
20 + } else {
21 + recs = append(recs, nil)
22 + }
23 + }
24 +
25 + return selectRecord(recs, vals)
26 +}
27 +
28 +func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) {
29 + var best_seq uint64
30 + best_i := -1
31 +
32 + for i, r := range recs {
33 + if r == nil || r.GetSequence() < best_seq {
34 + continue
35 + }
36 +
37 + if best_i == -1 || r.GetSequence() > best_seq {
38 + best_seq = r.GetSequence()
39 + best_i = i
40 + } else if r.GetSequence() == best_seq {
41 + rt, err := u.ParseRFC3339(string(r.GetValidity()))
42 + if err != nil {
43 + continue
44 + }
45 +
46 + bestt, err := u.ParseRFC3339(string(recs[best_i].GetValidity()))
47 + if err != nil {
48 + continue
49 + }
50 +
51 + if rt.After(bestt) {
52 + best_i = i
53 + } else if rt == bestt {
54 + if bytes.Compare(vals[i], vals[best_i]) > 0 {
55 + best_i = i
56 + }
57 + }
58 + }
59 + }
60 + if best_i == -1 {
61 + return 0, errors.New("no usable records in given set")
62 + }
63 +
64 + return best_i, nil
65 +}
namesys/validator.go new
+86
@@ -0,0 +1,86 @@
1 +package namesys
2 +
3 +import (
4 + "errors"
5 + "time"
6 +
7 + pb "github.com/ipfs/go-ipfs/namesys/pb"
8 + peer "gx/ipfs/QmZoWKhxUmZ2seW4BzX6fJkNR8hh9PsGModr7q171yq2SS/go-libp2p-peer"
9 + pstore "gx/ipfs/QmXauCuJzmzapetmC6W4TuDJLL1yFFrVzSHoWv8YdbmnxH/go-libp2p-peerstore"
10 +
11 + u "gx/ipfs/QmNiJuT8Ja3hMVpBHXv3Q6dwmperaQ6JjLtpMQgMCD7xvx/go-ipfs-util"
12 + proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
13 + record "gx/ipfs/QmUpttFinNDmNPgFwKN8sZK6BUtBmA68Y4KdSBDXa8t9sJ/go-libp2p-record"
14 +)
15 +
16 +// ErrExpiredRecord should be returned when an ipns record is
17 +// invalid due to being too old
18 +var ErrExpiredRecord = errors.New("expired record")
19 +
20 +// ErrUnrecognizedValidity is returned when an IpnsRecord has an
21 +// unknown validity type.
22 +var ErrUnrecognizedValidity = errors.New("unrecognized validity type")
23 +
24 +// ErrInvalidPath should be returned when an ipns record path
25 +// is not in a valid format
26 +var ErrInvalidPath = errors.New("record path invalid")
27 +
28 +// ErrSignature should be returned when an ipns record fails
29 +// signature verification
30 +var ErrSignature = errors.New("record signature verification failed")
31 +
32 +func NewIpnsRecordValidator(kbook pstore.KeyBook) *record.ValidChecker {
33 + // ValidateIpnsRecord implements ValidatorFunc and verifies that the
34 + // given 'val' is an IpnsEntry and that that entry is valid.
35 + ValidateIpnsRecord := func(r *record.ValidationRecord) error {
36 + if r.Namespace != "ipns" {
37 + return ErrInvalidPath
38 + }
39 +
40 + // Parse the value into an IpnsEntry
41 + entry := new(pb.IpnsEntry)
42 + err := proto.Unmarshal(r.Value, entry)
43 + if err != nil {
44 + return err
45 + }
46 +
47 + // Get the public key defined by the ipns path
48 + pid, err := peer.IDFromString(r.Key)
49 + if err != nil {
50 + log.Debugf("failed to parse ipns record key %s into public key hash", r.Key)
51 + return ErrSignature
52 + }
53 + pubk := kbook.PubKey(pid)
54 + if pubk == nil {
55 + log.Debugf("public key with hash %s not found in peer store", pid)
56 + return ErrSignature
57 + }
58 +
59 + // Check the ipns record signature with the public key
60 + if ok, err := pubk.Verify(ipnsEntryDataForSig(entry), entry.GetSignature()); err != nil || !ok {
61 + log.Debugf("failed to verify signature for ipns record %s", r.Key)
62 + return ErrSignature
63 + }
64 +
65 + // Check that record has not expired
66 + switch entry.GetValidityType() {
67 + case pb.IpnsEntry_EOL:
68 + t, err := u.ParseRFC3339(string(entry.GetValidity()))
69 + if err != nil {
70 + log.Debugf("failed parsing time for ipns record EOL in record %s", r.Key)
71 + return err
72 + }
73 + if time.Now().After(t) {
74 + return ErrExpiredRecord
75 + }
76 + default:
77 + return ErrUnrecognizedValidity
78 + }
79 + return nil
80 + }
81 +
82 + return &record.ValidChecker{
83 + Func: ValidateIpnsRecord,
84 + Sign: false,
85 + }
86 +}