@cryptotaxi247 / kubo / commits / d67a4e06c

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 }