Skip to content

Commit

Permalink
Introducing EncodedFSKeystore with base32 encoding (#5947)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
AuHau committed Mar 14, 2019
1 parent e922edd commit 12c0ef9
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 1 deletion.
124 changes: 124 additions & 0 deletions keystore/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

logging "github.com/ipfs/go-log"
ci "github.com/libp2p/go-libp2p-crypto"
mbase "github.com/multiformats/go-multibase"
)

var log = logging.Logger("keystore")
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
89 changes: 89 additions & 0 deletions keystore/keystore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
2 changes: 1 addition & 1 deletion repo/fsrepo/fsrepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 12c0ef9

Please sign in to comment.