2020-03-10 12:20:34 -07:00
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package keystore
import (
"errors"
"fmt"
"net/http"
"sync"
"github.com/gorilla/rpc/v2"
2020-04-04 13:57:13 -07:00
"github.com/ava-labs/gecko/chains/atomic"
2020-03-10 12:20:34 -07:00
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/encdb"
"github.com/ava-labs/gecko/database/prefixdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/utils/formatting"
"github.com/ava-labs/gecko/utils/logging"
"github.com/ava-labs/gecko/vms/components/codec"
jsoncodec "github.com/ava-labs/gecko/utils/json"
2020-03-13 14:04:57 -07:00
zxcvbn "github.com/nbutton23/zxcvbn-go"
2020-03-10 12:20:34 -07:00
)
2020-03-13 02:28:32 -07:00
const (
// maxUserPassLen is the maximum length of the username or password allowed
maxUserPassLen = 1024
2020-03-13 14:04:57 -07:00
// requiredPassScore defines the score a password must achieve to be accepted
// as a password with strong characteristics by the zxcvbn package
//
// The scoring mechanism defined is as follows;
//
// 0 # too guessable: risky password. (guesses < 10^3)
// 1 # very guessable: protection from throttled online attacks. (guesses < 10^6)
// 2 # somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8)
// 3 # safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10)
// 4 # very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10)
requiredPassScore = 2
2020-03-13 02:28:32 -07:00
)
2020-03-10 12:20:34 -07:00
var (
2020-03-13 02:28:32 -07:00
errEmptyUsername = errors . New ( "username can't be the empty string" )
2020-03-13 12:29:42 -07:00
errUserPassMaxLength = fmt . Errorf ( "CreateUser call rejected due to username or password exceeding maximum length of %d chars" , maxUserPassLen )
2020-03-17 11:53:25 -07:00
errWeakPassword = errors . New ( "Failed to create user as the given password is too weak. A stronger password is one of 8 or more characters containing attributes of upper and lowercase letters, numbers, and/or special characters" )
2020-03-10 12:20:34 -07:00
)
// KeyValuePair ...
type KeyValuePair struct {
Key [ ] byte ` serialize:"true" `
Value [ ] byte ` serialize:"true" `
}
// UserDB describes the full content of a user
type UserDB struct {
User ` serialize:"true" `
Data [ ] KeyValuePair ` serialize:"true" `
}
// Keystore is the RPC interface for keystore management
type Keystore struct {
lock sync . Mutex
log logging . Logger
codec codec . Codec
// Key: username
// Value: The user with that name
users map [ string ] * User
// Used to persist users and their data
userDB database . Database
bcDB database . Database
// BaseDB
// / \
// UserDB BlockchainDB
// / | \
// Usr Usr Usr
// / | \
// BID BID BID
}
// Initialize the keystore
func ( ks * Keystore ) Initialize ( log logging . Logger , db database . Database ) {
ks . log = log
ks . codec = codec . NewDefault ( )
ks . users = make ( map [ string ] * User )
ks . userDB = prefixdb . New ( [ ] byte ( "users" ) , db )
ks . bcDB = prefixdb . New ( [ ] byte ( "bcs" ) , db )
}
// CreateHandler returns a new service object that can send requests to thisAPI.
func ( ks * Keystore ) CreateHandler ( ) * common . HTTPHandler {
newServer := rpc . NewServer ( )
codec := jsoncodec . NewCodec ( )
newServer . RegisterCodec ( codec , "application/json" )
newServer . RegisterCodec ( codec , "application/json;charset=UTF-8" )
newServer . RegisterService ( ks , "keystore" )
return & common . HTTPHandler { LockOptions : common . NoLock , Handler : newServer }
}
// Get the user whose name is [username]
func ( ks * Keystore ) getUser ( username string ) ( * User , error ) {
// If the user is already in memory, return it
usr , exists := ks . users [ username ]
if exists {
return usr , nil
}
// The user is not in memory; try the database
usrBytes , err := ks . userDB . Get ( [ ] byte ( username ) )
if err != nil { // Most likely bc user doesn't exist in database
return nil , err
}
usr = & User { }
return usr , ks . codec . Unmarshal ( usrBytes , usr )
}
// CreateUserArgs are arguments for passing into CreateUser requests
type CreateUserArgs struct {
Username string ` json:"username" `
Password string ` json:"password" `
}
// CreateUserReply is the response from calling CreateUser
type CreateUserReply struct {
Success bool ` json:"success" `
}
// CreateUser creates an empty user with the provided username and password
func ( ks * Keystore ) CreateUser ( _ * http . Request , args * CreateUserArgs , reply * CreateUserReply ) error {
ks . lock . Lock ( )
defer ks . lock . Unlock ( )
2020-03-16 12:55:52 -07:00
ks . log . Verbo ( "CreateUser called with %.*s" , maxUserPassLen , args . Username )
2020-03-16 12:22:16 -07:00
2020-03-13 02:28:32 -07:00
if len ( args . Username ) > maxUserPassLen || len ( args . Password ) > maxUserPassLen {
return errUserPassMaxLength
}
2020-03-10 12:20:34 -07:00
if args . Username == "" {
return errEmptyUsername
}
if usr , err := ks . getUser ( args . Username ) ; err == nil || usr != nil {
return fmt . Errorf ( "user already exists: %s" , args . Username )
}
2020-03-13 14:04:57 -07:00
if zxcvbn . PasswordStrength ( args . Password , nil ) . Score < requiredPassScore {
return errWeakPassword
}
2020-03-10 12:20:34 -07:00
usr := & User { }
if err := usr . Initialize ( args . Password ) ; err != nil {
return err
}
usrBytes , err := ks . codec . Marshal ( usr )
if err != nil {
return err
}
if err := ks . userDB . Put ( [ ] byte ( args . Username ) , usrBytes ) ; err != nil {
return err
}
ks . users [ args . Username ] = usr
reply . Success = true
return nil
}
// ListUsersArgs are the arguments to ListUsers
type ListUsersArgs struct { }
// ListUsersReply is the reply from ListUsers
type ListUsersReply struct {
Users [ ] string ` json:"users" `
}
// ListUsers lists all the registered usernames
func ( ks * Keystore ) ListUsers ( _ * http . Request , args * ListUsersArgs , reply * ListUsersReply ) error {
ks . lock . Lock ( )
defer ks . lock . Unlock ( )
ks . log . Verbo ( "ListUsers called" )
reply . Users = [ ] string { }
it := ks . userDB . NewIterator ( )
defer it . Release ( )
for it . Next ( ) {
reply . Users = append ( reply . Users , string ( it . Key ( ) ) )
}
return it . Error ( )
}
// ExportUserArgs are the arguments to ExportUser
type ExportUserArgs struct {
Username string ` json:"username" `
Password string ` json:"password" `
}
// ExportUserReply is the reply from ExportUser
type ExportUserReply struct {
2020-04-04 13:57:13 -07:00
User formatting . CB58 ` json:"user" `
2020-03-10 12:20:34 -07:00
}
// ExportUser exports a serialized encoding of a user's information complete with encrypted database values
func ( ks * Keystore ) ExportUser ( _ * http . Request , args * ExportUserArgs , reply * ExportUserReply ) error {
ks . lock . Lock ( )
defer ks . lock . Unlock ( )
ks . log . Verbo ( "ExportUser called for %s" , args . Username )
usr , err := ks . getUser ( args . Username )
if err != nil {
return err
}
if ! usr . CheckPassword ( args . Password ) {
return fmt . Errorf ( "incorrect password for %s" , args . Username )
}
userDB := prefixdb . New ( [ ] byte ( args . Username ) , ks . bcDB )
userData := UserDB {
User : * usr ,
}
it := userDB . NewIterator ( )
defer it . Release ( )
for it . Next ( ) {
userData . Data = append ( userData . Data , KeyValuePair {
Key : it . Key ( ) ,
Value : it . Value ( ) ,
} )
}
if err := it . Error ( ) ; err != nil {
return err
}
b , err := ks . codec . Marshal ( & userData )
if err != nil {
return err
}
2020-04-04 13:57:13 -07:00
reply . User . Bytes = b
2020-03-10 12:20:34 -07:00
return nil
}
// ImportUserArgs are arguments for ImportUser
type ImportUserArgs struct {
2020-04-04 13:57:13 -07:00
Username string ` json:"username" `
Password string ` json:"password" `
User formatting . CB58 ` json:"user" `
2020-03-10 12:20:34 -07:00
}
// ImportUserReply is the response for ImportUser
type ImportUserReply struct {
Success bool ` json:"success" `
}
// ImportUser imports a serialized encoding of a user's information complete with encrypted database values, integrity checks the password, and adds it to the database
func ( ks * Keystore ) ImportUser ( r * http . Request , args * ImportUserArgs , reply * ImportUserReply ) error {
ks . lock . Lock ( )
defer ks . lock . Unlock ( )
ks . log . Verbo ( "ImportUser called for %s" , args . Username )
if usr , err := ks . getUser ( args . Username ) ; err == nil || usr != nil {
return fmt . Errorf ( "user already exists: %s" , args . Username )
}
userData := UserDB { }
2020-04-04 13:57:13 -07:00
if err := ks . codec . Unmarshal ( args . User . Bytes , & userData ) ; err != nil {
2020-03-10 12:20:34 -07:00
return err
}
usrBytes , err := ks . codec . Marshal ( & userData . User )
if err != nil {
return err
}
2020-04-04 13:57:13 -07:00
userBatch := ks . userDB . NewBatch ( )
if err := userBatch . Put ( [ ] byte ( args . Username ) , usrBytes ) ; err != nil {
2020-03-10 12:20:34 -07:00
return err
}
2020-04-04 13:57:13 -07:00
userDataDB := prefixdb . New ( [ ] byte ( args . Username ) , ks . bcDB )
dataBatch := userDataDB . NewBatch ( )
2020-03-10 12:20:34 -07:00
for _ , kvp := range userData . Data {
2020-04-04 13:57:13 -07:00
dataBatch . Put ( kvp . Key , kvp . Value )
}
if err := atomic . WriteAll ( dataBatch , userBatch ) ; err != nil {
return err
2020-03-10 12:20:34 -07:00
}
2020-04-04 13:57:13 -07:00
ks . users [ args . Username ] = & userData . User
2020-03-10 12:20:34 -07:00
reply . Success = true
2020-04-04 13:57:13 -07:00
return nil
2020-03-10 12:20:34 -07:00
}
2020-05-07 00:38:42 -07:00
// DeleteUserArgs are arguments for passing into DeleteUser requests
2020-05-07 00:30:27 -07:00
type DeleteUserArgs struct {
Username string ` json:"username" `
Password string ` json:"password" `
}
2020-05-07 00:38:42 -07:00
// DeleteUserReply is the response from calling DeleteUser
2020-05-07 00:30:27 -07:00
type DeleteUserReply struct {
Success bool ` json:"success" `
}
// DeleteUser deletes user with the provided username and password.
func ( ks * Keystore ) DeleteUser ( _ * http . Request , args * DeleteUserArgs , reply * DeleteUserReply ) error {
ks . lock . Lock ( )
defer ks . lock . Unlock ( )
ks . log . Verbo ( "DeleteUser called with %s" , args . Username )
if args . Username == "" {
return errEmptyUsername
}
// check if user exists and valid user.
usr , err := ks . getUser ( args . Username )
switch {
case err != nil || usr == nil :
2020-05-11 21:40:46 -07:00
return fmt . Errorf ( "user doesn't exist: %s" , args . Username )
2020-05-07 00:30:27 -07:00
case ! usr . CheckPassword ( args . Password ) :
return fmt . Errorf ( "incorrect password for user %q" , args . Username )
}
2020-05-11 12:16:26 -07:00
userNameBytes := [ ] byte ( args . Username )
2020-05-11 12:11:04 -07:00
userBatch := ks . userDB . NewBatch ( )
2020-05-11 12:16:26 -07:00
if err := userBatch . Delete ( userNameBytes ) ; err != nil {
2020-05-11 12:11:04 -07:00
return err
}
2020-05-11 12:16:26 -07:00
userDataDB := prefixdb . New ( userNameBytes , ks . bcDB )
2020-05-11 14:49:16 -07:00
var data [ ] KeyValuePair
it := userDataDB . NewIterator ( )
defer it . Release ( )
for it . Next ( ) {
data = append ( data , KeyValuePair {
Key : it . Key ( ) ,
Value : it . Value ( ) ,
} )
}
if err = it . Error ( ) ; err != nil {
2020-05-11 12:11:04 -07:00
return err
}
2020-05-11 14:49:16 -07:00
dataBatch := userDataDB . NewBatch ( )
for _ , kvp := range data {
if err = dataBatch . Delete ( kvp . Key ) ; err != nil {
return err
}
}
2020-05-11 12:11:04 -07:00
if err := atomic . WriteAll ( dataBatch , userBatch ) ; err != nil {
return err
}
2020-05-07 00:30:27 -07:00
// delete from users map.
delete ( ks . users , args . Username )
reply . Success = true
return nil
}
2020-03-10 12:20:34 -07:00
// NewBlockchainKeyStore ...
func ( ks * Keystore ) NewBlockchainKeyStore ( blockchainID ids . ID ) * BlockchainKeystore {
return & BlockchainKeystore {
blockchainID : blockchainID ,
ks : ks ,
}
}
// GetDatabase ...
func ( ks * Keystore ) GetDatabase ( bID ids . ID , username , password string ) ( database . Database , error ) {
ks . lock . Lock ( )
defer ks . lock . Unlock ( )
usr , err := ks . getUser ( username )
if err != nil {
return nil , err
}
if ! usr . CheckPassword ( password ) {
return nil , fmt . Errorf ( "incorrect password for user '%s'" , username )
}
userDB := prefixdb . New ( [ ] byte ( username ) , ks . bcDB )
bcDB := prefixdb . NewNested ( bID . Bytes ( ) , userDB )
encDB , err := encdb . New ( [ ] byte ( password ) , bcDB )
if err != nil {
return nil , err
}
return encDB , nil
}