aboutsummaryrefslogtreecommitdiffstats
path: root/signer/storage/aes_gcm_storage.go
diff options
context:
space:
mode:
Diffstat (limited to 'signer/storage/aes_gcm_storage.go')
-rw-r--r--signer/storage/aes_gcm_storage.go164
1 files changed, 164 insertions, 0 deletions
diff --git a/signer/storage/aes_gcm_storage.go b/signer/storage/aes_gcm_storage.go
new file mode 100644
index 000000000..1ac347558
--- /dev/null
+++ b/signer/storage/aes_gcm_storage.go
@@ -0,0 +1,164 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
+//
+package storage
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "encoding/json"
+ "io"
+ "io/ioutil"
+ "os"
+
+ "github.com/ethereum/go-ethereum/log"
+)
+
+type storedCredential struct {
+ // The iv
+ Iv []byte `json:"iv"`
+ // The ciphertext
+ CipherText []byte `json:"c"`
+}
+
+// AESEncryptedStorage is a storage type which is backed by a json-faile. The json-file contains
+// key-value mappings, where the keys are _not_ encrypted, only the values are.
+type AESEncryptedStorage struct {
+ // File to read/write credentials
+ filename string
+ // Key stored in base64
+ key []byte
+}
+
+// NewAESEncryptedStorage creates a new encrypted storage backed by the given file/key
+func NewAESEncryptedStorage(filename string, key []byte) *AESEncryptedStorage {
+ return &AESEncryptedStorage{
+ filename: filename,
+ key: key,
+ }
+}
+
+// Put stores a value by key. 0-length keys results in no-op
+func (s *AESEncryptedStorage) Put(key, value string) {
+ if len(key) == 0 {
+ return
+ }
+ data, err := s.readEncryptedStorage()
+ if err != nil {
+ log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename)
+ return
+ }
+ ciphertext, iv, err := encrypt(s.key, []byte(value))
+ if err != nil {
+ log.Warn("Failed to encrypt entry", "err", err)
+ return
+ }
+ encrypted := storedCredential{Iv: iv, CipherText: ciphertext}
+ data[key] = encrypted
+ if err = s.writeEncryptedStorage(data); err != nil {
+ log.Warn("Failed to write entry", "err", err)
+ }
+}
+
+// Get returns the previously stored value, or the empty string if it does not exist or key is of 0-length
+func (s *AESEncryptedStorage) Get(key string) string {
+ if len(key) == 0 {
+ return ""
+ }
+ data, err := s.readEncryptedStorage()
+ if err != nil {
+ log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename)
+ return ""
+ }
+ encrypted, exist := data[key]
+ if !exist {
+ log.Warn("Key does not exist", "key", key)
+ return ""
+ }
+ entry, err := decrypt(s.key, encrypted.Iv, encrypted.CipherText)
+ if err != nil {
+ log.Warn("Failed to decrypt key", "key", key)
+ return ""
+ }
+ return string(entry)
+}
+
+// readEncryptedStorage reads the file with encrypted creds
+func (s *AESEncryptedStorage) readEncryptedStorage() (map[string]storedCredential, error) {
+ creds := make(map[string]storedCredential)
+ raw, err := ioutil.ReadFile(s.filename)
+
+ if err != nil {
+ if os.IsNotExist(err) {
+ // Doesn't exist yet
+ return creds, nil
+
+ } else {
+ log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename)
+ }
+ }
+ if err = json.Unmarshal(raw, &creds); err != nil {
+ log.Warn("Failed to unmarshal encrypted storage", "err", err, "file", s.filename)
+ return nil, err
+ }
+ return creds, nil
+}
+
+// writeEncryptedStorage write the file with encrypted creds
+func (s *AESEncryptedStorage) writeEncryptedStorage(creds map[string]storedCredential) error {
+ raw, err := json.Marshal(creds)
+ if err != nil {
+ return err
+ }
+ if err = ioutil.WriteFile(s.filename, raw, 0600); err != nil {
+ return err
+ }
+ return nil
+}
+
+func encrypt(key []byte, plaintext []byte) ([]byte, []byte, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, nil, err
+ }
+ aesgcm, err := cipher.NewGCM(block)
+ nonce := make([]byte, aesgcm.NonceSize())
+ if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
+ return nil, nil, err
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+ ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil)
+ return ciphertext, nonce, nil
+}
+
+func decrypt(key []byte, nonce []byte, ciphertext []byte) ([]byte, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+ aesgcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return nil, err
+ }
+ plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
+ if err != nil {
+ return nil, err
+ }
+ return plaintext, nil
+}