Use SHA-256 for master_passphrase_hash, since SHA-512 is too new.
[claws.git] / src / password.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2016 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
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #include "claws-features.h"
23 #endif
24
25 #ifdef PASSWORD_CRYPTO_GNUTLS
26 # include <gnutls/gnutls.h>
27 # include <gnutls/crypto.h>
28 #endif
29
30 #include <glib.h>
31 #include <glib/gi18n.h>
32
33 #if defined G_OS_UNIX
34 #include <fcntl.h>
35 #include <unistd.h>
36 #elif defined G_OS_WIN32
37 #include <windows.h>
38 #include <wincrypt.h>
39 #endif
40
41 #include "common/passcrypt.h"
42 #include "common/plugin.h"
43 #include "common/utils.h"
44 #include "account.h"
45 #include "alertpanel.h"
46 #include "inputdialog.h"
47 #include "password.h"
48 #include "passwordstore.h"
49 #include "prefs_common.h"
50
51 #ifndef PASSWORD_CRYPTO_OLD
52 static gchar *_master_passphrase = NULL;
53
54 static const gchar *master_passphrase()
55 {
56         gchar *input;
57         gboolean end = FALSE;
58
59         if (!prefs_common_get_prefs()->use_master_passphrase) {
60                 return PASSCRYPT_KEY;
61         }
62
63         if (_master_passphrase != NULL) {
64                 debug_print("Master passphrase is in memory, offering it.\n");
65                 return _master_passphrase;
66         }
67
68         while (!end) {
69                 input = input_dialog_with_invisible(_("Input master passphrase"),
70                                 _("Input master passphrase"), NULL);
71
72                 if (input == NULL) {
73                         debug_print("Cancel pressed at master passphrase dialog.\n");
74                         break;
75                 }
76
77                 if (master_passphrase_is_correct(input)) {
78                         debug_print("Entered master passphrase seems to be correct, remembering it.\n");
79                         _master_passphrase = input;
80                         end = TRUE;
81                 } else {
82                         alertpanel_error(_("Incorrect master passphrase."));
83                 }
84         }
85
86         return _master_passphrase;
87 }
88
89 const gboolean master_passphrase_is_set()
90 {
91         if (prefs_common_get_prefs()->master_passphrase_hash == NULL
92                         || strlen(prefs_common_get_prefs()->master_passphrase_hash) == 0)
93                 return FALSE;
94
95         return TRUE;
96 }
97
98 const gboolean master_passphrase_is_correct(const gchar *input)
99 {
100         gchar *hash;
101         gchar **tokens;
102         gchar *stored_hash = prefs_common_get_prefs()->master_passphrase_hash;
103         const GChecksumType hashtype = G_CHECKSUM_SHA256;
104         const gssize hashlen = g_checksum_type_get_length(hashtype);
105         gssize stored_len;
106
107         g_return_val_if_fail(input != NULL, FALSE);
108
109         if (stored_hash == NULL)
110                 return FALSE;
111
112         tokens = g_strsplit_set(stored_hash, "{}", 3);
113         if (strlen(tokens[0]) != 0 ||
114                         strcmp(tokens[1], "SHA-256") ||
115                         strlen(tokens[2]) == 0) {
116                 debug_print("Mangled master_passphrase_hash in config, can not use it.\n");
117                 g_strfreev(tokens);
118                 return FALSE;
119         }
120
121         stored_hash = tokens[2];
122         stored_len = strlen(stored_hash);
123         g_return_val_if_fail(stored_len == 2*hashlen, FALSE);
124
125         hash = g_compute_checksum_for_string(hashtype, input, -1);
126
127         if (!strncasecmp(hash, stored_hash, stored_len)) {
128                 g_free(hash);
129                 g_strfreev(tokens);
130                 return TRUE;
131         }
132         g_strfreev(tokens);
133         g_free(hash);
134
135         return FALSE;
136 }
137
138 gboolean master_passphrase_is_entered()
139 {
140         return (_master_passphrase == NULL) ? FALSE : TRUE;
141 }
142
143 void master_passphrase_forget()
144 {
145         /* If master passphrase is currently in memory (entered by user),
146          * get rid of it. User will have to enter the new one again. */
147         if (_master_passphrase != NULL) {
148                 memset(_master_passphrase, 0, strlen(_master_passphrase));
149                 g_free(_master_passphrase);
150                 _master_passphrase = NULL;
151         }
152 }
153
154 void master_passphrase_change(const gchar *oldp, const gchar *newp)
155 {
156         const GChecksumType hashtype = G_CHECKSUM_SHA256;
157         gchar *hash;
158
159         if (oldp == NULL) {
160                 /* If oldp is NULL, make sure the user has to enter the
161                  * current master passphrase before being able to change it. */
162                 master_passphrase_forget();
163                 oldp = master_passphrase();
164         }
165         g_return_if_fail(oldp != NULL);
166
167         /* Update master passphrase hash in prefs */
168         if (prefs_common_get_prefs()->master_passphrase_hash != NULL)
169                 g_free(prefs_common_get_prefs()->master_passphrase_hash);
170
171         if (newp != NULL) {
172                 debug_print("Storing hash of new master passphrase\n");
173                 hash = g_compute_checksum_for_string(hashtype, newp, -1);
174                 prefs_common_get_prefs()->master_passphrase_hash =
175                         g_strconcat("{SHA-256}", hash, NULL);
176                 g_free(hash);
177         } else {
178                 debug_print("Setting master_passphrase_hash to NULL\n");
179                 prefs_common_get_prefs()->master_passphrase_hash = NULL;
180         }
181
182         /* Now go over all accounts, reencrypting their passwords using
183          * the new master passphrase. */
184
185         if (oldp == NULL)
186                 oldp = PASSCRYPT_KEY;
187         if (newp == NULL)
188                 newp = PASSCRYPT_KEY;
189
190         debug_print("Reencrypting all account passwords...\n");
191         passwd_store_reencrypt_all(oldp, newp);
192
193         /* Now reencrypt all plugins passwords fields 
194          * FIXME: Unloaded plugins won't be able to update their stored passwords
195          */
196         plugins_master_passphrase_change(oldp, newp);
197
198         master_passphrase_forget();
199 }
200 #endif
201
202 gchar *password_encrypt_old(const gchar *password)
203 {
204         if (!password || strlen(password) == 0) {
205                 return NULL;
206         }
207
208         gchar *encrypted = g_strdup(password);
209         gchar *encoded, *result;
210         gsize len = strlen(password);
211
212         passcrypt_encrypt(encrypted, len);
213         encoded = g_base64_encode(encrypted, len);
214         g_free(encrypted);
215         result = g_strconcat("!", encoded, NULL);
216         g_free(encoded);
217
218         return result;
219 }
220
221 gchar *password_decrypt_old(const gchar *password)
222 {
223         if (!password || strlen(password) == 0) {
224                 return NULL;
225         }
226
227         if (*password != '!' || strlen(password) < 2) {
228                 return NULL;
229         }
230
231         gsize len;
232         gchar *decrypted = g_base64_decode(password + 1, &len);
233
234         passcrypt_decrypt(decrypted, len);
235         return decrypted;
236 }
237
238 #ifdef PASSWORD_CRYPTO_GNUTLS
239 #define BUFSIZE 128
240
241 gchar *password_encrypt_gnutls(const gchar *password,
242                 const gchar *encryption_passphrase)
243 {
244         /* Another, slightly inferior combination is AES-128-CBC + SHA-256.
245          * Any block cipher in CBC mode with keysize N and a hash algo with
246          * digest length 2*N would do. */
247         gnutls_cipher_algorithm_t algo = GNUTLS_CIPHER_AES_256_CBC;
248         gnutls_digest_algorithm_t digest = GNUTLS_DIG_SHA512;
249         gnutls_cipher_hd_t handle;
250         gnutls_datum_t key, iv;
251         int ivlen, keylen, digestlen, blocklen, ret, i;
252         unsigned char hashbuf[BUFSIZE], *buf, *encbuf, *base, *output;
253 #if defined G_OS_UNIX
254         int rnd;
255 #elif defined G_OS_WIN32
256         HCRYPTPROV rnd;
257 #endif
258
259         g_return_val_if_fail(password != NULL, NULL);
260         g_return_val_if_fail(encryption_passphrase != NULL, NULL);
261
262         ivlen = gnutls_cipher_get_iv_size(algo);
263         keylen = gnutls_cipher_get_key_size(algo);
264         blocklen = gnutls_cipher_get_block_size(algo);
265         digestlen = gnutls_hash_get_len(digest);
266
267         /* Prepare key for cipher - first half of hash of passkey XORed with
268          * the second. */
269         memset(&hashbuf, 0, BUFSIZE);
270         if ((ret = gnutls_hash_fast(digest, encryption_passphrase,
271                                         strlen(encryption_passphrase), &hashbuf)) < 0) {
272                 debug_print("Hashing passkey failed: %s\n", gnutls_strerror(ret));
273                 return NULL;
274         }
275         for (i = 0; i < digestlen/2; i++) {
276                 hashbuf[i] = hashbuf[i] ^ hashbuf[i+digestlen/2];
277         }
278
279         key.data = malloc(keylen);
280         memcpy(key.data, &hashbuf, keylen);
281         key.size = keylen;
282
283         /* Prepare our source of random data. */
284 #if defined G_OS_UNIX
285         rnd = open("/dev/urandom", O_RDONLY);
286         if (rnd == -1) {
287                 perror("fopen on /dev/urandom");
288 #elif defined G_OS_WIN32
289         if (!CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, 0) &&
290                         !CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
291                 debug_print("Could not acquire a CSP handle.\n");
292 #endif
293                 g_free(key.data);
294                 g_free(iv.data);
295                 return NULL;
296         }
297
298         /* Prepare random IV for cipher */
299         iv.data = malloc(ivlen);
300         iv.size = ivlen;
301 #if defined G_OS_UNIX
302         ret = read(rnd, iv.data, ivlen);
303         if (ret != ivlen) {
304                 perror("read into iv");
305                 close(rnd);
306 #elif defined G_OS_WIN32
307         if (!CryptGenRandom(rnd, ivlen, iv.data)) {
308                 debug_print("Could not read random data for IV\n");
309                 CryptReleaseContext(rnd, 0);
310 #endif
311                 g_free(key.data);
312                 g_free(iv.data);
313                 return NULL;
314         }
315
316         /* Initialize the encryption */
317         ret = gnutls_cipher_init(&handle, algo, &key, &iv);
318         if (ret < 0) {
319                 g_free(key.data);
320                 g_free(iv.data);
321 #if defined G_OS_UNIX
322                 close(rnd);
323 #elif defined G_OS_WIN32
324                 CryptReleaseContext(rnd, 0);
325 #endif
326                 return NULL;
327         }
328
329         /* Fill buf with one block of random data, our password, pad the
330          * rest with zero bytes. */
331         buf = malloc(BUFSIZE + blocklen);
332         memset(buf, 0, BUFSIZE);
333 #if defined G_OS_UNIX
334         ret = read(rnd, buf, blocklen);
335         if (ret != blocklen) {
336                 perror("read into buffer");
337                 close(rnd);
338 #elif defined G_OS_WIN32
339         if (!CryptGenRandom(rnd, blocklen, buf)) {
340                 debug_print("Could not read random data for IV\n");
341                 CryptReleaseContext(rnd, 0);
342 #endif
343                 g_free(buf);
344                 g_free(key.data);
345                 g_free(iv.data);
346                 gnutls_cipher_deinit(handle);
347                 return NULL;
348         }
349
350         /* We don't need any more random data. */
351 #if defined G_OS_UNIX
352         close(rnd);
353 #elif defined G_OS_WIN32
354         CryptReleaseContext(rnd, 0);
355 #endif
356
357         memcpy(buf + blocklen, password, strlen(password));
358
359         /* Encrypt into encbuf */
360         encbuf = malloc(BUFSIZE + blocklen);
361         memset(encbuf, 0, BUFSIZE + blocklen);
362         ret = gnutls_cipher_encrypt2(handle, buf, BUFSIZE + blocklen,
363                         encbuf, BUFSIZE + blocklen);
364         if (ret < 0) {
365                 g_free(key.data);
366                 g_free(iv.data);
367                 g_free(buf);
368                 g_free(encbuf);
369                 gnutls_cipher_deinit(handle);
370                 return NULL;
371         }
372
373         /* Cleanup */
374         gnutls_cipher_deinit(handle);
375         g_free(key.data);
376         g_free(iv.data);
377         g_free(buf);
378
379         /* And finally prepare the resulting string:
380          * "{algorithm}base64encodedciphertext" */
381         base = g_base64_encode(encbuf, BUFSIZE);
382         g_free(encbuf);
383         output = g_strdup_printf("{%s}%s", gnutls_cipher_get_name(algo), base);
384         g_free(base);
385
386         return output;
387 }
388
389 gchar *password_decrypt_gnutls(const gchar *password,
390                 const gchar *decryption_passphrase)
391 {
392         gchar **tokens, *tmp;
393         gnutls_cipher_algorithm_t algo;
394         gnutls_digest_algorithm_t digest = GNUTLS_DIG_UNKNOWN;
395         gnutls_cipher_hd_t handle;
396         gnutls_datum_t key, iv;
397         int ivlen, keylen, digestlen, blocklen, ret, i;
398         gsize len;
399         unsigned char hashbuf[BUFSIZE], *buf;
400 #if defined G_OS_UNIX
401         int rnd;
402 #elif defined G_OS_WIN32
403         HCRYPTPROV rnd;
404 #endif
405
406         g_return_val_if_fail(password != NULL, NULL);
407         g_return_val_if_fail(decryption_passphrase != NULL, NULL);
408
409         tokens = g_strsplit_set(password, "{}", 3);
410
411         /* Parse the string, retrieving algorithm and encrypted data.
412          * We expect "{algorithm}base64encodedciphertext". */
413         if (strlen(tokens[0]) != 0 ||
414                         (algo = gnutls_cipher_get_id(tokens[1])) == GNUTLS_CIPHER_UNKNOWN ||
415                         strlen(tokens[2]) == 0)
416                 return NULL;
417
418         /* Our hash algo needs to have digest length twice as long as our
419          * cipher algo's key length. */
420         if (algo == GNUTLS_CIPHER_AES_256_CBC) {
421                 debug_print("Using AES-256-CBC + SHA-512 for decryption\n");
422                 digest = GNUTLS_DIG_SHA512;
423         } else if (algo == GNUTLS_CIPHER_AES_128_CBC) {
424                 debug_print("Using AES-128-CBC + SHA-256 for decryption\n");
425                 digest = GNUTLS_DIG_SHA256;
426         }
427         if (digest == GNUTLS_DIG_UNKNOWN) {
428                 debug_print("Password is encrypted with unsupported cipher, giving up.\n");
429                 g_strfreev(tokens);
430                 return NULL;
431         }
432
433         ivlen = gnutls_cipher_get_iv_size(algo);
434         keylen = gnutls_cipher_get_key_size(algo);
435         blocklen = gnutls_cipher_get_block_size(algo);
436         digestlen = gnutls_hash_get_len(digest);
437
438         /* Prepare key for cipher - first half of hash of passkey XORed with
439          * the second. AES-256 has key length 32 and length of SHA-512 hash
440          * is exactly twice that, 64. */
441         memset(&hashbuf, 0, BUFSIZE);
442         if ((ret = gnutls_hash_fast(digest, decryption_passphrase,
443                                         strlen(decryption_passphrase), &hashbuf)) < 0) {
444                 debug_print("Hashing passkey failed: %s\n", gnutls_strerror(ret));
445                 g_strfreev(tokens);
446                 return NULL;
447         }
448         for (i = 0; i < digestlen/2; i++) {
449                 hashbuf[i] = hashbuf[i] ^ hashbuf[i+digestlen/2];
450         }
451
452         key.data = malloc(keylen);
453         memcpy(key.data, &hashbuf, keylen);
454         key.size = keylen;
455
456         /* Prepare our source of random data. */
457 #if defined G_OS_UNIX
458         rnd = open("/dev/urandom", O_RDONLY);
459         if (rnd == -1) {
460                 perror("fopen on /dev/urandom");
461 #elif defined G_OS_WIN32
462         if (!CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, 0) &&
463                         !CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
464                 debug_print("Could not acquire a CSP handle.\n");
465 #endif
466                 g_free(key.data);
467                 g_free(iv.data);
468                 g_strfreev(tokens);
469                 return NULL;
470         }
471
472         /* Prepare random IV for cipher */
473         iv.data = malloc(ivlen);
474         iv.size = ivlen;
475 #if defined G_OS_UNIX
476         ret = read(rnd, iv.data, ivlen);
477         if (ret != ivlen) {
478                 perror("read into iv");
479                 close(rnd);
480 #elif defined G_OS_WIN32
481         if (!CryptGenRandom(rnd, ivlen, iv.data)) {
482                 debug_print("Could not read random data for IV\n");
483                 CryptReleaseContext(rnd, 0);
484 #endif
485                 g_free(key.data);
486                 g_free(iv.data);
487                 g_strfreev(tokens);
488                 return NULL;
489         }
490
491         /* We don't need any more random data. */
492 #if defined G_OS_UNIX
493         close(rnd);
494 #elif defined G_OS_WIN32
495         CryptReleaseContext(rnd, 0);
496 #endif
497
498         /* Prepare encrypted password string for decryption. */
499         tmp = g_base64_decode(tokens[2], &len);
500         g_strfreev(tokens);
501
502         /* Initialize the decryption */
503         ret = gnutls_cipher_init(&handle, algo, &key, &iv);
504         if (ret < 0) {
505                 debug_print("Cipher init failed: %s\n", gnutls_strerror(ret));
506                 g_free(key.data);
507                 g_free(iv.data);
508                 return NULL;
509         }
510
511         buf = malloc(BUFSIZE + blocklen);
512         memset(buf, 0, BUFSIZE + blocklen);
513         ret = gnutls_cipher_decrypt2(handle, tmp, len,
514                         buf, BUFSIZE + blocklen);
515         if (ret < 0) {
516                 debug_print("Decryption failed: %s\n", gnutls_strerror(ret));
517                 g_free(key.data);
518                 g_free(iv.data);
519                 g_free(buf);
520                 gnutls_cipher_deinit(handle);
521                 return NULL;
522         }
523
524         /* Cleanup */
525         gnutls_cipher_deinit(handle);
526         g_free(key.data);
527         g_free(iv.data);
528
529         tmp = g_strndup(buf + blocklen, MIN(strlen(buf + blocklen), BUFSIZE));
530         g_free(buf);
531         return tmp;
532 }
533
534 #undef BUFSIZE
535
536 #endif
537
538 gchar *password_encrypt(const gchar *password,
539                 const gchar *encryption_passphrase)
540 {
541         if (password == NULL || strlen(password) == 0) {
542                 return NULL;
543         }
544
545 #ifndef PASSWORD_CRYPTO_OLD
546         if (encryption_passphrase == NULL)
547                 encryption_passphrase = master_passphrase();
548
549         return password_encrypt_real(password, encryption_passphrase);
550 #endif
551
552         return password_encrypt_old(password);
553 }
554
555 gchar *password_decrypt(const gchar *password,
556                 const gchar *decryption_passphrase)
557 {
558         if (password == NULL || strlen(password) == 0) {
559                 return NULL;
560         }
561
562         /* First, check if the password was possibly decrypted using old,
563          * obsolete method */
564         if (*password == '!') {
565                 debug_print("Trying to decrypt password using the old method...\n");
566                 return password_decrypt_old(password);
567         }
568
569         /* Try available crypto backend */
570 #ifndef PASSWORD_CRYPTO_OLD
571         if (decryption_passphrase == NULL)
572                 decryption_passphrase = master_passphrase();
573
574         if (*password == '{') {
575                 debug_print("Trying to decrypt password...\n");
576                 return password_decrypt_real(password, decryption_passphrase);
577         }
578 #endif
579
580         /* Fallback, in case the configuration is really old and
581          * stored password in plaintext */
582         debug_print("Assuming password was stored plaintext, returning it unchanged\n");
583         return g_strdup(password);
584 }