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.

177 lines
4.4 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 is the extension for private keys.
  20. PrivExt = "tlc"
  21. // PubExt is the extensions for public keys.
  22. PubExt = "pub"
  23. keyPerm = os.FileMode(0600)
  24. // pubPerm = os.FileMode(0644)
  25. dirPerm = os.FileMode(0700)
  26. )
  27. type FileStore struct {
  28. keyDir string
  29. }
  30. // New creates an instance of file-based key storage with tight permissions
  31. //
  32. // dir should be an absolute path of a directory owner by this user. It will
  33. // be created if it doesn't exist already.
  34. func New(dir string) FileStore {
  35. err := os.MkdirAll(dir, dirPerm)
  36. if err != nil {
  37. panic(err)
  38. }
  39. return FileStore{dir}
  40. }
  41. // assert FileStore satisfies keys.Storage
  42. var _ keys.Storage = FileStore{}
  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. defer dir.Close()
  75. names, err := dir.Readdirnames(0)
  76. if err != nil {
  77. return nil, errors.Wrap(err, "List Keys")
  78. }
  79. // filter names for .pub ending and load them one by one
  80. // half the files is a good guess for pre-allocating the slice
  81. infos := make([]keys.Info, 0, len(names)/2)
  82. for _, name := range names {
  83. if strings.HasSuffix(name, PubExt) {
  84. p := path.Join(s.keyDir, name)
  85. info, err := readInfo(p)
  86. if err != nil {
  87. return nil, err
  88. }
  89. infos = append(infos, info.Format())
  90. }
  91. }
  92. return infos, nil
  93. }
  94. // Delete permanently removes the public and private info for the named key
  95. // The calling function should provide some security checks first.
  96. func (s FileStore) Delete(name string) error {
  97. pub, priv := s.nameToPaths(name)
  98. err := os.Remove(priv)
  99. if err != nil {
  100. return errors.Wrap(err, "Deleting Private Key")
  101. }
  102. err = os.Remove(pub)
  103. return errors.Wrap(err, "Deleting Public Key")
  104. }
  105. func (s FileStore) nameToPaths(name string) (pub, priv string) {
  106. privName := fmt.Sprintf("%s.%s", name, PrivExt)
  107. pubName := fmt.Sprintf("%s.%s", name, PubExt)
  108. return path.Join(s.keyDir, pubName), path.Join(s.keyDir, privName)
  109. }
  110. func writeInfo(path string, info keys.Info) error {
  111. return write(path, info.Name, info.PubKey.Bytes())
  112. }
  113. func readInfo(path string) (info keys.Info, err error) {
  114. var data []byte
  115. data, info.Name, err = read(path)
  116. if err != nil {
  117. return
  118. }
  119. pk, err := crypto.PubKeyFromBytes(data)
  120. info.PubKey = pk
  121. return
  122. }
  123. func read(path string) ([]byte, string, error) {
  124. f, err := os.Open(path)
  125. if err != nil {
  126. return nil, "", errors.Wrap(err, "Reading data")
  127. }
  128. defer f.Close()
  129. d, err := ioutil.ReadAll(f)
  130. if err != nil {
  131. return nil, "", errors.Wrap(err, "Reading data")
  132. }
  133. block, headers, key, err := crypto.DecodeArmor(string(d))
  134. if err != nil {
  135. return nil, "", errors.Wrap(err, "Invalid Armor")
  136. }
  137. if block != BlockType {
  138. return nil, "", errors.Errorf("Unknown key type: %s", block)
  139. }
  140. return key, headers["name"], nil
  141. }
  142. func write(path, name string, key []byte) error {
  143. f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm)
  144. if err != nil {
  145. return errors.Wrap(err, "Writing data")
  146. }
  147. defer f.Close()
  148. headers := map[string]string{"name": name}
  149. text := crypto.EncodeArmor(BlockType, headers, key)
  150. _, err = f.WriteString(text)
  151. return errors.Wrap(err, "Writing data")
  152. }