From bee2249e21279b3f2abd173933986f153da6e25b Mon Sep 17 00:00:00 2001 From: Huiyi Li Date: Fri, 24 Jan 2020 22:53:43 -0800 Subject: [PATCH] signer/storage creaate new db_storage and related tests --- go.mod | 3 + go.sum | 6 ++ signer/storage/db_storage.go | 173 ++++++++++++++++++++++++++++++ signer/storage/db_storage_test.go | 72 +++++++++++++ 4 files changed, 254 insertions(+) create mode 100644 signer/storage/db_storage.go create mode 100644 signer/storage/db_storage_test.go diff --git a/go.mod b/go.mod index 223086f8c5..f8ddf5709e 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff github.com/go-ole/go-ole v1.2.1 // indirect + github.com/go-sql-driver/mysql v1.5.0 github.com/go-stack/stack v1.8.0 github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c github.com/golang/snappy v0.0.1 @@ -37,8 +38,10 @@ require ( github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 github.com/kr/pretty v0.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lib/pq v1.3.0 github.com/mattn/go-colorable v0.1.0 github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 + github.com/mattn/go-sqlite3 v2.0.2+incompatible github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c diff --git a/go.sum b/go.sum index 4d18c8c20e..a9a82cae49 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2i github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -116,6 +118,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= @@ -126,6 +130,8 @@ github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXT github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U= +github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= diff --git a/signer/storage/db_storage.go b/signer/storage/db_storage.go new file mode 100644 index 0000000000..e7a679418d --- /dev/null +++ b/signer/storage/db_storage.go @@ -0,0 +1,173 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package storage + +import ( + "database/sql" + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/log" + + // here we are adding multiple default supported db drivers + _ "github.com/go-sql-driver/mysql" + _ "github.com/lib/pq" +) + +// DBStorage is a storage type which is backed by a general purpose database +type DBStorage struct { + driverName string + dataSourceName string + db *sql.DB + key []byte +} + +// KPS is the structure to hold a row of our credentials database +type KPS struct { + id int + address string + json string +} + +// NewDBStorage create new database backed storage +func NewDBStorage(key []byte, driverName, dataSourceName string) (*DBStorage, error) { + // sql.Open only validates the input, but didn't create a connection + db, err := sql.Open(driverName, dataSourceName) + if err != nil { + log.Error("failed to validate driver: #{driverName}, #{dataSourceName}") + db.Close() + return nil, err + } + + // Connects to the database and make sure it is ok, connection will be closed shortly since default MaxIdle is 0 + err = db.Ping() + if err != nil { + log.Error("failed to connect to database: #{dataSourceName}") + db.Close() + return nil, err + } + + // set connection limits + db.SetMaxOpenConns(5) + + return &DBStorage{ + driverName: driverName, + dataSourceName: dataSourceName, + db: db, + key: key, + }, nil +} + +// Put stores a value by key. 0-length keys results in noop. +func (s *DBStorage) Put(key, value string) { + if len(key) == 0 { + return + } + ciphertext, iv, err := Encrypt(s.key, []byte(value), []byte(key)) + if err != nil { + log.Warn("Failed to encrypt entry", "err", err) + return + } + + creds := StoredCredential{Iv: iv, CipherText: ciphertext} + _, exist, err := s.queryRow("SELECT * FROM kps WHERE address = ?", key) + if err != nil { + log.Warn("Failed to execute SQL", "err", err) + return + } + + raw, err := json.Marshal(creds) + if err != nil { + log.Warn("Failed to marshal StoredCredential data") + return + } + + if !exist { + s.exec("INSERT INTO kps (address, json) VALUES (?, ?)", key, raw) + } else { + s.exec("UPDATE kps SET json = ? WHERE address = ?", raw, key) + } +} + +// Get returns the previously stored value, or an error if it does not exist or +// key is of 0-length. +func (s *DBStorage) Get(key string) (string, error) { + kps, exist, err := s.queryRow("SELECT * FROM kps WHERE address = ?", key) + if err != nil { + log.Warn("Failed to execute SQL", "err", err) + return "", err + } + if !exist { + log.Warn("Key does not exist", "key", key) + return "", ErrNotFound + } + + cred := StoredCredential{} + if err = json.Unmarshal([]byte(kps.json), &cred); err != nil { + log.Warn("Failed to unmarshall stored json", "err", err) + return "", err + } + + entry, err := Decrypt(s.key, cred.Iv, cred.CipherText, []byte(key)) + if err != nil { + log.Warn("Failed to decrypt key", "key", key) + return "", err + } + + return string(entry), nil +} + +// Del removes a key-value pair. If the key doesn't exist, the method is a noop. +func (s *DBStorage) Del(key string) { + s.exec("DELETE FROM kps WHERE address = ?", key) +} + +func (s *DBStorage) exec(query string, args ...interface{}) { + _, err := s.db.Exec(query, args...) + if err != nil { + log.Warn("Failed to execute sql", query, args) + } +} + +func (s *DBStorage) queryRow(query string, args ...interface{}) (*KPS, bool, error) { + kps := KPS{} + err := s.db.QueryRow(query, args...).Scan(&kps.id, &kps.address, &kps.json) + if err != nil && err != sql.ErrNoRows { + return nil, false, err + } + + if kps.id == 0 { + return nil, false, nil + } + return &kps, true, nil +} + +// Close sql.DB +func (s *DBStorage) Close() { + s.db.Close() +} + +func main() { + // "root:900406.mysql@tcp(localhost:3306)/adv_database" + db, err := sql.Open("mysql", "server=localhost;user id=root;password=900406.mysql;port=3306;database=adv_database") + if err != nil { + fmt.Println("failure") + panic(err) + } + fmt.Println("success") + defer db.Close() +} diff --git a/signer/storage/db_storage_test.go b/signer/storage/db_storage_test.go new file mode 100644 index 0000000000..f62d4e49ab --- /dev/null +++ b/signer/storage/db_storage_test.go @@ -0,0 +1,72 @@ +package storage + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "testing" + + _ "github.com/mattn/go-sqlite3" +) + +var ( + ds *DBStorage + key string +) + +func init() { + key = "AES256Key-32Characters1234567890" + tmpDir, _ := ioutil.TempDir("", "eth-encrypted-db-storge-test") + fmt.Println(tmpDir) + ds, _ = NewDBStorage([]byte(key), "sqlite3", filepath.Join(tmpDir, "test.db")) + ds.exec("CREATE TABLE IF NOT EXISTS kps (id INTEGER PRIMARY KEY, address TEXT, json TEXT)") +} + +func TestDBStorage(t *testing.T) { + // test Put + k1, v1 := "k1", "v1" + ds.Put(k1, v1) + + // test Get + ret, err := ds.Get(k1) + if err != nil || ret != v1 { + t.Fatal("Get didn't return correct result") + } + + // test Put when there's duplicate + v2 := "v2" + ds.Put(k1, v2) + ret, err = ds.Get(k1) + if err != nil || ret != v2 { + t.Fatal("Get didn't return correct result") + } + + // test Del + ds.Del(k1) + ret, err = ds.Get(k1) + if err != ErrNotFound { + t.Fatal("Del didn't work as expected") + } +} + +func TestSwappedKeysForDBStorage(t *testing.T) { + ds.Put("k1", "v1") + ds.Put("k2", "v2") + + // now make a modified copy + swap := func() { + creds1, _, _ := ds.queryRow("SELECT * FROM kps WHERE address = 'k1'") + creds2, _, _ := ds.queryRow("SELECT * FROM kps WHERE address = 'k2'") + ds.exec("UPDATE kps SET json = ? WHERE address = ?", creds1.json, "k2") + ds.exec("UPDATE kps SET json = ? WHERE address = ?", creds2.json, "k1") + } + swap() + if v, _ := ds.Get("k1"); v != "" { + t.Errorf("swapped value should return empty") + } + swap() + if v, _ := ds.Get("k1"); v != "v1" { + t.Errorf(v) + t.Errorf("double-swapped value should work fine") + } +}