177 lines
3.5 KiB
Go
177 lines
3.5 KiB
Go
package keystore
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
logging "github.com/ipfs/go-log"
|
|
ci "github.com/libp2p/go-libp2p-crypto"
|
|
)
|
|
|
|
var log = logging.Logger("keystore")
|
|
|
|
// Keystore provides a key management interface
|
|
type Keystore interface {
|
|
// Has returns whether or not a key exist in the Keystore
|
|
Has(string) (bool, error)
|
|
// Put stores a key in the Keystore, if a key with the same name already exists, returns ErrKeyExists
|
|
Put(string, ci.PrivKey) error
|
|
// Get retrieves a key from the Keystore if it exists, and returns ErrNoSuchKey
|
|
// otherwise.
|
|
Get(string) (ci.PrivKey, error)
|
|
// Delete removes a key from the Keystore
|
|
Delete(string) error
|
|
// List returns a list of key identifier
|
|
List() ([]string, error)
|
|
}
|
|
|
|
var ErrNoSuchKey = fmt.Errorf("no key by the given name was found")
|
|
var ErrKeyExists = fmt.Errorf("key by that name already exists, refusing to overwrite")
|
|
|
|
// FSKeystore is a keystore backed by files in a given directory stored on disk.
|
|
type FSKeystore struct {
|
|
dir string
|
|
}
|
|
|
|
func validateName(name string) error {
|
|
if name == "" {
|
|
return fmt.Errorf("key names must be at least one character")
|
|
}
|
|
|
|
if strings.Contains(name, "/") {
|
|
return fmt.Errorf("key names may not contain slashes")
|
|
}
|
|
|
|
if strings.HasPrefix(name, ".") {
|
|
return fmt.Errorf("key names may not begin with a period")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewFSKeystore(dir string) (*FSKeystore, error) {
|
|
_, err := os.Stat(dir)
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
if err := os.Mkdir(dir, 0700); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &FSKeystore{dir}, nil
|
|
}
|
|
|
|
// Has returns whether or not a key exist in the Keystore
|
|
func (ks *FSKeystore) Has(name string) (bool, error) {
|
|
kp := filepath.Join(ks.dir, name)
|
|
|
|
_, err := os.Stat(kp)
|
|
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if err := validateName(name); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Put stores a key in the Keystore, if a key with the same name already exists, returns ErrKeyExists
|
|
func (ks *FSKeystore) Put(name string, k ci.PrivKey) error {
|
|
if err := validateName(name); err != nil {
|
|
return err
|
|
}
|
|
|
|
b, err := k.Bytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
kp := filepath.Join(ks.dir, name)
|
|
|
|
_, err = os.Stat(kp)
|
|
if err == nil {
|
|
return ErrKeyExists
|
|
} else if !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
|
|
fi, err := os.Create(kp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer fi.Close()
|
|
|
|
_, err = fi.Write(b)
|
|
|
|
return err
|
|
}
|
|
|
|
// Get retrieves a key from the Keystore if it exists, and returns ErrNoSuchKey
|
|
// otherwise.
|
|
func (ks *FSKeystore) Get(name string) (ci.PrivKey, error) {
|
|
if err := validateName(name); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
kp := filepath.Join(ks.dir, name)
|
|
|
|
data, err := ioutil.ReadFile(kp)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, ErrNoSuchKey
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return ci.UnmarshalPrivateKey(data)
|
|
}
|
|
|
|
// Delete removes a key from the Keystore
|
|
func (ks *FSKeystore) Delete(name string) error {
|
|
if err := validateName(name); err != nil {
|
|
return err
|
|
}
|
|
|
|
kp := filepath.Join(ks.dir, name)
|
|
|
|
return os.Remove(kp)
|
|
}
|
|
|
|
// List return a list of key identifier
|
|
func (ks *FSKeystore) List() ([]string, error) {
|
|
dir, err := os.Open(ks.dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dirs, err := dir.Readdirnames(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
list := make([]string, 0, len(dirs))
|
|
|
|
for _, name := range dirs {
|
|
err := validateName(name)
|
|
if err == nil {
|
|
list = append(list, name)
|
|
} else {
|
|
log.Warningf("Ignoring the invalid keyfile: %s", name)
|
|
}
|
|
}
|
|
|
|
return list, nil
|
|
}
|