github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/accounts/keystore/key.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 package keystore 13 14 import ( 15 "bytes" 16 "crypto/ecdsa" 17 "encoding/hex" 18 "encoding/json" 19 "fmt" 20 "io" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "strings" 25 "time" 26 27 "github.com/Sberex/go-sberex/accounts" 28 "github.com/Sberex/go-sberex/common" 29 "github.com/Sberex/go-sberex/crypto" 30 "github.com/pborman/uuid" 31 ) 32 33 const ( 34 version = 3 35 ) 36 37 type Key struct { 38 Id uuid.UUID // Version 4 "random" for unique id not derived from key data 39 // to simplify lookups we also store the address 40 Address common.Address 41 // we only store privkey as pubkey/address can be derived from it 42 // privkey in this struct is always in plaintext 43 PrivateKey *ecdsa.PrivateKey 44 } 45 46 type keyStore interface { 47 // Loads and decrypts the key from disk. 48 GetKey(addr common.Address, filename string, auth string) (*Key, error) 49 // Writes and encrypts the key. 50 StoreKey(filename string, k *Key, auth string) error 51 // Joins filename with the key directory unless it is already absolute. 52 JoinPath(filename string) string 53 } 54 55 type plainKeyJSON struct { 56 Address string `json:"address"` 57 PrivateKey string `json:"privatekey"` 58 Id string `json:"id"` 59 Version int `json:"version"` 60 } 61 62 type encryptedKeyJSONV3 struct { 63 Address string `json:"address"` 64 Crypto cryptoJSON `json:"crypto"` 65 Id string `json:"id"` 66 Version int `json:"version"` 67 } 68 69 type encryptedKeyJSONV1 struct { 70 Address string `json:"address"` 71 Crypto cryptoJSON `json:"crypto"` 72 Id string `json:"id"` 73 Version string `json:"version"` 74 } 75 76 type cryptoJSON struct { 77 Cipher string `json:"cipher"` 78 CipherText string `json:"ciphertext"` 79 CipherParams cipherparamsJSON `json:"cipherparams"` 80 KDF string `json:"kdf"` 81 KDFParams map[string]interface{} `json:"kdfparams"` 82 MAC string `json:"mac"` 83 } 84 85 type cipherparamsJSON struct { 86 IV string `json:"iv"` 87 } 88 89 func (k *Key) MarshalJSON() (j []byte, err error) { 90 jStruct := plainKeyJSON{ 91 hex.EncodeToString(k.Address[:]), 92 hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)), 93 k.Id.String(), 94 version, 95 } 96 j, err = json.Marshal(jStruct) 97 return j, err 98 } 99 100 func (k *Key) UnmarshalJSON(j []byte) (err error) { 101 keyJSON := new(plainKeyJSON) 102 err = json.Unmarshal(j, &keyJSON) 103 if err != nil { 104 return err 105 } 106 107 u := new(uuid.UUID) 108 *u = uuid.Parse(keyJSON.Id) 109 k.Id = *u 110 addr, err := hex.DecodeString(keyJSON.Address) 111 if err != nil { 112 return err 113 } 114 privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey) 115 if err != nil { 116 return err 117 } 118 119 k.Address = common.BytesToAddress(addr) 120 k.PrivateKey = privkey 121 122 return nil 123 } 124 125 func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { 126 id := uuid.NewRandom() 127 key := &Key{ 128 Id: id, 129 Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), 130 PrivateKey: privateKeyECDSA, 131 } 132 return key 133 } 134 135 // NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit 136 // into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we 137 // retry until the first byte is 0. 138 func NewKeyForDirectICAP(rand io.Reader) *Key { 139 randBytes := make([]byte, 64) 140 _, err := rand.Read(randBytes) 141 if err != nil { 142 panic("key generation: could not read from random source: " + err.Error()) 143 } 144 reader := bytes.NewReader(randBytes) 145 privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader) 146 if err != nil { 147 panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) 148 } 149 key := newKeyFromECDSA(privateKeyECDSA) 150 if !strings.HasPrefix(key.Address.Hex(), "0x00") { 151 return NewKeyForDirectICAP(rand) 152 } 153 return key 154 } 155 156 func newKey(rand io.Reader) (*Key, error) { 157 privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand) 158 if err != nil { 159 return nil, err 160 } 161 return newKeyFromECDSA(privateKeyECDSA), nil 162 } 163 164 func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) { 165 key, err := newKey(rand) 166 if err != nil { 167 return nil, accounts.Account{}, err 168 } 169 a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}} 170 if err := ks.StoreKey(a.URL.Path, key, auth); err != nil { 171 zeroKey(key.PrivateKey) 172 return nil, a, err 173 } 174 return key, a, err 175 } 176 177 func writeKeyFile(file string, content []byte) error { 178 // Create the keystore directory with appropriate permissions 179 // in case it is not present yet. 180 const dirPerm = 0700 181 if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil { 182 return err 183 } 184 // Atomic write: create a temporary hidden file first 185 // then move it into place. TempFile assigns mode 0600. 186 f, err := ioutil.TempFile(filepath.Dir(file), "."+filepath.Base(file)+".tmp") 187 if err != nil { 188 return err 189 } 190 if _, err := f.Write(content); err != nil { 191 f.Close() 192 os.Remove(f.Name()) 193 return err 194 } 195 f.Close() 196 return os.Rename(f.Name(), file) 197 } 198 199 // keyFileName implements the naming convention for keyfiles: 200 // UTC--<created_at UTC ISO8601>-<address hex> 201 func keyFileName(keyAddr common.Address) string { 202 ts := time.Now().UTC() 203 return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:])) 204 } 205 206 func toISO8601(t time.Time) string { 207 var tz string 208 name, offset := t.Zone() 209 if name == "UTC" { 210 tz = "Z" 211 } else { 212 tz = fmt.Sprintf("%03d00", offset/3600) 213 } 214 return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz) 215 }