From d67a4e06cf65405489567701ef5fbda1fbbb48f2 Mon Sep 17 00:00:00 2001 From: Adam Uhlir Date: Thu, 21 Feb 2019 11:53:44 -0800 Subject: [PATCH] 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 --- keystore/keystore.go | 120 ++++++++++++++++++++++++++++++++++++++ keystore/keystore_test.go | 89 ++++++++++++++++++++++++++++ repo/fsrepo/fsrepo.go | 4 +- 3 files changed, 211 insertions(+), 2 deletions(-) diff --git a/keystore/keystore.go b/keystore/keystore.go index 991de5dd1..a40ab5cb0 100644 --- a/keystore/keystore.go +++ b/keystore/keystore.go @@ -9,6 +9,7 @@ import ( logging "github.com/ipfs/go-log" ci "github.com/libp2p/go-libp2p-core/crypto" + base32 "github.com/whyrusleeping/base32" ) var log = logging.Logger("keystore") @@ -52,6 +53,22 @@ func validateName(name string) error { return nil } +// NewKeystore is a factory for getting instance of Keystore interface implementation +func NewKeystore(dir string) (Keystore, error) { + return NewEncodedFSKeystore(dir) +} + +// NewEncodedFSKeystore is a factory for getting instance of EncodedFSKeystore +func NewEncodedFSKeystore(dir string) (*EncodedFSKeystore, error) { + keystore, err := NewFSKeystore(dir) + + if err != nil { + return nil, err + } + + return &EncodedFSKeystore{keystore}, nil +} + func NewFSKeystore(dir string) (*FSKeystore, error) { _, err := os.Stat(dir) if err != nil { @@ -174,3 +191,106 @@ func (ks *FSKeystore) List() ([]string, error) { return list, nil } + +const keyFilenamePrefix = "key_" + +func encode(name string) (string, error) { + if name == "" { + return "", fmt.Errorf("key name must be at least one character") + } + + encodedName := base32.RawStdEncoding.EncodeToString([]byte(name)) + log.Debugf("Encoded key name: %s to: %s", name, encodedName) + + return keyFilenamePrefix + strings.ToLower(encodedName), nil +} + +func decode(name string) (string, error) { + if !strings.HasPrefix(name, keyFilenamePrefix) { + return "", fmt.Errorf("key's filename has unexpected format") + } + + nameWithoutPrefix := strings.ToUpper(name[len(keyFilenamePrefix):]) + data, err := base32.RawStdEncoding.DecodeString(nameWithoutPrefix) + + if err != nil { + return "", err + } + + decodedName := string(data[:]) + + log.Debugf("Decoded key name: %s to: %s", name, decodedName) + + return decodedName, nil +} + +// EncodedFSKeystore is extension of FSKeystore that encodes the key filenames in base32 +type EncodedFSKeystore struct { + *FSKeystore +} + +// Has indicates if key is in keystore +func (ks *EncodedFSKeystore) Has(name string) (bool, error) { + encodedName, err := encode(name) + + if err != nil { + return false, err + } + + return ks.FSKeystore.Has(encodedName) +} + +// Put places key into the keystore +func (ks *EncodedFSKeystore) Put(name string, k ci.PrivKey) error { + encodedName, err := encode(name) + + if err != nil { + return err + } + + return ks.FSKeystore.Put(encodedName, k) +} + +// Get retrieves key by its name from the keystore +func (ks *EncodedFSKeystore) Get(name string) (ci.PrivKey, error) { + encodedName, err := encode(name) + + if err != nil { + return nil, err + } + + return ks.FSKeystore.Get(encodedName) +} + +// Delete removes key from the keystore +func (ks *EncodedFSKeystore) Delete(name string) error { + encodedName, err := encode(name) + + if err != nil { + return err + } + + return ks.FSKeystore.Delete(encodedName) +} + +// List returns list of all keys in keystore +func (ks *EncodedFSKeystore) List() ([]string, error) { + dirs, err := ks.FSKeystore.List() + + if err != nil { + return nil, err + } + + list := make([]string, 0, len(dirs)) + + for _, name := range dirs { + decodedName, err := decode(name) + if err == nil { + list = append(list, decodedName) + } else { + log.Warningf("Ignoring keyfile with invalid encoded filename: %s", name) + } + } + + return list, nil +} diff --git a/keystore/keystore_test.go b/keystore/keystore_test.go index 37f59ebff..685e5d942 100644 --- a/keystore/keystore_test.go +++ b/keystore/keystore_test.go @@ -271,3 +271,92 @@ func assertDirContents(dir string, exp []string) error { } return nil } + +func TestEncodedKeystoreBasics(t *testing.T) { + tdir, err := ioutil.TempDir("", "encoded-keystore-test") + if err != nil { + t.Fatal(err) + } + + ks, err := NewEncodedFSKeystore(tdir) + if err != nil { + t.Fatal(err) + } + + l, err := ks.List() + if err != nil { + t.Fatal(err) + } + + if len(l) != 0 { + t.Fatal("expected no keys") + } + + k1 := privKeyOrFatal(t) + k1Name, err := encode("foo") + if err != nil { + t.Fatal(err) + } + + k2 := privKeyOrFatal(t) + k2Name, err := encode("bar") + if err != nil { + t.Fatal(err) + } + + err = ks.Put("foo", k1) + if err != nil { + t.Fatal(err) + } + + err = ks.Put("bar", k2) + if err != nil { + t.Fatal(err) + } + + l, err = ks.List() + if err != nil { + t.Fatal(err) + } + + sort.Strings(l) + if l[0] != "bar" || l[1] != "foo" { + t.Fatal("wrong entries listed") + } + + if err := assertDirContents(tdir, []string{k1Name, k2Name}); err != nil { + t.Fatal(err) + } + + exist, err := ks.Has("foo") + if !exist { + t.Fatal("should know it has a key named foo") + } + if err != nil { + t.Fatal(err) + } + + if err := ks.Delete("bar"); err != nil { + t.Fatal(err) + } + + if err := assertDirContents(tdir, []string{k1Name}); err != nil { + t.Fatal(err) + } + + if err := assertGetKey(ks, "foo", k1); err != nil { + t.Fatal(err) + } + + if err := ks.Put("..///foo/", k1); err != nil { + t.Fatal(err) + } + + if err := ks.Put("", k1); err == nil { + t.Fatal("shouldnt be able to put a key with no name") + } + + if err := ks.Put(".foo", k1); err != nil { + t.Fatal(err) + } +} diff --git a/repo/fsrepo/fsrepo.go b/repo/fsrepo/fsrepo.go index 6ee436605..59728dc46 100644 --- a/repo/fsrepo/fsrepo.go +++ b/repo/fsrepo/fsrepo.go @@ -36,7 +36,7 @@ const LockFile = "repo.lock" var log = logging.Logger("fsrepo") // version number that we are currently expecting to see -var RepoVersion = 7 +var RepoVersion = 8 var migrationInstructions = `See https://github.com/ipfs/fs-repo-migrations/blob/master/run.md Sorry for the inconvenience. In the future, these will run automatically.` @@ -385,7 +385,7 @@ func (r *FSRepo) openConfig() error { func (r *FSRepo) openKeystore() error { ksp := filepath.Join(r.path, "keystore") - ks, err := keystore.NewFSKeystore(ksp) + ks, err := keystore.NewKeystore(ksp) if err != nil { return err }