[Commits] [SCM] claws branch, master, updated. 3.13.2-110-gffd418a

ticho at claws-mail.org ticho at claws-mail.org
Fri Apr 8 10:06:17 CEST 2016


The branch, master has been updated
       via  ffd418aaa7b4bdf401193a96194346ff7e403b9e (commit)
       via  e63f8db4c73928a41343fcc027fd85dfc1c4af90 (commit)
      from  b998857198668a5d6b54f96c9ebc54098c2c001b (commit)

Summary of changes:
 doc/src/password_encryption.txt      |   18 +++-
 src/common/Makefile.am               |    6 +-
 src/common/pkcs5_pbkdf2.c            |  132 +++++++++++++++++++++++++++
 src/common/{md5.h => pkcs5_pbkdf2.h} |   33 +++----
 src/password.c                       |  167 +++++++++++++++++++++++++++-------
 src/prefs_common.c                   |    4 +-
 src/prefs_common.h                   |    4 +-
 7 files changed, 303 insertions(+), 61 deletions(-)
 create mode 100644 src/common/pkcs5_pbkdf2.c
 copy src/common/{md5.h => pkcs5_pbkdf2.h} (54%)


- Log -----------------------------------------------------------------
commit ffd418aaa7b4bdf401193a96194346ff7e403b9e
Author: Andrej Kacian <ticho at claws-mail.org>
Date:   Thu Apr 7 14:56:48 2016 +0200

    Use PBKDF2 with HMAC-SHA1 for master passphrase in clawsrc.
    
    The 64 bytes long key derivation is stored in 'master_passphrase'
    pref, together with number of rounds used in its computation.
    
    Introducing also two new common prefs:
    master_passphrase_salt - holds a randomly generated 64 bytes
      for use as salt with PBKDF2. Base64-encoded.
    master_passphrase_pbkdf2_rounds - number of rounds (or
      iterations) for next passphrase key derivation
    
    The latter can be tweaked by user in case they want to use more
    or less rounds, e.g. if they're running on weaker hardware and
    KD with default number of rounds takes too long.

diff --git a/doc/src/password_encryption.txt b/doc/src/password_encryption.txt
index 76a8c9e..4709f55 100644
--- a/doc/src/password_encryption.txt
+++ b/doc/src/password_encryption.txt
@@ -16,7 +16,6 @@ IV for the cipher is filled with random bytes.
 
 Encryption
 ----------
-
 We prepare a buffer 128+blocksize bytes long, with one block of random
 data at the beginning, followed by the password we want to encrypt,
 rest is padded with zero bytes.
@@ -40,8 +39,23 @@ with our password.
 
 Why the random block at the beginning?
 --------------------------------------
-
 We are taking advantage of property of CBC mode where decryption with
 a wrong IV results in only first block being garbled. Therefore we
 prepend a random block to our plaintext before encryption, and discard
 first block from plaintext after decryption.
+
+
+Master passphrase
+-----------------
+This can be any string user chooses. We store its 64 bytes long key
+derivation (KD), using PBKDF2 with HMAC-SHA1, and later check correctness
+of user-entered passphrase by making same KD from it and comparing it
+to the stored one. Only if the two KDs match, the passphrase is accepted
+and remembered for session, thus giving access to account or plugin
+passwords.
+
+Salt used for PBKDF2 is stored in 'master_passphrase_salt', encoded
+as base64. It consists of 64 randomly generated bytes.
+
+Number of rounds for PBKDF2 is stored in hidden preference
+'master_passphrase_pbkdf2_rounds'.
diff --git a/src/password.c b/src/password.c
index f24229b..ac8fe50 100644
--- a/src/password.c
+++ b/src/password.c
@@ -40,6 +40,7 @@
 
 #include "common/passcrypt.h"
 #include "common/plugin.h"
+#include "common/pkcs5_pbkdf2.h"
 #include "common/utils.h"
 #include "account.h"
 #include "alertpanel.h"
@@ -51,6 +52,87 @@
 #ifndef PASSWORD_CRYPTO_OLD
 static gchar *_master_passphrase = NULL;
 
+/* Length of stored key derivation, before base64. */
+#define KD_LENGTH 64
+
+/* Length of randomly generated and saved salt, used for key derivation.
+ * Also before base64. */
+#define KD_SALT_LENGTH 64
+
+static void _generate_salt()
+{
+#if defined G_OS_UNIX
+	int rnd;
+#elif defined G_OS_WIN32
+	HCRYPTPROV rnd;
+#endif
+	gint ret;
+	guchar salt[KD_SALT_LENGTH];
+
+	if (prefs_common_get_prefs()->master_passphrase_salt != NULL) {
+		g_free(prefs_common_get_prefs()->master_passphrase_salt);
+	}
+
+	/* Prepare our source of random data. */
+#if defined G_OS_UNIX
+	rnd = open("/dev/urandom", O_RDONLY);
+	if (rnd == -1) {
+		perror("fopen on /dev/urandom");
+#elif defined G_OS_WIN32
+	if (!CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, 0) &&
+			!CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
+		debug_print("Could not acquire a CSP handle.\n");
+#endif
+		return;
+	}
+
+#if defined G_OS_UNIX
+	ret = read(rnd, salt, KD_SALT_LENGTH);
+	if (ret != KD_SALT_LENGTH) {
+		perror("read into salt");
+		close(rnd);
+#elif defined G_OS_WIN32
+	if (!CryptGenRandom(rnd, KD_SALT_LENGTH, salt)) {
+		debug_print("Could not read random data for salt\n");
+		CryptReleaseContext(rnd, 0);
+#endif
+		return;
+	}
+
+	prefs_common_get_prefs()->master_passphrase_salt =
+		g_base64_encode(salt, KD_SALT_LENGTH);
+}
+
+#undef KD_SALT_LENGTH
+
+static guchar *_make_key_deriv(const gchar *passphrase, guint rounds)
+{
+	guchar *kd, *salt;
+	gchar *saltpref = prefs_common_get_prefs()->master_passphrase_salt;
+	gsize saltlen;
+	gint ret;
+
+	/* Grab our salt, generating and saving a new random one if needed. */
+	if (saltpref == NULL || strlen(saltpref) == 0) {
+		_generate_salt();
+		saltpref = prefs_common_get_prefs()->master_passphrase_salt;
+	}
+	salt = g_base64_decode(saltpref, &saltlen);
+	kd = g_malloc0(KD_LENGTH);
+
+	ret = pkcs5_pbkdf2(passphrase, strlen(passphrase), salt, saltlen,
+			kd, KD_LENGTH, rounds);
+
+	g_free(salt);
+
+	if (ret == 0) {
+		return kd;
+	}
+
+	g_free(kd);
+	return NULL;
+}
+
 static const gchar *master_passphrase()
 {
 	gchar *input;
@@ -88,8 +170,8 @@ static const gchar *master_passphrase()
 
 const gboolean master_passphrase_is_set()
 {
-	if (prefs_common_get_prefs()->master_passphrase_hash == NULL
-			|| strlen(prefs_common_get_prefs()->master_passphrase_hash) == 0)
+	if (prefs_common_get_prefs()->master_passphrase == NULL
+			|| strlen(prefs_common_get_prefs()->master_passphrase) == 0)
 		return FALSE;
 
 	return TRUE;
@@ -97,40 +179,52 @@ const gboolean master_passphrase_is_set()
 
 const gboolean master_passphrase_is_correct(const gchar *input)
 {
-	gchar *hash;
+	guchar *kd, *input_kd;
 	gchar **tokens;
-	gchar *stored_hash = prefs_common_get_prefs()->master_passphrase_hash;
-	const GChecksumType hashtype = G_CHECKSUM_SHA256;
-	const gssize hashlen = g_checksum_type_get_length(hashtype);
-	gssize stored_len;
+	gchar *stored_kd = prefs_common_get_prefs()->master_passphrase;
+	gsize kd_len;
+	guint rounds = 0;
+	gint ret;
 
+	g_return_val_if_fail(stored_kd != NULL && strlen(stored_kd) > 0, FALSE);
 	g_return_val_if_fail(input != NULL, FALSE);
 
-	if (stored_hash == NULL)
+	if (stored_kd == NULL)
 		return FALSE;
 
-	tokens = g_strsplit_set(stored_hash, "{}", 3);
-	if (strlen(tokens[0]) != 0 ||
-			strcmp(tokens[1], "SHA-256") ||
-			strlen(tokens[2]) == 0) {
-		debug_print("Mangled master_passphrase_hash in config, can not use it.\n");
+	tokens = g_strsplit_set(stored_kd, "{}", 3);
+	if (tokens[0] == NULL ||
+			strlen(tokens[0]) != 0 || /* nothing before { */
+			tokens[1] == NULL ||
+			strncmp(tokens[1], "PBKDF2-HMAC-SHA1,", 17) || /* correct tag */
+			strlen(tokens[1]) <= 17 || /* something after , */
+			(rounds = atoi(tokens[1] + 17)) <= 0 || /* valid rounds # */
+			tokens[2] == NULL ||
+			strlen(tokens[2]) == 0) { /* string continues after } */
+		debug_print("Mangled master_passphrase format in config, can not use it.\n");
 		g_strfreev(tokens);
 		return FALSE;
 	}
 
-	stored_hash = tokens[2];
-	stored_len = strlen(stored_hash);
-	g_return_val_if_fail(stored_len == 2*hashlen, FALSE);
+	stored_kd = tokens[2];
+	kd = g_base64_decode(stored_kd, &kd_len); /* should be 64 */
+	g_strfreev(tokens);
 
-	hash = g_compute_checksum_for_string(hashtype, input, -1);
+	if (kd_len != KD_LENGTH) {
+		debug_print("master_passphrase is %ld bytes long, should be %d.\n",
+				kd_len, KD_LENGTH);
+		g_free(kd);
+		return FALSE;
+	}
 
-	if (!strncasecmp(hash, stored_hash, stored_len)) {
-		g_free(hash);
-		g_strfreev(tokens);
+	input_kd = _make_key_deriv(input, rounds);
+	ret = memcmp(kd, input_kd, kd_len);
+
+	g_free(input_kd);
+	g_free(kd);
+
+	if (ret == 0)
 		return TRUE;
-	}
-	g_strfreev(tokens);
-	g_free(hash);
 
 	return FALSE;
 }
@@ -153,8 +247,11 @@ void master_passphrase_forget()
 
 void master_passphrase_change(const gchar *oldp, const gchar *newp)
 {
-	const GChecksumType hashtype = G_CHECKSUM_SHA256;
-	gchar *hash;
+	guchar *kd;
+	gchar *base64_kd;
+	guint rounds = prefs_common_get_prefs()->master_passphrase_pbkdf2_rounds;
+
+	g_return_if_fail(rounds > 0);
 
 	if (oldp == NULL) {
 		/* If oldp is NULL, make sure the user has to enter the
@@ -165,18 +262,20 @@ void master_passphrase_change(const gchar *oldp, const gchar *newp)
 	g_return_if_fail(oldp != NULL);
 
 	/* Update master passphrase hash in prefs */
-	if (prefs_common_get_prefs()->master_passphrase_hash != NULL)
-		g_free(prefs_common_get_prefs()->master_passphrase_hash);
+	if (prefs_common_get_prefs()->master_passphrase != NULL)
+		g_free(prefs_common_get_prefs()->master_passphrase);
 
 	if (newp != NULL) {
-		debug_print("Storing hash of new master passphrase\n");
-		hash = g_compute_checksum_for_string(hashtype, newp, -1);
-		prefs_common_get_prefs()->master_passphrase_hash =
-			g_strconcat("{SHA-256}", hash, NULL);
-		g_free(hash);
+		debug_print("Storing key derivation of new master passphrase\n");
+		kd = _make_key_deriv(newp, rounds);
+		base64_kd = g_base64_encode(kd, 64);
+		prefs_common_get_prefs()->master_passphrase =
+			g_strdup_printf("{PBKDF2-HMAC-SHA1,%d}%s", rounds, base64_kd);
+		g_free(kd);
+		g_free(base64_kd);
 	} else {
-		debug_print("Setting master_passphrase_hash to NULL\n");
-		prefs_common_get_prefs()->master_passphrase_hash = NULL;
+		debug_print("Setting master_passphrase to NULL\n");
+		prefs_common_get_prefs()->master_passphrase = NULL;
 	}
 
 	/* Now go over all accounts, reencrypting their passwords using
diff --git a/src/prefs_common.c b/src/prefs_common.c
index 8895b5f..f4acf34 100644
--- a/src/prefs_common.c
+++ b/src/prefs_common.c
@@ -1191,7 +1191,9 @@ static PrefParam param[] = {
 	{"enable_avatars", "3", &prefs_common.enable_avatars, P_INT, NULL, NULL, NULL},
 #ifndef PASSWORD_CRYPTO_OLD
 	{"use_master_passphrase", FALSE, &prefs_common.use_master_passphrase, P_BOOL, NULL, NULL, NULL },
-	{"master_passphrase_hash", "", &prefs_common.master_passphrase_hash, P_STRING, NULL, NULL, NULL },
+	{"master_passphrase", "", &prefs_common.master_passphrase, P_STRING, NULL, NULL, NULL },
+	{"master_passphrase_salt", "", &prefs_common.master_passphrase_salt, P_STRING, NULL, NULL, NULL },
+	{"master_passphrase_pbkdf2_rounds", "50000", &prefs_common.master_passphrase_pbkdf2_rounds, P_INT, NULL, NULL, NULL},
 #endif
 
 	{NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
diff --git a/src/prefs_common.h b/src/prefs_common.h
index 8a38e42..e1c2df9 100644
--- a/src/prefs_common.h
+++ b/src/prefs_common.h
@@ -541,7 +541,9 @@ struct _PrefsCommon
 
 #ifndef PASSWORD_CRYPTO_OLD
 	gboolean use_master_passphrase;
-	gchar *master_passphrase_hash;
+	gchar *master_passphrase;
+	gchar *master_passphrase_salt;
+	guint master_passphrase_pbkdf2_rounds;
 #endif
 };
 

commit e63f8db4c73928a41343fcc027fd85dfc1c4af90
Author: Andrej Kacian <ticho at claws-mail.org>
Date:   Thu Apr 7 14:49:37 2016 +0200

    Added PBKDF2 implementation, copied from OpenBSD.

diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index 65b3ab0..1dd8e91 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -41,7 +41,8 @@ libclawscommon_la_SOURCES = $(arch_sources) \
 	utils.c \
 	uuencode.c \
 	xml.c \
-	xmlprops.c
+	xmlprops.c \
+	pkcs5_pbkdf2.c
 
 clawscommonincludedir = $(pkgincludedir)/common
 clawscommoninclude_HEADERS = $(arch_headers) \
@@ -71,7 +72,8 @@ clawscommoninclude_HEADERS = $(arch_headers) \
 	uuencode.h \
 	version.h \
 	xml.h \
-	xmlprops.h
+	xmlprops.h \
+	pkcs5_pbkdf2.h
 
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/intl \
diff --git a/src/common/pkcs5_pbkdf2.c b/src/common/pkcs5_pbkdf2.c
new file mode 100644
index 0000000..f9ca702
--- /dev/null
+++ b/src/common/pkcs5_pbkdf2.c
@@ -0,0 +1,132 @@
+/* pkcs5_pbkdf2.c - Password-Based Key Derivation Function 2
+ * Copyright (c) 2008 Damien Bergamini <damien.bergamini at free.fr>
+ *
+ * Modifications for Claws Mail are:
+ * Copyright (c) 2016 the Claws Mail team
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <glib.h>
+#include <sys/types.h>
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define CHECKSUM_BLOCKLEN 64
+/*
+ * HMAC-SHA-1 (from RFC 2202).
+ */
+static void
+hmac_sha1(const guchar *text, size_t text_len, const guchar *key,
+    size_t key_len, guchar *digest)
+{
+	GChecksum *cksum;
+	gssize digestlen = g_checksum_type_get_length(G_CHECKSUM_SHA1);
+	gsize outlen;
+	guchar k_pad[CHECKSUM_BLOCKLEN];
+	guchar tk[digestlen];
+	gint i;
+
+	if (key_len > CHECKSUM_BLOCKLEN) {
+		cksum = g_checksum_new(G_CHECKSUM_SHA1);
+		g_checksum_update(cksum, key, key_len);
+		outlen = digestlen;
+		g_checksum_get_digest(cksum, tk, &outlen);
+		g_checksum_free(cksum);
+
+		key = tk;
+		key_len = digestlen;
+	}
+
+	bzero(k_pad, sizeof k_pad);
+	bcopy(key, k_pad, key_len);
+	for (i = 0; i < CHECKSUM_BLOCKLEN; i++)
+		k_pad[i] ^= 0x36;
+
+	cksum = g_checksum_new(G_CHECKSUM_SHA1);
+	g_checksum_update(cksum, k_pad, CHECKSUM_BLOCKLEN);
+	g_checksum_update(cksum, text, text_len);
+	outlen = digestlen;
+	g_checksum_get_digest(cksum, digest, &outlen);
+	g_checksum_free(cksum);
+
+	bzero(k_pad, sizeof k_pad);
+	bcopy(key, k_pad, key_len);
+	for (i = 0; i < CHECKSUM_BLOCKLEN; i++)
+		k_pad[i] ^= 0x5c;
+
+	cksum = g_checksum_new(G_CHECKSUM_SHA1);
+	g_checksum_update(cksum, k_pad, CHECKSUM_BLOCKLEN);
+	g_checksum_update(cksum, digest, digestlen);
+	outlen = digestlen;
+	g_checksum_get_digest(cksum, digest, &outlen);
+	g_checksum_free(cksum);
+}
+
+#undef CHECKSUM_BLOCKLEN
+
+/*
+ * Password-Based Key Derivation Function 2 (PKCS #5 v2.0).
+ * Code based on IEEE Std 802.11-2007, Annex H.4.2.
+ */
+gint
+pkcs5_pbkdf2(const gchar *pass, size_t pass_len, const guchar *salt,
+    size_t salt_len, guchar *key, size_t key_len, guint rounds)
+{
+	gssize digestlen = g_checksum_type_get_length(G_CHECKSUM_SHA1);
+	guchar *asalt, obuf[digestlen];
+	guchar d1[digestlen], d2[digestlen];
+	guint i, j;
+	guint count;
+	size_t r;
+
+	if (rounds < 1 || key_len == 0)
+		return -1;
+	if (salt_len == 0 || salt_len > SIZE_MAX - 4)
+		return -1;
+	if ((asalt = malloc(salt_len + 4)) == NULL)
+		return -1;
+
+	memcpy(asalt, salt, salt_len);
+
+	for (count = 1; key_len > 0; count++) {
+		asalt[salt_len + 0] = (count >> 24) & 0xff;
+		asalt[salt_len + 1] = (count >> 16) & 0xff;
+		asalt[salt_len + 2] = (count >> 8) & 0xff;
+		asalt[salt_len + 3] = count & 0xff;
+		hmac_sha1(asalt, salt_len + 4, pass, pass_len, d1);
+		memcpy(obuf, d1, sizeof(obuf));
+
+		for (i = 1; i < rounds; i++) {
+			hmac_sha1(d1, sizeof(d1), pass, pass_len, d2);
+			memcpy(d1, d2, sizeof(d1));
+			for (j = 0; j < sizeof(obuf); j++)
+				obuf[j] ^= d1[j];
+		}
+
+		r = MIN(key_len, digestlen);
+		memcpy(key, obuf, r);
+		key += r;
+		key_len -= r;
+	};
+	bzero(asalt, salt_len + 4);
+	free(asalt);
+	bzero(d1, sizeof(d1));
+	bzero(d2, sizeof(d2));
+	bzero(obuf, sizeof(obuf));
+
+	return 0;
+}
diff --git a/src/common/pkcs5_pbkdf2.h b/src/common/pkcs5_pbkdf2.h
new file mode 100644
index 0000000..f10896d
--- /dev/null
+++ b/src/common/pkcs5_pbkdf2.h
@@ -0,0 +1,32 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 2016 The Claws Mail Team
+ *
+ * pkcs5_pbkdf2.h - Password-Based Key Derivation Function 2
+ *
+ * according to the definition of MD5 in RFC 1321 from April 1992.
+ * NOTE: This is *not* the same file as the one from glibc
+ *
+ * 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 3, 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PKCS_PBKDF2_H
+#define __PKCS_PBKDF2_H
+
+/* The output will be placed into memory pointed to by key parameter.
+ * Memory needs to be pre-allocated with key_len bytes. */
+gint pkcs5_pbkdf2(const gchar *pass, size_t pass_len, const guchar *salt,
+    size_t salt_len, guchar *key, size_t key_len, guint rounds);
+
+#endif /* __PKCS_PBKDF2_H */

-----------------------------------------------------------------------


hooks/post-receive
-- 
Claws Mail


More information about the Commits mailing list