OSDN Git Service

96cba3d359184d9c4a5bdec36a0d528514883dcc
[bytom/bytom.git] / blockchain / pseudohsm / pseudohsm.go
1 // Package pseudohsm provides a pseudo HSM for development environments.
2 package pseudohsm
3
4 import (
5         "os"
6         "path/filepath"
7         "strconv"
8         "sync"
9
10         "github.com/bytom/crypto/ed25519/chainkd"
11         "github.com/bytom/errors"
12         "github.com/pborman/uuid"
13 )
14
15 // listKeyMaxAliases limits the alias filter to a sane maximum size.
16 const listKeyMaxAliases = 200
17
18 var (
19         ErrDuplicateKeyAlias    = errors.New("duplicate key alias")
20         ErrInvalidAfter         = errors.New("invalid after")
21         ErrNoKey                = errors.New("key not found")
22         ErrInvalidKeySize       = errors.New("key invalid size")
23         ErrTooManyAliasesToList = errors.New("requested aliases exceeds limit")
24         ErrAmbiguousAlias       = errors.New("multiple keys match alias")
25         ErrDecrypt              = errors.New("could not decrypt key with given passphrase")
26         ErrInvalidKeyType       = errors.New("key type stored invalid")
27 )
28
29 // HSM type for storing pubkey and privatekey
30 type HSM struct {
31         cacheMu  sync.Mutex
32         keyStore keyStore
33         cache    *keyCache
34         kdCache  map[chainkd.XPub]chainkd.XPrv
35 }
36
37 // XPub type for pubkey for anyone can see
38 type XPub struct {
39         Alias string       `json:"alias"`
40         XPub  chainkd.XPub `json:"xpub"`
41         File  string       `json:"file"`
42 }
43
44 // New method for HSM struct
45 func New(keypath string) (*HSM, error) {
46         keydir, _ := filepath.Abs(keypath)
47         return &HSM{
48                 keyStore: &keyStorePassphrase{keydir, LightScryptN, LightScryptP},
49                 cache:    newKeyCache(keydir),
50                 kdCache:  make(map[chainkd.XPub]chainkd.XPrv),
51         }, nil
52 }
53
54 // XCreate produces a new random xprv and stores it in the db.
55 func (h *HSM) XCreate(alias string, auth string) (*XPub, error) {
56         if ok := h.cache.hasAlias(alias); ok {
57                 return nil, ErrDuplicateKeyAlias
58         }
59         xpub, _, err := h.createChainKDKey(auth, alias, false)
60         if err != nil {
61                 return nil, err
62         }
63         h.cache.add(*xpub)
64         return xpub, err
65 }
66
67 func (h *HSM) createChainKDKey(auth string, alias string, get bool) (*XPub, bool, error) {
68         xprv, xpub, err := chainkd.NewXKeys(nil)
69         if err != nil {
70                 return nil, false, err
71         }
72         id := uuid.NewRandom()
73         key := &XKey{
74                 ID:      id,
75                 KeyType: "bytom_kd",
76                 XPub:    xpub,
77                 XPrv:    xprv,
78                 Alias:   alias,
79         }
80         file := h.keyStore.JoinPath(keyFileName(key.ID.String()))
81         if err := h.keyStore.StoreKey(file, key, auth); err != nil {
82                 return nil, false, errors.Wrap(err, "storing keys")
83         }
84         return &XPub{XPub: xpub, Alias: alias, File: file}, true, nil
85 }
86
87 // ListKeys returns a list of all xpubs from the store
88 func (h *HSM) ListKeys(after string, limit int) ([]XPub, string, error) {
89
90         xpubs := h.cache.keys()
91         start, end := 0, len(xpubs)
92
93         var (
94                 zafter int
95                 err    error
96         )
97
98         if after != "" {
99                 zafter, err = strconv.Atoi(after)
100                 if err != nil {
101                         return nil, "", errors.WithDetailf(ErrInvalidAfter, "value: %q", zafter)
102                 }
103         }
104
105         if len(xpubs) > zafter {
106                 start = zafter
107         } else {
108                 return nil, "", errors.WithDetailf(ErrInvalidAfter, "value: %v", zafter)
109         }
110         if len(xpubs) > zafter+limit {
111                 end = zafter + limit
112         }
113         return xpubs[start:end], strconv.Itoa(start), nil
114 }
115
116 // XSign looks up the xprv given the xpub, optionally derives a new
117 // xprv with the given path (but does not store the new xprv), and
118 // signs the given msg.
119 func (h *HSM) XSign(xpub chainkd.XPub, path [][]byte, msg []byte, auth string) ([]byte, error) {
120         xprv, err := h.loadChainKDKey(xpub, auth)
121         if err != nil {
122                 return nil, err
123         }
124         if len(path) > 0 {
125                 xprv = xprv.Derive(path)
126         }
127         return xprv.Sign(msg), nil
128 }
129
130 func (h *HSM) loadChainKDKey(xpub chainkd.XPub, auth string) (xprv chainkd.XPrv, err error) {
131         h.cacheMu.Lock()
132         defer h.cacheMu.Unlock()
133
134         if xprv, ok := h.kdCache[xpub]; ok {
135                 return xprv, nil
136         }
137
138         xpb, xkey, err := h.loadDecryptedKey(xpub, auth)
139         if err != nil {
140                 return xprv, ErrNoKey
141         }
142         h.kdCache[xpb.XPub] = xkey.XPrv
143         return xkey.XPrv, nil
144 }
145
146 // XDelete deletes the key matched by xpub if the passphrase is correct.
147 // If a contains no filename, the address must match a unique key.
148 func (h *HSM) XDelete(xpub chainkd.XPub, auth string) error {
149         // Decrypting the key isn't really necessary, but we do
150         // it anyway to check the password and zero out the key
151         // immediately afterwards.
152
153         xpb, xkey, err := h.loadDecryptedKey(xpub, auth)
154         if xkey != nil {
155                 zeroKey(xkey)
156         }
157         if err != nil {
158                 return err
159         }
160
161         // The order is crucial here. The key is dropped from the
162         // cache after the file is gone so that a reload happening in
163         // between won't insert it into the cache again.
164         err = os.Remove(xpb.File)
165         if err == nil {
166                 h.cache.delete(xpb)
167         }
168         h.cacheMu.Lock()
169         delete(h.kdCache, xpub)
170         h.cacheMu.Unlock()
171         return err
172 }
173
174 func (h *HSM) loadDecryptedKey(xpub chainkd.XPub, auth string) (XPub, *XKey, error) {
175         h.cache.maybeReload()
176         h.cache.mu.Lock()
177         xpb, err := h.cache.find(XPub{XPub: xpub})
178
179         h.cache.mu.Unlock()
180         if err != nil {
181                 return xpb, nil, err
182         }
183         xkey, err := h.keyStore.GetKey(xpb.Alias, xpb.File, auth)
184
185         return xpb, xkey, err
186 }
187
188 // ResetPassword the passphrase of an existing xpub
189 func (h *HSM) ResetPassword(xpub chainkd.XPub, auth, newAuth string) error {
190         xpb, xkey, err := h.loadDecryptedKey(xpub, auth)
191         if err != nil {
192                 return err
193         }
194         return h.keyStore.StoreKey(xpb.File, xkey, newAuth)
195 }