/cpw/tomkiewicz/masterpassword: 3ba4989ede79: Complete master pa...

Tomasz Wasilczyk tomkiewicz at cpw.pidgin.im
Mon May 13 12:45:03 EDT 2013


Changeset: 3ba4989ede794f2947cf3cac12ea5760b2485f1d
Author:	 Tomasz Wasilczyk <tomkiewicz at cpw.pidgin.im>
Date:	 2013-05-13 18:44 +0200
Branch:	 soc.2008.masterpassword
URL: https://hg.pidgin.im/cpw/tomkiewicz/masterpassword/rev/3ba4989ede79

Description:

Complete master password implementation

diffstat:

 libpurple/account.c                          |    5 +-
 libpurple/plugins/keyrings/internalkeyring.c |  523 +++++++++++++++++++++++++-
 2 files changed, 491 insertions(+), 37 deletions(-)

diffs (truncated from 727 to 300 lines):

diff --git a/libpurple/account.c b/libpurple/account.c
--- a/libpurple/account.c
+++ b/libpurple/account.c
@@ -1222,9 +1222,6 @@ request_password_ok_cb(PurpleAccount *ac
 		return;
 	}
 
-	if (!remember)
-		purple_keyring_set_password(account, NULL, NULL, NULL);
-
 	purple_account_set_remember_password(account, remember);
 
 	purple_account_set_password(account, entry, NULL, NULL);
@@ -2907,7 +2904,7 @@ connection_error_cb(PurpleConnection *gc
 static void
 password_migration_cb(PurpleAccount *account)
 {
-	g_return_if_fail(account != NULL);
+	/* account may be NULL (means: all) */
 
 	schedule_accounts_save();
 }
diff --git a/libpurple/plugins/keyrings/internalkeyring.c b/libpurple/plugins/keyrings/internalkeyring.c
--- a/libpurple/plugins/keyrings/internalkeyring.c
+++ b/libpurple/plugins/keyrings/internalkeyring.c
@@ -35,8 +35,8 @@
 #define INTKEYRING_NAME N_("Internal keyring")
 #define INTKEYRING_DESCRIPTION N_("This plugin provides the default password " \
 	"storage behaviour for libpurple.")
-#define INTKEYRING_AUTHOR      "Tomek Wasilczyk (tomkiewicz at cpw.pidgin.im)"
-#define INTKEYRING_ID          PURPLE_DEFAULT_KEYRING
+#define INTKEYRING_AUTHOR "Tomek Wasilczyk (tomkiewicz at cpw.pidgin.im)"
+#define INTKEYRING_ID PURPLE_DEFAULT_KEYRING
 
 #define INTKEYRING_VERIFY_STR "[verification-string]"
 #define INTKEYRING_PBKDF2_ITERATIONS 10000
@@ -44,19 +44,66 @@
 #define INTKEYRING_PBKDF2_ITERATIONS_MAX 1000000000
 #define INTKEYRING_KEY_LEN (256/8)
 #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/"
 
 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 GHashTable *intkeyring_passwords = NULL;
+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)
 {
@@ -71,12 +118,30 @@ intkeyring_buff_new(guchar *data, size_t
 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 = purple_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)
 {
 	PurpleCipherContext *context;
@@ -137,7 +202,27 @@ intkeyring_gen_salt(size_t len)
 	return ret;
 }
 
-/* TODO: describe encrypted contents structure */
+/**
+ * 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)
 {
@@ -153,14 +238,20 @@ intkeyring_encrypt(intkeyring_buff_t *ke
 
 	text_len = strlen(str);
 	verify_len = strlen(INTKEYRING_VERIFY_STR);
-	g_return_val_if_fail(text_len + verify_len <= sizeof(plaintext), NULL);
+	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);
 
 	context = purple_cipher_context_new_by_name("aes", NULL);
 	g_return_val_if_fail(context != NULL, NULL);
 
+	memset(plaintext, 0, plaintext_len);
 	memcpy(plaintext, str, text_len);
-	memcpy(plaintext + text_len, INTKEYRING_VERIFY_STR, verify_len);
-	plaintext_len = text_len + verify_len;
+	memcpy(plaintext + plaintext_len, INTKEYRING_VERIFY_STR, verify_len);
+	plaintext_len += verify_len;
 
 	iv = intkeyring_gen_salt(purple_cipher_context_get_block_size(context));
 	purple_cipher_context_set_iv(context, iv->data, iv->len);
@@ -243,27 +334,267 @@ intkeyring_decrypt(intkeyring_buff_t *ke
 	return ret;
 }
 
-static void
-intkeyring_change_master_password(const gchar *new_password)
+/************************************************************************/
+/* Password encryption                                                  */
+/************************************************************************/
+
+static gboolean
+intkeyring_change_masterpw(const gchar *new_password)
 {
 	intkeyring_buff_t *salt, *key;
-	gchar *verifier, *test;
+	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);
 
-	/* 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);
-	purple_debug_info("test-tmp", "verifier=[%s]\n", verifier);
+	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 = purple_base64_encode(salt->data, salt->len);
+	}
 
-	test = intkeyring_decrypt(key, verifier);
-	purple_debug_info("test-tmp", "test=[%s]\n", test);
+	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);
 
-	g_free(test);
+		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(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);
+		}
+



More information about the Commits mailing list