github.com/ethxdao/go-ethereum@v0.0.0-20221218102228-5ae34a9cc189/signer/storage/aes_gcm_storage.go (about)

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