You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
5.7 KiB

  1. /*
  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.
  5. */
  6. package filestorage
  7. import (
  8. "encoding/hex"
  9. "fmt"
  10. "io/ioutil"
  11. "os"
  12. "path"
  13. "strings"
  14. "github.com/pkg/errors"
  15. crypto "github.com/tendermint/go-crypto"
  16. keys "github.com/tendermint/go-crypto/keys"
  17. )
  18. const (
  19. // BlockType is the type of block.
  20. BlockType = "Tendermint Light Client"
  21. // PrivExt is the extension for private keys.
  22. PrivExt = "tlc"
  23. // PubExt is the extensions for public keys.
  24. PubExt = "pub"
  25. keyPerm = os.FileMode(0600)
  26. // pubPerm = os.FileMode(0644)
  27. dirPerm = os.FileMode(0700)
  28. )
  29. // FileStore is a file-based key storage with tight permissions.
  30. type FileStore struct {
  31. keyDir string
  32. }
  33. // New creates an instance of file-based key storage with tight permissions
  34. //
  35. // dir should be an absolute path of a directory owner by this user. It will
  36. // be created if it doesn't exist already.
  37. func New(dir string) FileStore {
  38. err := os.MkdirAll(dir, dirPerm)
  39. if err != nil {
  40. panic(err)
  41. }
  42. return FileStore{dir}
  43. }
  44. // assert FileStore satisfies keys.Storage
  45. var _ keys.Storage = FileStore{}
  46. // Put creates two files, one with the public info as json, the other
  47. // with the (encoded) private key as gpg ascii-armor style
  48. func (s FileStore) Put(name string, salt, key []byte, info keys.Info) error {
  49. pub, priv := s.nameToPaths(name)
  50. // write public info
  51. err := writeInfo(pub, info)
  52. if err != nil {
  53. return err
  54. }
  55. // write private info
  56. return write(priv, name, salt, key)
  57. }
  58. // Get loads the info and (encoded) private key from the directory
  59. // It uses `name` to generate the filename, and returns an error if the
  60. // files don't exist or are in the incorrect format
  61. func (s FileStore) Get(name string) (salt []byte, key []byte, info keys.Info, err error) {
  62. pub, priv := s.nameToPaths(name)
  63. info, err = readInfo(pub)
  64. if err != nil {
  65. return nil, nil, info, err
  66. }
  67. salt, key, _, err = read(priv)
  68. return salt, key, info.Format(), err
  69. }
  70. // List parses the key directory for public info and returns a list of
  71. // Info for all keys located in this directory.
  72. func (s FileStore) List() (keys.Infos, error) {
  73. dir, err := os.Open(s.keyDir)
  74. if err != nil {
  75. return nil, errors.Wrap(err, "List Keys")
  76. }
  77. defer dir.Close()
  78. names, err := dir.Readdirnames(0)
  79. if err != nil {
  80. return nil, errors.Wrap(err, "List Keys")
  81. }
  82. // filter names for .pub ending and load them one by one
  83. // half the files is a good guess for pre-allocating the slice
  84. infos := make([]keys.Info, 0, len(names)/2)
  85. for _, name := range names {
  86. if strings.HasSuffix(name, PubExt) {
  87. p := path.Join(s.keyDir, name)
  88. info, err := readInfo(p)
  89. if err != nil {
  90. return nil, err
  91. }
  92. infos = append(infos, info.Format())
  93. }
  94. }
  95. return infos, nil
  96. }
  97. // Delete permanently removes the public and private info for the named key
  98. // The calling function should provide some security checks first.
  99. func (s FileStore) Delete(name string) error {
  100. pub, priv := s.nameToPaths(name)
  101. err := os.Remove(priv)
  102. if err != nil {
  103. return errors.Wrap(err, "Deleting Private Key")
  104. }
  105. err = os.Remove(pub)
  106. return errors.Wrap(err, "Deleting Public Key")
  107. }
  108. func (s FileStore) nameToPaths(name string) (pub, priv string) {
  109. privName := fmt.Sprintf("%s.%s", name, PrivExt)
  110. pubName := fmt.Sprintf("%s.%s", name, PubExt)
  111. return path.Join(s.keyDir, pubName), path.Join(s.keyDir, privName)
  112. }
  113. func readInfo(path string) (info keys.Info, err error) {
  114. f, err := os.Open(path)
  115. if err != nil {
  116. return info, errors.Wrap(err, "Reading data")
  117. }
  118. defer f.Close()
  119. d, err := ioutil.ReadAll(f)
  120. if err != nil {
  121. return info, errors.Wrap(err, "Reading data")
  122. }
  123. block, headers, key, err := crypto.DecodeArmor(string(d))
  124. if err != nil {
  125. return info, errors.Wrap(err, "Invalid Armor")
  126. }
  127. if block != BlockType {
  128. return info, errors.Errorf("Unknown key type: %s", block)
  129. }
  130. pk, _ := crypto.PubKeyFromBytes(key)
  131. info.Name = headers["name"]
  132. info.PubKey = pk
  133. return info, nil
  134. }
  135. func read(path string) (salt, key []byte, name string, err error) {
  136. f, err := os.Open(path)
  137. if err != nil {
  138. return nil, nil, "", errors.Wrap(err, "Reading data")
  139. }
  140. defer f.Close()
  141. d, err := ioutil.ReadAll(f)
  142. if err != nil {
  143. return nil, nil, "", errors.Wrap(err, "Reading data")
  144. }
  145. block, headers, key, err := crypto.DecodeArmor(string(d))
  146. if err != nil {
  147. return nil, nil, "", errors.Wrap(err, "Invalid Armor")
  148. }
  149. if block != BlockType {
  150. return nil, nil, "", errors.Errorf("Unknown key type: %s", block)
  151. }
  152. if headers["kdf"] != "bcrypt" {
  153. return nil, nil, "", errors.Errorf("Unrecognized KDF type: %v", headers["kdf"])
  154. }
  155. if headers["salt"] == "" {
  156. return nil, nil, "", errors.Errorf("Missing salt bytes")
  157. }
  158. salt, err = hex.DecodeString(headers["salt"])
  159. if err != nil {
  160. return nil, nil, "", errors.Errorf("Error decoding salt: %v", err.Error())
  161. }
  162. return salt, key, headers["name"], nil
  163. }
  164. func writeInfo(path string, info keys.Info) error {
  165. f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm)
  166. if err != nil {
  167. return errors.Wrap(err, "Writing data")
  168. }
  169. defer f.Close()
  170. headers := map[string]string{"name": info.Name}
  171. text := crypto.EncodeArmor(BlockType, headers, info.PubKey.Bytes())
  172. _, err = f.WriteString(text)
  173. return errors.Wrap(err, "Writing data")
  174. }
  175. func write(path, name string, salt, key []byte) error {
  176. f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm)
  177. if err != nil {
  178. return errors.Wrap(err, "Writing data")
  179. }
  180. defer f.Close()
  181. headers := map[string]string{
  182. "name": name,
  183. "kdf": "bcrypt",
  184. "salt": fmt.Sprintf("%X", salt),
  185. }
  186. text := crypto.EncodeArmor(BlockType, headers, key)
  187. _, err = f.WriteString(text)
  188. return errors.Wrap(err, "Writing data")
  189. }