diff --git a/keystore/keystore.go b/keystore/keystore.go index bafc859b9f2b..ccc91dfc41d5 100644 --- a/keystore/keystore.go +++ b/keystore/keystore.go @@ -9,6 +9,7 @@ import ( ci "gx/ipfs/QmTW4SdgBWq9GjsBsHeUx8WuGxzhgzAf88UMH2w62PC8yK/go-libp2p-crypto" logging "gx/ipfs/QmbkT7eMTyXfpeyB3ZMxxcxg7XH8t6uXp49jqzz4HB7BGF/go-log" + mbase "gx/ipfs/QmekxXDhCxCJRNuzmHreuaT3BsuJcsjcXWNrtV9C8DRHtd/go-multibase" ) 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,110 @@ func (ks *FSKeystore) List() ([]string, error) { return list, nil } + +const encodingBase = mbase.Base32 +const keyFilenamePrefix = "key_" + +func encode(name string) (string, error) { + if name == "" { + return "", fmt.Errorf("key name must be at least one character") + } + + encodedName, err := mbase.Encode(encodingBase, []byte(name)) + + if err != nil { + return "", err + } + + return keyFilenamePrefix + encodedName, nil +} + +func decode(name string) (string, error) { + if !strings.HasPrefix(name, keyFilenamePrefix) { + return "", fmt.Errorf("key's filename has unexpexcted format") + } + + nameWithoutPrefix := name[len(keyFilenamePrefix):] + encoding, data, err := mbase.Decode(nameWithoutPrefix) + + if err != nil { + return "", err + } + + if encoding != encodingBase { + log.Warningf("Key's filename was encoded in unexpexted base: %s; instead of base: %s", mbase.EncodingToStr[encoding], mbase.EncodingToStr[encodingBase]) + } + + return string(data[:]), 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 fe8276872a4b..a481f1095cf2 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 8c7d84de159d..dc64a39c761f 100644 --- a/repo/fsrepo/fsrepo.go +++ b/repo/fsrepo/fsrepo.go @@ -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 }