diff --git a/tools/config/config-helper.go b/tools/config/config-helper.go new file mode 100644 index 00000000..222f8fee --- /dev/null +++ b/tools/config/config-helper.go @@ -0,0 +1,185 @@ +package main + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "log" +) + +const ( + ENCRYPTION_CONFIRMATION_STRING = "THORS-HAMMER" +) + +func EncryptOrDecrypt(encrypt bool) string { + if encrypt { + return "encrypted" + } + return "decrypted" +} + +func main() { + var inFile, outFile, key string + var encrypt bool + var err error + flag.StringVar(&inFile, "infile", "config.dat", "The config input file to process.") + flag.StringVar(&outFile, "outfile", "config.dat.out", "The config output file.") + flag.BoolVar(&encrypt, "encrypt", true, "Wether to encrypt or decrypt.") + flag.StringVar(&key, "key", "", "The key to use for AES encryption.") + flag.Parse() + + log.Println("GoCryptoTrader: config-helper tool.") + + if key == "" { + result, err := PromptForConfigKey() + if err != nil { + log.Fatal("Unable to obtain encryption/decryption key.") + } + key = string(result) + } + + file, err := ReadFile(inFile) + if err != nil { + log.Fatalf("Unable to read input file %s. Error: %s.", inFile, err) + } + + if ConfirmECS(file) && encrypt { + log.Println("File is already encrypted. Decrypting..") + encrypt = false + } + + if !ConfirmECS(file) && !encrypt { + var result interface{} + err := ConfirmConfigJSON(file, result) + if err != nil { + log.Fatal("File isn't in JSON format") + } + log.Println("File is already decrypted. Encrypting..") + encrypt = true + } + + var data []byte + if encrypt { + data, err = EncryptConfigFile(file, []byte(key)) + if err != nil { + log.Fatalf("Unable to encrypt config data. Error: %s.", err) + } + } else { + data, err = DecryptConfigFile(file, []byte(key)) + if err != nil { + log.Fatalf("Unable to decrypt config data. Error: %s.", err) + } + } + + err = WriteFile(outFile, data) + if err != nil { + log.Fatalf("Unable to write output file %s. Error: %s", outFile, err) + } + log.Printf("Successfully %s input file %s and wrote output to %s.\n", EncryptOrDecrypt(encrypt), inFile, outFile) +} + +func ReadFile(path string) ([]byte, error) { + file, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return file, nil +} + +func WriteFile(file string, data []byte) error { + err := ioutil.WriteFile(file, data, 0644) + if err != nil { + return err + } + return nil +} + +func PromptForConfigKey() ([]byte, error) { + var cryptoKey []byte + + for len(cryptoKey) != 32 { + log.Println("Enter password (32 characters):") + + _, err := fmt.Scanln(&cryptoKey) + if err != nil { + return nil, err + } + + if len(cryptoKey) > 32 || len(cryptoKey) < 32 { + continue + } + } + + 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 ConfirmConfigJSON(file []byte, result interface{}) error { + err := json.Unmarshal(file, &result) + + if err != nil { + return err + } + + return nil +} + +func ConfirmECS(file []byte) bool { + subslice := []byte(ENCRYPTION_CONFIRMATION_STRING) + return bytes.Contains(file, subslice) +} + +func RemoveECS(file []byte) []byte { + return bytes.Trim(file, ENCRYPTION_CONFIRMATION_STRING) +}