Add config encryption support

This commit is contained in:
Adrian Gallagher
2017-03-03 17:32:48 +11:00
parent bb1d0a26d0
commit fed5367240
6 changed files with 157 additions and 146 deletions

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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
View File

@@ -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 {