github.com/MetalBlockchain/metalgo@v1.11.9/api/keystore/keystore.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package keystore 5 6 import ( 7 "errors" 8 "fmt" 9 "net/http" 10 "sync" 11 12 "github.com/gorilla/rpc/v2" 13 14 "github.com/MetalBlockchain/metalgo/chains/atomic" 15 "github.com/MetalBlockchain/metalgo/database" 16 "github.com/MetalBlockchain/metalgo/database/encdb" 17 "github.com/MetalBlockchain/metalgo/database/prefixdb" 18 "github.com/MetalBlockchain/metalgo/ids" 19 "github.com/MetalBlockchain/metalgo/utils/json" 20 "github.com/MetalBlockchain/metalgo/utils/logging" 21 "github.com/MetalBlockchain/metalgo/utils/password" 22 ) 23 24 const ( 25 // maxUserLen is the maximum allowed length of a username 26 maxUserLen = 1024 27 ) 28 29 var ( 30 errEmptyUsername = errors.New("empty username") 31 errUserMaxLength = fmt.Errorf("username exceeds maximum length of %d chars", maxUserLen) 32 errUserAlreadyExists = errors.New("user already exists") 33 errIncorrectPassword = errors.New("incorrect password") 34 errNonexistentUser = errors.New("user doesn't exist") 35 36 usersPrefix = []byte("users") 37 bcsPrefix = []byte("bcs") 38 39 _ Keystore = (*keystore)(nil) 40 ) 41 42 type Keystore interface { 43 // Create the API endpoint for this keystore. 44 CreateHandler() (http.Handler, error) 45 46 // NewBlockchainKeyStore returns this keystore limiting the functionality to 47 // a single blockchain database. 48 NewBlockchainKeyStore(blockchainID ids.ID) BlockchainKeystore 49 50 // Get a database that is able to read and write unencrypted values from the 51 // underlying database. 52 GetDatabase(bID ids.ID, username, password string) (*encdb.Database, error) 53 54 // Get the underlying database that is able to read and write encrypted 55 // values. This Database will not perform any encrypting or decrypting of 56 // values and is not recommended to be used when implementing a VM. 57 GetRawDatabase(bID ids.ID, username, password string) (database.Database, error) 58 59 // CreateUser attempts to register this username and password as a new user 60 // of the keystore. 61 CreateUser(username, pw string) error 62 63 // DeleteUser attempts to remove the provided username and all of its data 64 // from the keystore. 65 DeleteUser(username, pw string) error 66 67 // ListUsers returns all the users that currently exist in this keystore. 68 ListUsers() ([]string, error) 69 70 // ImportUser imports a serialized encoding of a user's information complete 71 // with encrypted database values. The password is integrity checked. 72 ImportUser(username, pw string, user []byte) error 73 74 // ExportUser exports a serialized encoding of a user's information complete 75 // with encrypted database values. 76 ExportUser(username, pw string) ([]byte, error) 77 78 // Get the password that is used by [username]. If [username] doesn't exist, 79 // no error is returned and a nil password hash is returned. 80 getPassword(username string) (*password.Hash, error) 81 } 82 83 type kvPair struct { 84 Key []byte `serialize:"true"` 85 Value []byte `serialize:"true"` 86 } 87 88 // user describes the full content of a user 89 type user struct { 90 password.Hash `serialize:"true"` 91 Data []kvPair `serialize:"true"` 92 } 93 94 type keystore struct { 95 lock sync.Mutex 96 log logging.Logger 97 98 // Key: username 99 // Value: The hash of that user's password 100 usernameToPassword map[string]*password.Hash 101 102 // Used to persist users and their data 103 userDB database.Database 104 bcDB database.Database 105 } 106 107 func New(log logging.Logger, db database.Database) Keystore { 108 return &keystore{ 109 log: log, 110 usernameToPassword: make(map[string]*password.Hash), 111 userDB: prefixdb.New(usersPrefix, db), 112 bcDB: prefixdb.New(bcsPrefix, db), 113 } 114 } 115 116 func (ks *keystore) CreateHandler() (http.Handler, error) { 117 newServer := rpc.NewServer() 118 codec := json.NewCodec() 119 newServer.RegisterCodec(codec, "application/json") 120 newServer.RegisterCodec(codec, "application/json;charset=UTF-8") 121 if err := newServer.RegisterService(&service{ks: ks}, "keystore"); err != nil { 122 return nil, err 123 } 124 return newServer, nil 125 } 126 127 func (ks *keystore) NewBlockchainKeyStore(blockchainID ids.ID) BlockchainKeystore { 128 return &blockchainKeystore{ 129 blockchainID: blockchainID, 130 ks: ks, 131 } 132 } 133 134 func (ks *keystore) GetDatabase(bID ids.ID, username, password string) (*encdb.Database, error) { 135 bcDB, err := ks.GetRawDatabase(bID, username, password) 136 if err != nil { 137 return nil, err 138 } 139 return encdb.New([]byte(password), bcDB) 140 } 141 142 func (ks *keystore) GetRawDatabase(bID ids.ID, username, pw string) (database.Database, error) { 143 if username == "" { 144 return nil, errEmptyUsername 145 } 146 147 ks.lock.Lock() 148 defer ks.lock.Unlock() 149 150 passwordHash, err := ks.getPassword(username) 151 if err != nil { 152 return nil, err 153 } 154 if passwordHash == nil || !passwordHash.Check(pw) { 155 return nil, fmt.Errorf("%w: user %q", errIncorrectPassword, username) 156 } 157 158 userDB := prefixdb.New([]byte(username), ks.bcDB) 159 bcDB := prefixdb.NewNested(bID[:], userDB) 160 return bcDB, nil 161 } 162 163 func (ks *keystore) CreateUser(username, pw string) error { 164 if username == "" { 165 return errEmptyUsername 166 } 167 if len(username) > maxUserLen { 168 return errUserMaxLength 169 } 170 171 ks.lock.Lock() 172 defer ks.lock.Unlock() 173 174 passwordHash, err := ks.getPassword(username) 175 if err != nil { 176 return err 177 } 178 if passwordHash != nil { 179 return fmt.Errorf("%w: %s", errUserAlreadyExists, username) 180 } 181 182 if err := password.IsValid(pw, password.OK); err != nil { 183 return err 184 } 185 186 passwordHash = &password.Hash{} 187 if err := passwordHash.Set(pw); err != nil { 188 return err 189 } 190 191 passwordBytes, err := Codec.Marshal(CodecVersion, passwordHash) 192 if err != nil { 193 return err 194 } 195 196 if err := ks.userDB.Put([]byte(username), passwordBytes); err != nil { 197 return err 198 } 199 ks.usernameToPassword[username] = passwordHash 200 201 return nil 202 } 203 204 func (ks *keystore) DeleteUser(username, pw string) error { 205 if username == "" { 206 return errEmptyUsername 207 } 208 if len(username) > maxUserLen { 209 return errUserMaxLength 210 } 211 212 ks.lock.Lock() 213 defer ks.lock.Unlock() 214 215 // check if user exists and valid user. 216 passwordHash, err := ks.getPassword(username) 217 switch { 218 case err != nil: 219 return err 220 case passwordHash == nil: 221 return fmt.Errorf("%w: %s", errNonexistentUser, username) 222 case !passwordHash.Check(pw): 223 return fmt.Errorf("%w: user %q", errIncorrectPassword, username) 224 } 225 226 userNameBytes := []byte(username) 227 userBatch := ks.userDB.NewBatch() 228 if err := userBatch.Delete(userNameBytes); err != nil { 229 return err 230 } 231 232 userDataDB := prefixdb.New(userNameBytes, ks.bcDB) 233 dataBatch := userDataDB.NewBatch() 234 235 it := userDataDB.NewIterator() 236 defer it.Release() 237 238 for it.Next() { 239 if err := dataBatch.Delete(it.Key()); err != nil { 240 return err 241 } 242 } 243 244 if err := it.Error(); err != nil { 245 return err 246 } 247 248 if err := atomic.WriteAll(dataBatch, userBatch); err != nil { 249 return err 250 } 251 252 // delete from users map. 253 delete(ks.usernameToPassword, username) 254 return nil 255 } 256 257 func (ks *keystore) ListUsers() ([]string, error) { 258 users := []string{} 259 260 ks.lock.Lock() 261 defer ks.lock.Unlock() 262 263 it := ks.userDB.NewIterator() 264 defer it.Release() 265 for it.Next() { 266 users = append(users, string(it.Key())) 267 } 268 return users, it.Error() 269 } 270 271 func (ks *keystore) ImportUser(username, pw string, userBytes []byte) error { 272 if username == "" { 273 return errEmptyUsername 274 } 275 if len(username) > maxUserLen { 276 return errUserMaxLength 277 } 278 279 ks.lock.Lock() 280 defer ks.lock.Unlock() 281 282 passwordHash, err := ks.getPassword(username) 283 if err != nil { 284 return err 285 } 286 if passwordHash != nil { 287 return fmt.Errorf("%w: %s", errUserAlreadyExists, username) 288 } 289 290 userData := user{} 291 if _, err := Codec.Unmarshal(userBytes, &userData); err != nil { 292 return err 293 } 294 if !userData.Hash.Check(pw) { 295 return fmt.Errorf("%w: user %q", errIncorrectPassword, username) 296 } 297 298 usrBytes, err := Codec.Marshal(CodecVersion, &userData.Hash) 299 if err != nil { 300 return err 301 } 302 303 userBatch := ks.userDB.NewBatch() 304 if err := userBatch.Put([]byte(username), usrBytes); err != nil { 305 return err 306 } 307 308 userDataDB := prefixdb.New([]byte(username), ks.bcDB) 309 dataBatch := userDataDB.NewBatch() 310 for _, kvp := range userData.Data { 311 if err := dataBatch.Put(kvp.Key, kvp.Value); err != nil { 312 return fmt.Errorf("error on database put: %w", err) 313 } 314 } 315 316 if err := atomic.WriteAll(dataBatch, userBatch); err != nil { 317 return err 318 } 319 ks.usernameToPassword[username] = &userData.Hash 320 return nil 321 } 322 323 func (ks *keystore) ExportUser(username, pw string) ([]byte, error) { 324 if username == "" { 325 return nil, errEmptyUsername 326 } 327 if len(username) > maxUserLen { 328 return nil, errUserMaxLength 329 } 330 331 ks.lock.Lock() 332 defer ks.lock.Unlock() 333 334 passwordHash, err := ks.getPassword(username) 335 if err != nil { 336 return nil, err 337 } 338 if passwordHash == nil || !passwordHash.Check(pw) { 339 return nil, fmt.Errorf("%w: user %q", errIncorrectPassword, username) 340 } 341 342 userDB := prefixdb.New([]byte(username), ks.bcDB) 343 344 userData := user{Hash: *passwordHash} 345 it := userDB.NewIterator() 346 defer it.Release() 347 for it.Next() { 348 userData.Data = append(userData.Data, kvPair{ 349 Key: it.Key(), 350 Value: it.Value(), 351 }) 352 } 353 if err := it.Error(); err != nil { 354 return nil, err 355 } 356 357 // Return the byte representation of the user 358 return Codec.Marshal(CodecVersion, &userData) 359 } 360 361 func (ks *keystore) getPassword(username string) (*password.Hash, error) { 362 // If the user is already in memory, return it 363 passwordHash, exists := ks.usernameToPassword[username] 364 if exists { 365 return passwordHash, nil 366 } 367 368 // The user is not in memory; try the database 369 userBytes, err := ks.userDB.Get([]byte(username)) 370 if err == database.ErrNotFound { 371 // The user doesn't exist 372 return nil, nil 373 } 374 if err != nil { 375 // An unexpected database error occurred 376 return nil, err 377 } 378 379 passwordHash = &password.Hash{} 380 _, err = Codec.Unmarshal(userBytes, passwordHash) 381 return passwordHash, err 382 }