Introducing EncodedFSKeystore with base32 encoding (#5947)
Encoding the key's filename with base32 introduces coherent behaviour across different platforms and their case-sensitive/case-insensitive file-systems. Moreover it allows wider character set to be used for the name of the keys as the original restriction for special FS's characters (e.g. '/', '.') will not apply. License: MIT Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>
Adam Uhlir committed
Feb 21, 2019 at 11:53 UTC
d67a4e06cf65405489567701ef5fbda1fbbb48f2
3 files changed
+211
-2
keystore/keystore.go
+120
@@ -9,6 +9,7 @@ import (
9
10
logging "github.com/ipfs/go-log"
11
ci "github.com/libp2p/go-libp2p-core/crypto"
12
+ base32 "github.com/whyrusleeping/base32"
13
)
14
15
var log = logging.Logger("keystore")
@@ -52,6 +53,22 @@ func validateName(name string) error {
53
return nil
54
}
55
56
+// NewKeystore is a factory for getting instance of Keystore interface implementation
57
+func NewKeystore(dir string) (Keystore, error) {
58
+ return NewEncodedFSKeystore(dir)
59
+}
60
+
61
+// NewEncodedFSKeystore is a factory for getting instance of EncodedFSKeystore
62
+func NewEncodedFSKeystore(dir string) (*EncodedFSKeystore, error) {
63
+ keystore, err := NewFSKeystore(dir)
64
+
65
+ if err != nil {
66
+ return nil, err
67
+ }
68
+
69
+ return &EncodedFSKeystore{keystore}, nil
70
+}
71
+
72
func NewFSKeystore(dir string) (*FSKeystore, error) {
73
_, err := os.Stat(dir)
74
if err != nil {
@@ -174,3 +191,106 @@ func (ks *FSKeystore) List() ([]string, error) {
191
192
return list, nil
193
}
194
+
195
+const keyFilenamePrefix = "key_"
196
+
197
+func encode(name string) (string, error) {
198
+ if name == "" {
199
+ return "", fmt.Errorf("key name must be at least one character")
200
+ }
201
+
202
+ encodedName := base32.RawStdEncoding.EncodeToString([]byte(name))
203
+ log.Debugf("Encoded key name: %s to: %s", name, encodedName)
204
+
205
+ return keyFilenamePrefix + strings.ToLower(encodedName), nil
206
+}
207
+
208
+func decode(name string) (string, error) {
209
+ if !strings.HasPrefix(name, keyFilenamePrefix) {
210
+ return "", fmt.Errorf("key's filename has unexpected format")
211
+ }
212
+
213
+ nameWithoutPrefix := strings.ToUpper(name[len(keyFilenamePrefix):])
214
+ data, err := base32.RawStdEncoding.DecodeString(nameWithoutPrefix)
215
+
216
+ if err != nil {
217
+ return "", err
218
+ }
219
+
220
+ decodedName := string(data[:])
221
+
222
+ log.Debugf("Decoded key name: %s to: %s", name, decodedName)
223
+
224
+ return decodedName, nil
225
+}
226
+
227
+// EncodedFSKeystore is extension of FSKeystore that encodes the key filenames in base32
228
+type EncodedFSKeystore struct {
229
+ *FSKeystore
230
+}
231
+
232
+// Has indicates if key is in keystore
233
+func (ks *EncodedFSKeystore) Has(name string) (bool, error) {
234
+ encodedName, err := encode(name)
235
+
236
+ if err != nil {
237
+ return false, err
238
+ }
239
+
240
+ return ks.FSKeystore.Has(encodedName)
241
+}
242
+
243
+// Put places key into the keystore
244
+func (ks *EncodedFSKeystore) Put(name string, k ci.PrivKey) error {
245
+ encodedName, err := encode(name)
246
+
247
+ if err != nil {
248
+ return err
249
+ }
250
+
251
+ return ks.FSKeystore.Put(encodedName, k)
252
+}
253
+
254
+// Get retrieves key by its name from the keystore
255
+func (ks *EncodedFSKeystore) Get(name string) (ci.PrivKey, error) {
256
+ encodedName, err := encode(name)
257
+
258
+ if err != nil {
259
+ return nil, err
260
+ }
261
+
262
+ return ks.FSKeystore.Get(encodedName)
263
+}
264
+
265
+// Delete removes key from the keystore
266
+func (ks *EncodedFSKeystore) Delete(name string) error {
267
+ encodedName, err := encode(name)
268
+
269
+ if err != nil {
270
+ return err
271
+ }
272
+
273
+ return ks.FSKeystore.Delete(encodedName)
274
+}
275
+
276
+// List returns list of all keys in keystore
277
+func (ks *EncodedFSKeystore) List() ([]string, error) {
278
+ dirs, err := ks.FSKeystore.List()
279
+
280
+ if err != nil {
281
+ return nil, err
282
+ }
283
+
284
+ list := make([]string, 0, len(dirs))
285
+
286
+ for _, name := range dirs {
287
+ decodedName, err := decode(name)
288
+ if err == nil {
289
+ list = append(list, decodedName)
290
+ } else {
291
+ log.Warningf("Ignoring keyfile with invalid encoded filename: %s", name)
292
+ }
293
+ }
294
+
295
+ return list, nil
296
+}
keystore/keystore_test.go
+89
@@ -271,3 +271,92 @@ func assertDirContents(dir string, exp []string) error {
271
}
272
return nil
273
}
274
+
275
+func TestEncodedKeystoreBasics(t *testing.T) {
276
+ tdir, err := ioutil.TempDir("", "encoded-keystore-test")
277
+ if err != nil {
278
+ t.Fatal(err)
279
+ }
280
+
281
+ ks, err := NewEncodedFSKeystore(tdir)
282
+ if err != nil {
283
+ t.Fatal(err)
284
+ }
285
+
286
+ l, err := ks.List()
287
+ if err != nil {
288
+ t.Fatal(err)
289
+ }
290
+
291
+ if len(l) != 0 {
292
+ t.Fatal("expected no keys")
293
+ }
294
+
295
+ k1 := privKeyOrFatal(t)
296
+ k1Name, err := encode("foo")
297
+ if err != nil {
298
+ t.Fatal(err)
299
+ }
300
+
301
+ k2 := privKeyOrFatal(t)
302
+ k2Name, err := encode("bar")
303
+ if err != nil {
304
+ t.Fatal(err)
305
+ }
306
+
307
+ err = ks.Put("foo", k1)
308
+ if err != nil {
309
+ t.Fatal(err)
310
+ }
311
+
312
+ err = ks.Put("bar", k2)
313
+ if err != nil {
314
+ t.Fatal(err)
315
+ }
316
+
317
+ l, err = ks.List()
318
+ if err != nil {
319
+ t.Fatal(err)
320
+ }
321
+
322
+ sort.Strings(l)
323
+ if l[0] != "bar" || l[1] != "foo" {
324
+ t.Fatal("wrong entries listed")
325
+ }
326
+
327
+ if err := assertDirContents(tdir, []string{k1Name, k2Name}); err != nil {
328
+ t.Fatal(err)
329
+ }
330
+
331
+ exist, err := ks.Has("foo")
332
+ if !exist {
333
+ t.Fatal("should know it has a key named foo")
334
+ }
335
+ if err != nil {
336
+ t.Fatal(err)
337
+ }
338
+
339
+ if err := ks.Delete("bar"); err != nil {
340
+ t.Fatal(err)
341
+ }
342
+
343
+ if err := assertDirContents(tdir, []string{k1Name}); err != nil {
344
+ t.Fatal(err)
345
+ }
346
+
347
+ if err := assertGetKey(ks, "foo", k1); err != nil {
348
+ t.Fatal(err)
349
+ }
350
+
351
+ if err := ks.Put("..///foo/", k1); err != nil {
352
+ t.Fatal(err)
353
+ }
354
+
355
+ if err := ks.Put("", k1); err == nil {
356
+ t.Fatal("shouldnt be able to put a key with no name")
357
+ }
358
+
359
+ if err := ks.Put(".foo", k1); err != nil {
360
+ t.Fatal(err)
361
+ }
362
+}
repo/fsrepo/fsrepo.go
+2
-2
@@ -36,7 +36,7 @@ const LockFile = "repo.lock"
36
var log = logging.Logger("fsrepo")
37
38
// version number that we are currently expecting to see
39
-var RepoVersion = 7
39
+var RepoVersion = 8
40
41
var migrationInstructions = `See https://github.com/ipfs/fs-repo-migrations/blob/master/run.md
42
Sorry for the inconvenience. In the future, these will run automatically.`
@@ -385,7 +385,7 @@ func (r *FSRepo) openConfig() error {
385
386
func (r *FSRepo) openKeystore() error {
387
ksp := filepath.Join(r.path, "keystore")
388
- ks, err := keystore.NewFSKeystore(ksp)
388
+ ks, err := keystore.NewKeystore(ksp)
389
if err != nil {
390
return err
391
}