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.

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