github.com/oskarth/go-ethereum@v1.6.8-0.20191013093314-dac24a9d3494/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 no-op
    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 the empty string if it does not exist or key is of 0-length
    79  func (s *AESEncryptedStorage) Get(key string) string {
    80  	if len(key) == 0 {
    81  		return ""
    82  	}
    83  	data, err := s.readEncryptedStorage()
    84  	if err != nil {
    85  		log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename)
    86  		return ""
    87  	}
    88  	encrypted, exist := data[key]
    89  	if !exist {
    90  		log.Warn("Key does not exist", "key", key)
    91  		return ""
    92  	}
    93  	entry, err := decrypt(s.key, encrypted.Iv, encrypted.CipherText, []byte(key))
    94  	if err != nil {
    95  		log.Warn("Failed to decrypt key", "key", key)
    96  		return ""
    97  	}
    98  	return string(entry)
    99  }
   100  
   101  // readEncryptedStorage reads the file with encrypted creds
   102  func (s *AESEncryptedStorage) readEncryptedStorage() (map[string]storedCredential, error) {
   103  	creds := make(map[string]storedCredential)
   104  	raw, err := ioutil.ReadFile(s.filename)
   105  
   106  	if err != nil {
   107  		if os.IsNotExist(err) {
   108  			// Doesn't exist yet
   109  			return creds, nil
   110  		}
   111  		log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename)
   112  	}
   113  	if err = json.Unmarshal(raw, &creds); err != nil {
   114  		log.Warn("Failed to unmarshal encrypted storage", "err", err, "file", s.filename)
   115  		return nil, err
   116  	}
   117  	return creds, nil
   118  }
   119  
   120  // writeEncryptedStorage write the file with encrypted creds
   121  func (s *AESEncryptedStorage) writeEncryptedStorage(creds map[string]storedCredential) error {
   122  	raw, err := json.Marshal(creds)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	if err = ioutil.WriteFile(s.filename, raw, 0600); err != nil {
   127  		return err
   128  	}
   129  	return nil
   130  }
   131  
   132  // encrypt encrypts plaintext with the given key, with additional data
   133  // The 'additionalData' is used to place the (plaintext) KV-store key into the V,
   134  // to prevent the possibility to alter a K, or swap two entries in the KV store with eachother.
   135  func encrypt(key []byte, plaintext []byte, additionalData []byte) ([]byte, []byte, error) {
   136  	block, err := aes.NewCipher(key)
   137  	if err != nil {
   138  		return nil, nil, err
   139  	}
   140  	aesgcm, err := cipher.NewGCM(block)
   141  	nonce := make([]byte, aesgcm.NonceSize())
   142  	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
   143  		return nil, nil, err
   144  	}
   145  	if err != nil {
   146  		return nil, nil, err
   147  	}
   148  	ciphertext := aesgcm.Seal(nil, nonce, plaintext, additionalData)
   149  	return ciphertext, nonce, nil
   150  }
   151  
   152  func decrypt(key []byte, nonce []byte, ciphertext []byte, additionalData []byte) ([]byte, error) {
   153  	block, err := aes.NewCipher(key)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	aesgcm, err := cipher.NewGCM(block)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	plaintext, err := aesgcm.Open(nil, nonce, ciphertext, additionalData)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	return plaintext, nil
   166  }