github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/auth/key/store.go (about) 1 package key 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "sync" 11 12 "github.com/gofrs/flock" 13 "github.com/libp2p/go-libp2p-core/crypto" 14 "github.com/qri-io/qri/config" 15 ) 16 17 // ErrKeyAndIDMismatch occurs when a key identifier doesn't match it's public 18 // key 19 var ErrKeyAndIDMismatch = fmt.Errorf("public key does not match identifier") 20 21 // Store is an abstraction over a KeyBook 22 // In the future we may expand this interface to store symmetric encryption keys 23 type Store interface { 24 Book 25 } 26 27 // NewStore constructs a keys.Store backed by memory or local file 28 func NewStore(cfg *config.Config) (Store, error) { 29 if cfg.Repo == nil { 30 return NewMemStore() 31 } 32 33 switch cfg.Repo.Type { 34 case "fs": 35 // Don't create a localstore with the empty path, this will use the current directory 36 if cfg.Path() == "" { 37 return nil, fmt.Errorf("new key.LocalStore requires non-empty path") 38 } 39 return NewLocalStore(filepath.Join(filepath.Dir(cfg.Path()), "keystore.json")) 40 case "mem": 41 return NewMemStore() 42 default: 43 return nil, fmt.Errorf("unknown repo type: %s", cfg.Repo.Type) 44 } 45 } 46 47 type memStore struct { 48 Book 49 } 50 51 // NewMemStore constructs an in-memory key.Store 52 func NewMemStore() (Store, error) { 53 return &memStore{ 54 Book: newKeyBook(), 55 }, nil 56 } 57 58 type localStore struct { 59 sync.Mutex 60 filename string 61 flock *flock.Flock 62 } 63 64 // NewLocalStore constructs a local file backed key.Store 65 func NewLocalStore(filename string) (Store, error) { 66 return &localStore{ 67 filename: filename, 68 flock: flock.New(lockPath(filename)), 69 }, nil 70 } 71 72 func lockPath(filename string) string { 73 return fmt.Sprintf("%s.lock", filename) 74 } 75 76 // PubKey returns the public key for a given ID if it exists 77 func (s *localStore) PubKey(ctx context.Context, keyID ID) crypto.PubKey { 78 s.Lock() 79 defer s.Unlock() 80 81 kb, err := s.keys() 82 if err != nil { 83 return nil 84 } 85 return kb.PubKey(ctx, keyID) 86 } 87 88 // PrivKey returns the private key for a given ID if it exists 89 func (s *localStore) PrivKey(ctx context.Context, keyID ID) crypto.PrivKey { 90 s.Lock() 91 defer s.Unlock() 92 93 kb, err := s.keys() 94 if err != nil { 95 return nil 96 } 97 return kb.PrivKey(ctx, keyID) 98 } 99 100 // AddPubKey inserts a public key for a given ID 101 func (s *localStore) AddPubKey(ctx context.Context, keyID ID, pubKey crypto.PubKey) error { 102 s.Lock() 103 defer s.Unlock() 104 105 kb, err := s.keys() 106 if err != nil { 107 return err 108 } 109 if !keyID.MatchesPublicKey(pubKey) { 110 return fmt.Errorf("%w id: %q", ErrKeyAndIDMismatch, keyID.Pretty()) 111 } 112 err = kb.AddPubKey(ctx, keyID, pubKey) 113 if err != nil { 114 return err 115 } 116 117 return s.saveFile(kb) 118 } 119 120 // AddPrivKey inserts a private key for a given ID 121 func (s *localStore) AddPrivKey(ctx context.Context, keyID ID, privKey crypto.PrivKey) error { 122 s.Lock() 123 defer s.Unlock() 124 125 if !keyID.MatchesPrivateKey(privKey) { 126 return fmt.Errorf("%w id: %q", ErrKeyAndIDMismatch, keyID.Pretty()) 127 } 128 129 kb, err := s.keys() 130 if err != nil { 131 return err 132 } 133 err = kb.AddPrivKey(ctx, keyID, privKey) 134 if err != nil { 135 return err 136 } 137 138 return s.saveFile(kb) 139 } 140 141 // IDsWithKeys returns the list of IDs in the KeyBook 142 func (s *localStore) IDsWithKeys(ctx context.Context) []ID { 143 s.Lock() 144 defer s.Unlock() 145 146 kb, err := s.keys() 147 if err != nil { 148 // the keys method will safely return an empty list which we can use bellow 149 log.Debugf("error loading peers with keys: %q", err.Error()) 150 return []ID{} 151 } 152 return kb.IDsWithKeys(ctx) 153 } 154 155 func (s *localStore) keys() (Book, error) { 156 log.Debug("reading keys") 157 158 if err := s.flock.Lock(); err != nil { 159 return nil, err 160 } 161 defer func() { 162 log.Debug("keys read") 163 s.flock.Unlock() 164 }() 165 166 kb := newKeyBook() 167 data, err := ioutil.ReadFile(s.filename) 168 if err != nil { 169 if os.IsNotExist(err) { 170 return kb, nil 171 } 172 log.Debug(err.Error()) 173 return kb, fmt.Errorf("error loading keys: %s", err.Error()) 174 } 175 176 if err := json.Unmarshal(data, kb); err != nil { 177 log.Error(err.Error()) 178 // on bad parsing we simply return an empty keybook 179 return kb, nil 180 } 181 return kb, nil 182 } 183 184 func (s *localStore) saveFile(kb Book) error { 185 data, err := json.Marshal(kb) 186 if err != nil { 187 log.Debug(err.Error()) 188 return err 189 } 190 191 log.Debugf("writing keys: %s", s.filename) 192 if err := s.flock.Lock(); err != nil { 193 return err 194 } 195 defer func() { 196 s.flock.Unlock() 197 log.Debug("keys written") 198 }() 199 return ioutil.WriteFile(s.filename, data, 0644) 200 }