github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/blockchain/pseudohsm/pseudohsm.go (about)

     1  // Package pseudohsm provides a pseudo HSM for development environments.
     2  package pseudohsm
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/json"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/pborman/uuid"
    14  
    15  	"github.com/bytom/bytom/crypto/ed25519/chainkd"
    16  	"github.com/bytom/bytom/errors"
    17  	mnem "github.com/bytom/bytom/wallet/mnemonic"
    18  )
    19  
    20  // pre-define errors for supporting bytom errorFormatter
    21  var (
    22  	ErrDuplicateKeyAlias = errors.New("duplicate key alias")
    23  	ErrXPubFormat        = errors.New("xpub format error")
    24  	ErrLoadKey           = errors.New("key not found or wrong password ")
    25  	ErrDecrypt           = errors.New("could not decrypt key with given passphrase")
    26  	ErrMnemonicLength    = errors.New("mnemonic length error")
    27  )
    28  
    29  // EntropyLength random entropy length to generate mnemonics.
    30  const EntropyLength = 128
    31  
    32  // HSM type for storing pubkey and privatekey
    33  type HSM struct {
    34  	cacheMu  sync.Mutex
    35  	keyStore keyStore
    36  	cache    *keyCache
    37  	//kdCache  map[chainkd.XPub]chainkd.XPrv
    38  }
    39  
    40  // XPub type for pubkey for anyone can see
    41  type XPub struct {
    42  	Alias string       `json:"alias"`
    43  	XPub  chainkd.XPub `json:"xpub"`
    44  	File  string       `json:"file"`
    45  }
    46  
    47  // New method for HSM struct
    48  func New(keypath string) (*HSM, error) {
    49  	keydir, _ := filepath.Abs(keypath)
    50  	return &HSM{
    51  		keyStore: &keyStorePassphrase{keydir, LightScryptN, LightScryptP},
    52  		cache:    newKeyCache(keydir),
    53  		//kdCache:  make(map[chainkd.XPub]chainkd.XPrv),
    54  	}, nil
    55  }
    56  
    57  // XCreate produces a new random xprv and stores it in the db.
    58  func (h *HSM) XCreate(alias string, auth string, language string) (*XPub, *string, error) {
    59  	h.cacheMu.Lock()
    60  	defer h.cacheMu.Unlock()
    61  
    62  	normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
    63  	if ok := h.cache.hasAlias(normalizedAlias); ok {
    64  		return nil, nil, ErrDuplicateKeyAlias
    65  	}
    66  
    67  	xpub, mnemonic, err := h.createChainKDKey(normalizedAlias, auth, language)
    68  	if err != nil {
    69  		return nil, nil, err
    70  	}
    71  	h.cache.add(*xpub)
    72  	return xpub, mnemonic, err
    73  }
    74  
    75  // ImportKeyFromMnemonic produces a xprv from mnemonic and stores it in the db.
    76  func (h *HSM) ImportKeyFromMnemonic(alias string, auth string, mnemonic string, language string) (*XPub, error) {
    77  	h.cacheMu.Lock()
    78  	defer h.cacheMu.Unlock()
    79  
    80  	// checksum length = entropy length /32
    81  	// mnemonic length = (entropy length + checksum length)/11
    82  	if len(strings.Fields(mnemonic)) != (EntropyLength+EntropyLength/32)/11 {
    83  		return nil, ErrMnemonicLength
    84  	}
    85  
    86  	normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
    87  	if ok := h.cache.hasAlias(normalizedAlias); ok {
    88  		return nil, ErrDuplicateKeyAlias
    89  	}
    90  
    91  	// Pre validate that the mnemonic is well formed and only contains words that
    92  	// are present in the word list
    93  	if !mnem.IsMnemonicValid(mnemonic, language) {
    94  		return nil, mnem.ErrInvalidMnemonic
    95  	}
    96  
    97  	xpub, err := h.createKeyFromMnemonic(alias, auth, mnemonic)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	h.cache.add(*xpub)
   103  	return xpub, nil
   104  }
   105  
   106  func (h *HSM) createKeyFromMnemonic(alias string, auth string, mnemonic string) (*XPub, error) {
   107  	// Generate a Bip32 HD wallet for the mnemonic and a user supplied password
   108  	seed := mnem.NewSeed(mnemonic, "")
   109  	xprv, xpub, err := chainkd.NewXKeys(bytes.NewBuffer(seed))
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	id := uuid.NewRandom()
   114  	key := &XKey{
   115  		ID:      id,
   116  		KeyType: "bytom_kd",
   117  		XPub:    xpub,
   118  		XPrv:    xprv,
   119  		Alias:   alias,
   120  	}
   121  	file := h.keyStore.JoinPath(keyFileName(key.ID.String()))
   122  	if err := h.keyStore.StoreKey(file, key, auth); err != nil {
   123  		return nil, errors.Wrap(err, "storing keys")
   124  	}
   125  	return &XPub{XPub: xpub, Alias: alias, File: file}, nil
   126  }
   127  
   128  func (h *HSM) createChainKDKey(alias string, auth string, language string) (*XPub, *string, error) {
   129  	// Generate a mnemonic for memorization or user-friendly seeds
   130  	entropy, err := mnem.NewEntropy(EntropyLength)
   131  	if err != nil {
   132  		return nil, nil, err
   133  	}
   134  	mnemonic, err := mnem.NewMnemonic(entropy, language)
   135  	if err != nil {
   136  		return nil, nil, err
   137  	}
   138  	xpub, err := h.createKeyFromMnemonic(alias, auth, mnemonic)
   139  	if err != nil {
   140  		return nil, nil, err
   141  	}
   142  	return xpub, &mnemonic, nil
   143  }
   144  
   145  // UpdateKeyAlias update key alias
   146  func (h *HSM) UpdateKeyAlias(xpub chainkd.XPub, newAlias string) error {
   147  	h.cacheMu.Lock()
   148  	defer h.cacheMu.Unlock()
   149  
   150  	h.cache.maybeReload()
   151  	h.cache.mu.Lock()
   152  	xpb, err := h.cache.find(XPub{XPub: xpub})
   153  	h.cache.mu.Unlock()
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	keyjson, err := ioutil.ReadFile(xpb.File)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	encrptKeyJSON := new(encryptedKeyJSON)
   164  	if err := json.Unmarshal(keyjson, encrptKeyJSON); err != nil {
   165  		return err
   166  	}
   167  
   168  	normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
   169  	if ok := h.cache.hasAlias(normalizedAlias); ok {
   170  		return ErrDuplicateKeyAlias
   171  	}
   172  
   173  	encrptKeyJSON.Alias = normalizedAlias
   174  	keyJSON, err := json.Marshal(encrptKeyJSON)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	if err := writeKeyFile(xpb.File, keyJSON); err != nil {
   180  		return err
   181  	}
   182  
   183  	// update key alias
   184  	h.cache.delete(xpb)
   185  	xpb.Alias = normalizedAlias
   186  	h.cache.add(xpb)
   187  
   188  	return nil
   189  }
   190  
   191  // ListKeys returns a list of all xpubs from the store
   192  func (h *HSM) ListKeys() []XPub {
   193  	xpubs := h.cache.keys()
   194  	return xpubs
   195  }
   196  
   197  // XSign looks up the xprv given the xpub, optionally derives a new
   198  // xprv with the given path (but does not store the new xprv), and
   199  // signs the given msg.
   200  func (h *HSM) XSign(xpub chainkd.XPub, path [][]byte, msg []byte, auth string) ([]byte, error) {
   201  	xprv, err := h.LoadChainKDKey(xpub, auth)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	if len(path) > 0 {
   206  		xprv = xprv.Derive(path)
   207  	}
   208  	return xprv.Sign(msg), nil
   209  }
   210  
   211  //LoadChainKDKey get xprv from xpub
   212  func (h *HSM) LoadChainKDKey(xpub chainkd.XPub, auth string) (xprv chainkd.XPrv, err error) {
   213  	h.cacheMu.Lock()
   214  	defer h.cacheMu.Unlock()
   215  
   216  	//if xprv, ok := h.kdCache[xpub]; ok {
   217  	//	return xprv, nil
   218  	//}
   219  
   220  	_, xkey, err := h.loadDecryptedKey(xpub, auth)
   221  	if err != nil {
   222  		return xprv, ErrLoadKey
   223  	}
   224  	//h.kdCache[xpb.XPub] = xkey.XPrv
   225  	return xkey.XPrv, nil
   226  }
   227  
   228  // XDelete deletes the key matched by xpub if the passphrase is correct.
   229  // If a contains no filename, the address must match a unique key.
   230  func (h *HSM) XDelete(xpub chainkd.XPub, auth string) error {
   231  	// Decrypting the key isn't really necessary, but we do
   232  	// it anyway to check the password and zero out the key
   233  	// immediately afterwards.
   234  
   235  	xpb, xkey, err := h.loadDecryptedKey(xpub, auth)
   236  	if xkey != nil {
   237  		zeroKey(xkey)
   238  	}
   239  	if err != nil {
   240  		return err
   241  	}
   242  
   243  	h.cacheMu.Lock()
   244  	// The order is crucial here. The key is dropped from the
   245  	// cache after the file is gone so that a reload happening in
   246  	// between won't insert it into the cache again.
   247  	err = os.Remove(xpb.File)
   248  	if err == nil {
   249  		h.cache.delete(xpb)
   250  	}
   251  	h.cacheMu.Unlock()
   252  	return err
   253  }
   254  
   255  func (h *HSM) loadDecryptedKey(xpub chainkd.XPub, auth string) (XPub, *XKey, error) {
   256  	h.cache.maybeReload()
   257  	h.cache.mu.Lock()
   258  	xpb, err := h.cache.find(XPub{XPub: xpub})
   259  
   260  	h.cache.mu.Unlock()
   261  	if err != nil {
   262  		return xpb, nil, err
   263  	}
   264  	xkey, err := h.keyStore.GetKey(xpb.Alias, xpb.File, auth)
   265  	return xpb, xkey, err
   266  }
   267  
   268  // ResetPassword reset passphrase for an existing xpub
   269  func (h *HSM) ResetPassword(xpub chainkd.XPub, oldAuth, newAuth string) error {
   270  	xpb, xkey, err := h.loadDecryptedKey(xpub, oldAuth)
   271  	if err != nil {
   272  		return err
   273  	}
   274  	return h.keyStore.StoreKey(xpb.File, xkey, newAuth)
   275  }
   276  
   277  // HasAlias check whether the key alias exists
   278  func (h *HSM) HasAlias(alias string) bool {
   279  	return h.cache.hasAlias(alias)
   280  }
   281  
   282  // HasKey check whether the private key exists
   283  func (h *HSM) HasKey(xprv chainkd.XPrv) bool {
   284  	return h.cache.hasKey(xprv.XPub())
   285  }