Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Config: Fix config version downgrade #1770

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 47 additions & 67 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

import (
"errors"
"context"
"flag"
"fmt"
"os"
Expand All @@ -11,9 +11,10 @@ import (
"github.com/buger/jsonparser"
"github.com/thrasher-corp/gocryptotrader/common/file"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/config/versions"
)

var commands = []string{"upgrade", "encrypt", "decrypt"}
var commands = []string{"upgrade", "downgrade", "encrypt", "decrypt"}

func main() {
fmt.Println("GoCryptoTrader: config-helper tool")
Expand All @@ -22,13 +23,15 @@ func main() {

var in, out, keyStr string
var inplace bool
var version int

fs := flag.NewFlagSet("config", flag.ExitOnError)
fs.Usage = func() { usage(fs) }
fs.StringVar(&in, "in", defaultCfgFile, "The config input file to process")
fs.StringVar(&out, "out", "[in].out", "The config output file")
fs.BoolVar(&inplace, "edit", false, "Edit; Save result to the original file")
fs.StringVar(&keyStr, "key", "", "The key to use for AES encryption")
fs.IntVar(&version, "version", 0, "The version to downgrade to")

cmd, args := parseCommand(os.Args[1:])
if cmd == "" {
Expand All @@ -46,83 +49,59 @@ func main() {
out = in + ".out"
}

key := []byte(keyStr)
var err error
switch cmd {
case "upgrade":
err = upgradeFile(in, out, key)
case "decrypt":
err = encryptWrapper(in, out, key, false, decryptFile)
case "encrypt":
err = encryptWrapper(in, out, key, true, encryptFile)
}
key := []byte(keyStr)
data := readFile(in)
isEncrypted := config.IsEncrypted(data)

if err != nil {
fatal(err.Error())
if cmd == "encrypt" && isEncrypted {
fatal("Error: File is already encrypted")
}

fmt.Println("Success! File written to " + out)
}

func upgradeFile(in, out string, key []byte) error {
c := &config.Config{
EncryptionKeyProvider: func(_ bool) ([]byte, error) {
if len(key) != 0 {
return key, nil
}
return config.PromptForConfigKey(false)
},
if len(key) == 0 && (isEncrypted || cmd == "encrypt") {
if key, err = config.PromptForConfigKey(cmd == "encrypt"); err != nil {
fatal(err.Error())
}
}

if err := c.ReadConfigFromFile(in, true); err != nil {
return err
if isEncrypted {
if data, err = config.DecryptConfigData(data, key); err != nil {
fatal(err.Error())
}
}

return c.SaveConfigToFile(out)
}

type encryptFunc func(string, []byte) ([]byte, error)

func encryptWrapper(in, out string, key []byte, confirmKey bool, fn encryptFunc) error {
if len(key) == 0 {
var err error
if key, err = config.PromptForConfigKey(confirmKey); err != nil {
return err
switch cmd {
case "decrypt":
if data, err = jsonparser.Set(data, []byte("-1"), "encryptConfig"); err != nil {
gbjk marked this conversation as resolved.
Show resolved Hide resolved
fatal("Unable to decrypt config data; Error: " + err.Error())
}
case "downgrade", "upgrade":
if version == 0 {
if cmd == "downgrade" {
fmt.Fprintln(os.Stderr, "Error: downgrade requires a version")
usage(fs)
os.Exit(3)
}
version = -1
}
if data, err = versions.Manager.Deploy(context.Background(), data, version); err != nil {
fatal("Unable to " + cmd + " config; Error: " + err.Error())
}
if !isEncrypted {
break
}
fallthrough
case "encrypt":
if data, err = config.EncryptConfigData(data, key); err != nil {
fatal("Unable to encrypt config data; Error: " + err.Error())
}
}
outData, err := fn(in, key)
if err != nil {
return err
}
if err := file.Write(out, outData); err != nil {
return fmt.Errorf("unable to write output file %s; Error: %w", out, err)
}
return nil
}

func encryptFile(in string, key []byte) ([]byte, error) {
if config.IsFileEncrypted(in) {
return nil, errors.New("file is already encrypted")
}
outData, err := config.EncryptConfigFile(readFile(in), key)
if err != nil {
return nil, fmt.Errorf("unable to encrypt config data. Error: %w", err)
if err := file.Write(out, data); err != nil {
fatal("Unable to write output file `" + out + "`; Error: " + err.Error())
}
return outData, nil
}

func decryptFile(in string, key []byte) ([]byte, error) {
if !config.IsFileEncrypted(in) {
return nil, errors.New("file is already decrypted")
}
outData, err := config.DecryptConfigFile(readFile(in), key)
if err != nil {
return nil, fmt.Errorf("unable to decrypt config data. Error: %w", err)
}
if outData, err = jsonparser.Set(outData, []byte("-1"), "encryptConfig"); err != nil {
return nil, fmt.Errorf("unable to decrypt config data. Error: %w", err)
}
return outData, nil
fmt.Println("Success! File written to " + out)
}

func readFile(in string) []byte {
Expand Down Expand Up @@ -152,7 +131,7 @@ func parseCommand(a []string) (cmd string, args []string) {
switch len(cmds) {
case 0:
fmt.Fprintln(os.Stderr, "No command provided")
case 1: //
case 1:
return cmds[0], rem
default:
fmt.Fprintln(os.Stderr, "Too many commands provided: "+strings.Join(cmds, ", "))
Expand All @@ -171,6 +150,7 @@ The commands are:
encrypt encrypt infile and write to outfile
decrypt decrypt infile and write to outfile
upgrade upgrade the version of a decrypted config file
downgrade downgrade the version of a decrypted config file to a specific version

The arguments are:`)
fs.PrintDefaults()
Expand Down
4 changes: 2 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1512,7 +1512,7 @@ func (c *Config) readConfig(d io.Reader) error {
}
}

if j, err = versions.Manager.Deploy(context.Background(), j); err != nil {
if j, err = versions.Manager.Deploy(context.Background(), j, -1); err != nil {
gbjk marked this conversation as resolved.
Show resolved Hide resolved
return err
}

Expand Down Expand Up @@ -1603,7 +1603,7 @@ func (c *Config) Save(writerProvider func() (io.Writer, error)) error {
}
c.sessionDK, c.storedSalt = sessionDK, storedSalt
}
payload, err = c.encryptConfigFile(payload)
payload, err = c.encryptConfigData(payload)
if err != nil {
return err
}
Expand Down
14 changes: 7 additions & 7 deletions config/config_encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ func getSensitiveInput(prompt string) (resp []byte, err error) {
return bytes.TrimRight(resp, "\r\n"), err
}

// EncryptConfigFile encrypts json config data with a key
func EncryptConfigFile(configData, key []byte) ([]byte, error) {
// EncryptConfigData encrypts json config data with a key
func EncryptConfigData(configData, key []byte) ([]byte, error) {
sessionDK, salt, err := makeNewSessionDK(key)
if err != nil {
return nil, err
Expand All @@ -105,12 +105,12 @@ func EncryptConfigFile(configData, key []byte) ([]byte, error) {
sessionDK: sessionDK,
storedSalt: salt,
}
return c.encryptConfigFile(configData)
return c.encryptConfigData(configData)
}

// encryptConfigFile encrypts json config data with a key
// encryptConfigData encrypts json config data with a key
// The EncryptConfig field is set to config enabled (1)
func (c *Config) encryptConfigFile(configData []byte) ([]byte, error) {
func (c *Config) encryptConfigData(configData []byte) ([]byte, error) {
configData, err := jsonparser.Set(configData, []byte("1"), "encryptConfig")
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrSettingEncryptConfig, err)
Expand All @@ -135,8 +135,8 @@ func (c *Config) encryptConfigFile(configData []byte) ([]byte, error) {
return appendedFile, nil
}

// DecryptConfigFile decrypts config data with a key
func DecryptConfigFile(d, key []byte) ([]byte, error) {
// DecryptConfigData decrypts config data with a key
func DecryptConfigData(d, key []byte) ([]byte, error) {
return (&Config{}).decryptConfigData(d, key)
}

Expand Down
18 changes: 9 additions & 9 deletions config/config_encryption_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,16 @@ func TestPromptForConfigKey(t *testing.T) {

func TestEncryptConfigFile(t *testing.T) {
t.Parallel()
_, err := EncryptConfigFile([]byte("test"), nil)
_, err := EncryptConfigData([]byte("test"), nil)
require.ErrorIs(t, err, errKeyIsEmpty)

c := &Config{
sessionDK: []byte("a"),
}
_, err = c.encryptConfigFile([]byte(`test`))
_, err = c.encryptConfigData([]byte(`test`))
require.ErrorIs(t, err, ErrSettingEncryptConfig)

_, err = c.encryptConfigFile([]byte(`{"test":1}`))
_, err = c.encryptConfigData([]byte(`{"test":1}`))
require.Error(t, err)
require.IsType(t, aes.KeySizeError(1), err)

Expand All @@ -79,26 +79,26 @@ func TestEncryptConfigFile(t *testing.T) {
sessionDK: sessDk,
storedSalt: salt,
}
_, err = c.encryptConfigFile([]byte(`{"test":1}`))
_, err = c.encryptConfigData([]byte(`{"test":1}`))
require.NoError(t, err)
}

func TestDecryptConfigFile(t *testing.T) {
t.Parallel()
e, err := EncryptConfigFile([]byte(`{"test":1}`), []byte("key"))
e, err := EncryptConfigData([]byte(`{"test":1}`), []byte("key"))
require.NoError(t, err)

d, err := DecryptConfigFile(e, []byte("key"))
d, err := DecryptConfigData(e, []byte("key"))
require.NoError(t, err)
assert.Equal(t, `{"test":1,"encryptConfig":1}`, string(d), "encryptConfig should be set to 1 after first encryption")

_, err = DecryptConfigFile(e, nil)
_, err = DecryptConfigData(e, nil)
require.ErrorIs(t, err, errKeyIsEmpty)

_, err = DecryptConfigFile([]byte("test"), nil)
_, err = DecryptConfigData([]byte("test"), nil)
require.ErrorIs(t, err, errNoPrefix)

_, err = DecryptConfigFile(encryptionPrefix, []byte("AAAAAAAAAAAAAAAA"))
_, err = DecryptConfigData(encryptionPrefix, []byte("AAAAAAAAAAAAAAAA"))
require.ErrorIs(t, err, errAESBlockSize)
}

Expand Down
Loading
Loading