mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-16 23:16:48 +00:00
Add config encryption support
This commit is contained in:
@@ -168,6 +168,13 @@ func IsEnabled(isEnabled bool) string {
|
||||
}
|
||||
}
|
||||
|
||||
func YesOrNo(input string) bool {
|
||||
if StringToLower(input) == "y" || StringToLower(input) == "yes" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func CalculateAmountWithFee(amount, fee float64) float64 {
|
||||
return amount + CalculateFee(amount, fee)
|
||||
}
|
||||
|
||||
81
config.go
81
config.go
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -13,6 +12,10 @@ import (
|
||||
|
||||
const (
|
||||
CONFIG_FILE = "config.json"
|
||||
|
||||
CONFIG_FILE_ENCRYPTION_PROMPT = 0
|
||||
CONFIG_FILE_ENCRYPTION_ENABLED = 1
|
||||
CONFIG_FILE_ENCRYPTION_DISABLED = -1
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -20,16 +23,19 @@ var (
|
||||
ErrExchangeAvailablePairsEmpty = "Exchange %s: Available pairs is empty."
|
||||
ErrExchangeEnabledPairsEmpty = "Exchange %s: Enabled pairs is empty."
|
||||
ErrExchangeBaseCurrenciesEmpty = "Exchange %s: Base currencies is empty."
|
||||
WarningExchangeAuthAPIDefaultOrEmptyValues = "WARNING -- Exchange %s: Authenticated API support disabled due to default/empty APIKey/Secret/ClientID values."
|
||||
ErrExchangeNotFound = "Exchange %s: Not found."
|
||||
ErrNoEnabledExchanges = "No Exchanges enabled."
|
||||
ErrCryptocurrenciesEmpty = "Cryptocurrencies variable is empty."
|
||||
ErrFailureOpeningConfig = "Fatal error opening config.json file. Error: %s"
|
||||
ErrCheckingConfigValues = "Fatal error checking config values. Error: %s"
|
||||
ErrSavingConfigBytesMismatch = "Config file %q bytes comparison doesn't match, read %s expected %s."
|
||||
WarningSMSGlobalDefaultOrEmptyValues = "WARNING -- SMS Support disabled due to default or empty Username/Password values."
|
||||
WarningSSMSGlobalSMSContactDefaultOrEmptyValues = "WARNING -- SMS contact #%d Name/Number disabled due to default or empty values."
|
||||
WarningSSMSGlobalSMSNoContacts = "WARNING -- SMS Support disabled due to no enabled contacts."
|
||||
WarningWebserverCredentialValuesEmpty = "WARNING -- Webserver support disabled due to empty Username/Password values."
|
||||
WarningWebserverListenAddressInvalid = "WARNING -- Webserver support disabled due to invalid listen address."
|
||||
WarningWebserverRootWebFolderNotFound = "WARNING -- Webserver support disabled due to missing web folder."
|
||||
WarningExchangeAuthAPIDefaultOrEmptyValues = "WARNING -- Exchange %s: Authenticated API support disabled due to default/empty APIKey/Secret/ClientID values."
|
||||
)
|
||||
|
||||
type Webserver struct {
|
||||
@@ -55,6 +61,7 @@ type ConfigPost struct {
|
||||
|
||||
type Config struct {
|
||||
Name string
|
||||
EncryptConfig int
|
||||
Cryptocurrencies string
|
||||
SMS SMSGlobal `json:"SMSGlobal"`
|
||||
Webserver Webserver `json:"Webserver"`
|
||||
@@ -168,7 +175,6 @@ func CheckExchangeConfigValues() error {
|
||||
}
|
||||
|
||||
func CheckWebserverValues() error {
|
||||
|
||||
if bot.config.Webserver.AdminUsername == "" || bot.config.Webserver.AdminPassword == "" {
|
||||
return errors.New(WarningWebserverCredentialValuesEmpty)
|
||||
}
|
||||
@@ -189,38 +195,79 @@ func CheckWebserverValues() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadConfig() (Config, error) {
|
||||
func ReadConfig() error {
|
||||
file, err := ioutil.ReadFile(CONFIG_FILE)
|
||||
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := Config{}
|
||||
err = json.Unmarshal(file, &cfg)
|
||||
return cfg, err
|
||||
if !ConfirmECS(file) {
|
||||
err := json.Unmarshal(file, &bot.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bot.config.EncryptConfig == CONFIG_FILE_ENCRYPTION_DISABLED {
|
||||
return nil
|
||||
}
|
||||
|
||||
if bot.config.EncryptConfig == CONFIG_FILE_ENCRYPTION_PROMPT {
|
||||
if PromptForConfigEncryption() {
|
||||
bot.config.EncryptConfig = CONFIG_FILE_ENCRYPTION_ENABLED
|
||||
SaveConfig()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
key, err := PromptForConfigKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := DecryptConfigFile(file, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &bot.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveConfig() error {
|
||||
log.Println("Saving config")
|
||||
log.Println("Saving config.")
|
||||
payload, err := json.MarshalIndent(bot.config, "", " ")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
if bot.config.EncryptConfig == CONFIG_FILE_ENCRYPTION_ENABLED {
|
||||
key, err := PromptForConfigKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload, err = EncryptConfigFile(payload, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(CONFIG_FILE, payload, 0644)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
retrieved, err := ioutil.ReadFile(CONFIG_FILE)
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadConfig() error {
|
||||
err := ReadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf(ErrFailureOpeningConfig, err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(retrieved, payload) {
|
||||
return fmt.Errorf("file %q content doesn't match, read %s expected %s\n", CONFIG_FILE, retrieved, payload)
|
||||
err = CheckExchangeConfigValues()
|
||||
if err != nil {
|
||||
return fmt.Errorf(ErrCheckingConfigValues, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -38,9 +37,8 @@ func SaveAllSettings(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bot.config, err = ReadConfig()
|
||||
err = LoadConfig()
|
||||
if err != nil {
|
||||
log.Println("Fatal error checking config values. Error:", err)
|
||||
panic(err)
|
||||
}
|
||||
setupBotExchanges()
|
||||
|
||||
@@ -5,138 +5,106 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
ENCRYPTION_CONFIRMATION_STRING = "THORS-HAMMER"
|
||||
)
|
||||
|
||||
type Encryption struct {
|
||||
CryptoKey []byte
|
||||
EncryptedFile []byte
|
||||
Nonce []byte
|
||||
encrypPerm bool
|
||||
decryptPerm bool
|
||||
}
|
||||
func PromptForConfigEncryption() bool {
|
||||
log.Println("Would you like to encrypt your config file (y/n)?")
|
||||
|
||||
func (e *Encryption) SetUp() {
|
||||
for len(e.CryptoKey) != 32 {
|
||||
fmt.Println("Please enter a unique 32Char key to set up encryption: \n")
|
||||
|
||||
_, err := fmt.Scanln(&e.CryptoKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(e.CryptoKey) > 32 || len(e.CryptoKey) < 32 {
|
||||
fmt.Println("Please Re-enter a 32char key..\n")
|
||||
}
|
||||
}
|
||||
|
||||
e.Nonce = make([]byte, 12)
|
||||
if _, err := io.ReadFull(rand.Reader, e.Nonce); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
e.encrypPerm = false
|
||||
e.decryptPerm = false
|
||||
}
|
||||
|
||||
func (e *Encryption) Encrypt() []byte {
|
||||
block, err := aes.NewCipher(e.CryptoKey)
|
||||
input := ""
|
||||
_, err := fmt.Scanln(&input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return false
|
||||
}
|
||||
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configfile := e.ReadFile(CONFIG_FILE)
|
||||
|
||||
if e.ConfirmJSON(configfile) != true {
|
||||
log.Println("File cannot be encrypted.\n")
|
||||
if e.ConfirmECS(configfile) != true {
|
||||
log.Println("File Corrupted.")
|
||||
panic(err)
|
||||
}
|
||||
log.Println("File already encrypted.")
|
||||
return configfile
|
||||
}
|
||||
e.EncryptedFile = aesgcm.Seal(nil, e.Nonce, configfile, nil)
|
||||
|
||||
appendedFile := []byte(ENCRYPTION_CONFIRMATION_STRING)
|
||||
appendedFile = append(appendedFile, e.EncryptedFile...)
|
||||
return appendedFile
|
||||
}
|
||||
|
||||
func (e *Encryption) Decrypt() []byte {
|
||||
blockDecrypt, err := aes.NewCipher(e.CryptoKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
aesgcmDecrypt, err := cipher.NewGCM(blockDecrypt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configfile := e.ReadFile(CONFIG_FILE)
|
||||
if e.ConfirmECS(configfile) != true {
|
||||
log.Println("File cannot be decrypted..\n")
|
||||
if e.ConfirmJSON(configfile) != true {
|
||||
log.Println("File corrupted.")
|
||||
panic(err)
|
||||
}
|
||||
log.Println("File already decrypted.")
|
||||
return configfile
|
||||
}
|
||||
|
||||
unencryptedFile, err := aesgcmDecrypt.Open(nil, e.Nonce, e.RemoveECS(configfile), nil)
|
||||
if err != nil {
|
||||
log.Println("File Corrupted")
|
||||
panic(err)
|
||||
}
|
||||
return unencryptedFile
|
||||
}
|
||||
|
||||
func (e *Encryption) ReadFile(filename string) []byte {
|
||||
file, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
func (e *Encryption) SaveFile(file []byte) {
|
||||
mode := int(0777)
|
||||
perm := os.FileMode(mode)
|
||||
err := ioutil.WriteFile(CONFIG_FILE, file, perm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Encryption) ConfirmJSON(file []byte) bool {
|
||||
err := json.Unmarshal(file, nil) //Needs Revision
|
||||
if err != nil {
|
||||
return true //Fix after revision
|
||||
if !YesOrNo(input) {
|
||||
bot.config.EncryptConfig = CONFIG_FILE_ENCRYPTION_DISABLED
|
||||
SaveConfig()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *Encryption) ConfirmECS(file []byte) bool {
|
||||
func PromptForConfigKey() ([]byte, error) {
|
||||
var cryptoKey []byte
|
||||
|
||||
for len(cryptoKey) != 32 {
|
||||
log.Println("Please enter your 32 character AES key:")
|
||||
|
||||
_, err := fmt.Scanln(&cryptoKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cryptoKey) > 32 || len(cryptoKey) < 32 {
|
||||
fmt.Println("Please re-enter a 32char key:")
|
||||
}
|
||||
}
|
||||
|
||||
nonce := make([]byte, 12)
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cryptoKey, nil
|
||||
}
|
||||
|
||||
func EncryptConfigFile(configData, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, aes.BlockSize+len(configData))
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(ciphertext[aes.BlockSize:], configData)
|
||||
|
||||
appendedFile := []byte(ENCRYPTION_CONFIRMATION_STRING)
|
||||
appendedFile = append(appendedFile, ciphertext...)
|
||||
return appendedFile, nil
|
||||
}
|
||||
|
||||
func DecryptConfigFile(configData, key []byte) ([]byte, error) {
|
||||
configData = RemoveECS(configData)
|
||||
blockDecrypt, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(configData) < aes.BlockSize {
|
||||
return nil, errors.New("The config file data is too small for the AES required block size.")
|
||||
}
|
||||
|
||||
iv := configData[:aes.BlockSize]
|
||||
configData = configData[aes.BlockSize:]
|
||||
|
||||
stream := cipher.NewCFBDecrypter(blockDecrypt, iv)
|
||||
stream.XORKeyStream(configData, configData)
|
||||
result := configData
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ConfirmWalletJSON(file []byte, result interface{}) error {
|
||||
return JSONDecode(file, result)
|
||||
}
|
||||
|
||||
func ConfirmECS(file []byte) bool {
|
||||
subslice := []byte(ENCRYPTION_CONFIRMATION_STRING)
|
||||
return bytes.Contains(file, subslice)
|
||||
}
|
||||
|
||||
func (e *Encryption) RemoveECS(file []byte) []byte {
|
||||
func RemoveECS(file []byte) []byte {
|
||||
return bytes.Trim(file, ENCRYPTION_CONFIRMATION_STRING)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"Name": "Skynet",
|
||||
"EncryptConfig": 0,
|
||||
"DisplayCurrency":"USD",
|
||||
"Cryptocurrencies": "BTC,XBT,LTC,XRP,XDG,DOGE,STR,NMC,STR,XDG,XRP,XVN",
|
||||
"SMSGlobal": {
|
||||
|
||||
18
main.go
18
main.go
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -63,20 +62,11 @@ func setupBotExchanges() {
|
||||
|
||||
func main() {
|
||||
HandleInterrupt()
|
||||
log.Println("Loading config file config.json..")
|
||||
log.Printf("Loading config file %s..\n", CONFIG_FILE)
|
||||
|
||||
err := errors.New("")
|
||||
bot.config, err = ReadConfig()
|
||||
err := LoadConfig()
|
||||
if err != nil {
|
||||
log.Printf("Fatal error opening config.json file. Error: %s", err)
|
||||
return
|
||||
}
|
||||
log.Println("Config file loaded. Checking settings.. ")
|
||||
|
||||
err = CheckExchangeConfigValues()
|
||||
if err != nil {
|
||||
log.Println("Fatal error checking config values. Error:", err)
|
||||
return
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Printf("Bot '%s' started.\n", bot.config.Name)
|
||||
@@ -129,7 +119,7 @@ func main() {
|
||||
err = RetrieveConfigCurrencyPairs(bot.config)
|
||||
|
||||
if err != nil {
|
||||
log.Println("Fatal error retrieving config currency AvailablePairs. Error: ", err)
|
||||
log.Fatalf("Fatal error retrieving config currency AvailablePairs. Error: ", err)
|
||||
}
|
||||
|
||||
if bot.config.Webserver.Enabled {
|
||||
|
||||
Reference in New Issue
Block a user