2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2016 The Claws Mail Team
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.
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.
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/>.
22 #include "claws-features.h"
25 #ifdef PASSWORD_CRYPTO_GNUTLS
26 # include <gnutls/gnutls.h>
27 # include <gnutls/crypto.h>
31 #include <glib/gi18n.h>
36 #elif defined G_OS_WIN32
41 #include "common/passcrypt.h"
42 #include "common/plugin.h"
43 #include "common/pkcs5_pbkdf2.h"
44 #include "common/utils.h"
46 #include "alertpanel.h"
47 #include "inputdialog.h"
49 #include "passwordstore.h"
50 #include "prefs_common.h"
52 #ifndef PASSWORD_CRYPTO_OLD
53 static gchar *_master_passphrase = NULL;
55 /* Length of stored key derivation, before base64. */
58 /* Length of randomly generated and saved salt, used for key derivation.
59 * Also before base64. */
60 #define KD_SALT_LENGTH 64
62 static void _generate_salt()
66 #elif defined G_OS_WIN32
70 guchar salt[KD_SALT_LENGTH];
72 if (prefs_common_get_prefs()->master_passphrase_salt != NULL) {
73 g_free(prefs_common_get_prefs()->master_passphrase_salt);
76 /* Prepare our source of random data. */
78 rnd = open("/dev/urandom", O_RDONLY);
80 perror("fopen on /dev/urandom");
81 #elif defined G_OS_WIN32
82 if (!CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, 0) &&
83 !CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
84 debug_print("Could not acquire a CSP handle.\n");
90 ret = read(rnd, salt, KD_SALT_LENGTH);
91 if (ret != KD_SALT_LENGTH) {
92 perror("read into salt");
94 #elif defined G_OS_WIN32
95 if (!CryptGenRandom(rnd, KD_SALT_LENGTH, salt)) {
96 debug_print("Could not read random data for salt\n");
97 CryptReleaseContext(rnd, 0);
102 #if defined G_OS_UNIX
104 #elif defined G_OS_WIN32
105 CryptReleaseContext(rnd, 0);
108 prefs_common_get_prefs()->master_passphrase_salt =
109 g_base64_encode(salt, KD_SALT_LENGTH);
112 #undef KD_SALT_LENGTH
114 static guchar *_make_key_deriv(const gchar *passphrase, guint rounds)
117 gchar *saltpref = prefs_common_get_prefs()->master_passphrase_salt;
121 /* Grab our salt, generating and saving a new random one if needed. */
122 if (saltpref == NULL || strlen(saltpref) == 0) {
124 saltpref = prefs_common_get_prefs()->master_passphrase_salt;
126 salt = g_base64_decode(saltpref, &saltlen);
127 kd = g_malloc0(KD_LENGTH);
129 ret = pkcs5_pbkdf2(passphrase, strlen(passphrase), salt, saltlen,
130 kd, KD_LENGTH, rounds);
142 static const gchar *master_passphrase()
145 gboolean end = FALSE;
147 if (!prefs_common_get_prefs()->use_master_passphrase) {
148 return PASSCRYPT_KEY;
151 if (_master_passphrase != NULL) {
152 debug_print("Master passphrase is in memory, offering it.\n");
153 return _master_passphrase;
157 input = input_dialog_with_invisible(_("Input master passphrase"),
158 _("Input master passphrase"), NULL);
161 debug_print("Cancel pressed at master passphrase dialog.\n");
165 if (master_passphrase_is_correct(input)) {
166 debug_print("Entered master passphrase seems to be correct, remembering it.\n");
167 _master_passphrase = input;
170 alertpanel_error(_("Incorrect master passphrase."));
174 return _master_passphrase;
177 const gboolean master_passphrase_is_set()
179 if (prefs_common_get_prefs()->master_passphrase == NULL
180 || strlen(prefs_common_get_prefs()->master_passphrase) == 0)
186 const gboolean master_passphrase_is_correct(const gchar *input)
188 guchar *kd, *input_kd;
190 gchar *stored_kd = prefs_common_get_prefs()->master_passphrase;
195 g_return_val_if_fail(stored_kd != NULL && strlen(stored_kd) > 0, FALSE);
196 g_return_val_if_fail(input != NULL, FALSE);
198 if (stored_kd == NULL)
201 tokens = g_strsplit_set(stored_kd, "{}", 3);
202 if (tokens[0] == NULL ||
203 strlen(tokens[0]) != 0 || /* nothing before { */
205 strncmp(tokens[1], "PBKDF2-HMAC-SHA1,", 17) || /* correct tag */
206 strlen(tokens[1]) <= 17 || /* something after , */
207 (rounds = atoi(tokens[1] + 17)) <= 0 || /* valid rounds # */
209 strlen(tokens[2]) == 0) { /* string continues after } */
210 debug_print("Mangled master_passphrase format in config, can not use it.\n");
215 stored_kd = tokens[2];
216 kd = g_base64_decode(stored_kd, &kd_len); /* should be 64 */
219 if (kd_len != KD_LENGTH) {
220 debug_print("master_passphrase is %ld bytes long, should be %d.\n",
226 input_kd = _make_key_deriv(input, rounds);
227 ret = memcmp(kd, input_kd, kd_len);
238 gboolean master_passphrase_is_entered()
240 return (_master_passphrase == NULL) ? FALSE : TRUE;
243 void master_passphrase_forget()
245 /* If master passphrase is currently in memory (entered by user),
246 * get rid of it. User will have to enter the new one again. */
247 if (_master_passphrase != NULL) {
248 memset(_master_passphrase, 0, strlen(_master_passphrase));
249 g_free(_master_passphrase);
250 _master_passphrase = NULL;
254 void master_passphrase_change(const gchar *oldp, const gchar *newp)
258 guint rounds = prefs_common_get_prefs()->master_passphrase_pbkdf2_rounds;
260 g_return_if_fail(rounds > 0);
263 /* If oldp is NULL, make sure the user has to enter the
264 * current master passphrase before being able to change it. */
265 master_passphrase_forget();
266 oldp = master_passphrase();
268 g_return_if_fail(oldp != NULL);
270 /* Update master passphrase hash in prefs */
271 if (prefs_common_get_prefs()->master_passphrase != NULL)
272 g_free(prefs_common_get_prefs()->master_passphrase);
275 debug_print("Storing key derivation of new master passphrase\n");
276 kd = _make_key_deriv(newp, rounds);
277 base64_kd = g_base64_encode(kd, 64);
278 prefs_common_get_prefs()->master_passphrase =
279 g_strdup_printf("{PBKDF2-HMAC-SHA1,%d}%s", rounds, base64_kd);
283 debug_print("Setting master_passphrase to NULL\n");
284 prefs_common_get_prefs()->master_passphrase = NULL;
287 /* Now go over all accounts, reencrypting their passwords using
288 * the new master passphrase. */
291 oldp = PASSCRYPT_KEY;
293 newp = PASSCRYPT_KEY;
295 debug_print("Reencrypting all account passwords...\n");
296 passwd_store_reencrypt_all(oldp, newp);
298 /* Now reencrypt all plugins passwords fields
299 * FIXME: Unloaded plugins won't be able to update their stored passwords
301 plugins_master_passphrase_change(oldp, newp);
303 master_passphrase_forget();
307 gchar *password_encrypt_old(const gchar *password)
309 if (!password || strlen(password) == 0) {
313 gchar *encrypted = g_strdup(password);
314 gchar *encoded, *result;
315 gsize len = strlen(password);
317 passcrypt_encrypt(encrypted, len);
318 encoded = g_base64_encode(encrypted, len);
320 result = g_strconcat("!", encoded, NULL);
326 gchar *password_decrypt_old(const gchar *password)
328 if (!password || strlen(password) == 0) {
332 if (*password != '!' || strlen(password) < 2) {
337 gchar *decrypted = g_base64_decode(password + 1, &len);
339 passcrypt_decrypt(decrypted, len);
343 #ifdef PASSWORD_CRYPTO_GNUTLS
346 /* Since we can't count on having GnuTLS new enough to have
347 * gnutls_cipher_get_iv_size(), we hardcode the IV length for now. */
350 gchar *password_encrypt_gnutls(const gchar *password,
351 const gchar *encryption_passphrase)
353 /* Another, slightly inferior combination is AES-128-CBC + SHA-256.
354 * Any block cipher in CBC mode with keysize N and a hash algo with
355 * digest length 2*N would do. */
356 gnutls_cipher_algorithm_t algo = GNUTLS_CIPHER_AES_256_CBC;
357 gnutls_digest_algorithm_t digest = GNUTLS_DIG_SHA512;
358 gnutls_cipher_hd_t handle;
359 gnutls_datum_t key, iv;
360 int keylen, digestlen, blocklen, ret, i;
361 unsigned char hashbuf[BUFSIZE], *buf, *encbuf, *base, *output;
362 #if defined G_OS_UNIX
364 #elif defined G_OS_WIN32
368 g_return_val_if_fail(password != NULL, NULL);
369 g_return_val_if_fail(encryption_passphrase != NULL, NULL);
371 /* ivlen = gnutls_cipher_get_iv_size(algo);*/
372 keylen = gnutls_cipher_get_key_size(algo);
373 blocklen = gnutls_cipher_get_block_size(algo);
374 digestlen = gnutls_hash_get_len(digest);
376 /* Prepare key for cipher - first half of hash of passkey XORed with
378 memset(&hashbuf, 0, BUFSIZE);
379 if ((ret = gnutls_hash_fast(digest, encryption_passphrase,
380 strlen(encryption_passphrase), &hashbuf)) < 0) {
381 debug_print("Hashing passkey failed: %s\n", gnutls_strerror(ret));
384 for (i = 0; i < digestlen/2; i++) {
385 hashbuf[i] = hashbuf[i] ^ hashbuf[i+digestlen/2];
388 key.data = malloc(keylen);
389 memcpy(key.data, &hashbuf, keylen);
392 /* Prepare our source of random data. */
393 #if defined G_OS_UNIX
394 rnd = open("/dev/urandom", O_RDONLY);
396 perror("fopen on /dev/urandom");
397 #elif defined G_OS_WIN32
398 if (!CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, 0) &&
399 !CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
400 debug_print("Could not acquire a CSP handle.\n");
406 /* Prepare random IV for cipher */
407 iv.data = malloc(IVLEN);
409 #if defined G_OS_UNIX
410 ret = read(rnd, iv.data, IVLEN);
412 perror("read into iv");
414 #elif defined G_OS_WIN32
415 if (!CryptGenRandom(rnd, IVLEN, iv.data)) {
416 debug_print("Could not read random data for IV\n");
417 CryptReleaseContext(rnd, 0);
424 /* Initialize the encryption */
425 ret = gnutls_cipher_init(&handle, algo, &key, &iv);
429 #if defined G_OS_UNIX
431 #elif defined G_OS_WIN32
432 CryptReleaseContext(rnd, 0);
437 /* Fill buf with one block of random data, our password, pad the
438 * rest with zero bytes. */
439 buf = malloc(BUFSIZE + blocklen);
440 memset(buf, 0, BUFSIZE);
441 #if defined G_OS_UNIX
442 ret = read(rnd, buf, blocklen);
443 if (ret != blocklen) {
444 perror("read into buffer");
446 #elif defined G_OS_WIN32
447 if (!CryptGenRandom(rnd, blocklen, buf)) {
448 debug_print("Could not read random data for IV\n");
449 CryptReleaseContext(rnd, 0);
454 gnutls_cipher_deinit(handle);
458 /* We don't need any more random data. */
459 #if defined G_OS_UNIX
461 #elif defined G_OS_WIN32
462 CryptReleaseContext(rnd, 0);
465 memcpy(buf + blocklen, password, strlen(password));
467 /* Encrypt into encbuf */
468 encbuf = malloc(BUFSIZE + blocklen);
469 memset(encbuf, 0, BUFSIZE + blocklen);
470 ret = gnutls_cipher_encrypt2(handle, buf, BUFSIZE + blocklen,
471 encbuf, BUFSIZE + blocklen);
477 gnutls_cipher_deinit(handle);
482 gnutls_cipher_deinit(handle);
487 /* And finally prepare the resulting string:
488 * "{algorithm}base64encodedciphertext" */
489 base = g_base64_encode(encbuf, BUFSIZE);
491 output = g_strdup_printf("{%s}%s", gnutls_cipher_get_name(algo), base);
497 gchar *password_decrypt_gnutls(const gchar *password,
498 const gchar *decryption_passphrase)
500 gchar **tokens, *tmp;
501 gnutls_cipher_algorithm_t algo;
502 gnutls_digest_algorithm_t digest = GNUTLS_DIG_UNKNOWN;
503 gnutls_cipher_hd_t handle;
504 gnutls_datum_t key, iv;
505 int keylen, digestlen, blocklen, ret, i;
507 unsigned char hashbuf[BUFSIZE], *buf;
508 #if defined G_OS_UNIX
510 #elif defined G_OS_WIN32
514 g_return_val_if_fail(password != NULL, NULL);
515 g_return_val_if_fail(decryption_passphrase != NULL, NULL);
517 tokens = g_strsplit_set(password, "{}", 3);
519 /* Parse the string, retrieving algorithm and encrypted data.
520 * We expect "{algorithm}base64encodedciphertext". */
521 if (strlen(tokens[0]) != 0 ||
522 (algo = gnutls_cipher_get_id(tokens[1])) == GNUTLS_CIPHER_UNKNOWN ||
523 strlen(tokens[2]) == 0)
526 /* Our hash algo needs to have digest length twice as long as our
527 * cipher algo's key length. */
528 if (algo == GNUTLS_CIPHER_AES_256_CBC) {
529 debug_print("Using AES-256-CBC + SHA-512 for decryption\n");
530 digest = GNUTLS_DIG_SHA512;
531 } else if (algo == GNUTLS_CIPHER_AES_128_CBC) {
532 debug_print("Using AES-128-CBC + SHA-256 for decryption\n");
533 digest = GNUTLS_DIG_SHA256;
535 if (digest == GNUTLS_DIG_UNKNOWN) {
536 debug_print("Password is encrypted with unsupported cipher, giving up.\n");
541 /* ivlen = gnutls_cipher_get_iv_size(algo); */
542 keylen = gnutls_cipher_get_key_size(algo);
543 blocklen = gnutls_cipher_get_block_size(algo);
544 digestlen = gnutls_hash_get_len(digest);
546 /* Prepare key for cipher - first half of hash of passkey XORed with
547 * the second. AES-256 has key length 32 and length of SHA-512 hash
548 * is exactly twice that, 64. */
549 memset(&hashbuf, 0, BUFSIZE);
550 if ((ret = gnutls_hash_fast(digest, decryption_passphrase,
551 strlen(decryption_passphrase), &hashbuf)) < 0) {
552 debug_print("Hashing passkey failed: %s\n", gnutls_strerror(ret));
556 for (i = 0; i < digestlen/2; i++) {
557 hashbuf[i] = hashbuf[i] ^ hashbuf[i+digestlen/2];
560 key.data = malloc(keylen);
561 memcpy(key.data, &hashbuf, keylen);
564 /* Prepare our source of random data. */
565 #if defined G_OS_UNIX
566 rnd = open("/dev/urandom", O_RDONLY);
568 perror("fopen on /dev/urandom");
569 #elif defined G_OS_WIN32
570 if (!CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, 0) &&
571 !CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
572 debug_print("Could not acquire a CSP handle.\n");
579 /* Prepare random IV for cipher */
580 iv.data = malloc(IVLEN);
582 #if defined G_OS_UNIX
583 ret = read(rnd, iv.data, IVLEN);
585 perror("read into iv");
587 #elif defined G_OS_WIN32
588 if (!CryptGenRandom(rnd, IVLEN, iv.data)) {
589 debug_print("Could not read random data for IV\n");
590 CryptReleaseContext(rnd, 0);
598 /* We don't need any more random data. */
599 #if defined G_OS_UNIX
601 #elif defined G_OS_WIN32
602 CryptReleaseContext(rnd, 0);
605 /* Prepare encrypted password string for decryption. */
606 tmp = g_base64_decode(tokens[2], &len);
609 /* Initialize the decryption */
610 ret = gnutls_cipher_init(&handle, algo, &key, &iv);
612 debug_print("Cipher init failed: %s\n", gnutls_strerror(ret));
618 buf = malloc(BUFSIZE + blocklen);
619 memset(buf, 0, BUFSIZE + blocklen);
620 ret = gnutls_cipher_decrypt2(handle, tmp, len,
621 buf, BUFSIZE + blocklen);
623 debug_print("Decryption failed: %s\n", gnutls_strerror(ret));
627 gnutls_cipher_deinit(handle);
632 gnutls_cipher_deinit(handle);
636 tmp = g_strndup(buf + blocklen, MIN(strlen(buf + blocklen), BUFSIZE));
645 gchar *password_encrypt(const gchar *password,
646 const gchar *encryption_passphrase)
648 if (password == NULL || strlen(password) == 0) {
652 #ifndef PASSWORD_CRYPTO_OLD
653 if (encryption_passphrase == NULL)
654 encryption_passphrase = master_passphrase();
656 return password_encrypt_real(password, encryption_passphrase);
658 return password_encrypt_old(password);
662 gchar *password_decrypt(const gchar *password,
663 const gchar *decryption_passphrase)
665 if (password == NULL || strlen(password) == 0) {
669 /* First, check if the password was possibly decrypted using old,
671 if (*password == '!') {
672 debug_print("Trying to decrypt password using the old method...\n");
673 return password_decrypt_old(password);
676 /* Try available crypto backend */
677 #ifndef PASSWORD_CRYPTO_OLD
678 if (decryption_passphrase == NULL)
679 decryption_passphrase = master_passphrase();
681 if (*password == '{') {
682 debug_print("Trying to decrypt password...\n");
683 return password_decrypt_real(password, decryption_passphrase);
687 /* Fallback, in case the configuration is really old and
688 * stored password in plaintext */
689 debug_print("Assuming password was stored plaintext, returning it unchanged\n");
690 return g_strdup(password);