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  }