Merged pidgin/main into default
[pidgin-git.git] / libpurple / plugins / keyrings / internalkeyring.c
blob7492b8302368f2d12a47d3fe78ca2e91ccae51c0
1 /**
2 * @file internalkeyring.c internal keyring
3 * @ingroup plugins
4 */
6 /* purple
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program ; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #include "internal.h"
28 #include "account.h"
29 #include "debug.h"
30 #include "keyring.h"
31 #include "plugins.h"
32 #include "version.h"
34 #include <nettle/aes.h>
35 #include <nettle/cbc.h>
36 #include <nettle/pbkdf2.h>
38 #define INTKEYRING_NAME N_("Internal keyring")
39 #define INTKEYRING_DESCRIPTION N_("This plugin provides the default password " \
40 "storage behaviour for libpurple.")
41 #define INTKEYRING_AUTHORS { "Tomek Wasilczyk <twasilczyk@pidgin.im>",NULL }
42 #define INTKEYRING_ID PURPLE_DEFAULT_KEYRING
43 #define INTKEYRING_DOMAIN (g_quark_from_static_string(INTKEYRING_ID))
45 #define INTKEYRING_VERIFY_STR "[verification-string]"
46 #define INTKEYRING_PBKDF2_ITERATIONS 10000
47 #define INTKEYRING_PBKDF2_ITERATIONS_MIN 1000
48 #define INTKEYRING_PBKDF2_ITERATIONS_MAX 1000000000
49 #define INTKEYRING_KEY_LEN AES256_KEY_SIZE
50 #define INTKEYRING_ENCRYPT_BUFF_LEN 1000
51 #define INTKEYRING_ENCRYPTED_MIN_LEN 50
52 #define INTKEYRING_ENCRYPTION_METHOD "pbkdf2-sha256-aes256"
54 #define INTKEYRING_PREFS "/plugins/keyrings/internal/"
56 /* win32 build defines such macro to override read() routine */
57 #undef read
59 typedef struct
61 enum
63 INTKEYRING_REQUEST_READ,
64 INTKEYRING_REQUEST_SAVE
65 } type;
66 PurpleAccount *account;
67 gchar *password;
68 union
70 PurpleKeyringReadCallback read;
71 PurpleKeyringSaveCallback save;
72 } cb;
73 gpointer cb_data;
74 } intkeyring_request;
76 typedef struct
78 guchar *data;
79 size_t len;
80 } intkeyring_buff_t;
82 static intkeyring_buff_t *intkeyring_key;
83 static GHashTable *intkeyring_passwords = NULL;
84 static GHashTable *intkeyring_ciphertexts = NULL;
86 static gboolean intkeyring_opened = FALSE;
87 static gboolean intkeyring_unlocked = FALSE;
89 static GList *intkeyring_pending_requests = NULL;
90 static void *intkeyring_masterpw_uirequest = NULL;
92 static PurpleKeyring *keyring_handler = NULL;
94 static void
95 intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
96 gpointer data);
97 static void
98 intkeyring_save(PurpleAccount *account, const gchar *password,
99 PurpleKeyringSaveCallback cb, gpointer data);
100 static void
101 intkeyring_reencrypt_passwords(void);
102 static void
103 intkeyring_unlock(const gchar *message);
105 static void
106 intkeyring_request_free(intkeyring_request *req)
108 g_return_if_fail(req != NULL);
110 purple_str_wipe(req->password);
111 g_free(req);
114 static intkeyring_buff_t *
115 intkeyring_buff_new(guchar *data, size_t len)
117 intkeyring_buff_t *ret = g_new(intkeyring_buff_t, 1);
119 ret->data = data;
120 ret->len = len;
122 return ret;
125 static void
126 intkeyring_buff_free(intkeyring_buff_t *buff)
128 if (buff == NULL)
129 return;
131 memset(buff->data, 0, buff->len);
132 g_free(buff->data);
133 g_free(buff);
136 static intkeyring_buff_t *
137 intkeyring_buff_from_base64(const gchar *base64)
139 guchar *data;
140 gsize len;
142 data = g_base64_decode(base64, &len);
144 return intkeyring_buff_new(data, len);
147 /************************************************************************/
148 /* Generic encryption stuff */
149 /************************************************************************/
151 static intkeyring_buff_t *
152 intkeyring_derive_key(const gchar *passphrase, intkeyring_buff_t *salt)
154 intkeyring_buff_t *ret;
156 g_return_val_if_fail(passphrase != NULL, NULL);
158 ret = intkeyring_buff_new(g_new(guchar, INTKEYRING_KEY_LEN),
159 INTKEYRING_KEY_LEN);
161 pbkdf2_hmac_sha256(strlen(passphrase), (const uint8_t *)passphrase,
162 purple_prefs_get_int(INTKEYRING_PREFS"pbkdf2_iterations"),
163 salt->len, salt->data, ret->len, ret->data);
165 return ret;
168 static intkeyring_buff_t *
169 intkeyring_gen_salt(size_t len)
171 intkeyring_buff_t *ret;
172 size_t filled = 0;
174 g_return_val_if_fail(len > 0, NULL);
176 ret = intkeyring_buff_new(g_new(guchar, len), len);
178 while (filled < len) {
179 guint32 r = g_random_int();
180 int i;
182 for (i = 0; i < 4; i++) {
183 ret->data[filled++] = r & 0xFF;
184 if (filled >= len)
185 break;
186 r >>= 8;
190 return ret;
194 * Encrypts a plaintext using the specified key.
196 * Random IV will be generated and stored with ciphertext.
198 * Encryption scheme:
199 * [ IV ] ++ AES( [ plaintext ] ++ [ min length padding ] ++
200 * [ control string ] ++ [ pkcs7 padding ] )
201 * where:
202 * IV: Random, 128bit IV.
203 * plaintext: The plaintext.
204 * min length padding: The padding used to hide the rough length of short
205 * plaintexts, may have a length of 0.
206 * control string: Constant string, verifies corectness of decryption.
207 * pkcs7 padding: The padding used to determine total length of encrypted
208 * content (also provides some verification).
210 * @param key The AES key.
211 * @param str The NUL-terminated plaintext.
212 * @return The ciphertext with IV, encoded as base64. Must be g_free'd.
214 static gchar *
215 intkeyring_encrypt(intkeyring_buff_t *key, const gchar *str)
217 struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
218 intkeyring_buff_t *iv;
219 guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
220 size_t plaintext_len, text_len, verify_len;
221 int padding_len;
222 guchar encrypted_raw[INTKEYRING_ENCRYPT_BUFF_LEN];
223 ssize_t encrypted_size;
225 g_return_val_if_fail(key != NULL, NULL);
226 g_return_val_if_fail(str != NULL, NULL);
228 text_len = strlen(str);
229 verify_len = strlen(INTKEYRING_VERIFY_STR);
230 plaintext_len = INTKEYRING_ENCRYPTED_MIN_LEN;
231 if (plaintext_len < text_len)
232 plaintext_len = text_len;
234 g_return_val_if_fail(plaintext_len + verify_len <= sizeof(plaintext),
235 NULL);
237 memset(plaintext, 0, plaintext_len);
238 memcpy(plaintext, str, text_len);
239 memcpy(plaintext + plaintext_len, INTKEYRING_VERIFY_STR, verify_len);
240 plaintext_len += verify_len;
242 /* Pad PKCS7 */
243 padding_len = AES_BLOCK_SIZE - (plaintext_len % AES_BLOCK_SIZE);
245 if (plaintext_len + padding_len > INTKEYRING_ENCRYPT_BUFF_LEN) {
246 purple_debug_error("keyring-internal",
247 "Internal keyring encrypt buffer too small");
248 return NULL;
251 memset(plaintext + plaintext_len, padding_len, padding_len);
252 plaintext_len += padding_len;
254 /* Encrypt */
255 iv = intkeyring_gen_salt(AES_BLOCK_SIZE);
256 g_return_val_if_fail(iv != NULL, NULL);
257 aes256_set_encrypt_key(&ctx.ctx, key->data);
258 CBC_SET_IV(&ctx, iv->data);
260 memcpy(encrypted_raw, iv->data, iv->len);
262 CBC_ENCRYPT(&ctx, aes256_encrypt, plaintext_len,
263 encrypted_raw + iv->len, plaintext);
264 encrypted_size = plaintext_len;
265 encrypted_size += iv->len;
267 memset(plaintext, 0, plaintext_len);
268 intkeyring_buff_free(iv);
270 if (encrypted_size < 0)
271 return NULL;
273 return g_base64_encode(encrypted_raw, encrypted_size);
277 static gchar *
278 intkeyring_decrypt(intkeyring_buff_t *key, const gchar *str)
280 struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
281 guchar *encrypted_raw;
282 gsize encrypted_size;
283 size_t iv_len, verify_len, text_len;
284 guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
285 const gchar *verify_str = NULL;
286 size_t plaintext_len;
287 guint padding_len;
288 guint i;
289 gchar *ret;
291 g_return_val_if_fail(key != NULL, NULL);
292 g_return_val_if_fail(str != NULL, NULL);
294 encrypted_raw = g_base64_decode(str, &encrypted_size);
295 g_return_val_if_fail(encrypted_raw != NULL, NULL);
297 iv_len = AES_BLOCK_SIZE;
298 if (encrypted_size < iv_len) {
299 g_free(encrypted_raw);
300 return NULL;
303 /* Decrypt */
304 aes256_set_decrypt_key(&ctx.ctx, key->data);
305 CBC_SET_IV(&ctx, encrypted_raw);
306 CBC_DECRYPT(&ctx, aes256_decrypt, encrypted_size - iv_len,
307 plaintext, encrypted_raw + iv_len);
308 plaintext_len = encrypted_size - iv_len;
309 g_free(encrypted_raw);
311 /* Unpad PKCS7 */
312 padding_len = plaintext[plaintext_len - 1];
313 if (padding_len == 0 || padding_len > AES_BLOCK_SIZE ||
314 padding_len > plaintext_len) {
315 purple_debug_warning("internal-keyring",
316 "Invalid padding length: %d (total %" G_GSIZE_FORMAT
317 ") - most probably, the key was invalid\n",
318 padding_len, plaintext_len);
319 return NULL;
322 plaintext_len -= padding_len;
323 for (i = 0; i < padding_len; ++i) {
324 if (plaintext[plaintext_len + i] != padding_len) {
325 purple_debug_warning("internal-keyring",
326 "Padding doesn't match at pos %d (found %02x, "
327 "expected %02x) - "
328 "most probably, the key was invalid\n",
329 i, plaintext[plaintext_len + i], padding_len);
330 return NULL;
334 memset(plaintext + plaintext_len, 0, padding_len);
336 /* Verify */
337 verify_len = strlen(INTKEYRING_VERIFY_STR);
338 /* Don't remove the len > 0 check! */
339 if (plaintext_len > 0 && (gsize)plaintext_len > verify_len &&
340 plaintext[plaintext_len] == '\0')
342 verify_str = (gchar*)plaintext + plaintext_len - verify_len;
345 if (g_strcmp0(verify_str, INTKEYRING_VERIFY_STR) != 0) {
346 purple_debug_warning("keyring-internal",
347 "Verification failed on decryption\n");
348 memset(plaintext, 0, sizeof(plaintext));
349 return NULL;
351 g_assert(plaintext_len > 0);
353 text_len = plaintext_len - verify_len;
354 ret = g_new(gchar, text_len + 1);
355 memcpy(ret, plaintext, text_len);
356 memset(plaintext, 0, plaintext_len);
357 ret[text_len] = '\0';
359 return ret;
362 /************************************************************************/
363 /* Password encryption */
364 /************************************************************************/
366 static gboolean
367 intkeyring_change_masterpw(const gchar *new_password)
369 intkeyring_buff_t *salt, *key;
370 gchar *verifier = NULL, *salt_b64 = NULL;
371 int old_iter;
372 gboolean succ = TRUE;;
374 g_return_val_if_fail(intkeyring_unlocked, FALSE);
376 old_iter = purple_prefs_get_int(INTKEYRING_PREFS "pbkdf2_iterations");
377 purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
378 purple_prefs_get_int(INTKEYRING_PREFS
379 "pbkdf2_desired_iterations"));
381 salt = intkeyring_gen_salt(32);
382 key = intkeyring_derive_key(new_password, salt);
384 if (salt && key && key->len == INTKEYRING_KEY_LEN) {
385 /* In fact, verify str will be concatenated twice before
386 * encryption (it's used as a suffix in encryption routine),
387 * but it's not a problem.
389 verifier = intkeyring_encrypt(key, INTKEYRING_VERIFY_STR);
390 salt_b64 = g_base64_encode(salt->data, salt->len);
393 if (!verifier || !salt_b64) {
394 purple_debug_error("keyring-internal", "Failed to change "
395 "master password\n");
396 succ = FALSE;
397 purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
398 old_iter);
399 } else {
400 purple_prefs_set_string(INTKEYRING_PREFS "pbkdf2_salt",
401 salt_b64);
402 purple_prefs_set_string(INTKEYRING_PREFS "key_verifier",
403 verifier);
405 intkeyring_buff_free(intkeyring_key);
406 intkeyring_key = key;
407 key = NULL;
409 intkeyring_reencrypt_passwords();
411 purple_signal_emit(purple_keyring_get_handle(),
412 "password-migration", NULL);
415 g_free(salt_b64);
416 g_free(verifier);
417 intkeyring_buff_free(salt);
418 intkeyring_buff_free(key);
420 return succ;
423 static void
424 intkeyring_process_queue(void)
426 GList *requests, *it;
427 gboolean open = intkeyring_unlocked;
429 requests = g_list_first(intkeyring_pending_requests);
430 intkeyring_pending_requests = NULL;
432 for (it = requests; it != NULL; it = g_list_next(it)) {
433 intkeyring_request *req = it->data;
435 if (open && req->type == INTKEYRING_REQUEST_READ) {
436 intkeyring_read(req->account, req->cb.read,
437 req->cb_data);
438 } else if (open && req->type == INTKEYRING_REQUEST_SAVE) {
439 intkeyring_save(req->account, req->password,
440 req->cb.save, req->cb_data);
441 } else if (open)
442 g_assert_not_reached();
443 else if (req->cb.read != NULL /* || req->cb.write != NULL */ ) {
444 GError *error = g_error_new(PURPLE_KEYRING_ERROR,
445 PURPLE_KEYRING_ERROR_CANCELLED,
446 _("Operation cancelled."));
447 if (req->type == INTKEYRING_REQUEST_READ) {
448 req->cb.read(req->account, NULL, error,
449 req->cb_data);
450 } else if (req->type == INTKEYRING_REQUEST_SAVE)
451 req->cb.save(req->account, error, req->cb_data);
452 else
453 g_assert_not_reached();
454 g_error_free(error);
457 intkeyring_request_free(req);
459 g_list_free(requests);
462 static void
463 intkeyring_decrypt_password(PurpleAccount *account, const gchar *ciphertext)
465 gchar *plaintext;
467 plaintext = intkeyring_decrypt(intkeyring_key, ciphertext);
468 if (plaintext == NULL) {
469 purple_debug_warning("keyring-internal",
470 "Failed to decrypt a password\n");
471 return;
474 g_hash_table_replace(intkeyring_passwords, account, plaintext);
477 static void
478 intkeyring_encrypt_password_if_needed(PurpleAccount *account)
480 const gchar *plaintext;
481 gchar *ciphertext;
483 if (intkeyring_key == NULL) {
484 g_hash_table_remove(intkeyring_ciphertexts, account);
485 return;
488 ciphertext = g_hash_table_lookup(intkeyring_ciphertexts, account);
489 if (ciphertext != NULL)
490 return;
492 plaintext = g_hash_table_lookup(intkeyring_passwords, account);
493 if (plaintext == NULL)
494 return;
496 ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
497 g_return_if_fail(ciphertext != NULL);
499 g_hash_table_replace(intkeyring_ciphertexts, account, ciphertext);
502 static void
503 intkeyring_encrypt_passwords_if_needed_it(gpointer account, gpointer plaintext,
504 gpointer _unused)
506 intkeyring_encrypt_password_if_needed(account);
509 static void
510 intkeyring_reencrypt_passwords(void)
512 g_hash_table_remove_all(intkeyring_ciphertexts);
513 g_hash_table_foreach(intkeyring_passwords,
514 intkeyring_encrypt_passwords_if_needed_it, NULL);
517 static void
518 intkeyring_unlock_decrypt(gpointer account, gpointer ciphertext,
519 gpointer _unused)
521 intkeyring_decrypt_password(account, ciphertext);
524 /************************************************************************/
525 /* Opening and unlocking keyring */
526 /************************************************************************/
528 static void
529 intkeyring_unlock_ok(gpointer _unused,
530 PurpleRequestFields *fields)
532 const gchar *masterpw;
533 gchar *verifier;
534 intkeyring_buff_t *salt, *key;
536 intkeyring_masterpw_uirequest = NULL;
538 if (g_strcmp0(purple_prefs_get_string(INTKEYRING_PREFS
539 "encryption_method"), INTKEYRING_ENCRYPTION_METHOD) != 0)
541 purple_notify_error(NULL,
542 _("Unlocking internal keyring"),
543 _("Selected encryption method is not supported."),
544 _("Most probably, your passwords were encrypted with "
545 "newer Pidgin/libpurple version, please update."),
546 NULL);
547 return;
550 masterpw = purple_request_fields_get_string(fields, "password");
552 if (masterpw == NULL || masterpw[0] == '\0') {
553 intkeyring_unlock(_("No password entered."));
554 return;
557 salt = intkeyring_buff_from_base64(purple_prefs_get_string(
558 INTKEYRING_PREFS "pbkdf2_salt"));
559 key = intkeyring_derive_key(masterpw, salt);
560 intkeyring_buff_free(salt);
562 verifier = intkeyring_decrypt(key, purple_prefs_get_string(
563 INTKEYRING_PREFS "key_verifier"));
565 if (g_strcmp0(verifier, INTKEYRING_VERIFY_STR) != 0) {
566 g_free(verifier);
567 intkeyring_buff_free(key);
568 intkeyring_unlock(_("Invalid master password entered, "
569 "try again."));
570 return;
573 g_free(verifier);
574 intkeyring_key = key;
575 intkeyring_unlocked = TRUE;
577 g_hash_table_foreach(intkeyring_ciphertexts,
578 intkeyring_unlock_decrypt, NULL);
580 intkeyring_process_queue();
583 static void
584 intkeyring_unlock_cancel(gpointer _unused,
585 PurpleRequestFields *fields)
587 intkeyring_masterpw_uirequest = NULL;
588 intkeyring_process_queue();
591 static void
592 intkeyring_unlock(const gchar *message)
594 PurpleRequestFields *fields;
595 PurpleRequestFieldGroup *group;
596 PurpleRequestField *field;
597 const gchar *primary_msg, *secondary_msg = NULL;
599 if (intkeyring_unlocked || intkeyring_masterpw_uirequest != NULL)
600 return;
602 if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
603 intkeyring_unlocked = TRUE;
604 intkeyring_process_queue();
605 return;
608 fields = purple_request_fields_new();
609 group = purple_request_field_group_new(NULL);
610 purple_request_fields_add_group(fields, group);
612 field = purple_request_field_string_new("password",
613 _("Master password"), "", FALSE);
614 purple_request_field_string_set_masked(field, TRUE);
615 purple_request_field_group_add_field(group, field);
617 primary_msg = _("Please, enter master password");
618 if (message) {
619 secondary_msg = primary_msg;
620 primary_msg = message;
623 intkeyring_masterpw_uirequest = purple_request_fields(NULL,
624 _("Unlocking internal keyring"),
625 primary_msg, secondary_msg, fields,
626 _("OK"), G_CALLBACK(intkeyring_unlock_ok),
627 _("Cancel"), G_CALLBACK(intkeyring_unlock_cancel),
628 NULL, NULL);
631 static void
632 intkeyring_open(void)
634 if (intkeyring_opened)
635 return;
636 intkeyring_opened = TRUE;
638 intkeyring_passwords = g_hash_table_new_full(g_direct_hash,
639 g_direct_equal, NULL, (GDestroyNotify)purple_str_wipe);
640 intkeyring_ciphertexts = g_hash_table_new_full(g_direct_hash,
641 g_direct_equal, NULL, g_free);
644 /************************************************************************/
645 /* Keyring interface implementation */
646 /************************************************************************/
648 static void
649 intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
650 gpointer data)
652 const char *password;
653 GError *error;
655 intkeyring_open();
657 if (!intkeyring_unlocked && g_hash_table_lookup(intkeyring_ciphertexts,
658 account) != NULL)
660 intkeyring_request *req = g_new0(intkeyring_request, 1);
662 req->type = INTKEYRING_REQUEST_READ;
663 req->account = account;
664 req->cb.read = cb;
665 req->cb_data = data;
666 intkeyring_pending_requests =
667 g_list_append(intkeyring_pending_requests, req);
669 intkeyring_unlock(NULL);
670 return;
673 password = g_hash_table_lookup(intkeyring_passwords, account);
675 if (password != NULL) {
676 purple_debug_misc("keyring-internal",
677 "Got password for account %s (%s).\n",
678 purple_account_get_username(account),
679 purple_account_get_protocol_id(account));
680 if (cb != NULL)
681 cb(account, password, NULL, data);
682 } else {
683 if (purple_debug_is_verbose()) {
684 purple_debug_misc("keyring-internal",
685 "No password for account %s (%s).\n",
686 purple_account_get_username(account),
687 purple_account_get_protocol_id(account));
689 error = g_error_new(PURPLE_KEYRING_ERROR,
690 PURPLE_KEYRING_ERROR_NOPASSWORD,
691 _("Password not found."));
692 if (cb != NULL)
693 cb(account, NULL, error, data);
694 g_error_free(error);
698 static void
699 intkeyring_save(PurpleAccount *account, const gchar *password,
700 PurpleKeyringSaveCallback cb, gpointer data)
702 void *old_password;
704 intkeyring_open();
706 if (!intkeyring_unlocked) {
707 intkeyring_request *req;
709 if (password == NULL) {
710 g_hash_table_remove(intkeyring_ciphertexts, account);
711 g_hash_table_remove(intkeyring_passwords, account);
712 if (cb)
713 cb(account, NULL, data);
714 return;
717 req = g_new0(intkeyring_request, 1);
718 req->type = INTKEYRING_REQUEST_SAVE;
719 req->account = account;
720 req->password = g_strdup(password);
721 req->cb.save = cb;
722 req->cb_data = data;
723 intkeyring_pending_requests =
724 g_list_append(intkeyring_pending_requests, req);
726 intkeyring_unlock(NULL);
727 return;
730 g_hash_table_remove(intkeyring_ciphertexts, account);
732 old_password = g_hash_table_lookup(intkeyring_passwords, account);
734 if (password == NULL)
735 g_hash_table_remove(intkeyring_passwords, account);
736 else {
737 g_hash_table_replace(intkeyring_passwords, account,
738 g_strdup(password));
741 intkeyring_encrypt_password_if_needed(account);
743 if (!(password == NULL && old_password == NULL)) {
744 purple_debug_misc("keyring-internal",
745 "Password %s for account %s (%s).\n",
746 (password == NULL ? "removed" : (old_password == NULL ?
747 "saved" : "updated")),
748 purple_account_get_username(account),
749 purple_account_get_protocol_id(account));
750 } else if (purple_debug_is_verbose()) {
751 purple_debug_misc("keyring-internal",
752 "Password for account %s (%s) was already removed.\n",
753 purple_account_get_username(account),
754 purple_account_get_protocol_id(account));
757 if (cb != NULL)
758 cb(account, NULL, data);
761 static void
762 intkeyring_close(void)
764 if (!intkeyring_opened)
765 return;
766 intkeyring_opened = FALSE;
767 intkeyring_unlocked = FALSE;
769 if (intkeyring_masterpw_uirequest) {
770 purple_request_close(PURPLE_REQUEST_FIELDS,
771 intkeyring_masterpw_uirequest);
773 g_warn_if_fail(intkeyring_masterpw_uirequest == NULL);
774 g_warn_if_fail(intkeyring_pending_requests == NULL);
776 intkeyring_buff_free(intkeyring_key);
777 intkeyring_key = NULL;
778 g_hash_table_destroy(intkeyring_passwords);
779 intkeyring_passwords = NULL;
780 g_hash_table_destroy(intkeyring_ciphertexts);
781 intkeyring_ciphertexts = NULL;
784 static gboolean
785 intkeyring_import_password(PurpleAccount *account, const char *mode,
786 const char *data, GError **error)
788 g_return_val_if_fail(account != NULL, FALSE);
789 g_return_val_if_fail(data != NULL, FALSE);
791 intkeyring_open();
793 if (mode == NULL)
794 mode = "cleartext";
796 if (g_strcmp0(mode, "cleartext") == 0) {
797 g_hash_table_replace(intkeyring_passwords, account,
798 g_strdup(data));
799 return TRUE;
800 } else if (g_strcmp0(mode, "ciphertext") == 0) {
801 if (intkeyring_unlocked)
802 intkeyring_decrypt_password(account, data);
803 else {
804 g_hash_table_replace(intkeyring_ciphertexts, account,
805 g_strdup(data));
807 return TRUE;
808 } else {
809 if (error != NULL) {
810 *error = g_error_new(PURPLE_KEYRING_ERROR,
811 PURPLE_KEYRING_ERROR_BACKENDFAIL,
812 _("Invalid password storage mode."));
814 return FALSE;
818 static gboolean
819 intkeyring_export_password(PurpleAccount *account, const char **mode,
820 char **data, GError **error, GDestroyNotify *destroy)
822 gchar *ciphertext = NULL;
823 intkeyring_open();
825 if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
826 gchar *cleartext = g_hash_table_lookup(intkeyring_passwords,
827 account);
829 if (cleartext == NULL)
830 return FALSE;
832 *mode = "cleartext";
833 *data = g_strdup(cleartext);
834 *destroy = (GDestroyNotify)purple_str_wipe;
835 return TRUE;
838 ciphertext = g_strdup(g_hash_table_lookup(intkeyring_ciphertexts,
839 account));
841 if (ciphertext == NULL && intkeyring_unlocked) {
842 gchar *plaintext = g_hash_table_lookup(intkeyring_passwords,
843 account);
845 if (plaintext == NULL)
846 return FALSE;
848 purple_debug_warning("keyring-internal", "Encrypted password "
849 "is missing at export (it shouldn't happen)\n");
850 ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
853 if (ciphertext == NULL)
854 return FALSE;
856 *mode = "ciphertext";
857 *data = ciphertext;
858 *destroy = (GDestroyNotify)g_free;
859 return TRUE;
862 static PurpleRequestFields *
863 intkeyring_read_settings(void)
865 PurpleRequestFields *fields;
866 PurpleRequestFieldGroup *group;
867 PurpleRequestField *field;
869 fields = purple_request_fields_new();
870 group = purple_request_field_group_new(NULL);
871 purple_request_fields_add_group(fields, group);
873 field = purple_request_field_bool_new("encrypt_passwords",
874 _("Encrypt passwords"), purple_prefs_get_bool(
875 INTKEYRING_PREFS "encrypt_passwords"));
876 purple_request_field_group_add_field(group, field);
878 group = purple_request_field_group_new(_("Master password"));
879 purple_request_fields_add_group(fields, group);
881 field = purple_request_field_string_new("passphrase1",
882 _("New passphrase:"), "", FALSE);
883 purple_request_field_string_set_masked(field, TRUE);
884 purple_request_field_group_add_field(group, field);
886 field = purple_request_field_string_new("passphrase2",
887 _("New passphrase (again):"), "", FALSE);
888 purple_request_field_string_set_masked(field, TRUE);
889 purple_request_field_group_add_field(group, field);
891 group = purple_request_field_group_new(_("Advanced settings"));
892 purple_request_fields_add_group(fields, group);
894 field = purple_request_field_int_new("pbkdf2_desired_iterations",
895 _("Number of PBKDF2 iterations:"), purple_prefs_get_int(
896 INTKEYRING_PREFS "pbkdf2_desired_iterations"),
897 INTKEYRING_PBKDF2_ITERATIONS_MIN,
898 INTKEYRING_PBKDF2_ITERATIONS_MAX);
899 purple_request_field_group_add_field(group, field);
901 return fields;
904 static gboolean
905 intkeyring_apply_settings(void *notify_handle,
906 PurpleRequestFields *fields)
908 const gchar *passphrase, *passphrase2;
910 intkeyring_unlock(_("You have to unlock the keyring first."));
911 if (!intkeyring_unlocked)
912 return FALSE;
914 passphrase = purple_request_fields_get_string(fields, "passphrase1");
915 if (g_strcmp0(passphrase, "") == 0)
916 passphrase = NULL;
917 passphrase2 = purple_request_fields_get_string(fields, "passphrase2");
918 if (g_strcmp0(passphrase2, "") == 0)
919 passphrase2 = NULL;
921 if (g_strcmp0(passphrase, passphrase2) != 0) {
922 purple_notify_error(notify_handle,
923 _("Internal keyring settings"),
924 _("Passphrases do not match"), NULL, NULL);
925 return FALSE;
928 if (purple_request_fields_get_bool(fields, "encrypt_passwords") &&
929 !passphrase && !intkeyring_key)
931 purple_notify_error(notify_handle,
932 _("Internal keyring settings"),
933 _("You have to set up a Master password, if you want "
934 "to enable encryption"), NULL, NULL);
935 return FALSE;
938 if (!purple_request_fields_get_bool(fields, "encrypt_passwords") &&
939 passphrase)
941 purple_notify_error(notify_handle,
942 _("Internal keyring settings"),
943 _("You don't need any master password, if you won't "
944 "enable passwords encryption"), NULL, NULL);
945 return FALSE;
948 purple_prefs_set_string(INTKEYRING_PREFS "encryption_method",
949 INTKEYRING_ENCRYPTION_METHOD);
951 purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_desired_iterations",
952 purple_request_fields_get_integer(fields,
953 "pbkdf2_desired_iterations"));
955 if (passphrase != NULL) {
956 if (!intkeyring_change_masterpw(passphrase))
957 return FALSE;
960 purple_prefs_set_bool(INTKEYRING_PREFS "encrypt_passwords",
961 purple_request_fields_get_bool(fields, "encrypt_passwords"));
963 purple_signal_emit(purple_keyring_get_handle(), "password-migration",
964 NULL);
967 return TRUE;
970 static PurplePluginInfo *
971 plugin_query(GError **error)
973 const gchar * const authors[] = INTKEYRING_AUTHORS;
975 return purple_plugin_info_new(
976 "id", INTKEYRING_ID,
977 "name", INTKEYRING_NAME,
978 "version", DISPLAY_VERSION,
979 "category", N_("Keyring"),
980 "summary", "Internal Keyring Plugin",
981 "description", INTKEYRING_DESCRIPTION,
982 "authors", authors,
983 "website", PURPLE_WEBSITE,
984 "abi-version", PURPLE_ABI_VERSION,
985 "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL,
986 NULL
990 static gboolean
991 plugin_load(PurplePlugin *plugin, GError **error)
993 purple_prefs_add_none("/plugins/keyrings");
994 purple_prefs_add_none("/plugins/keyrings/internal");
995 purple_prefs_add_bool(INTKEYRING_PREFS "encrypt_passwords", FALSE);
996 purple_prefs_add_string(INTKEYRING_PREFS "encryption_method",
997 INTKEYRING_ENCRYPTION_METHOD);
998 purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_desired_iterations",
999 INTKEYRING_PBKDF2_ITERATIONS);
1000 purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_iterations",
1001 INTKEYRING_PBKDF2_ITERATIONS);
1002 purple_prefs_add_string(INTKEYRING_PREFS "pbkdf2_salt", "");
1003 purple_prefs_add_string(INTKEYRING_PREFS "key_verifier", "");
1005 keyring_handler = purple_keyring_new();
1007 purple_keyring_set_name(keyring_handler, _(INTKEYRING_NAME));
1008 purple_keyring_set_id(keyring_handler, INTKEYRING_ID);
1009 purple_keyring_set_read_password(keyring_handler,
1010 intkeyring_read);
1011 purple_keyring_set_save_password(keyring_handler,
1012 intkeyring_save);
1013 purple_keyring_set_close_keyring(keyring_handler,
1014 intkeyring_close);
1015 purple_keyring_set_import_password(keyring_handler,
1016 intkeyring_import_password);
1017 purple_keyring_set_export_password(keyring_handler,
1018 intkeyring_export_password);
1019 purple_keyring_set_read_settings(keyring_handler,
1020 intkeyring_read_settings);
1021 purple_keyring_set_apply_settings(keyring_handler,
1022 intkeyring_apply_settings);
1024 purple_keyring_register(keyring_handler);
1026 return TRUE;
1029 static gboolean
1030 plugin_unload(PurplePlugin *plugin, GError **error)
1032 if (purple_keyring_get_inuse() == keyring_handler) {
1033 g_set_error(error, INTKEYRING_DOMAIN, 0, "The keyring is currently "
1034 "in use.");
1035 purple_debug_warning("keyring-internal",
1036 "keyring in use, cannot unload\n");
1037 return FALSE;
1040 intkeyring_close();
1042 purple_keyring_unregister(keyring_handler);
1043 purple_keyring_free(keyring_handler);
1044 keyring_handler = NULL;
1046 if (intkeyring_key != NULL) {
1047 purple_debug_warning("keyring-internal", "Master key should be "
1048 "cleaned up at this point\n");
1049 intkeyring_buff_free(intkeyring_key);
1050 intkeyring_key = NULL;
1053 return TRUE;
1056 PURPLE_PLUGIN_INIT(internal_keyring, plugin_query, plugin_load, plugin_unload);