pidgin/pidgin

closing merged branch
port-changes-from-branch-2.x.y-to-default
2020-02-03, Gary Kramlich
2f836435c33c
closing merged branch
/**
* @file internalkeyring.c internal keyring
* @ingroup plugins
*/
/* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "internal.h"
#include <purple.h>
#include <nettle/aes.h>
#include <nettle/cbc.h>
#include <nettle/pbkdf2.h>
#define INTKEYRING_NAME N_("Internal keyring")
#define INTKEYRING_DESCRIPTION N_("This plugin provides the default password " \
"storage behaviour for libpurple.")
#define INTKEYRING_AUTHORS { "Tomek Wasilczyk <twasilczyk@pidgin.im>",NULL }
#define INTKEYRING_ID PURPLE_DEFAULT_KEYRING
#define INTKEYRING_DOMAIN (g_quark_from_static_string(INTKEYRING_ID))
#define INTKEYRING_VERIFY_STR "[verification-string]"
#define INTKEYRING_PBKDF2_ITERATIONS 10000
#define INTKEYRING_PBKDF2_ITERATIONS_MIN 1000
#define INTKEYRING_PBKDF2_ITERATIONS_MAX 1000000000
#define INTKEYRING_KEY_LEN AES256_KEY_SIZE
#define INTKEYRING_ENCRYPT_BUFF_LEN 1000
#define INTKEYRING_ENCRYPTED_MIN_LEN 50
#define INTKEYRING_ENCRYPTION_METHOD "pbkdf2-sha256-aes256"
#define INTKEYRING_PREFS "/plugins/keyrings/internal/"
/* win32 build defines such macro to override read() routine */
#undef read
typedef struct
{
enum
{
INTKEYRING_REQUEST_READ,
INTKEYRING_REQUEST_SAVE
} type;
PurpleAccount *account;
gchar *password;
union
{
PurpleKeyringReadCallback read;
PurpleKeyringSaveCallback save;
} cb;
gpointer cb_data;
} intkeyring_request;
typedef struct
{
guchar *data;
size_t len;
} intkeyring_buff_t;
static intkeyring_buff_t *intkeyring_key;
static GHashTable *intkeyring_passwords = NULL;
static GHashTable *intkeyring_ciphertexts = NULL;
static gboolean intkeyring_opened = FALSE;
static gboolean intkeyring_unlocked = FALSE;
static GList *intkeyring_pending_requests = NULL;
static void *intkeyring_masterpw_uirequest = NULL;
static PurpleKeyring *keyring_handler = NULL;
static void
intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
gpointer data);
static void
intkeyring_save(PurpleAccount *account, const gchar *password,
PurpleKeyringSaveCallback cb, gpointer data);
static void
intkeyring_reencrypt_passwords(void);
static void
intkeyring_unlock(const gchar *message);
static void
intkeyring_request_free(intkeyring_request *req)
{
g_return_if_fail(req != NULL);
purple_str_wipe(req->password);
g_free(req);
}
static intkeyring_buff_t *
intkeyring_buff_new(guchar *data, size_t len)
{
intkeyring_buff_t *ret = g_new(intkeyring_buff_t, 1);
ret->data = data;
ret->len = len;
return ret;
}
static void
intkeyring_buff_free(intkeyring_buff_t *buff)
{
if (buff == NULL)
return;
memset(buff->data, 0, buff->len);
g_free(buff->data);
g_free(buff);
}
static intkeyring_buff_t *
intkeyring_buff_from_base64(const gchar *base64)
{
guchar *data;
gsize len;
data = g_base64_decode(base64, &len);
return intkeyring_buff_new(data, len);
}
/************************************************************************/
/* Generic encryption stuff */
/************************************************************************/
static intkeyring_buff_t *
intkeyring_derive_key(const gchar *passphrase, intkeyring_buff_t *salt)
{
intkeyring_buff_t *ret;
g_return_val_if_fail(passphrase != NULL, NULL);
ret = intkeyring_buff_new(g_new(guchar, INTKEYRING_KEY_LEN),
INTKEYRING_KEY_LEN);
pbkdf2_hmac_sha256(strlen(passphrase), (const uint8_t *)passphrase,
purple_prefs_get_int(INTKEYRING_PREFS"pbkdf2_iterations"),
salt->len, salt->data, ret->len, ret->data);
return ret;
}
static intkeyring_buff_t *
intkeyring_gen_salt(size_t len)
{
intkeyring_buff_t *ret;
size_t filled = 0;
g_return_val_if_fail(len > 0, NULL);
ret = intkeyring_buff_new(g_new(guchar, len), len);
while (filled < len) {
guint32 r = g_random_int();
int i;
for (i = 0; i < 4; i++) {
ret->data[filled++] = r & 0xFF;
if (filled >= len)
break;
r >>= 8;
}
}
return ret;
}
/**
* Encrypts a plaintext using the specified key.
*
* Random IV will be generated and stored with ciphertext.
*
* Encryption scheme:
* [ IV ] ++ AES( [ plaintext ] ++ [ min length padding ] ++
* [ control string ] ++ [ pkcs7 padding ] )
* where:
* IV: Random, 128bit IV.
* plaintext: The plaintext.
* min length padding: The padding used to hide the rough length of short
* plaintexts, may have a length of 0.
* control string: Constant string, verifies corectness of decryption.
* pkcs7 padding: The padding used to determine total length of encrypted
* content (also provides some verification).
*
* @param key The AES key.
* @param str The NUL-terminated plaintext.
* @return The ciphertext with IV, encoded as base64. Must be g_free'd.
*/
static gchar *
intkeyring_encrypt(intkeyring_buff_t *key, const gchar *str)
{
struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
intkeyring_buff_t *iv;
guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
size_t plaintext_len, text_len, verify_len;
int padding_len;
guchar encrypted_raw[INTKEYRING_ENCRYPT_BUFF_LEN];
ssize_t encrypted_size;
g_return_val_if_fail(key != NULL, NULL);
g_return_val_if_fail(str != NULL, NULL);
text_len = strlen(str);
verify_len = strlen(INTKEYRING_VERIFY_STR);
plaintext_len = INTKEYRING_ENCRYPTED_MIN_LEN;
if (plaintext_len < text_len)
plaintext_len = text_len;
g_return_val_if_fail(plaintext_len + verify_len <= sizeof(plaintext),
NULL);
memset(plaintext, 0, plaintext_len);
memcpy(plaintext, str, text_len);
memcpy(plaintext + plaintext_len, INTKEYRING_VERIFY_STR, verify_len);
plaintext_len += verify_len;
/* Pad PKCS7 */
padding_len = AES_BLOCK_SIZE - (plaintext_len % AES_BLOCK_SIZE);
if (plaintext_len + padding_len > INTKEYRING_ENCRYPT_BUFF_LEN) {
purple_debug_error("keyring-internal",
"Internal keyring encrypt buffer too small");
return NULL;
}
memset(plaintext + plaintext_len, padding_len, padding_len);
plaintext_len += padding_len;
/* Encrypt */
iv = intkeyring_gen_salt(AES_BLOCK_SIZE);
g_return_val_if_fail(iv != NULL, NULL);
aes256_set_encrypt_key(&ctx.ctx, key->data);
CBC_SET_IV(&ctx, iv->data);
memcpy(encrypted_raw, iv->data, iv->len);
CBC_ENCRYPT(&ctx, aes256_encrypt, plaintext_len,
encrypted_raw + iv->len, plaintext);
encrypted_size = plaintext_len;
encrypted_size += iv->len;
memset(plaintext, 0, plaintext_len);
intkeyring_buff_free(iv);
if (encrypted_size < 0)
return NULL;
return g_base64_encode(encrypted_raw, encrypted_size);
}
static gchar *
intkeyring_decrypt(intkeyring_buff_t *key, const gchar *str)
{
struct CBC_CTX(struct aes256_ctx, AES_BLOCK_SIZE) ctx;
guchar *encrypted_raw;
gsize encrypted_size;
size_t iv_len, verify_len, text_len;
guchar plaintext[INTKEYRING_ENCRYPT_BUFF_LEN];
const gchar *verify_str = NULL;
size_t plaintext_len;
guint padding_len;
guint i;
gchar *ret;
g_return_val_if_fail(key != NULL, NULL);
g_return_val_if_fail(str != NULL, NULL);
encrypted_raw = g_base64_decode(str, &encrypted_size);
g_return_val_if_fail(encrypted_raw != NULL, NULL);
iv_len = AES_BLOCK_SIZE;
if (encrypted_size < iv_len) {
g_free(encrypted_raw);
return NULL;
}
/* Decrypt */
aes256_set_decrypt_key(&ctx.ctx, key->data);
CBC_SET_IV(&ctx, encrypted_raw);
CBC_DECRYPT(&ctx, aes256_decrypt, encrypted_size - iv_len,
plaintext, encrypted_raw + iv_len);
plaintext_len = encrypted_size - iv_len;
g_free(encrypted_raw);
/* Unpad PKCS7 */
padding_len = plaintext[plaintext_len - 1];
if (padding_len == 0 || padding_len > AES_BLOCK_SIZE ||
padding_len > plaintext_len) {
purple_debug_warning("internal-keyring",
"Invalid padding length: %d (total %" G_GSIZE_FORMAT
") - most probably, the key was invalid\n",
padding_len, plaintext_len);
return NULL;
}
plaintext_len -= padding_len;
for (i = 0; i < padding_len; ++i) {
if (plaintext[plaintext_len + i] != padding_len) {
purple_debug_warning("internal-keyring",
"Padding doesn't match at pos %d (found %02x, "
"expected %02x) - "
"most probably, the key was invalid\n",
i, plaintext[plaintext_len + i], padding_len);
return NULL;
}
}
memset(plaintext + plaintext_len, 0, padding_len);
/* Verify */
verify_len = strlen(INTKEYRING_VERIFY_STR);
/* Don't remove the len > 0 check! */
if (plaintext_len > 0 && (gsize)plaintext_len > verify_len &&
plaintext[plaintext_len] == '\0')
{
verify_str = (gchar*)plaintext + plaintext_len - verify_len;
}
if (g_strcmp0(verify_str, INTKEYRING_VERIFY_STR) != 0) {
purple_debug_warning("keyring-internal",
"Verification failed on decryption\n");
memset(plaintext, 0, sizeof(plaintext));
return NULL;
}
g_assert(plaintext_len > 0);
text_len = plaintext_len - verify_len;
ret = g_new(gchar, text_len + 1);
memcpy(ret, plaintext, text_len);
memset(plaintext, 0, plaintext_len);
ret[text_len] = '\0';
return ret;
}
/************************************************************************/
/* Password encryption */
/************************************************************************/
static gboolean
intkeyring_change_masterpw(const gchar *new_password)
{
intkeyring_buff_t *salt, *key;
gchar *verifier = NULL, *salt_b64 = NULL;
int old_iter;
gboolean succ = TRUE;;
g_return_val_if_fail(intkeyring_unlocked, FALSE);
old_iter = purple_prefs_get_int(INTKEYRING_PREFS "pbkdf2_iterations");
purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
purple_prefs_get_int(INTKEYRING_PREFS
"pbkdf2_desired_iterations"));
salt = intkeyring_gen_salt(32);
key = intkeyring_derive_key(new_password, salt);
if (salt && key && key->len == INTKEYRING_KEY_LEN) {
/* In fact, verify str will be concatenated twice before
* encryption (it's used as a suffix in encryption routine),
* but it's not a problem.
*/
verifier = intkeyring_encrypt(key, INTKEYRING_VERIFY_STR);
salt_b64 = g_base64_encode(salt->data, salt->len);
}
if (!verifier || !salt_b64) {
purple_debug_error("keyring-internal", "Failed to change "
"master password\n");
succ = FALSE;
purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_iterations",
old_iter);
} else {
purple_prefs_set_string(INTKEYRING_PREFS "pbkdf2_salt",
salt_b64);
purple_prefs_set_string(INTKEYRING_PREFS "key_verifier",
verifier);
intkeyring_buff_free(intkeyring_key);
intkeyring_key = key;
key = NULL;
intkeyring_reencrypt_passwords();
purple_signal_emit(purple_keyring_get_handle(),
"password-migration", NULL);
}
g_free(salt_b64);
g_free(verifier);
intkeyring_buff_free(salt);
intkeyring_buff_free(key);
return succ;
}
static void
intkeyring_process_queue(void)
{
GList *requests, *it;
gboolean open = intkeyring_unlocked;
requests = g_list_first(intkeyring_pending_requests);
intkeyring_pending_requests = NULL;
for (it = requests; it != NULL; it = g_list_next(it)) {
intkeyring_request *req = it->data;
if (open && req->type == INTKEYRING_REQUEST_READ) {
intkeyring_read(req->account, req->cb.read,
req->cb_data);
} else if (open && req->type == INTKEYRING_REQUEST_SAVE) {
intkeyring_save(req->account, req->password,
req->cb.save, req->cb_data);
} else if (open)
g_assert_not_reached();
else if (req->cb.read != NULL /* || req->cb.write != NULL */ ) {
GError *error = g_error_new_literal(PURPLE_KEYRING_ERROR,
PURPLE_KEYRING_ERROR_CANCELLED,
_("Operation cancelled."));
if (req->type == INTKEYRING_REQUEST_READ) {
req->cb.read(req->account, NULL, error,
req->cb_data);
} else if (req->type == INTKEYRING_REQUEST_SAVE)
req->cb.save(req->account, error, req->cb_data);
else
g_assert_not_reached();
g_error_free(error);
}
intkeyring_request_free(req);
}
g_list_free(requests);
}
static void
intkeyring_decrypt_password(PurpleAccount *account, const gchar *ciphertext)
{
gchar *plaintext;
plaintext = intkeyring_decrypt(intkeyring_key, ciphertext);
if (plaintext == NULL) {
purple_debug_warning("keyring-internal",
"Failed to decrypt a password\n");
return;
}
g_hash_table_replace(intkeyring_passwords, account, plaintext);
}
static void
intkeyring_encrypt_password_if_needed(PurpleAccount *account)
{
const gchar *plaintext;
gchar *ciphertext;
if (intkeyring_key == NULL) {
g_hash_table_remove(intkeyring_ciphertexts, account);
return;
}
ciphertext = g_hash_table_lookup(intkeyring_ciphertexts, account);
if (ciphertext != NULL)
return;
plaintext = g_hash_table_lookup(intkeyring_passwords, account);
if (plaintext == NULL)
return;
ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
g_return_if_fail(ciphertext != NULL);
g_hash_table_replace(intkeyring_ciphertexts, account, ciphertext);
}
static void
intkeyring_encrypt_passwords_if_needed_it(gpointer account, gpointer plaintext,
gpointer _unused)
{
intkeyring_encrypt_password_if_needed(account);
}
static void
intkeyring_reencrypt_passwords(void)
{
g_hash_table_remove_all(intkeyring_ciphertexts);
g_hash_table_foreach(intkeyring_passwords,
intkeyring_encrypt_passwords_if_needed_it, NULL);
}
static void
intkeyring_unlock_decrypt(gpointer account, gpointer ciphertext,
gpointer _unused)
{
intkeyring_decrypt_password(account, ciphertext);
}
/************************************************************************/
/* Opening and unlocking keyring */
/************************************************************************/
static void
intkeyring_unlock_ok(gpointer _unused,
PurpleRequestFields *fields)
{
const gchar *masterpw;
gchar *verifier;
intkeyring_buff_t *salt, *key;
intkeyring_masterpw_uirequest = NULL;
if (g_strcmp0(purple_prefs_get_string(INTKEYRING_PREFS
"encryption_method"), INTKEYRING_ENCRYPTION_METHOD) != 0)
{
purple_notify_error(NULL,
_("Unlocking internal keyring"),
_("Selected encryption method is not supported."),
_("Most probably, your passwords were encrypted with "
"newer Pidgin/libpurple version, please update."),
NULL);
return;
}
masterpw = purple_request_fields_get_string(fields, "password");
if (masterpw == NULL || masterpw[0] == '\0') {
intkeyring_unlock(_("No password entered."));
return;
}
salt = intkeyring_buff_from_base64(purple_prefs_get_string(
INTKEYRING_PREFS "pbkdf2_salt"));
key = intkeyring_derive_key(masterpw, salt);
intkeyring_buff_free(salt);
verifier = intkeyring_decrypt(key, purple_prefs_get_string(
INTKEYRING_PREFS "key_verifier"));
if (g_strcmp0(verifier, INTKEYRING_VERIFY_STR) != 0) {
g_free(verifier);
intkeyring_buff_free(key);
intkeyring_unlock(_("Invalid master password entered, "
"try again."));
return;
}
g_free(verifier);
intkeyring_key = key;
intkeyring_unlocked = TRUE;
g_hash_table_foreach(intkeyring_ciphertexts,
intkeyring_unlock_decrypt, NULL);
intkeyring_process_queue();
}
static void
intkeyring_unlock_cancel(gpointer _unused,
PurpleRequestFields *fields)
{
intkeyring_masterpw_uirequest = NULL;
intkeyring_process_queue();
}
static void
intkeyring_unlock(const gchar *message)
{
PurpleRequestFields *fields;
PurpleRequestFieldGroup *group;
PurpleRequestField *field;
const gchar *primary_msg, *secondary_msg = NULL;
if (intkeyring_unlocked || intkeyring_masterpw_uirequest != NULL)
return;
if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
intkeyring_unlocked = TRUE;
intkeyring_process_queue();
return;
}
fields = purple_request_fields_new();
group = purple_request_field_group_new(NULL);
purple_request_fields_add_group(fields, group);
field = purple_request_field_string_new("password",
_("Master password"), "", FALSE);
purple_request_field_string_set_masked(field, TRUE);
purple_request_field_group_add_field(group, field);
primary_msg = _("Please, enter master password");
if (message) {
secondary_msg = primary_msg;
primary_msg = message;
}
intkeyring_masterpw_uirequest = purple_request_fields(NULL,
_("Unlocking internal keyring"),
primary_msg, secondary_msg, fields,
_("OK"), G_CALLBACK(intkeyring_unlock_ok),
_("Cancel"), G_CALLBACK(intkeyring_unlock_cancel),
NULL, NULL);
}
static void
intkeyring_open(void)
{
if (intkeyring_opened)
return;
intkeyring_opened = TRUE;
intkeyring_passwords = g_hash_table_new_full(g_direct_hash,
g_direct_equal, NULL, (GDestroyNotify)purple_str_wipe);
intkeyring_ciphertexts = g_hash_table_new_full(g_direct_hash,
g_direct_equal, NULL, g_free);
}
/************************************************************************/
/* Keyring interface implementation */
/************************************************************************/
static void
intkeyring_read(PurpleAccount *account, PurpleKeyringReadCallback cb,
gpointer data)
{
const char *password;
GError *error;
intkeyring_open();
if (!intkeyring_unlocked && g_hash_table_lookup(intkeyring_ciphertexts,
account) != NULL)
{
intkeyring_request *req = g_new0(intkeyring_request, 1);
req->type = INTKEYRING_REQUEST_READ;
req->account = account;
req->cb.read = cb;
req->cb_data = data;
intkeyring_pending_requests =
g_list_append(intkeyring_pending_requests, req);
intkeyring_unlock(NULL);
return;
}
password = g_hash_table_lookup(intkeyring_passwords, account);
if (password != NULL) {
purple_debug_misc("keyring-internal",
"Got password for account %s (%s).\n",
purple_account_get_username(account),
purple_account_get_protocol_id(account));
if (cb != NULL)
cb(account, password, NULL, data);
} else {
if (purple_debug_is_verbose()) {
purple_debug_misc("keyring-internal",
"No password for account %s (%s).\n",
purple_account_get_username(account),
purple_account_get_protocol_id(account));
}
error = g_error_new_literal(PURPLE_KEYRING_ERROR,
PURPLE_KEYRING_ERROR_NOPASSWORD,
_("Password not found."));
if (cb != NULL)
cb(account, NULL, error, data);
g_error_free(error);
}
}
static void
intkeyring_save(PurpleAccount *account, const gchar *password,
PurpleKeyringSaveCallback cb, gpointer data)
{
void *old_password;
intkeyring_open();
if (!intkeyring_unlocked) {
intkeyring_request *req;
if (password == NULL) {
g_hash_table_remove(intkeyring_ciphertexts, account);
g_hash_table_remove(intkeyring_passwords, account);
if (cb)
cb(account, NULL, data);
return;
}
req = g_new0(intkeyring_request, 1);
req->type = INTKEYRING_REQUEST_SAVE;
req->account = account;
req->password = g_strdup(password);
req->cb.save = cb;
req->cb_data = data;
intkeyring_pending_requests =
g_list_append(intkeyring_pending_requests, req);
intkeyring_unlock(NULL);
return;
}
g_hash_table_remove(intkeyring_ciphertexts, account);
old_password = g_hash_table_lookup(intkeyring_passwords, account);
if (password == NULL)
g_hash_table_remove(intkeyring_passwords, account);
else {
g_hash_table_replace(intkeyring_passwords, account,
g_strdup(password));
}
intkeyring_encrypt_password_if_needed(account);
if (!(password == NULL && old_password == NULL)) {
purple_debug_misc("keyring-internal",
"Password %s for account %s (%s).\n",
(password == NULL ? "removed" : (old_password == NULL ?
"saved" : "updated")),
purple_account_get_username(account),
purple_account_get_protocol_id(account));
} else if (purple_debug_is_verbose()) {
purple_debug_misc("keyring-internal",
"Password for account %s (%s) was already removed.\n",
purple_account_get_username(account),
purple_account_get_protocol_id(account));
}
if (cb != NULL)
cb(account, NULL, data);
}
static void
intkeyring_close(void)
{
if (!intkeyring_opened)
return;
intkeyring_opened = FALSE;
intkeyring_unlocked = FALSE;
if (intkeyring_masterpw_uirequest) {
purple_request_close(PURPLE_REQUEST_FIELDS,
intkeyring_masterpw_uirequest);
}
g_warn_if_fail(intkeyring_masterpw_uirequest == NULL);
g_warn_if_fail(intkeyring_pending_requests == NULL);
intkeyring_buff_free(intkeyring_key);
intkeyring_key = NULL;
g_hash_table_destroy(intkeyring_passwords);
intkeyring_passwords = NULL;
g_hash_table_destroy(intkeyring_ciphertexts);
intkeyring_ciphertexts = NULL;
}
static gboolean
intkeyring_import_password(PurpleAccount *account, const char *mode,
const char *data, GError **error)
{
g_return_val_if_fail(account != NULL, FALSE);
g_return_val_if_fail(data != NULL, FALSE);
intkeyring_open();
if (mode == NULL)
mode = "cleartext";
if (g_strcmp0(mode, "cleartext") == 0) {
g_hash_table_replace(intkeyring_passwords, account,
g_strdup(data));
return TRUE;
} else if (g_strcmp0(mode, "ciphertext") == 0) {
if (intkeyring_unlocked)
intkeyring_decrypt_password(account, data);
else {
g_hash_table_replace(intkeyring_ciphertexts, account,
g_strdup(data));
}
return TRUE;
} else {
g_set_error_literal(error, PURPLE_KEYRING_ERROR,
PURPLE_KEYRING_ERROR_BACKENDFAIL,
_("Invalid password storage mode."));
return FALSE;
}
}
static gboolean
intkeyring_export_password(PurpleAccount *account, const char **mode,
char **data, GError **error, GDestroyNotify *destroy)
{
gchar *ciphertext = NULL;
intkeyring_open();
if (!purple_prefs_get_bool(INTKEYRING_PREFS "encrypt_passwords")) {
gchar *cleartext = g_hash_table_lookup(intkeyring_passwords,
account);
if (cleartext == NULL)
return FALSE;
*mode = "cleartext";
*data = g_strdup(cleartext);
*destroy = (GDestroyNotify)purple_str_wipe;
return TRUE;
}
ciphertext = g_strdup(g_hash_table_lookup(intkeyring_ciphertexts,
account));
if (ciphertext == NULL && intkeyring_unlocked) {
gchar *plaintext = g_hash_table_lookup(intkeyring_passwords,
account);
if (plaintext == NULL)
return FALSE;
purple_debug_warning("keyring-internal", "Encrypted password "
"is missing at export (it shouldn't happen)\n");
ciphertext = intkeyring_encrypt(intkeyring_key, plaintext);
}
if (ciphertext == NULL)
return FALSE;
*mode = "ciphertext";
*data = ciphertext;
*destroy = g_free;
return TRUE;
}
static PurpleRequestFields *
intkeyring_read_settings(void)
{
PurpleRequestFields *fields;
PurpleRequestFieldGroup *group;
PurpleRequestField *field;
fields = purple_request_fields_new();
group = purple_request_field_group_new(NULL);
purple_request_fields_add_group(fields, group);
field = purple_request_field_bool_new("encrypt_passwords",
_("Encrypt passwords"), purple_prefs_get_bool(
INTKEYRING_PREFS "encrypt_passwords"));
purple_request_field_group_add_field(group, field);
group = purple_request_field_group_new(_("Master password"));
purple_request_fields_add_group(fields, group);
field = purple_request_field_string_new("passphrase1",
_("New passphrase:"), "", FALSE);
purple_request_field_string_set_masked(field, TRUE);
purple_request_field_group_add_field(group, field);
field = purple_request_field_string_new("passphrase2",
_("New passphrase (again):"), "", FALSE);
purple_request_field_string_set_masked(field, TRUE);
purple_request_field_group_add_field(group, field);
group = purple_request_field_group_new(_("Advanced settings"));
purple_request_fields_add_group(fields, group);
field = purple_request_field_int_new("pbkdf2_desired_iterations",
_("Number of PBKDF2 iterations:"), purple_prefs_get_int(
INTKEYRING_PREFS "pbkdf2_desired_iterations"),
INTKEYRING_PBKDF2_ITERATIONS_MIN,
INTKEYRING_PBKDF2_ITERATIONS_MAX);
purple_request_field_group_add_field(group, field);
return fields;
}
static gboolean
intkeyring_apply_settings(void *notify_handle,
PurpleRequestFields *fields)
{
const gchar *passphrase, *passphrase2;
intkeyring_unlock(_("You have to unlock the keyring first."));
if (!intkeyring_unlocked)
return FALSE;
passphrase = purple_request_fields_get_string(fields, "passphrase1");
if (g_strcmp0(passphrase, "") == 0)
passphrase = NULL;
passphrase2 = purple_request_fields_get_string(fields, "passphrase2");
if (g_strcmp0(passphrase2, "") == 0)
passphrase2 = NULL;
if (g_strcmp0(passphrase, passphrase2) != 0) {
purple_notify_error(notify_handle,
_("Internal keyring settings"),
_("Passphrases do not match"), NULL, NULL);
return FALSE;
}
if (purple_request_fields_get_bool(fields, "encrypt_passwords") &&
!passphrase && !intkeyring_key)
{
purple_notify_error(notify_handle,
_("Internal keyring settings"),
_("You have to set up a Master password, if you want "
"to enable encryption"), NULL, NULL);
return FALSE;
}
if (!purple_request_fields_get_bool(fields, "encrypt_passwords") &&
passphrase)
{
purple_notify_error(notify_handle,
_("Internal keyring settings"),
_("You don't need any master password, if you won't "
"enable passwords encryption"), NULL, NULL);
return FALSE;
}
purple_prefs_set_string(INTKEYRING_PREFS "encryption_method",
INTKEYRING_ENCRYPTION_METHOD);
purple_prefs_set_int(INTKEYRING_PREFS "pbkdf2_desired_iterations",
purple_request_fields_get_integer(fields,
"pbkdf2_desired_iterations"));
if (passphrase != NULL) {
if (!intkeyring_change_masterpw(passphrase))
return FALSE;
}
purple_prefs_set_bool(INTKEYRING_PREFS "encrypt_passwords",
purple_request_fields_get_bool(fields, "encrypt_passwords"));
purple_signal_emit(purple_keyring_get_handle(), "password-migration",
NULL);
return TRUE;
}
static PurplePluginInfo *
plugin_query(GError **error)
{
const gchar * const authors[] = INTKEYRING_AUTHORS;
return purple_plugin_info_new(
"id", INTKEYRING_ID,
"name", INTKEYRING_NAME,
"version", DISPLAY_VERSION,
"category", N_("Keyring"),
"summary", "Internal Keyring Plugin",
"description", INTKEYRING_DESCRIPTION,
"authors", authors,
"website", PURPLE_WEBSITE,
"abi-version", PURPLE_ABI_VERSION,
"flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL,
NULL
);
}
static gboolean
plugin_load(PurplePlugin *plugin, GError **error)
{
purple_prefs_add_none("/plugins/keyrings");
purple_prefs_add_none("/plugins/keyrings/internal");
purple_prefs_add_bool(INTKEYRING_PREFS "encrypt_passwords", FALSE);
purple_prefs_add_string(INTKEYRING_PREFS "encryption_method",
INTKEYRING_ENCRYPTION_METHOD);
purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_desired_iterations",
INTKEYRING_PBKDF2_ITERATIONS);
purple_prefs_add_int(INTKEYRING_PREFS "pbkdf2_iterations",
INTKEYRING_PBKDF2_ITERATIONS);
purple_prefs_add_string(INTKEYRING_PREFS "pbkdf2_salt", "");
purple_prefs_add_string(INTKEYRING_PREFS "key_verifier", "");
keyring_handler = purple_keyring_new();
purple_keyring_set_name(keyring_handler, _(INTKEYRING_NAME));
purple_keyring_set_id(keyring_handler, INTKEYRING_ID);
purple_keyring_set_read_password(keyring_handler,
intkeyring_read);
purple_keyring_set_save_password(keyring_handler,
intkeyring_save);
purple_keyring_set_close_keyring(keyring_handler,
intkeyring_close);
purple_keyring_set_import_password(keyring_handler,
intkeyring_import_password);
purple_keyring_set_export_password(keyring_handler,
intkeyring_export_password);
purple_keyring_set_read_settings(keyring_handler,
intkeyring_read_settings);
purple_keyring_set_apply_settings(keyring_handler,
intkeyring_apply_settings);
purple_keyring_register(keyring_handler);
return TRUE;
}
static gboolean
plugin_unload(PurplePlugin *plugin, GError **error)
{
if (purple_keyring_get_inuse() == keyring_handler) {
g_set_error(error, INTKEYRING_DOMAIN, 0, "The keyring is currently "
"in use.");
purple_debug_warning("keyring-internal",
"keyring in use, cannot unload\n");
return FALSE;
}
intkeyring_close();
purple_keyring_unregister(keyring_handler);
purple_keyring_free(keyring_handler);
keyring_handler = NULL;
if (intkeyring_key != NULL) {
purple_debug_warning("keyring-internal", "Master key should be "
"cleaned up at this point\n");
intkeyring_buff_free(intkeyring_key);
intkeyring_key = NULL;
}
return TRUE;
}
PURPLE_PLUGIN_INIT(internal_keyring, plugin_query, plugin_load, plugin_unload);