signer/core,storage, cmd/clef/dbutil finish rework storages for clef

This commit is contained in:
Huiyi Li 2020-01-31 20:38:13 -08:00
parent 17c81b5e64
commit 2f6298765d
10 changed files with 87 additions and 111 deletions

View file

@ -15,13 +15,6 @@ import (
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
// Configuration database default table names
const (
PasswordTable = "kps"
ConfigTable = "config"
JsTable = "js"
)
// Keystore database default table names // Keystore database default table names
const ( const (
AccountTable = "accounts" AccountTable = "accounts"
@ -148,15 +141,15 @@ func (kvstore *KVStore) Get(key string) (string, error) {
} }
// Put stores a value by key. 0-length keys results in noop. // Put stores a value by key. 0-length keys results in noop.
func (kvstore *KVStore) Put(key, value string) { func (kvstore *KVStore) Put(key, value string) error {
if len(key) == 0 { if len(key) == 0 {
return return errors.New("0-length key")
} }
_, err := kvstore.Get(key) _, err := kvstore.Get(key)
if err != nil || err == sql.ErrNoRows { if err != nil || err == sql.ErrNoRows {
kvstore.insertRow(key, value) return kvstore.insertRow(key, value)
} else { } else {
kvstore.updateRow(key, value) return kvstore.updateRow(key, value)
} }
} }
@ -199,14 +192,14 @@ func (kvstore *KVStore) All() []string {
return result return result
} }
func (kvstore *KVStore) insertRow(key, value string) { func (kvstore *KVStore) insertRow(key, value string) error {
sql := kvstore.adjustSQLPlaceholder(insertSQL) sql := kvstore.adjustSQLPlaceholder(insertSQL)
kvstore.exec(sql, key, value) return kvstore.exec(sql, key, value)
} }
func (kvstore *KVStore) updateRow(key, value string) { func (kvstore *KVStore) updateRow(key, value string) error {
sql := kvstore.adjustSQLPlaceholder(updateSQL) sql := kvstore.adjustSQLPlaceholder(updateSQL)
kvstore.exec(sql, value, key) return kvstore.exec(sql, value, key)
} }
func (kvstore *KVStore) adjustSQLPlaceholder(sql string) string { func (kvstore *KVStore) adjustSQLPlaceholder(sql string) string {

View file

@ -55,7 +55,7 @@ func testPQConfig(t *testing.T, path string) {
} }
func TestKVStoreOperations(t *testing.T) { func TestKVStoreOperations(t *testing.T) {
kvstore, err := NewKVStore("./dbutil_test_sqlite3.yaml", PasswordTable) kvstore, err := NewKVStore("./dbutil_test_sqlite3.yaml", "testTable")
if err != nil { if err != nil {
log.Fatal("Cannot initiate KVStore:", err) log.Fatal("Cannot initiate KVStore:", err)
} }

View file

@ -132,19 +132,11 @@ var (
} }
keystoreDBFlag = cli.StringFlag{ keystoreDBFlag = cli.StringFlag{
Name: "keystore-db", Name: "keystore-db",
Usage: "General SQL database name which stores keys, currently supported db type are: mysql, postgres", Usage: "Keystore database yaml config file path, currently supported db type are: mysql, postgres",
}
keystoreDBDSNFlag = cli.StringFlag{
Name: "keystore-db-dsn",
Usage: "Data Source Name (DSN) for keystore-db",
} }
configDBFlag = cli.StringFlag{ configDBFlag = cli.StringFlag{
Name: "config-db", Name: "config-db",
Usage: "General SQL database name which is used to store configuration key value pairs, currently supported db type are: mysql, postgres", Usage: "Config database yaml config file path, currently supported db type are: mysql, postgres",
}
configDBDSNFlag = cli.StringFlag{
Name: "config-db-dsn",
Usage: "Data Source Name (DSN) for config-db",
} }
app = cli.NewApp() app = cli.NewApp()
initCommand = cli.Command{ initCommand = cli.Command{
@ -236,9 +228,7 @@ func init() {
stdiouiFlag, stdiouiFlag,
testFlag, testFlag,
keystoreDBFlag, keystoreDBFlag,
keystoreDBDSNFlag,
configDBFlag, configDBFlag,
configDBDSNFlag,
advancedMode, advancedMode,
} }
app.Action = signer app.Action = signer
@ -320,6 +310,7 @@ You should treat 'masterseed.json' with utmost secrecy and make a backup of it!
`) `)
return nil return nil
} }
func attestFile(ctx *cli.Context) error { func attestFile(ctx *cli.Context) error {
if len(ctx.Args()) < 1 { if len(ctx.Args()) < 1 {
utils.Fatalf("This command requires an argument.") utils.Fatalf("This command requires an argument.")
@ -332,12 +323,8 @@ func attestFile(ctx *cli.Context) error {
if err != nil { if err != nil {
utils.Fatalf(err.Error()) utils.Fatalf(err.Error())
} }
configDir := ctx.GlobalString(configdirFlag.Name) // Initialize the config storage
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) configStorage := initConfigStorage(stretchedKey, ctx)
confKey := crypto.Keccak256([]byte("config"), stretchedKey)
// Initialize the encrypted storages
configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey)
val := ctx.Args().First() val := ctx.Args().First()
configStorage.Put("ruleset_sha256", val) configStorage.Put("ruleset_sha256", val)
log.Info("Ruleset attestation updated", "sha256", val) log.Info("Ruleset attestation updated", "sha256", val)
@ -363,11 +350,7 @@ func setCredential(ctx *cli.Context) error {
if err != nil { if err != nil {
utils.Fatalf(err.Error()) utils.Fatalf(err.Error())
} }
configDir := ctx.GlobalString(configdirFlag.Name) pwStorage := initPasswordStorage(stretchedKey, ctx)
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
pwStorage.Put(address.Hex(), password) pwStorage.Put(address.Hex(), password)
log.Info("Credential store updated", "set", address) log.Info("Credential store updated", "set", address)
@ -391,11 +374,7 @@ func removeCredential(ctx *cli.Context) error {
if err != nil { if err != nil {
utils.Fatalf(err.Error()) utils.Fatalf(err.Error())
} }
configDir := ctx.GlobalString(configdirFlag.Name) pwStorage := initPasswordStorage(stretchedKey, ctx)
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
pwStorage.Del(address.Hex()) pwStorage.Del(address.Hex())
log.Info("Credential store updated", "unset", address) log.Info("Credential store updated", "unset", address)
@ -446,31 +425,11 @@ func ipcEndpoint(ipcPath, datadir string) string {
return ipcPath return ipcPath
} }
func checkFlag(c *cli.Context) error {
keystoreDB := c.GlobalString(keystoreDBFlag.Name)
keystoreDBDSN := c.GlobalString(keystoreDBDSNFlag.Name)
if keystoreDB != "" && keystoreDBDSN == "" {
return fmt.Errorf("%s has to be set along with %s", keystoreDBDSNFlag.Name, keystoreDBFlag.Name)
}
configDB := c.GlobalString(configDBFlag.Name)
configDBDSN := c.GlobalString(configDBDSNFlag.Name)
if configDB != "" && configDBDSN == "" {
return fmt.Errorf("%s has to be set along with %s", configDBDSNFlag.Name, configDBFlag.Name)
}
return nil
}
func signer(c *cli.Context) error { func signer(c *cli.Context) error {
// If we have some unrecognized command, bail out // If we have some unrecognized command, bail out
if args := c.Args(); len(args) > 0 { if args := c.Args(); len(args) > 0 {
return fmt.Errorf("invalid command: %q", args[0]) return fmt.Errorf("invalid command: %q", args[0])
} }
// check flag legitimacy
if err := checkFlag(c); err != nil {
return err
}
if err := initialize(c); err != nil { if err := initialize(c); err != nil {
return err return err
} }
@ -495,37 +454,19 @@ func signer(c *cli.Context) error {
var ( var (
api core.ExternalAPI api core.ExternalAPI
pwStorage storage.Storage = &storage.NoStorage{} pwStorage storage.Storage = storage.NewNoStorage()
jsStorage storage.Storage = &storage.NoStorage{} jsStorage storage.Storage = storage.NewNoStorage()
configStorage storage.Storage = &storage.NoStorage{} configStorage storage.Storage = storage.NewNoStorage()
) )
configDir := c.GlobalString(configdirFlag.Name) configDir := c.GlobalString(configdirFlag.Name)
if stretchedKey, err := readMasterKey(c, ui); err != nil { if stretchedKey, err := readMasterKey(c, ui); err != nil {
log.Warn("Failed to open master, rules disabled", "err", err) log.Warn("Failed to open master, rules disabled", "err", err)
} else { } else {
// Generate domain specific keys // create dedicated storages for different type of configuration
pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) pwStorage = initPasswordStorage(stretchedKey, c)
jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey) jsStorage = initJsStorage(stretchedKey, c)
confkey := crypto.Keccak256([]byte("config"), stretchedKey) configStorage = initConfigStorage(stretchedKey, c)
if configDB := c.GlobalString(configDBFlag.Name); configDB != "" {
configDBDSN := c.GlobalString(configDBDSNFlag.Name)
pwStorage, err = storage.NewDBStorage(pwkey, configDB, configDBDSN, storage.PasswordTable)
if err != nil {
utils.Fatalf("Could not connect to config db %v, %v", configDB, configDBDSN)
}
// since we're literally connecting to the same database, so we don't need to check for err for 3 times
jsStorage, _ = storage.NewDBStorage(jskey, configDB, configDBDSN, storage.JsTable)
configStorage, _ = storage.NewDBStorage(jskey, configDB, configDBDSN, storage.ConfigTable)
} else {
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
// Initialize the encrypted storages
pwStorage = storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
jsStorage = storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
configStorage = storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
}
// Do we have a rule-file? // Do we have a rule-file?
if ruleFile := c.GlobalString(ruleFlag.Name); ruleFile != "" { if ruleFile := c.GlobalString(ruleFlag.Name); ruleFile != "" {
@ -1113,3 +1054,40 @@ These data types are defined in the channel between clef and the UI`)
fmt.Println(elem) fmt.Println(elem)
} }
} }
// Constants for file/db storage vault/table name
const (
PasswordStorageVault string = "credentials"
JsStorageVault string = "jsstorage"
ConfigStorageVault string = "config"
)
func initPasswordStorage(stretchedKey []byte, c *cli.Context) storage.Storage {
return initStorage(PasswordStorageVault, stretchedKey, c)
}
func initJsStorage(stretchedKey []byte, c *cli.Context) storage.Storage {
return initStorage(JsStorageVault, stretchedKey, c)
}
func initConfigStorage(stretchedKey []byte, c *cli.Context) storage.Storage {
return initStorage(ConfigStorageVault, stretchedKey, c)
}
func initStorage(storageName string, stretchedKey []byte, c *cli.Context) storage.Storage {
configDir := c.GlobalString(configdirFlag.Name)
key := crypto.Keccak256([]byte(storageName), stretchedKey)
if configDB := c.GlobalString(configDBFlag.Name); configDB != "" {
// if config-db is specified, create db backed storage
storage, err := storage.NewDBStorage(configDB, storageName, key)
if err != nil {
utils.Fatalf("Cannot init database storage for %s", storageName)
}
return storage
} else {
// otherwise create file storage
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
return storage.NewFileStorage(filepath.Join(vaultLocation, storageName+".json"), key)
}
}

3
go.mod
View file

@ -70,5 +70,6 @@ require (
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772
gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/urfave/cli.v1 v1.20.0 gopkg.in/urfave/cli.v1 v1.20.0
gotest.tools v2.2.0+incompatible // indirect gopkg.in/yaml.v2 v2.2.2
gotest.tools v2.2.0+incompatible
) )

View file

@ -126,9 +126,8 @@ func setup(ksLoc string, t *testing.T) (*core.SignerAPI, *headlessUi) {
} }
ui := &headlessUi{make(chan string, 20), make(chan string, 20)} ui := &headlessUi{make(chan string, 20), make(chan string, 20)}
am := core.StartClefAccountManager(ksLoc, true, true, "") am := core.StartClefAccountManager(ksLoc, true, true, "")
api := core.NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{}) api := core.NewSignerAPI(am, 1337, true, ui, db, true, storage.NewNoStorage())
return api, ui return api, ui
} }
func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) {

View file

@ -32,8 +32,8 @@ func (api *DBStorageAPI) Get(key string) (string, error) {
} }
// Put stores a value by key. 0-length keys results in noop. // Put stores a value by key. 0-length keys results in noop.
func (api *DBStorageAPI) Put(key, value string) { func (api *DBStorageAPI) Put(key, value string) error {
api.kvstore.Put(key, value) return api.kvstore.Put(key, value)
} }
// Del removes a key-value pair. If the key doesn't exist, the method is a noop. // Del removes a key-value pair. If the key doesn't exist, the method is a noop.

View file

@ -7,11 +7,12 @@ type EphemeralStorageAPI struct {
} }
// Put stores a value by key. 0-length keys results in noop. // Put stores a value by key. 0-length keys results in noop.
func (s *EphemeralStorageAPI) Put(key, value string) { func (s *EphemeralStorageAPI) Put(key, value string) error {
if len(key) == 0 { if len(key) == 0 {
return return ErrZeroKey
} }
s.data[key] = value s.data[key] = value
return nil
} }
// Get returns the previously stored value, or an error if the key is 0-length // Get returns the previously stored value, or an error if the key is 0-length

View file

@ -31,19 +31,21 @@ type FileStorageAPI struct {
} }
// Put stores a value by key. 0-length keys results in noop. // Put stores a value by key. 0-length keys results in noop.
func (s *FileStorageAPI) Put(key, value string) { func (s *FileStorageAPI) Put(key, value string) error {
if len(key) == 0 { if len(key) == 0 {
return return ErrZeroKey
} }
data, err := s.readStorage() data, err := s.readStorage()
if err != nil { if err != nil {
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 err
} }
data[key] = value data[key] = value
if err = s.writeStorage(data); err != nil { if err = s.writeStorage(data); err != nil {
log.Warn("Failed to write entry", "err", err) log.Warn("Failed to write entry", "err", err)
return err
} }
return nil
} }
// Get returns the previously stored value, or an error if it does not exist or // Get returns the previously stored value, or an error if it does not exist or

View file

@ -6,7 +6,9 @@ import "errors"
type NoStorageAPI struct{} type NoStorageAPI struct{}
// Put is a dummy function that do nothing // Put is a dummy function that do nothing
func (s *NoStorageAPI) Put(key, value string) {} func (s *NoStorageAPI) Put(key, value string) error {
return errors.New("I don't know how to remember")
}
// Del is a dummy function that do nothing // Del is a dummy function that do nothing
func (s *NoStorageAPI) Del(key string) {} func (s *NoStorageAPI) Del(key string) {}

View file

@ -35,7 +35,7 @@ var (
// storageAPI is the interface that defines interactions with backend storage client // storageAPI is the interface that defines interactions with backend storage client
type storageAPI interface { type storageAPI interface {
// Put stores a value by key. 0-length keys results in noop. // Put stores a value by key. 0-length keys results in noop.
Put(key, value string) Put(key, value string) error
// Get returns the previously stored value, or an error if the key is 0-length // Get returns the previously stored value, or an error if the key is 0-length
// or unknown. // or unknown.
@ -80,24 +80,24 @@ func (s *Storage) Get(key string) (string, error) {
// Put encrypts the value field with key as additionalData to prevent value swap attack. // Put encrypts the value field with key as additionalData to prevent value swap attack.
// Then calls the underlying storageApi's Put function to persist the key/value pair // Then calls the underlying storageApi's Put function to persist the key/value pair
func (s *Storage) Put(key, value string) { func (s *Storage) Put(key, value string) error {
if len(key) == 0 { if len(key) == 0 {
return return ErrZeroKey
} }
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 err
} }
encrypted := StoredCredential{Iv: iv, CipherText: ciphertext} encrypted := StoredCredential{Iv: iv, CipherText: ciphertext}
raw, err := json.Marshal(encrypted) raw, err := json.Marshal(encrypted)
if err != nil { if err != nil {
log.Warn("Failed to marshal credential", "err", err) log.Warn("Failed to marshal credential", "err", err)
return return err
} }
s.api.Put(key, string(raw)) return s.api.Put(key, string(raw))
} }
// Del calls the underlying storageApi's Del function to delete the key/value pair // Del calls the underlying storageApi's Del function to delete the key/value pair
@ -127,26 +127,26 @@ func NewNoStorage() Storage {
} }
// NewDBStorage creates a database storage // NewDBStorage creates a database storage
func NewDBStorage(path, table string, key []byte) (*Storage, error) { func NewDBStorage(path, table string, key []byte) (Storage, error) {
kvstore, err := dbutil.NewKVStore(path, table) kvstore, err := dbutil.NewKVStore(path, table)
if err != nil { if err != nil {
return nil, err return NewNoStorage(), err
} }
api := &DBStorageAPI{ api := &DBStorageAPI{
kvstore: kvstore, kvstore: kvstore,
} }
return &Storage{ return Storage{
api: api, api: api,
key: key, key: key,
}, nil }, nil
} }
// NewFileStorage creates a storage type which is backed by a json-file. // NewFileStorage creates a storage type which is backed by a json-file.
func NewFileStorage(filename string, key []byte) *Storage { func NewFileStorage(filename string, key []byte) Storage {
api := &FileStorageAPI{ api := &FileStorageAPI{
filename: filename, filename: filename,
} }
return &Storage{ return Storage{
api: api, api: api,
key: key, key: key,
} }