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