talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

password.c (10196B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  * Copyright (C) 2016-2023 The Claws Mail Team
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
     17  */
     18 
     19 # include <gnutls/gnutls.h>
     20 # include <gnutls/crypto.h>
     21 
     22 #include <glib.h>
     23 #include <glib/gi18n.h>
     24 
     25 #include <fcntl.h>
     26 #include <unistd.h>
     27 
     28 #include "common/pkcs5_pbkdf2.h"
     29 #include "common/utils.h"
     30 #include "account.h"
     31 #include "alertpanel.h"
     32 #include "inputdialog.h"
     33 #include "password.h"
     34 #include "passwordstore.h"
     35 #include "prefs_common.h"
     36 
     37 /* Length of stored key derivation, before base64. */
     38 #define KD_LENGTH 64
     39 
     40 /* Length of randomly generated and saved salt, used for key derivation.
     41  * Also before base64. */
     42 #define KD_SALT_LENGTH 64
     43 
     44 static void _generate_salt()
     45 {
     46 	guchar salt[KD_SALT_LENGTH];
     47 
     48 	if (prefs_common_get_prefs()->primary_passphrase_salt != NULL) {
     49 		g_free(prefs_common_get_prefs()->primary_passphrase_salt);
     50 	}
     51 
     52 	if (!get_random_bytes(salt, KD_SALT_LENGTH)) {
     53 		debug_print("Could not get random bytes for kd salt.\n");
     54 		return;
     55 	}
     56 
     57 	prefs_common_get_prefs()->primary_passphrase_salt =
     58 		g_base64_encode(salt, KD_SALT_LENGTH);
     59 }
     60 
     61 #undef KD_SALT_LENGTH
     62 
     63 static guchar *_make_key_deriv(const gchar *passphrase, guint rounds,
     64 		guint length)
     65 {
     66 	guchar *kd, *salt;
     67 	gchar *saltpref = prefs_common_get_prefs()->primary_passphrase_salt;
     68 	gsize saltlen;
     69 	gint ret;
     70 
     71 	/* Grab our salt, generating and saving a new random one if needed. */
     72 	if (saltpref == NULL || strlen(saltpref) == 0) {
     73 		_generate_salt();
     74 		saltpref = prefs_common_get_prefs()->primary_passphrase_salt;
     75 	}
     76 	salt = g_base64_decode(saltpref, &saltlen);
     77 	kd = g_malloc0(length);
     78 
     79 	ret = pkcs5_pbkdf2(passphrase, strlen(passphrase), salt, saltlen,
     80 			kd, length, rounds);
     81 
     82 	g_free(salt);
     83 
     84 	if (ret == 0) {
     85 		return kd;
     86 	}
     87 
     88 	g_free(kd);
     89 	return NULL;
     90 }
     91 
     92 #define BUFSIZE 128
     93 
     94 /* Since we can't count on having GnuTLS new enough to have
     95  * gnutls_cipher_get_iv_size(), we hardcode the IV length for now. */
     96 #define IVLEN 16
     97 
     98 gchar *password_encrypt_gnutls(const gchar *password,
     99 		const gchar *encryption_passphrase)
    100 {
    101 	gnutls_cipher_algorithm_t algo = GNUTLS_CIPHER_AES_256_CBC;
    102 	gnutls_cipher_hd_t handle;
    103 	gnutls_datum_t key, iv;
    104 	int keylen, blocklen, ret, len, i;
    105 	unsigned char *buf, *encbuf, *base, *output;
    106 	guint rounds = prefs_common_get_prefs()->primary_passphrase_pbkdf2_rounds;
    107 
    108 	g_return_val_if_fail(password != NULL, NULL);
    109 	g_return_val_if_fail(encryption_passphrase != NULL, NULL);
    110 
    111 /*	ivlen = gnutls_cipher_get_iv_size(algo);*/
    112 	keylen = gnutls_cipher_get_key_size(algo);
    113 	blocklen = gnutls_cipher_get_block_size(algo);
    114 /*	digestlen = gnutls_hash_get_len(digest); */
    115 
    116 	/* Take the passphrase and compute a key derivation of suitable
    117 	 * length to be used as encryption key for our block cipher. */
    118 	key.data = _make_key_deriv(encryption_passphrase, rounds, keylen);
    119 	key.size = keylen;
    120 
    121 	/* Prepare random IV for cipher */
    122 	iv.data = malloc(IVLEN);
    123 	iv.size = IVLEN;
    124 	if (!get_random_bytes(iv.data, IVLEN)) {
    125 		g_free(key.data);
    126 		g_free(iv.data);
    127 		return NULL;
    128 	}
    129 
    130 	/* Initialize the encryption */
    131 	ret = gnutls_cipher_init(&handle, algo, &key, &iv);
    132 	if (ret < 0) {
    133 		g_free(key.data);
    134 		g_free(iv.data);
    135 		return NULL;
    136 	}
    137 
    138 	/* Find out how big buffer (in multiples of BUFSIZE)
    139 	 * we need to store the password. */
    140 	i = 1;
    141 	len = strlen(password);
    142 	while(len >= i * BUFSIZE)
    143 		i++;
    144 	len = i * BUFSIZE;
    145 
    146 	/* Fill buf with one block of random data, our password, pad the
    147 	 * rest with zero bytes. */
    148 	buf = malloc(len + blocklen);
    149 	memset(buf, 0, len + blocklen);
    150 	if (!get_random_bytes(buf, blocklen)) {
    151 		g_free(buf);
    152 		g_free(key.data);
    153 		g_free(iv.data);
    154 		gnutls_cipher_deinit(handle);
    155 		return NULL;
    156 	}
    157 
    158 	memcpy(buf + blocklen, password, strlen(password));
    159 
    160 	/* Encrypt into encbuf */
    161 	encbuf = malloc(len + blocklen);
    162 	memset(encbuf, 0, len + blocklen);
    163 	ret = gnutls_cipher_encrypt2(handle, buf, len + blocklen,
    164 			encbuf, len + blocklen);
    165 	if (ret < 0) {
    166 		g_free(key.data);
    167 		g_free(iv.data);
    168 		g_free(buf);
    169 		g_free(encbuf);
    170 		gnutls_cipher_deinit(handle);
    171 		return NULL;
    172 	}
    173 
    174 	/* Cleanup */
    175 	gnutls_cipher_deinit(handle);
    176 	g_free(key.data);
    177 	g_free(iv.data);
    178 	g_free(buf);
    179 
    180 	/* And finally prepare the resulting string:
    181 	 * "{algorithm,rounds}base64encodedciphertext" */
    182 	base = g_base64_encode(encbuf, len + blocklen);
    183 	g_free(encbuf);
    184 	output = g_strdup_printf("{%s,%d}%s",
    185 			gnutls_cipher_get_name(algo), rounds, base);
    186 	g_free(base);
    187 
    188 	return output;
    189 }
    190 
    191 gchar *password_decrypt_gnutls(const gchar *password,
    192 		const gchar *decryption_passphrase)
    193 {
    194 	gchar **tokens, *tmp;
    195 	gnutls_cipher_algorithm_t algo;
    196 	gnutls_cipher_hd_t handle;
    197 	gnutls_datum_t key, iv;
    198 	int keylen, blocklen, ret;
    199 	gsize len;
    200 	unsigned char *buf;
    201 	guint rounds;
    202 	size_t commapos;
    203 	gboolean valid_utf8;
    204 
    205 	g_return_val_if_fail(password != NULL, NULL);
    206 	g_return_val_if_fail(decryption_passphrase != NULL, NULL);
    207 
    208 	tokens = g_strsplit_set(password, "{}", 3);
    209 
    210 	/* Parse the string, retrieving algorithm and encrypted data.
    211 	 * We expect "{algorithm,rounds}base64encodedciphertext". */
    212 	if (tokens[0] == NULL || strlen(tokens[0]) != 0 ||
    213 			tokens[1] == NULL || strlen(tokens[1]) == 0 ||
    214 			tokens[2] == NULL || strlen(tokens[2]) == 0) {
    215 		debug_print("Garbled password string.\n");
    216 		g_strfreev(tokens);
    217 		return NULL;
    218 	}
    219 
    220 	commapos = strcspn(tokens[1], ",");
    221 	if (commapos == strlen(tokens[1]) || commapos == 0) {
    222 		debug_print("Garbled algorithm substring.\n");
    223 		g_strfreev(tokens);
    224 		return NULL;
    225 	}
    226 
    227 	buf = g_strndup(tokens[1], commapos);
    228 	if ((algo = gnutls_cipher_get_id(buf)) == GNUTLS_CIPHER_UNKNOWN) {
    229 		debug_print("Password string has unknown algorithm: '%s'\n", buf);
    230 		g_free(buf);
    231 		g_strfreev(tokens);
    232 		return NULL;
    233 	}
    234 	g_free(buf);
    235 
    236 	if ((rounds = atoi(tokens[1] + commapos + 1)) <= 0) {
    237 		debug_print("Invalid number of rounds: %d\n", rounds);
    238 		g_strfreev(tokens);
    239 		return NULL;
    240 	}
    241 
    242 /*	ivlen = gnutls_cipher_get_iv_size(algo); */
    243 	keylen = gnutls_cipher_get_key_size(algo);
    244 	blocklen = gnutls_cipher_get_block_size(algo);
    245 /*	digestlen = gnutls_hash_get_len(digest); */
    246 
    247 	/* Take the passphrase and compute a key derivation of suitable
    248 	 * length to be used as encryption key for our block cipher. */
    249 	key.data = _make_key_deriv(decryption_passphrase, rounds, keylen);
    250 	key.size = keylen;
    251 
    252 	/* Prepare random IV for cipher */
    253 	iv.data = malloc(IVLEN);
    254 	iv.size = IVLEN;
    255 	if (!get_random_bytes(iv.data, IVLEN)) {
    256 		g_free(key.data);
    257 		g_free(iv.data);
    258 		g_strfreev(tokens);
    259 		return NULL;
    260 	}
    261 
    262 	/* Prepare encrypted password string for decryption. */
    263 	tmp = g_base64_decode(tokens[2], &len);
    264 	g_strfreev(tokens);
    265 	if (tmp == NULL || len == 0) {
    266 		debug_print("Failed base64-decoding of stored password string\n");
    267 		g_free(key.data);
    268 		g_free(iv.data);
    269 		if (tmp != NULL)
    270 			g_free(tmp);
    271 		return NULL;
    272 	}
    273 	debug_print("Encrypted password string length: %"G_GSIZE_FORMAT"\n", len);
    274 
    275 	/* Initialize the decryption */
    276 	ret = gnutls_cipher_init(&handle, algo, &key, &iv);
    277 	if (ret < 0) {
    278 		debug_print("Cipher init failed: %s\n", gnutls_strerror(ret));
    279 		g_free(key.data);
    280 		g_free(iv.data);
    281 		g_free(tmp);
    282 		return NULL;
    283 	}
    284 
    285 	/* Allocate the buffer to store decrypted plaintext in. */
    286 	buf = malloc(len);
    287 	memset(buf, 0, len);
    288 
    289 	/* Decrypt! */
    290 	ret = gnutls_cipher_decrypt2(handle, tmp, len,
    291 			buf, len);
    292 	g_free(tmp);
    293 	if (ret < 0) {
    294 		debug_print("Decryption failed: %s\n", gnutls_strerror(ret));
    295 		g_free(key.data);
    296 		g_free(iv.data);
    297 		g_free(buf);
    298 		gnutls_cipher_deinit(handle);
    299 		return NULL;
    300 	}
    301 
    302 	/* Cleanup */
    303 	gnutls_cipher_deinit(handle);
    304 	g_free(key.data);
    305 	g_free(iv.data);
    306 
    307 	/* 'buf+blocklen' should now be pointing to the plaintext
    308 	 * password string.
    309 	 * (The first block contains random data from the IV.)
    310 	 *
    311 	 * At this point, it should be a valid UTF-8 string. Let's make sure. */
    312 
    313 	/* First, let's assume there's just garbage and play it safe
    314 	 * by looking for a first NULL byte within the decrypted range.
    315 	 * (We could really use g_strchr_len() here instead, but Glib
    316 	 * doesn't have that.) */
    317 	if (!g_strstr_len(buf + blocklen, len - blocklen, "\0")) {
    318 		debug_print("Could not find a NULL byte in the decrypted password.\n");
    319 		valid_utf8 = FALSE;
    320 	} else {
    321 		/* There is a NULL byte, we can rely on strlen() returning
    322 		 * a sane value, so we don't read past the end of the allocated
    323 		 * buffer. */
    324 		valid_utf8 = g_utf8_validate(buf + blocklen, strlen(buf + blocklen), NULL);
    325 	}
    326 
    327 	if (!valid_utf8)
    328 		debug_print("Decrypted password is not a valid UTF-8 string!\n");
    329 	cm_return_val_if_fail(valid_utf8, NULL);
    330 
    331 	tmp = g_strndup(buf + blocklen, strlen(buf + blocklen));
    332 	memset(buf, 0, len);
    333 	g_free(buf);
    334 
    335 	return tmp;
    336 }
    337 
    338 #undef BUFSIZE
    339 
    340 #define PASSCRYPT_KEY "passkey0"
    341 
    342 gchar *password_encrypt(const gchar *password,
    343 		const gchar *encryption_passphrase)
    344 {
    345 	if (password == NULL || strlen(password) == 0) {
    346 		return NULL;
    347 	}
    348 
    349 	if (encryption_passphrase == NULL)
    350 		encryption_passphrase = PASSCRYPT_KEY;
    351 
    352 	return password_encrypt_real(password, encryption_passphrase);
    353 }
    354 
    355 gchar *password_decrypt(const gchar *password,
    356 		const gchar *decryption_passphrase)
    357 {
    358 	if (password == NULL || strlen(password) == 0) {
    359 		return NULL;
    360 	}
    361 
    362 	/* Try available crypto backend */
    363 	if (decryption_passphrase == NULL)
    364 		decryption_passphrase = PASSCRYPT_KEY;
    365 
    366 	if (*password == '{') {
    367 		debug_print("Trying to decrypt password...\n");
    368 		return password_decrypt_real(password, decryption_passphrase);
    369 	}
    370 
    371 	/* Fallback, in case the configuration is really old and
    372 	 * stored password in plaintext */
    373 	debug_print("Assuming password was stored plaintext, returning it unchanged\n");
    374 	return g_strdup(password);
    375 }