2 package filestorage provides a secure on-disk storage of private keys and
3 metadata. Security is enforced by file and directory permissions, much
4 like standard ssh key storage.
15 "github.com/pkg/errors"
16 crypto "github.com/tendermint/go-crypto"
17 keys "github.com/tendermint/go-crypto/keys"
21 BlockType = "Tendermint Light Client"
23 // PrivExt is the extension for private keys.
25 // PubExt is the extensions for public keys.
28 keyPerm = os.FileMode(0600)
29 // pubPerm = os.FileMode(0644)
30 dirPerm = os.FileMode(0700)
33 type FileStore struct {
37 // New creates an instance of file-based key storage with tight permissions
39 // dir should be an absolute path of a directory owner by this user. It will
40 // be created if it doesn't exist already.
41 func New(dir string) FileStore {
42 err := os.MkdirAll(dir, dirPerm)
49 // assert FileStore satisfies keys.Storage
50 var _ keys.Storage = FileStore{}
52 // Put creates two files, one with the public info as json, the other
53 // with the (encoded) private key as gpg ascii-armor style
54 func (s FileStore) Put(name string, key []byte, info keys.Info) error {
55 pub, priv := s.nameToPaths(name)
58 err := writeInfo(pub, info)
64 return write(priv, name, key)
67 // Get loads the info and (encoded) private key from the directory
68 // It uses `name` to generate the filename, and returns an error if the
69 // files don't exist or are in the incorrect format
70 func (s FileStore) Get(name string) ([]byte, keys.Info, error) {
71 pub, priv := s.nameToPaths(name)
73 info, err := readInfo(pub)
78 key, _, err := read(priv)
79 return key, info.Format(), err
82 // List parses the key directory for public info and returns a list of
83 // Info for all keys located in this directory.
84 func (s FileStore) List() (keys.Infos, error) {
85 dir, err := os.Open(s.keyDir)
87 return nil, errors.Wrap(err, "List Keys")
91 names, err := dir.Readdirnames(0)
93 return nil, errors.Wrap(err, "List Keys")
96 // filter names for .pub ending and load them one by one
97 // half the files is a good guess for pre-allocating the slice
98 infos := make([]keys.Info, 0, len(names)/2)
99 for _, name := range names {
100 if strings.HasSuffix(name, PubExt) {
101 p := path.Join(s.keyDir, name)
102 info, err := readInfo(p)
106 infos = append(infos, info.Format())
113 // Delete permanently removes the public and private info for the named key
114 // The calling function should provide some security checks first.
115 func (s FileStore) Delete(name string) error {
116 pub, priv := s.nameToPaths(name)
117 err := os.Remove(priv)
119 return errors.Wrap(err, "Deleting Private Key")
122 return errors.Wrap(err, "Deleting Public Key")
125 func (s FileStore) nameToPaths(name string) (pub, priv string) {
126 privName := fmt.Sprintf("%s.%s", name, PrivExt)
127 pubName := fmt.Sprintf("%s.%s", name, PubExt)
128 return path.Join(s.keyDir, pubName), path.Join(s.keyDir, privName)
131 func writeInfo(path string, info keys.Info) error {
132 return write(path, info.Name, info.PubKey.Bytes())
135 func readInfo(path string) (info keys.Info, err error) {
137 data, info.Name, err = read(path)
141 pk, err := crypto.PubKeyFromBytes(data)
146 func read(path string) ([]byte, string, error) {
147 f, err := os.Open(path)
149 return nil, "", errors.Wrap(err, "Reading data")
153 d, err := ioutil.ReadAll(f)
155 return nil, "", errors.Wrap(err, "Reading data")
157 block, headers, key, err := crypto.DecodeArmor(string(d))
159 return nil, "", errors.Wrap(err, "Invalid Armor")
161 if block != BlockType {
162 return nil, "", errors.Errorf("Unknown key type: %s", block)
164 return key, headers["name"], nil
167 func write(path, name string, key []byte) error {
168 f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm)
170 return errors.Wrap(err, "Writing data")
173 headers := map[string]string{"name": name}
174 text := crypto.EncodeArmor(BlockType, headers, key)
175 _, err = f.WriteString(text)
176 return errors.Wrap(err, "Writing data")