1 /*
2 
3   Copyright (c) 2010-2015 Samuel Lidén Borell <samuel@kodafritt.se>
4 
5   Permission is hereby granted, free of charge, to any person obtaining a copy
6   of this software and associated documentation files (the "Software"), to deal
7   in the Software without restriction, including without limitation the rights
8   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9   copies of the Software, and to permit persons to whom the Software is
10   furnished to do so, subject to the following conditions:
11 
12   The above copyright notice and this permission notice shall be included in
13   all copies or substantial portions of the Software.
14 
15   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21   THE SOFTWARE.
22 
23 */
24 
25 #include <glib.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include "irc.h"
29 #include "fish.h"
30 #include "misc.h"
31 #include "keystore.h"
32 #include "plugin_xchat.h"
33 
34 
35 // TODO use CBC in keystore_store_key once this is implemented
36 static char *keystore_password = NULL;
37 
38 
39 /**
40  * Opens the key store file: ~/.xchat2/blow.ini
41  */
getConfigFile()42 static GKeyFile *getConfigFile() {
43     gchar *filename = get_config_filename();
44 
45     GKeyFile *keyfile = g_key_file_new();
46     g_key_file_load_from_file(keyfile, filename,
47                               G_KEY_FILE_KEEP_COMMENTS |
48                               G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
49 
50     g_free(filename);
51     return keyfile;
52 }
53 
54 
55 /**
56  * Returns the key store password, or the default.
57  */
get_keystore_password()58 static const char *get_keystore_password() {
59     return (keystore_password != NULL ?
60         keystore_password :
61         // Silly default value...
62         "blowinikey");
63 }
64 
65 
66 /**
67  * Gets a value for a nick/channel from blow.ini. Unlike
68  * g_key_file_get_string, this function is case insensitive.
69  */
get_nick_value(GKeyFile * keyfile,const char * nick,const char * item)70 static gchar *get_nick_value(GKeyFile *keyfile, const char *nick, const char *item) {
71     gchar **group;
72     gchar **groups = g_key_file_get_groups(keyfile, NULL);
73     gchar *result = NULL;
74 
75     for (group = groups; *group != NULL; group++) {
76         if (!irc_nick_cmp(*group, nick)) {
77             result = g_key_file_get_string(keyfile, *group, item, NULL);
78             break;
79         }
80     }
81 
82     g_strfreev(groups);
83     return result;
84 }
85 
86 
87 /**
88  * Extracts a key from the key store file.
89  */
keystore_get_key(const char * nick,bool * is_cbc)90 char *keystore_get_key(const char *nick, bool *is_cbc) {
91     // Get the key
92     GKeyFile *keyfile = getConfigFile();
93     gchar *value = get_nick_value(keyfile, nick, "key");
94     gchar *cbcval = get_nick_value(keyfile, nick, "cbc");
95     if (is_cbc) {
96         // All values except 0 means "use CBC".
97         // Mircryption parses "N" and "n" as false also.
98         *is_cbc = (cbcval && *cbcval && strcmp(cbcval, "0"));
99     }
100     g_free(cbcval);
101     g_key_file_free(keyfile);
102     if (!value) return NULL;
103 
104     // TODO if the key value begins with cbc: or CBC: then it's also a CBC key
105     if (strncmp(value, "+OK ", 4) != 0) {
106         // Key is stored in plaintext
107         return import_glib_string(value);
108     } else {
109         // Key is encrypted
110         const char *encrypted = value+4;
111         const char *password = get_keystore_password();
112         char *decrypted = fish_decrypt(password, strlen(password), encrypted);
113         secure_erase(value, strlen(value));
114         g_free(value);
115         return decrypted;
116     }
117 }
118 
119 /**
120  * Deletes a nick and the associated key in the key store file.
121  */
delete_nick(GKeyFile * keyfile,const char * nick)122 static bool delete_nick(GKeyFile *keyfile, const char *nick) {
123     gchar **group;
124     gchar **groups = g_key_file_get_groups(keyfile, NULL);
125     bool ok = false;
126 
127     for (group = groups; *group != NULL; group++) {
128         if (!irc_nick_cmp(*group, nick)) {
129             ok = g_key_file_remove_group(keyfile, *group, NULL);
130             break;
131         }
132     }
133 
134     g_strfreev(groups);
135     return ok;
136 }
137 
138 /**
139  * Writes the key store file to disk.
140  */
save_keystore(GKeyFile * keyfile)141 static bool save_keystore(GKeyFile *keyfile) {
142     char *filename;
143     bool ok;
144     // Serialize
145     gsize file_length;
146     gchar *file_data = g_key_file_to_data(keyfile, &file_length, NULL);
147     if (!file_data) return false;
148 
149     // Write to file
150     filename = get_config_filename();
151     ok = g_file_set_contents(filename, file_data, file_length, NULL);
152     g_free(filename);
153     g_free(file_data);
154     return ok;
155 }
156 
157 /**
158  * Sets a key in the key store file.
159  */
keystore_store_key(const char * nick,const char * key,bool is_cbc)160 bool keystore_store_key(const char *nick, const char *key, bool is_cbc) {
161     const char *password;
162     char *encrypted;
163     char *wrapped;
164     bool ok = false;
165     GKeyFile *keyfile = getConfigFile();
166 
167     // Remove old key
168     delete_nick(keyfile, nick);
169 
170     // Add new key
171     password = get_keystore_password();
172     if (password) {
173         // Encrypt the password
174         // TODO Should use CBC here once keystore_password is implemented
175         encrypted = fish_encrypt_ecb(password, strlen(password), key);
176         if (!encrypted) goto end;
177 
178         // Prepend "+OK "
179         wrapped = g_strconcat("+OK ", encrypted, NULL);
180         g_free(encrypted);
181 
182         // Store encrypted in file
183         g_key_file_set_string(keyfile, nick, "key", wrapped);
184         free(wrapped);
185     } else {
186         // Store unencrypted in file
187         g_key_file_set_string(keyfile, nick, "key", key);
188     }
189 
190     // Store the CBC status. Note that all keys for the nick are deleted above
191     if (is_cbc) {
192         g_key_file_set_string(keyfile, nick, "cbc", "1");
193     }
194 
195     // Save key store file
196     ok = save_keystore(keyfile);
197 
198   end:
199     g_key_file_free(keyfile);
200     return ok;
201 }
202 
203 /**
204  * Sets the block cipher mode for a nick to ECB or CBC.
205  */
keystore_set_mode(const char * nick,bool is_cbc)206 bool keystore_set_mode(const char *nick, bool is_cbc) {
207     bool ok = false;
208     GKeyFile *keyfile = getConfigFile();
209 
210     if (!g_key_file_has_group(keyfile, nick)) goto end;
211 
212     // Store the CBC status
213     if (is_cbc) {
214         g_key_file_set_string(keyfile, nick, "cbc", "1");
215     } else {
216         g_key_file_remove_key(keyfile, nick, "cbc", NULL);
217     }
218 
219     // Save key store file
220     ok = save_keystore(keyfile);
221 
222   end:
223     g_key_file_free(keyfile);
224     return ok;
225 }
226 
227 /**
228  * Deletes a nick from the key store.
229  */
keystore_delete_nick(const char * nick)230 bool keystore_delete_nick(const char *nick) {
231     GKeyFile *keyfile = getConfigFile();
232 
233     // Delete entry
234     bool ok = delete_nick(keyfile, nick);
235 
236     // Save
237     if (ok) save_keystore(keyfile);
238 
239     g_key_file_free(keyfile);
240     return ok;
241 }
242 
243 
keystore_secure_free(void * ptr,size_t size)244 void keystore_secure_free(void *ptr, size_t size) {
245     secure_erase(ptr, size);
246     free(ptr);
247 }
248 
249 
250