github.com/lsdbitrue/go-ethereum@v1.9.0/signer/storage/aes_gcm_storage.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 // 17 18 package storage 19 20 import ( 21 "crypto/aes" 22 "crypto/cipher" 23 "crypto/rand" 24 "encoding/json" 25 "io" 26 "io/ioutil" 27 "os" 28 29 "github.com/ethereum/go-ethereum/log" 30 ) 31 32 type storedCredential struct { 33 // The iv 34 Iv []byte `json:"iv"` 35 // The ciphertext 36 CipherText []byte `json:"c"` 37 } 38 39 // AESEncryptedStorage is a storage type which is backed by a json-file. The json-file contains 40 // key-value mappings, where the keys are _not_ encrypted, only the values are. 41 type AESEncryptedStorage struct { 42 // File to read/write credentials 43 filename string 44 // Key stored in base64 45 key []byte 46 } 47 48 // NewAESEncryptedStorage creates a new encrypted storage backed by the given file/key 49 func NewAESEncryptedStorage(filename string, key []byte) *AESEncryptedStorage { 50 return &AESEncryptedStorage{ 51 filename: filename, 52 key: key, 53 } 54 } 55 56 // Put stores a value by key. 0-length keys results in noop. 57 func (s *AESEncryptedStorage) Put(key, value string) { 58 if len(key) == 0 { 59 return 60 } 61 data, err := s.readEncryptedStorage() 62 if err != nil { 63 log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) 64 return 65 } 66 ciphertext, iv, err := encrypt(s.key, []byte(value), []byte(key)) 67 if err != nil { 68 log.Warn("Failed to encrypt entry", "err", err) 69 return 70 } 71 encrypted := storedCredential{Iv: iv, CipherText: ciphertext} 72 data[key] = encrypted 73 if err = s.writeEncryptedStorage(data); err != nil { 74 log.Warn("Failed to write entry", "err", err) 75 } 76 } 77 78 // Get returns the previously stored value, or an error if it does not exist or 79 // key is of 0-length. 80 func (s *AESEncryptedStorage) Get(key string) (string, error) { 81 if len(key) == 0 { 82 return "", ErrZeroKey 83 } 84 data, err := s.readEncryptedStorage() 85 if err != nil { 86 log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) 87 return "", err 88 } 89 encrypted, exist := data[key] 90 if !exist { 91 log.Warn("Key does not exist", "key", key) 92 return "", ErrNotFound 93 } 94 entry, err := decrypt(s.key, encrypted.Iv, encrypted.CipherText, []byte(key)) 95 if err != nil { 96 log.Warn("Failed to decrypt key", "key", key) 97 return "", err 98 } 99 return string(entry), nil 100 } 101 102 // Del removes a key-value pair. If the key doesn't exist, the method is a noop. 103 func (s *AESEncryptedStorage) Del(key string) { 104 data, err := s.readEncryptedStorage() 105 if err != nil { 106 log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) 107 return 108 } 109 delete(data, key) 110 if err = s.writeEncryptedStorage(data); err != nil { 111 log.Warn("Failed to write entry", "err", err) 112 } 113 } 114 115 // readEncryptedStorage reads the file with encrypted creds 116 func (s *AESEncryptedStorage) readEncryptedStorage() (map[string]storedCredential, error) { 117 creds := make(map[string]storedCredential) 118 raw, err := ioutil.ReadFile(s.filename) 119 120 if err != nil { 121 if os.IsNotExist(err) { 122 // Doesn't exist yet 123 return creds, nil 124 } 125 log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) 126 } 127 if err = json.Unmarshal(raw, &creds); err != nil { 128 log.Warn("Failed to unmarshal encrypted storage", "err", err, "file", s.filename) 129 return nil, err 130 } 131 return creds, nil 132 } 133 134 // writeEncryptedStorage write the file with encrypted creds 135 func (s *AESEncryptedStorage) writeEncryptedStorage(creds map[string]storedCredential) error { 136 raw, err := json.Marshal(creds) 137 if err != nil { 138 return err 139 } 140 if err = ioutil.WriteFile(s.filename, raw, 0600); err != nil { 141 return err 142 } 143 return nil 144 } 145 146 // encrypt encrypts plaintext with the given key, with additional data 147 // The 'additionalData' is used to place the (plaintext) KV-store key into the V, 148 // to prevent the possibility to alter a K, or swap two entries in the KV store with eachother. 149 func encrypt(key []byte, plaintext []byte, additionalData []byte) ([]byte, []byte, error) { 150 block, err := aes.NewCipher(key) 151 if err != nil { 152 return nil, nil, err 153 } 154 aesgcm, err := cipher.NewGCM(block) 155 nonce := make([]byte, aesgcm.NonceSize()) 156 if _, err := io.ReadFull(rand.Reader, nonce); err != nil { 157 return nil, nil, err 158 } 159 if err != nil { 160 return nil, nil, err 161 } 162 ciphertext := aesgcm.Seal(nil, nonce, plaintext, additionalData) 163 return ciphertext, nonce, nil 164 } 165 166 func decrypt(key []byte, nonce []byte, ciphertext []byte, additionalData []byte) ([]byte, error) { 167 block, err := aes.NewCipher(key) 168 if err != nil { 169 return nil, err 170 } 171 aesgcm, err := cipher.NewGCM(block) 172 if err != nil { 173 return nil, err 174 } 175 plaintext, err := aesgcm.Open(nil, nonce, ciphertext, additionalData) 176 if err != nil { 177 return nil, err 178 } 179 return plaintext, nil 180 }