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