· 6 years ago · Nov 18, 2019, 01:18 PM
1// Package settings provides a set of tools for configuration of the command's settings.
2package settings
3
4// TODO: Decryption doesn't work. Fix that. Error: "cipher: message authentication failed"
5
6import (
7 "crypto/aes"
8 "crypto/cipher"
9 "crypto/rand"
10 "encoding/hex"
11 "encoding/json"
12 "fmt"
13 "io"
14 "io/ioutil"
15 mathrand "math/rand"
16 "os"
17 "time"
18)
19
20// urls defines the urls used by the program. Those are mostly addresses to REST API servers.
21type urls struct {
22 DanbooruURL string `json:"danbooruURL"`
23 SafebooruURL string `json:"safebooruURL"`
24 GelbooruURL string `json:"gelbooruURL"`
25 KonachanURL string `json:"konachanURL"`
26}
27
28// settings defines basic settings of the command.
29type settings struct {
30 BotToken string `json:"botToken"`
31 DatabaseIPAddress string `json:"databaseIPAddress"`
32 DatabaseUsername string `json:"databaseUsername"`
33 DatabasePassword string `json:"databasePassword"`
34 DatabaseName string `json:"databaseName"`
35 Addresses urls `json:"urls"`
36 SettingsProcessed bool `json:"settingsProcessed"`
37 IV []byte `json:"initializationVector"`
38}
39
40// BotSettings is the global instance of settings that is used to access all of the command configuration.
41var BotSettings settings
42
43func init() {
44 loadCmdLineArgs()
45 loadSettings()
46 if !BotSettings.SettingsProcessed {
47 encryptSettings()
48 } else {
49 decryptSettings()
50 }
51}
52
53// loadSettings Loads the settings in raw form from the specified settings file
54func loadSettings() {
55 if _, err := os.Stat(StartupOptions.SettingsFilePath); os.IsNotExist(err) {
56 createSettingsFile()
57 }
58
59 dat, err := ioutil.ReadFile(StartupOptions.SettingsFilePath)
60 if err != nil {
61 fmt.Printf("[Error reading the bot settings file: %s]\r\n", err)
62 panic(err)
63 }
64
65 fmt.Println("[Loading the settings from the settings file]")
66 err = json.Unmarshal([]byte(string(dat)), &BotSettings)
67 if err != nil {
68 fmt.Printf("[Error while unmarshaling the bot settings file: %s]\r\n", err)
69 panic(err)
70 }
71}
72
73// decryptSettings decrypts encrypted settings of the settings file.
74func decryptSettings() {
75 // TODO: Check if this works
76 BotSettings.BotToken = settingsGCMDecrypt(StartupOptions.AESKey, BotSettings.BotToken)
77 fmt.Println("BotToken: " + BotSettings.BotToken)
78 BotSettings.DatabaseIPAddress = settingsGCMDecrypt(StartupOptions.AESKey, BotSettings.DatabaseIPAddress)
79 fmt.Println("DatabaseIPAddress: " + BotSettings.DatabaseIPAddress)
80 BotSettings.DatabaseUsername = settingsGCMDecrypt(StartupOptions.AESKey, BotSettings.DatabaseUsername)
81 fmt.Println("DatabaseUsername: " + BotSettings.DatabaseUsername)
82 BotSettings.DatabasePassword = settingsGCMDecrypt(StartupOptions.AESKey, BotSettings.DatabasePassword)
83 fmt.Println("DatabasePassword: " + BotSettings.DatabasePassword)
84}
85
86// encryptSettings encrypts all of the necessary settings data and saves it to a file.
87func encryptSettings() {
88 // TODO: Move the elements to the struct below (delete assigning references)
89 // create a new random encryption key
90 key := string(encryptGetKey())
91 tkn := string(settingsGCMEncrypt(key, BotSettings.BotToken))
92 ipAddr := string(settingsGCMEncrypt(key, BotSettings.DatabaseIPAddress))
93 dbn := string(settingsGCMEncrypt(key, BotSettings.DatabaseUsername))
94 dbp := string(settingsGCMEncrypt(key, BotSettings.DatabasePassword))
95
96 // Save to the file and notify about the new encryption key
97 writeOverritedSettings(settings{
98 tkn,
99 ipAddr,
100 dbn,
101 dbp,
102 BotSettings.DatabaseName,
103 BotSettings.Addresses,
104 true,
105 BotSettings.IV})
106 BotSettings.SettingsProcessed = true
107 StartupOptions.AESKey = string(key)
108 fmt.Println("ENCRYPTED THE '" + StartupOptions.SettingsFilePath + "' FILE WITH KEY: '" + StartupOptions.AESKey + "' !!!")
109 fmt.Println("Please save this key and use it each time you run the bot to decrypt your bot data from settings.json")
110}
111
112// encryptGetKey generates an encryption key.
113func encryptGetKey() []byte {
114 mathrand.Seed(time.Now().UnixNano())
115 key := make([]byte, 64) // TODO: Check if works after changing to 64 from 32
116 // letters := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
117 letters := "ABCDEF0123456789"
118 for i := 0; i < len(key); i++ {
119 key[i] = letters[mathrand.Intn(len(letters))]
120 }
121 return key
122}
123
124// createSettingsFile creates the default settings file.
125func createSettingsFile() {
126 sfc := []byte(`{
127 "botToken": "token",
128 "databaseIPAddress": "localhost",
129 "databaseUsername": "username",
130 "databasePassword": "password",
131 "databaseName": "kitsune",
132 "urls": {
133 "danbooruURL": "https://danbooru.donmai.us/",
134 "safebooruURL": "https://safebooru.org/",
135 "gelbooruURL": "https://gelbooru.com/",
136 "konachanURL": "https://konachan.com/"
137 },
138 "initializedVector": [],
139 "settingsProcessed": false
140}`)
141 err := ioutil.WriteFile(StartupOptions.SettingsFilePath, sfc, 0644)
142 if err != nil {
143 fmt.Printf("[Error creating a new settings file: %s]\r\n", err)
144 panic(err)
145 }
146 fmt.Println("A new settings file has been created under the path: " + StartupOptions.SettingsFilePath + ". Please fill out the settings and re-run the bot.")
147 os.Exit(0)
148}
149
150// writeOverritedSettings overwrites the settings with new data
151func writeOverritedSettings(s settings) {
152 d, err := json.Marshal(s)
153 if err != nil {
154 fmt.Printf("[Error while marshaling settings: %s]\r\n", err)
155 panic(err)
156 }
157 err = ioutil.WriteFile(StartupOptions.SettingsFilePath, d, 0644)
158 if err != nil {
159 fmt.Printf("[Error while saving overwrited settings: %s]\r\n", err)
160 panic(err)
161 }
162}
163
164// settingsGCMEncrypt encrypts a given plain text with the given string key with the use of AES.
165func settingsGCMEncrypt(strKey string, plaintext string) []byte {
166 key, err := hex.DecodeString(strKey)
167 if err != nil {
168 fmt.Printf("[Error while decoding the string key: %s]\r\n", err)
169 panic(err)
170 }
171 text := []byte(plaintext)
172
173 block, err := aes.NewCipher(key)
174 if err != nil {
175 fmt.Printf("[Error while creating a new cipher: %s]\r\n", err)
176 panic(err)
177 }
178
179 var nonce []byte
180 if len(BotSettings.IV) == 0 {
181 nonce = make([]byte, 12)
182 if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
183 fmt.Printf("[Error while creating a nonce: %s]\r\n", err)
184 panic(err)
185 }
186 BotSettings.IV = nonce
187 } else {
188 nonce = BotSettings.IV
189 }
190
191 aesgcm, err := cipher.NewGCM(block)
192 if err != nil {
193 fmt.Printf("[Error while creating GCM: %s]\r\n", err)
194 panic(err)
195 }
196
197 ciphertext := aesgcm.Seal(nil, nonce, text, nil)
198 return ciphertext
199}
200
201// settingsGCMDecrypt decrypts a given ciphertext with the given string key with the use of AES.
202func settingsGCMDecrypt(strKey string, strCipherText string) string {
203 // Load your secret key from a safe place and reuse it across multiple
204 // Seal/Open calls. (Obviously don't use this example key for anything
205 // real.) If you want to convert a passphrase to a key, use a suitable
206 // package like bcrypt or scrypt.
207 // When decoded the key should be 16 bytes (AES-128) or 32 (AES-256).
208 key, _ := hex.DecodeString(strKey) // TODO: Handle incorrect hex key
209 ciphertext, _ := hex.DecodeString(strCipherText)
210 nonce := BotSettings.IV
211
212 block, err := aes.NewCipher(key)
213 if err != nil {
214 panic(err.Error())
215 }
216
217 aesgcm, err := cipher.NewGCM(block)
218 if err != nil {
219 panic(err.Error())
220 }
221
222 plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
223 if err != nil {
224 fmt.Printf("[Error while decrypting to plain text: %s]\r\n", err) // That's where I get the error
225 panic(err)
226 }
227
228 return string(plaintext)
229}