signer/storage abstract aes_gcm related funcs to separate file for db_storage impl

This commit is contained in:
Huiyi Li 2020-01-19 14:47:10 -08:00
parent b2c09a7e62
commit 1ffab783a5
4 changed files with 89 additions and 78 deletions

View file

@ -17,24 +17,13 @@
package storage package storage
import ( import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json" "encoding/json"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/ethereum/go-ethereum/log" "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-file. The json-file contains // AESEncryptedStorage is a storage type which is backed by a json-file. The json-file contains
// key-value mappings, where the keys are _not_ encrypted, only the values are. // key-value mappings, where the keys are _not_ encrypted, only the values are.
type AESEncryptedStorage struct { type AESEncryptedStorage struct {
@ -62,12 +51,12 @@ func (s *AESEncryptedStorage) Put(key, value string) {
log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename)
return return
} }
ciphertext, iv, err := encrypt(s.key, []byte(value), []byte(key)) ciphertext, iv, err := Encrypt(s.key, []byte(value), []byte(key))
if err != nil { if err != nil {
log.Warn("Failed to encrypt entry", "err", err) log.Warn("Failed to encrypt entry", "err", err)
return return
} }
encrypted := storedCredential{Iv: iv, CipherText: ciphertext} encrypted := StoredCredential{Iv: iv, CipherText: ciphertext}
data[key] = encrypted data[key] = encrypted
if err = s.writeEncryptedStorage(data); err != nil { if err = s.writeEncryptedStorage(data); err != nil {
log.Warn("Failed to write entry", "err", err) log.Warn("Failed to write entry", "err", err)
@ -90,7 +79,7 @@ func (s *AESEncryptedStorage) Get(key string) (string, error) {
log.Warn("Key does not exist", "key", key) log.Warn("Key does not exist", "key", key)
return "", ErrNotFound return "", ErrNotFound
} }
entry, err := decrypt(s.key, encrypted.Iv, encrypted.CipherText, []byte(key)) entry, err := Decrypt(s.key, encrypted.Iv, encrypted.CipherText, []byte(key))
if err != nil { if err != nil {
log.Warn("Failed to decrypt key", "key", key) log.Warn("Failed to decrypt key", "key", key)
return "", err return "", err
@ -112,8 +101,8 @@ func (s *AESEncryptedStorage) Del(key string) {
} }
// readEncryptedStorage reads the file with encrypted creds // readEncryptedStorage reads the file with encrypted creds
func (s *AESEncryptedStorage) readEncryptedStorage() (map[string]storedCredential, error) { func (s *AESEncryptedStorage) readEncryptedStorage() (map[string]StoredCredential, error) {
creds := make(map[string]storedCredential) creds := make(map[string]StoredCredential)
raw, err := ioutil.ReadFile(s.filename) raw, err := ioutil.ReadFile(s.filename)
if err != nil { if err != nil {
@ -131,7 +120,7 @@ func (s *AESEncryptedStorage) readEncryptedStorage() (map[string]storedCredentia
} }
// writeEncryptedStorage write the file with encrypted creds // writeEncryptedStorage write the file with encrypted creds
func (s *AESEncryptedStorage) writeEncryptedStorage(creds map[string]storedCredential) error { func (s *AESEncryptedStorage) writeEncryptedStorage(creds map[string]StoredCredential) error {
raw, err := json.Marshal(creds) raw, err := json.Marshal(creds)
if err != nil { if err != nil {
return err return err
@ -141,39 +130,3 @@ func (s *AESEncryptedStorage) writeEncryptedStorage(creds map[string]storedCrede
} }
return nil return nil
} }
// encrypt encrypts plaintext with the given key, with additional data
// The 'additionalData' is used to place the (plaintext) KV-store key into the V,
// to prevent the possibility to alter a K, or swap two entries in the KV store with eachother.
func encrypt(key []byte, plaintext []byte, additionalData []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, additionalData)
return ciphertext, nonce, nil
}
func decrypt(key []byte, nonce []byte, ciphertext []byte, additionalData []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, additionalData)
if err != nil {
return nil, err
}
return plaintext, nil
}

View file

@ -28,31 +28,8 @@ import (
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
) )
func TestEncryption(t *testing.T) {
// key := []byte("AES256Key-32Characters1234567890")
// plaintext := []byte(value)
key := []byte("AES256Key-32Characters1234567890")
plaintext := []byte("exampleplaintext")
c, iv, err := encrypt(key, plaintext, nil)
if err != nil {
t.Fatal(err)
}
t.Logf("Ciphertext %x, nonce %x\n", c, iv)
p, err := decrypt(key, iv, c, nil)
if err != nil {
t.Fatal(err)
}
t.Logf("Plaintext %v\n", string(p))
if !bytes.Equal(plaintext, p) {
t.Errorf("Failed: expected plaintext recovery, got %v expected %v", string(plaintext), string(p))
}
}
func TestFileStorage(t *testing.T) { func TestFileStorage(t *testing.T) {
a := map[string]StoredCredential{
a := map[string]storedCredential{
"secret": { "secret": {
Iv: common.Hex2Bytes("cdb30036279601aeee60f16b"), Iv: common.Hex2Bytes("cdb30036279601aeee60f16b"),
CipherText: common.Hex2Bytes("f311ac49859d7260c2c464c28ffac122daf6be801d3cfd3edcbde7e00c9ff74f"), CipherText: common.Hex2Bytes("f311ac49859d7260c2c464c28ffac122daf6be801d3cfd3edcbde7e00c9ff74f"),
@ -133,7 +110,7 @@ func TestSwappedKeys(t *testing.T) {
s1.Put("k2", "v2") s1.Put("k2", "v2")
// Now make a modified copy // Now make a modified copy
creds := make(map[string]storedCredential) creds := make(map[string]StoredCredential)
raw, err := ioutil.ReadFile(s1.filename) raw, err := ioutil.ReadFile(s1.filename)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View file

@ -0,0 +1,53 @@
package storage
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"io"
)
// StoredCredential stores the json structure of the stored credential
type StoredCredential struct {
// The iv
Iv []byte `json:"iv"`
// The ciphertext
CipherText []byte `json:"c"`
}
// Encrypt encrypts plaintext with the given key, with additional data
// The 'additionalData' is used to place the (plaintext) KV-store key into the V,
// to prevent the possibility to alter a K, or swap two entries in the KV store with eachother.
func Encrypt(key []byte, plaintext []byte, additionalData []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, additionalData)
return ciphertext, nonce, nil
}
// Decrypt decrypts plaintext from given key, nounce, ciphertext and additionalData
func Decrypt(key []byte, nonce []byte, ciphertext []byte, additionalData []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, additionalData)
if err != nil {
return nil, err
}
return plaintext, nil
}

View file

@ -0,0 +1,28 @@
package storage
import (
"bytes"
"testing"
)
func TestEncryption(t *testing.T) {
// key := []byte("AES256Key-32Characters1234567890")
// plaintext := []byte(value)
key := []byte("AES256Key-32Characters1234567890")
plaintext := []byte("exampleplaintext")
c, iv, err := Encrypt(key, plaintext, nil)
if err != nil {
t.Fatal(err)
}
t.Logf("Ciphertext %x, nonce %x\n", c, iv)
p, err := Decrypt(key, iv, c, nil)
if err != nil {
t.Fatal(err)
}
t.Logf("Plaintext %v\n", string(p))
if !bytes.Equal(plaintext, p) {
t.Errorf("Failed: expected plaintext recovery, got %v expected %v", string(plaintext), string(p))
}
}