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
+}