summaryrefslogtreecommitdiff
path: root/mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch
diff options
context:
space:
mode:
Diffstat (limited to 'mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch')
-rw-r--r--mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch1850
1 files changed, 0 insertions, 1850 deletions
diff --git a/mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch b/mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch
deleted file mode 100644
index b7fc53a..0000000
--- a/mail-client/sylpheed/files/sylpheed-3.7.0-master-password.patch
+++ /dev/null
@@ -1,1850 +0,0 @@
1diff --git a/README.md b/README.md
2new file mode 100644
3index 0000000..1cf6d21
4--- /dev/null
5+++ b/README.md
6@@ -0,0 +1,219 @@
7+## Implemented features and fixes not present in the official Sylpheed release
8+
9+- (fix)
10+ PGP signature not verified properly when the message has no newline
11+ at the end. https://sylpheed.sraoss.jp/redmine/issues/288
12+
13+- (feature)
14+ Make it possible to select "Show signature check result in a popup window"
15+ only for bad signatures.
16+
17+- (feature)
18+ Support for encrypting and storing encrypted passwords using a master password.
19+ Read bellow for more details!
20+
21+
22+## Master password
23+
24+The master password feature is developed by Simeon Simeonov (sgs)
25+and is currently in an experimental state.
26+
27+
28+### Motivation
29+
30+Currently Sylpheed is storing passwords in plain-text. One can always refrain
31+from storing passwords and let Sylpheed prompt for them, but the more accounts
32+one has, the more annoying this becomes.
33+
34+The goal is to have the passwords stored in a secure way and let Sylpheed only
35+prompt for the master password.
36+
37+
38+### Security goals
39+
40+- attacker (A) should not be able to derive the password from the digest.
41+
42+- A should not be able to derive the master password even if she has
43+ read and write access to the storage.
44+
45+- A should not be able to determine the length of the encrypted password
46+ even if she has read and write access to the storage.
47+
48+- A should not be able to craft an edited password without obtaining the
49+ master password.
50+
51+- A should not be able to compromise the master password or force Sylpheed
52+ to store decrypted passwords even if she has access to a running Sylpheed.
53+
54+- a warning / prompt should be given if a user accidentally types in a "wrong"
55+ master password before decryption is initiated.
56+
57+
58+### Usage in Sylpheed
59+
60+- backup your Sylpheed profile (often $HOME/.sylpheed-2.0)!
61+
62+- start Sylpheed and open "Configuration" -> "Common preferences..."!
63+
64+- select the "Master password" tab, enable "Use master password" and
65+ apply the changes!
66+
67+- restart Sylpheed (exit and then start Sylpheed again)!
68+
69+- you will be asked to type and verify a new master password.
70+
71+- set your new passwords from the "Configuration" -> "Edit accounts..."!
72+
73+- select "Automatically unload master password after session initialization"
74+ for increased security and decreased convenience.
75+
76+Note:
77+Sylpheed will automatically convert existing stored passwords, but it will not
78+touch your backups. You will have to remove all remnants of plain-text
79+passwords manually.
80+
81+
82+### Choice of cryptographic primitives
83+
84+The primary concern when selecting cryptographic primitives was portability.
85+The desire was to go for primitives that are both strong and available in all
86+supported production distributions of OpenSSL and LibreSSL.
87+
88+
89+#### Cipher
90+
91+When it comes to implementation, there are several advantages in using stream
92+cipher or a block cipher that behaves like a stream cipher when used in a
93+certain mode of operation. One is avoiding to deal with padding.
94+
95+AES-256 operating in CFB was selected for these reasons.
96+ChaCha20 should be considered as a replacement in the future.
97+
98+
99+#### Hash-function
100+
101+Hash-functions are used for:
102+- key derivation
103+- plain-text digest
104+
105+Those operations do not have to use the same hash-function.
106+(See the "Encryption & decryption scheme" section for more details!)
107+
108+Key-derivation:
109+Since AES-256 uses a 256 bits key, we need a hash-function with at least
110+the same digest size or bigger.
111+Since the digest (which is the key itself) is considered confidential and is
112+stored only in memory for only a limited amount of time, SHA-256 is considered
113+sufficiently strong for that purpose.
114+
115+Size + plain-text + padding digest:
116+Since the digest is created of both the plain-text and the plain-text size,
117+as well as being encrypted, SHA-256 is considered sufficiently strong.
118+SHA-512 may increase security at the price of adding additional 32 bytes
119+to the encrypted password digest.
120+
121+Stronger hash-functions like SHA-3 or BLAKE2b can be considered as a
122+replacement in the future.
123+
124+
125+#### Master password digest
126+
127+In order to be able to decide whether the user typed a "wrong" master password,
128+before attempting to decrypt, Sylpheed stores a digest of the master password
129+in 'master_password_hash' in sylpheedrc.
130+100000 iterations of PBKDF2_HMAC with SHA-512 and 16 bytes salt is used.
131+Note that this digest is useless as a key and even if a plain-text that
132+produces the same digest is found, it will most probably be useless as a
133+master-password.
134+
135+
136+### Encryption & decryption scheme
137+
138+
139+#### Encryption
140+
141+
142+Input:
143+
144+- plain-text password to be encrypted (P)
145+
146+- plain-text master-password used for key derivation (M)
147+
148+- integer minimum password length (0 < L < 100)
149+
150+
151+Output:
152+
153+- an encrypted password digest (base64) (B)
154+
155+
156+Operation:
157+
158+- generate 16 bytes of random data to be used as a salt (S)
159+
160+- derive the key (K): K = SHA_256(S + M)
161+
162+- produce a 2 byte string (N) indicating the length of P
163+
164+- generate random padding and set N:
165+ - if the length of P < L, produce L - P bytes of random data (R), N = "%02d"
166+ - if the length of P >= L, N = "-1"
167+
168+- produce a hash digest (H): H = SHA_256(N + P + R (if the length of P < L))
169+
170+- encrypt (E): E = AES_256_CFB_ENCRYPT(H + N + P + R (if the length of P < L), K)
171+
172+- B = mpes1:BASE64_ENCODE(S + E)
173+
174+example:
175+mpes1:vo7lsIpD7i6byBA6+vlUoF4OVDfEe+aYRRk4FRtfJ2gMY8M43Kj6WfdfgbViIOl83bI4XEc96okhPW5Mla813aAR1gbPjDg0xmCyIbWOiUv/dg==
176+
177+
178+#### Decryption
179+
180+
181+Input:
182+
183+- encrypted password digest (base64) (B)
184+
185+- plain-text master-password used for key derivation (M)
186+
187+
188+Output:
189+
190+- plain-text password (P)
191+
192+
193+Operation:
194+
195+- remove the prefix (mpes1:) and base64-decode the rest of the digest: B = BASE64_DECODE(B)
196+
197+- fetch the first 16 bytes for the salt: S = B[0 : 15]
198+
199+- derive the key (K): K = SHA_256(S + M)
200+
201+- decrypt the rest of B (D): D = AES_256_CFB_DECRYPT(B[16 :], K)
202+
203+- extract the first 16 bytes for the hash digest (H): H = D[0 : 15]
204+
205+- in order to detect data-inconsistency, assert H == SHA_256(D[16 :])
206+
207+- extract the next 2 bytes for the length of P (N): N = D[16 : 17]
208+
209+- fetch the plain-text:
210+ - if N == "-1" the password is the remaining bytes of D: P = D[18 :]
211+ - if N != "-1", extract the next N-bytes from D: P = D[18 : (18 + N)]
212+
213+
214+### Limitations
215+
216+- currently only the 'password' and 'smtp_password' keys in accountrc
217+ are encrypted.
218+ A mechanism that allows for any key and even folders to be encrypted
219+ should be considered in the future.
220+
221+- currently it is not possible to select alternative ciphers, hash-functions
222+ and modes of operation (without editing the source code).
223+
224+- currently it is not possible to change your master password without having to
225+ (re)set your passwords manually.
226diff --git a/libsylph/Makefile.am b/libsylph/Makefile.am
227index 8f2b76b..40d26a9 100644
228--- a/libsylph/Makefile.am
229+++ b/libsylph/Makefile.am
230@@ -19,6 +19,7 @@ libsylph_0_la_SOURCES = \
231 folder.c \
232 html.c \
233 imap.c \
234+ masterpassword.c \
235 mbox.c \
236 md5.c \
237 md5_hmac.c \
238@@ -62,6 +63,7 @@ libsylph_0include_HEADERS = \
239 folder.h \
240 html.h \
241 imap.h \
242+ masterpassword.h \
243 mbox.h \
244 md5.h \
245 md5_hmac.h \
246diff --git a/libsylph/Makefile.in b/libsylph/Makefile.in
247index 6e3c999..ac8fefd 100644
248--- a/libsylph/Makefile.in
249+++ b/libsylph/Makefile.in
250@@ -129,8 +129,8 @@ libsylph_0_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
251 $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
252 am_libsylph_0_la_OBJECTS = account.lo base64.lo codeconv.lo \
253 customheader.lo displayheader.lo filter.lo folder.lo html.lo \
254- imap.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo nntp.lo \
255- pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
256+ imap.lo masterpassword.lo mbox.lo md5.lo md5_hmac.lo mh.lo news.lo \
257+ nntp.lo pop.lo prefs.lo prefs_account.lo prefs_common.lo procheader.lo \
258 procmime.lo procmsg.lo quoted-printable.lo recv.lo session.lo \
259 smtp.lo socket.lo socks.lo ssl.lo ssl_hostname_validation.lo \
260 stringtable.lo sylmain.lo unmime.lo utils.lo uuencode.lo \
261@@ -402,6 +402,7 @@ libsylph_0_la_SOURCES = \
262 folder.c \
263 html.c \
264 imap.c \
265+ masterpassword.c \
266 mbox.c \
267 md5.c \
268 md5_hmac.c \
269@@ -445,6 +446,7 @@ libsylph_0include_HEADERS = \
270 folder.h \
271 html.h \
272 imap.h \
273+ masterpassword.h \
274 mbox.h \
275 md5.h \
276 md5_hmac.h \
277@@ -579,6 +581,7 @@ distclean-compile:
278 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/folder.Plo@am__quote@
279 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/html.Plo@am__quote@
280 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imap.Plo@am__quote@
281+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/masterpassword.Plo@am__quote@
282 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mbox.Plo@am__quote@
283 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Plo@am__quote@
284 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5_hmac.Plo@am__quote@
285diff --git a/libsylph/defs.h b/libsylph/defs.h
286index 9e3f82b..67325bf 100644
287--- a/libsylph/defs.h
288+++ b/libsylph/defs.h
289@@ -58,6 +58,9 @@
290 #define DISPLAY_HEADER_RC "dispheaderrc"
291 #define MENU_RC "menurc"
292 #define ACTIONS_RC "actionsrc"
293+#ifdef USE_SSL
294+#define SECURE_RC "securerc"
295+#endif
296 #define COMMAND_HISTORY "command_history"
297 #define TEMPLATE_DIR "templates"
298 #define TMP_DIR "tmp"
299diff --git a/libsylph/imap.c b/libsylph/imap.c
300index aa0d737..e1c7cfd 100644
301--- a/libsylph/imap.c
302+++ b/libsylph/imap.c
303@@ -52,6 +52,7 @@
304 #include "utils.h"
305 #include "prefs_common.h"
306 #include "virtual.h"
307+#include "masterpassword.h"
308
309 #define IMAP4_PORT 143
310 #if USE_SSL
311@@ -705,9 +706,13 @@ static gint imap_session_connect(IMAPSession *session)
312 account = (PrefsAccount *)(SESSION(session)->data);
313
314 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
315- SESSION(session)->server, SESSION(session)->port);
316-
317- pass = account->passwd;
318+ SESSION(session)->server, SESSION(session)->port);
319+ if (master_password_active()) {
320+ pass = decrypt_with_master_password(account->passwd);
321+ /* a new string is allocated. To be removed ... */
322+ } else {
323+ pass = account->passwd;
324+ }
325 if (!pass)
326 pass = account->tmp_pass;
327 if (!pass) {
328@@ -770,8 +775,11 @@ static gint imap_session_connect(IMAPSession *session)
329 #endif
330
331 if (!session->authenticated &&
332- imap_auth(session, account->userid, pass, account->imap_auth_type)
333- != IMAP_SUCCESS) {
334+ imap_auth(session, account->userid, pass, account->imap_auth_type)
335+ != IMAP_SUCCESS) {
336+ if (master_password_active()) {
337+ g_free(pass); /* remove the decrypted password */
338+ }
339 if (account->tmp_pass) {
340 g_free(account->tmp_pass);
341 account->tmp_pass = NULL;
342@@ -780,6 +788,10 @@ static gint imap_session_connect(IMAPSession *session)
343 return IMAP_AUTHFAIL;
344 }
345
346+ if (master_password_active()) {
347+ g_free(pass); /* remove the decrypted password */
348+ }
349+
350 return IMAP_SUCCESS;
351 }
352
353diff --git a/libsylph/masterpassword.c b/libsylph/masterpassword.c
354new file mode 100644
355index 0000000..3d7af94
356--- /dev/null
357+++ b/libsylph/masterpassword.c
358@@ -0,0 +1,216 @@
359+/*
360+ * LibSylph -- E-Mail client library
361+ * Copyright (C) 1999-2018 Hiroyuki Yamamoto
362+ *
363+ * This library is free software; you can redistribute it and/or
364+ * modify it under the terms of the GNU Lesser General Public
365+ * License as published by the Free Software Foundation; either
366+ * version 2.1 of the License, or (at your option) any later version.
367+ *
368+ * This library is distributed in the hope that it will be useful,
369+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
370+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
371+ * Lesser General Public License for more details.
372+ *
373+ * You should have received a copy of the GNU Lesser General Public
374+ * License along with this library; if not, write to the Free Software
375+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
376+ */
377+
378+#ifdef HAVE_CONFIG_H
379+#include "config.h"
380+#endif
381+
382+#include <glib/gi18n.h>
383+
384+#include "prefs_common.h"
385+#include "ssl.h"
386+#include "utils.h"
387+#include "masterpassword.h"
388+
389+
390+gchar *master_password;
391+gboolean master_password_enabled_on_init;
392+
393+void set_master_password(const char *password) {
394+ master_password = password;
395+}
396+
397+gchar *get_master_password(void) {
398+ return master_password;
399+}
400+
401+void cleanse_buffer(void *buf, size_t len) {
402+#if USE_SSL
403+ OPENSSL_cleanse(buf, len);
404+#else
405+ memset(buf, 0, len); /* better than nothing */
406+#endif
407+}
408+
409+void unload_master_password(void) {
410+
411+ debug_print("Unloading master password\n");
412+ if (master_password == NULL) {
413+ /* not loaded / already unloaded */
414+ return;
415+ }
416+ cleanse_buffer(master_password, strlen(master_password));
417+ g_free(master_password);
418+ master_password = NULL;
419+ debug_print("Master password unloaded\n");
420+
421+}
422+
423+gint mpes_string_prefix(const gchar *str) {
424+
425+ /* this function will be expanded in time as the format changes */
426+ if (g_str_has_prefix(str, "mpes1:"))
427+ return 6;
428+ return 0;
429+
430+}
431+
432+gboolean master_password_active(void) {
433+
434+#if USE_SSL
435+ return ((master_password != NULL) &&
436+ prefs_common.use_master_password);
437+#else
438+ return FALSE;
439+#endif
440+
441+}
442+
443+gchar *decrypt_with_master_password(const gchar *str) {
444+
445+#if USE_SSL
446+ gchar *new_str;
447+ gint str_prefix;
448+
449+ if ((!str) || (!prefs_common.use_master_password))
450+ return g_strdup(str);
451+
452+ if (master_password == NULL) {
453+ /* we have empty or auto unloaded master password */
454+ if ((!prefs_common.auto_unload_master_password) ||
455+ (check_master_password_interactively(3) != MP_RC_OK)) {
456+ return g_strdup(str);
457+ }
458+ debug_print("Reloaded master password\n");
459+ }
460+
461+ str_prefix = mpes_string_prefix(str);
462+ if (!str_prefix)
463+ return g_strdup(str);
464+
465+ if (decrypt_data(&new_str,
466+ str + str_prefix,
467+ master_password,
468+ strlen(str) + 1 - str_prefix) != MP_RC_OK) {
469+ OPENSSL_cleanse(new_str, strlen(new_str));
470+ g_free(new_str);
471+ return g_strdup(str);
472+ }
473+
474+ return new_str;
475+#else
476+ return g_strdup(str);
477+#endif
478+
479+}
480+
481+gchar *encrypt_with_master_password(const gchar *str) {
482+
483+#if USE_SSL
484+ gchar *new_str, *mpes1_str;
485+ gint length_encrypted;
486+
487+ if ((!str) || (!master_password_active()))
488+ return NULL;
489+
490+ /*
491+ * unlike the decrypt function, here it is up to the caller
492+ * to make sure that auto unloaded master password is handled properly
493+ */
494+
495+ if (encrypt_data(&new_str,
496+ &length_encrypted,
497+ str,
498+ master_password,
499+ strlen(str) + 1,
500+ prefs_common.encrypted_password_min_length,
501+ TRUE) != MP_RC_OK) {
502+ g_free(new_str);
503+ return NULL;
504+ }
505+
506+ mpes1_str = g_strdup_printf("mpes1:%s", new_str);
507+ g_free(new_str);
508+
509+ return mpes1_str;
510+#else
511+ return NULL;
512+#endif
513+
514+}
515+
516+#if USE_SSL
517+gint set_master_password_interactively(guint max_attempts) {
518+
519+ if (master_password == NULL)
520+ master_password = input_set_new_password(max_attempts);
521+
522+ if (master_password == NULL)
523+ return 1;
524+
525+ if (generate_password_hash(
526+ &prefs_common.master_password_hash,
527+ master_password,
528+ NULL) != MP_RC_OK) {
529+ /* should not really happen unless buggy code / library */
530+ g_free(prefs_common.master_password_hash);
531+ prefs_common.master_password_hash = NULL;
532+ debug_print(_("Could not generate master password hash"));
533+ return 1;
534+ }
535+
536+ prefs_common_write_config();
537+ return 0;
538+
539+}
540+
541+gint check_master_password_interactively(guint max_attempts) {
542+
543+ guint cnt;
544+
545+ g_return_val_if_fail(max_attempts > 0, 1);
546+ g_return_val_if_fail(prefs_common.master_password_hash != NULL, 1);
547+
548+ if (master_password != NULL) {
549+ /* password already cached */
550+ debug_print("Master password already cached\n");
551+ return check_password(master_password,
552+ prefs_common.master_password_hash);
553+ }
554+
555+ for (cnt = 0; cnt < max_attempts; ++cnt) {
556+ master_password = input_query_master_password();
557+ if (master_password == NULL) {
558+ /* input canceled or query_master_password_func not set */
559+ continue;
560+ }
561+ if (check_password(master_password,
562+ prefs_common.master_password_hash) == MP_RC_OK) {
563+ return MP_RC_OK; /* match */
564+ }
565+ debug_print(_("Wrong master password entered (%d)\n"), cnt);
566+ OPENSSL_cleanse(master_password, strlen(master_password));
567+ g_free(master_password);
568+ master_password = NULL;
569+ }
570+
571+ return 1; /* no match */
572+
573+}
574+#endif /* USE_SSL */
575diff --git a/libsylph/masterpassword.h b/libsylph/masterpassword.h
576new file mode 100644
577index 0000000..7254643
578--- /dev/null
579+++ b/libsylph/masterpassword.h
580@@ -0,0 +1,47 @@
581+/*
582+ * LibSylph -- E-Mail client library
583+ * Copyright (C) 1999-2018 Hiroyuki Yamamoto
584+ *
585+ * This library is free software; you can redistribute it and/or
586+ * modify it under the terms of the GNU Lesser General Public
587+ * License as published by the Free Software Foundation; either
588+ * version 2.1 of the License, or (at your option) any later version.
589+ *
590+ * This library is distributed in the hope that it will be useful,
591+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
592+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
593+ * Lesser General Public License for more details.
594+ *
595+ * You should have received a copy of the GNU Lesser General Public
596+ * License along with this library; if not, write to the Free Software
597+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
598+ */
599+
600+#ifndef __MASTERPASSWORD_H__
601+#define __MASTERPASSWORD_H__
602+
603+#ifdef HAVE_CONFIG_H
604+#include "config.h"
605+#endif
606+
607+#define MP_RC_OK 0
608+#define MP_RC_WRONG_HASH_OR_KEY 1
609+#define MP_RC_INVALID_FORMAT 2 /* invalid digest format */
610+
611+extern gchar *master_password;
612+extern gboolean master_password_enabled_on_init; /* m.p. enabled on init? */
613+void set_master_password(const char *password);
614+gchar *get_master_password(void);
615+void cleanse_buffer(void *buf, size_t len);
616+void unload_master_password(void);
617+gint mpes_string_prefix(const gchar *str);
618+gboolean master_password_active(void);
619+gchar *decrypt_with_master_password(const gchar *str);
620+gchar *encrypt_with_master_password(const gchar *str);
621+
622+#if USE_SSL
623+gint set_master_password_interactively(guint max_attempts);
624+gint check_master_password_interactively(guint max_attempts);
625+#endif /* USE_SSL */
626+
627+#endif /* __MASTERPASSWORD_H__ */
628diff --git a/libsylph/news.c b/libsylph/news.c
629index ffff9f9..36d3b10 100644
630--- a/libsylph/news.c
631+++ b/libsylph/news.c
632@@ -44,6 +44,7 @@
633 #include "utils.h"
634 #include "prefs_common.h"
635 #include "prefs_account.h"
636+#include "masterpassword.h"
637 #if USE_SSL
638 # include "ssl.h"
639 #endif
640@@ -249,10 +250,11 @@ static Session *news_session_new_for_folder(Folder *folder)
641 ac = folder->account;
642 if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
643 userid = ac->userid;
644- if (ac->passwd && ac->passwd[0])
645- passwd = g_strdup(ac->passwd);
646- else
647+ if (ac->passwd && ac->passwd[0]) {
648+ passwd = decrypt_with_master_password(ac->passwd);
649+ } else {
650 passwd = input_query_password(ac->nntp_server, userid);
651+ }
652 }
653
654 if (ac->use_socks && ac->use_socks_for_recv && ac->proxy_host) {
655diff --git a/libsylph/pop.c b/libsylph/pop.c
656index 8cb7f5c..85f3ed9 100644
657--- a/libsylph/pop.c
658+++ b/libsylph/pop.c
659@@ -39,6 +39,7 @@
660 #include "prefs_account.h"
661 #include "utils.h"
662 #include "recv.h"
663+#include "masterpassword.h"
664
665 gint pop3_greeting_recv (Pop3Session *session,
666 const gchar *msg);
667@@ -437,8 +438,9 @@ Session *pop3_session_new(PrefsAccount *account)
668 session->error_msg = NULL;
669
670 session->user = g_strdup(account->userid);
671- session->pass = account->passwd ? g_strdup(account->passwd) :
672- account->tmp_pass ? g_strdup(account->tmp_pass) : NULL;
673+ session->pass = account->passwd ? decrypt_with_master_password(
674+ account->passwd) : account->tmp_pass ? g_strdup(
675+ account->tmp_pass) : NULL;
676
677 SESSION(session)->server = g_strdup(account->recv_server);
678
679diff --git a/libsylph/prefs_account.c b/libsylph/prefs_account.c
680index 1aecba9..54cbc89 100644
681--- a/libsylph/prefs_account.c
682+++ b/libsylph/prefs_account.c
683@@ -34,6 +34,7 @@
684 #include "customheader.h"
685 #include "account.h"
686 #include "utils.h"
687+#include "masterpassword.h"
688
689 static PrefsAccount tmp_ac_prefs;
690
691@@ -205,7 +206,7 @@ PrefsAccount *prefs_account_new(void)
692 void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
693 {
694 const gchar *p = label;
695- gchar *rcpath;
696+ gchar *rcpath, *tmp_str;
697 gint id;
698
699 g_return_if_fail(ac_prefs != NULL);
700@@ -229,6 +230,34 @@ void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label)
701 ac_prefs->use_apop_auth = TRUE;
702 }
703
704+ if (master_password_active()) {
705+ if ((ac_prefs->passwd != NULL) &&
706+ !mpes_string_prefix(ac_prefs->passwd)) {
707+ /* TODO: Perhaps some prompt? */
708+ debug_print(
709+ "%s -> converting passwd cleartext to encrypted\n",
710+ label);
711+ tmp_str = ac_prefs->passwd;
712+ ac_prefs->passwd = encrypt_with_master_password(ac_prefs->passwd);
713+ cleanse_buffer(tmp_str, strlen(tmp_str));
714+ g_free(tmp_str);
715+ }
716+
717+ if ((ac_prefs->smtp_passwd != NULL) &&
718+ !mpes_string_prefix(ac_prefs->smtp_passwd)) {
719+ /* TODO: Perhaps some prompt? */
720+ debug_print(
721+ "%s -> converting smtp_passwd from cleartext to encrypted\n",
722+ label);
723+ tmp_str = ac_prefs->smtp_passwd;
724+ ac_prefs->smtp_passwd = encrypt_with_master_password(
725+ ac_prefs->smtp_passwd);
726+ cleanse_buffer(tmp_str, strlen(tmp_str));
727+ g_free(tmp_str);
728+ }
729+
730+ }
731+
732 custom_header_read_config(ac_prefs);
733 }
734
735diff --git a/libsylph/prefs_common.c b/libsylph/prefs_common.c
736index 91814fd..192964e 100644
737--- a/libsylph/prefs_common.c
738+++ b/libsylph/prefs_common.c
739@@ -418,6 +418,17 @@ static PrefParam param[] = {
740 {"show_gpg_warning", "TRUE", &prefs_common.gpg_warning, P_BOOL},
741 #endif
742
743+ /* Master password */
744+ {"use_master_password", "FALSE", &prefs_common.use_master_password,
745+ P_BOOL},
746+ {"master_password_hash", NULL, &prefs_common.master_password_hash,
747+ P_STRING},
748+ {"encrypted_password_min_length", "32",
749+ &prefs_common.encrypted_password_min_length, P_INT},
750+ {"auto_unload_master_password", "FALSE",
751+ &prefs_common.auto_unload_master_password,
752+ P_BOOL},
753+
754 /* Interface */
755 {"separate_folder", "FALSE", &prefs_common.sep_folder, P_BOOL},
756 {"separate_message", "FALSE", &prefs_common.sep_msg, P_BOOL},
757diff --git a/libsylph/prefs_common.h b/libsylph/prefs_common.h
758index 910f370..6f9c364 100644
759--- a/libsylph/prefs_common.h
760+++ b/libsylph/prefs_common.h
761@@ -249,6 +249,12 @@ struct _PrefsCommon
762 gboolean passphrase_grab;
763 gboolean gpg_warning;
764
765+ /* Master password */
766+ gboolean use_master_password;
767+ gchar *master_password_hash;
768+ guint encrypted_password_min_length;
769+ gboolean auto_unload_master_password;
770+
771 /* Interface */
772 gboolean sep_folder;
773 gboolean sep_msg;
774diff --git a/libsylph/ssl.c b/libsylph/ssl.c
775index 8413925..e782b0e 100644
776--- a/libsylph/ssl.c
777+++ b/libsylph/ssl.c
778@@ -32,6 +32,13 @@
779 #include "ssl.h"
780 #include "ssl_hostname_validation.h"
781
782+#define SALT_SIZE 16
783+#define CIPHER EVP_aes_256_cfb()
784+#define KEY_HASH EVP_sha256()
785+#define DIGEST_HASH EVP_sha256()
786+#define PBKDF2_DIGEST_SIZE 64
787+#define PBKDF2_ITERATIONS 100000
788+
789 static SSL_CTX *ssl_ctx_SSLv23 = NULL;
790 static SSL_CTX *ssl_ctx_TLSv1 = NULL;
791
792@@ -402,4 +409,468 @@ void ssl_set_verify_func(SSLVerifyFunc func)
793 verify_ui_func = func;
794 }
795
796+/* master password related functions */
797+static gint secure_derive_key(guchar *key,
798+ gint length_key,
799+ const gchar *passphrase,
800+ const guchar *salt) {
801+
802+ guint length_buffer, length_hash;
803+ guchar *buffer, *ptr_hash;
804+
805+ EVP_MD_CTX *mdctx;
806+
807+ OPENSSL_cleanse(key, length_key);
808+
809+ length_buffer = SALT_SIZE + strlen(passphrase);
810+ buffer = OPENSSL_malloc(length_buffer);
811+ OPENSSL_cleanse(buffer, length_buffer);
812+
813+ memcpy(buffer, salt, SALT_SIZE);
814+ memcpy(buffer + SALT_SIZE, passphrase, strlen(passphrase));
815+
816+ mdctx = EVP_MD_CTX_create();
817+ EVP_DigestInit_ex(mdctx, KEY_HASH, NULL);
818+ EVP_DigestUpdate(mdctx, buffer, length_buffer);
819+ OPENSSL_cleanse(buffer, length_buffer);
820+
821+ ptr_hash = OPENSSL_malloc(EVP_MD_size(KEY_HASH));
822+ EVP_DigestFinal_ex(mdctx, ptr_hash, &length_hash);
823+
824+ memcpy(key,
825+ ptr_hash,
826+ (length_hash > length_key) ? length_key : length_hash);
827+
828+ OPENSSL_cleanse(ptr_hash, length_hash);
829+ OPENSSL_free(ptr_hash);
830+ OPENSSL_free(buffer);
831+ EVP_MD_CTX_destroy(mdctx);
832+
833+ return SSL_RC_OK;
834+
835+}
836+
837+gint encrypt_data(gchar **encrypted,
838+ gint *length_encrypted,
839+ const gchar *data,
840+ const gchar *passphrase,
841+ gint length_data,
842+ guint min_data_length,
843+ gboolean rnd_salt) {
844+
845+ gint crypt_buffer_cnt, rc;
846+ guint key_size, length_hash;
847+ guint length_cleartext, length_ciphertext, length_total;
848+ guchar salt[SALT_SIZE];
849+ guchar *ciphertext_buffer, *total_buffer;
850+ /* sensitive buffers and counters */
851+ guint length_data_payload, length_padding;
852+ gchar str_data_size[3];
853+ guchar *data_payload_buffer, *hash_buffer, *padding_buffer, *key;
854+ guchar *cleartext_buffer;
855+
856+ EVP_CIPHER_CTX *ctx;
857+ EVP_MD_CTX *mdctx;
858+
859+ rc = SSL_RC_ERROR;
860+
861+ if (length_data < 1) {
862+ return -1;
863+ }
864+ if (rnd_salt) {
865+ if (RAND_bytes(salt, SALT_SIZE) != 1) {
866+ debug_print("Random problems...\n");
867+ goto cleanup;
868+ }
869+ } else {
870+ strncpy((gchar *)salt, "FOR TESTING ONLY", SALT_SIZE);
871+ }
872+
873+ key_size = EVP_CIPHER_key_length(CIPHER);
874+ key = OPENSSL_malloc(key_size);
875+ OPENSSL_cleanse(key, key_size);
876+ if (secure_derive_key(key,
877+ key_size,
878+ passphrase,
879+ salt) != SSL_RC_OK) {
880+ OPENSSL_cleanse(key, key_size);
881+ debug_print("Could not generate secure key\n");
882+ goto cleanup;
883+ }
884+
885+ /* prepare the data-buffer */
886+ length_padding = 0;
887+ if (length_data < min_data_length) {
888+ g_snprintf(str_data_size, 3, "%02d", length_data);
889+ length_padding = min_data_length - length_data;
890+ padding_buffer = OPENSSL_malloc(length_padding);
891+ OPENSSL_cleanse(padding_buffer, length_padding);
892+ if (RAND_bytes(padding_buffer, length_padding) != 1) {
893+ debug_print("Random problems...\n");
894+ goto cleanup;
895+ }
896+ } else {
897+ g_snprintf(str_data_size, 3, "-1");
898+ }
899+
900+ length_data_payload = 2 + length_data + length_padding;
901+ data_payload_buffer = OPENSSL_malloc(length_data_payload);
902+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
903+
904+ memcpy(data_payload_buffer, str_data_size, 2);
905+ memcpy(data_payload_buffer + 2, data, length_data);
906+ if (length_padding > 0) {
907+ memcpy(data_payload_buffer + (2 + length_data),
908+ padding_buffer,
909+ length_padding);
910+ }
911+
912+ mdctx = EVP_MD_CTX_create();
913+ EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL);
914+ EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload);
915+
916+ length_hash = EVP_MD_size(DIGEST_HASH);
917+ hash_buffer = OPENSSL_malloc(length_hash);
918+ OPENSSL_cleanse(hash_buffer, length_hash);
919+ EVP_DigestFinal_ex(mdctx, hash_buffer, NULL);
920+
921+ length_cleartext = length_hash + length_data_payload;
922+
923+ cleartext_buffer = OPENSSL_malloc(length_cleartext);
924+ OPENSSL_cleanse(cleartext_buffer, length_cleartext);
925+
926+ /* assemble cleartext-buffer */
927+ memcpy(cleartext_buffer, hash_buffer, length_hash);
928+ memcpy(cleartext_buffer + length_hash,
929+ data_payload_buffer,
930+ length_data_payload);
931+ OPENSSL_cleanse(data_payload_buffer, length_data_payload); /* sensitive */
932+
933+ /* encryption */
934+ if (!(ctx = EVP_CIPHER_CTX_new())) {
935+ debug_print("New ctx failed\n");
936+ goto cleanup;
937+ }
938+
939+ length_ciphertext = 0;
940+ if (EVP_EncryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) {
941+ debug_print("EVP_EncryptInit_ex failed\n");
942+ goto cleanup;
943+ }
944+
945+ ciphertext_buffer = OPENSSL_malloc(length_cleartext);
946+
947+ crypt_buffer_cnt = 0;
948+ while(1) {
949+ if (EVP_EncryptUpdate(ctx,
950+ ciphertext_buffer + length_ciphertext,
951+ &crypt_buffer_cnt,
952+ cleartext_buffer + length_ciphertext,
953+ 1) != 1) { /* one byte at a time */
954+ debug_print("EVP_EncryptUpdate failed\n");
955+ goto cleanup;
956+ }
957+
958+ if (crypt_buffer_cnt != 1) { /* paranoia */
959+ debug_print("The sizes of enc and dec text do not correspond\n");
960+ goto cleanup;
961+ }
962+
963+ ++length_ciphertext;
964+
965+ if (length_ciphertext >= length_cleartext) {
966+ break;
967+ }
968+ }
969+ /* No padding required for the CFB mode */
970+
971+ OPENSSL_cleanse(cleartext_buffer, length_cleartext); /* sensitive */
972+ length_total = SALT_SIZE + length_ciphertext;
973+ total_buffer = OPENSSL_malloc(length_total);
974+
975+ memcpy(total_buffer, salt, SALT_SIZE);
976+ memcpy(total_buffer + SALT_SIZE, ciphertext_buffer, length_ciphertext);
977+
978+ *encrypted = g_base64_encode(total_buffer, length_total);
979+ *length_encrypted = strlen(*encrypted);
980+
981+ rc = SSL_RC_OK;
982+
983+cleanup:
984+ /* key */
985+ OPENSSL_cleanse(key, key_size);
986+ OPENSSL_free(key);
987+ /* cleartext buffer */
988+ OPENSSL_cleanse(cleartext_buffer, length_cleartext);
989+ OPENSSL_free(cleartext_buffer);
990+ /* payload buffer */
991+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
992+ OPENSSL_free(data_payload_buffer);
993+ /* hash */
994+ OPENSSL_cleanse(hash_buffer, length_hash);
995+ OPENSSL_free(hash_buffer);
996+ /* padding */
997+ if (length_padding > 0) {
998+ OPENSSL_cleanse(padding_buffer, length_padding);
999+ OPENSSL_free(padding_buffer);
1000+ }
1001+
1002+ OPENSSL_cleanse(str_data_size, 3); /* paranoia */
1003+
1004+ /* ciphertext buffer */
1005+ OPENSSL_free(ciphertext_buffer);
1006+
1007+ /* total buffer */
1008+ OPENSSL_free(total_buffer);
1009+
1010+ length_data_payload = 0;
1011+ length_padding = 0;
1012+
1013+ EVP_CIPHER_CTX_free(ctx);
1014+ EVP_MD_CTX_destroy(mdctx);
1015+
1016+ return rc;
1017+
1018+}
1019+
1020+gint decrypt_data(gchar **decrypted,
1021+ const gchar *data,
1022+ const gchar *passphrase,
1023+ gint length_data) {
1024+
1025+ gint rc; /* return code */
1026+ gint decrypt_buffer_cnt, length_ciphertext, length_decrypted;
1027+ guint key_size, length_hash, length_cleartext;
1028+ gsize length_total;
1029+ guchar salt[SALT_SIZE];
1030+ guchar *ciphertext_buffer, *total_buffer;
1031+ /* sensitive buffers and counters */
1032+ gint data_size;
1033+ guint length_data_payload;
1034+ gchar str_data_size[3];
1035+ guchar *data_payload_buffer, *hash_buffer, *key;
1036+ guchar *cleartext_buffer;
1037+
1038+ EVP_CIPHER_CTX *ctx;
1039+ EVP_MD_CTX *mdctx;
1040+
1041+ rc = -1;
1042+
1043+ if (length_data < 1) {
1044+ return -1;
1045+ }
1046+
1047+ total_buffer = g_base64_decode(data, &length_total);
1048+ length_ciphertext = length_total - SALT_SIZE;
1049+ ciphertext_buffer = OPENSSL_malloc(length_ciphertext);
1050+ OPENSSL_cleanse(ciphertext_buffer, length_ciphertext);
1051+
1052+ memcpy(salt, total_buffer, SALT_SIZE);
1053+ memcpy(ciphertext_buffer, total_buffer + SALT_SIZE, length_ciphertext);
1054+
1055+ key_size = EVP_CIPHER_key_length(CIPHER);
1056+
1057+ /* decryption */
1058+ if(!(ctx = EVP_CIPHER_CTX_new())) {
1059+ debug_print("New ctx failed\n");
1060+ goto cleanup;
1061+ }
1062+
1063+ key = OPENSSL_malloc(key_size);
1064+ OPENSSL_cleanse(key, key_size);
1065+ if (secure_derive_key(key,
1066+ key_size,
1067+ passphrase,
1068+ salt) != 0) {
1069+ OPENSSL_cleanse(key, key_size);
1070+ debug_print("Could not generate secure key\n");
1071+ goto cleanup;
1072+ }
1073+
1074+ if (EVP_DecryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) {
1075+ debug_print("EVP_DecryptInit_ex failed\n");
1076+ goto cleanup;
1077+ }
1078+ OPENSSL_cleanse(key, key_size); /* highly sensitive */
1079+ cleartext_buffer = OPENSSL_malloc(length_ciphertext);
1080+ OPENSSL_cleanse(cleartext_buffer, length_ciphertext);
1081+
1082+ length_cleartext = 0;
1083+ decrypt_buffer_cnt = 0;
1084+
1085+ while(1) {
1086+ if (EVP_DecryptUpdate(ctx,
1087+ cleartext_buffer + length_cleartext,
1088+ &decrypt_buffer_cnt,
1089+ ciphertext_buffer + length_cleartext,
1090+ 1) != 1) { /* one byte at a time */
1091+ debug_print("EVP_EncryptUpdate failed\n");
1092+ goto cleanup;
1093+ }
1094+
1095+ if (decrypt_buffer_cnt != 1) { /* paranoia */
1096+ debug_print("The sizes of enc and dec text do not correspond");
1097+ goto cleanup;
1098+ }
1099+
1100+ ++length_cleartext;
1101+
1102+ if (length_cleartext >= length_ciphertext) {
1103+ break;
1104+ }
1105+ }
1106+
1107+
1108+ length_hash = EVP_MD_size(DIGEST_HASH);
1109+ hash_buffer = OPENSSL_malloc(length_hash);
1110+ length_data_payload = length_cleartext - length_hash;
1111+ data_payload_buffer = OPENSSL_malloc(length_data_payload);
1112+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
1113+
1114+ memcpy(data_payload_buffer,
1115+ cleartext_buffer + length_hash,
1116+ length_data_payload);
1117+
1118+ mdctx = EVP_MD_CTX_create();
1119+ EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL);
1120+ EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload);
1121+ EVP_DigestFinal_ex(mdctx, hash_buffer, &length_hash);
1122+
1123+ if (strncmp((const gchar*) hash_buffer,
1124+ (const gchar*) cleartext_buffer,
1125+ length_hash) != 0) {
1126+ debug_print("Invalid hash\n");
1127+ rc = SSL_RC_WRONG_HASH_OR_KEY;
1128+ goto cleanup;
1129+ }
1130+
1131+ memcpy(str_data_size, cleartext_buffer + length_hash, 2);
1132+ data_size = atoi(str_data_size);
1133+
1134+ if (data_size < 0) {
1135+ length_decrypted = length_data_payload - 2;
1136+ } else {
1137+ length_decrypted = data_size;
1138+ }
1139+ *decrypted = OPENSSL_malloc(length_decrypted);
1140+ memcpy(*decrypted, data_payload_buffer + 2, length_decrypted);
1141+
1142+ rc = SSL_RC_OK;
1143+
1144+cleanup:
1145+
1146+ /* key */
1147+ OPENSSL_cleanse(key, key_size);
1148+ OPENSSL_free(key);
1149+ /* payload buffer */
1150+ OPENSSL_cleanse(data_payload_buffer, length_data_payload);
1151+ OPENSSL_free(data_payload_buffer);
1152+ /* hash */
1153+ OPENSSL_cleanse(hash_buffer, length_hash);
1154+ OPENSSL_free(hash_buffer);
1155+ /* ciphertext */
1156+ OPENSSL_free(ciphertext_buffer);
1157+ OPENSSL_free(total_buffer);
1158+
1159+ EVP_CIPHER_CTX_free(ctx);
1160+ EVP_MD_CTX_destroy(mdctx);
1161+
1162+ return rc;
1163+
1164+}
1165+
1166+gint generate_password_hash(gchar **password_hash,
1167+ const gchar *password,
1168+ const guchar *salt) {
1169+ /*
1170+ * Hashes 'password' using PKCS5_PBKDF2_HMAC with SHA512 and 'salt',
1171+ * and assigns a string to 'password_hash' with the following format:
1172+ * pbkdf2_sha512$iterations$base64(salt)$base64(password_hash)
1173+ */
1174+ guchar lsalt[SALT_SIZE];
1175+ gchar *PBKDF2_digest, *salt_b64, *digest_b64;
1176+
1177+ if (salt == NULL) {
1178+ if (RAND_bytes(lsalt, SALT_SIZE) != 1) {
1179+ debug_print("Random problems...\n");
1180+ return SSL_RC_ERROR;
1181+ }
1182+ } else {
1183+ memcpy(lsalt, salt, SALT_SIZE);
1184+ }
1185+
1186+ PBKDF2_digest = OPENSSL_malloc(PBKDF2_DIGEST_SIZE);
1187+ OPENSSL_cleanse(PBKDF2_digest, PBKDF2_DIGEST_SIZE);
1188+
1189+ PKCS5_PBKDF2_HMAC(password,
1190+ strlen(password),
1191+ lsalt,
1192+ SALT_SIZE,
1193+ PBKDF2_ITERATIONS,
1194+ EVP_sha512(),
1195+ PBKDF2_DIGEST_SIZE,
1196+ (guchar *) PBKDF2_digest);
1197+ digest_b64 = g_base64_encode((guchar *) PBKDF2_digest, PBKDF2_DIGEST_SIZE);
1198+ OPENSSL_cleanse(PBKDF2_digest, PBKDF2_DIGEST_SIZE);
1199+ OPENSSL_free(PBKDF2_digest);
1200+
1201+ salt_b64 = g_base64_encode(lsalt, SALT_SIZE);
1202+
1203+ *password_hash = g_strdup_printf("pbkdf2_sha512$%d$%s$%s",
1204+ PBKDF2_ITERATIONS,
1205+ salt_b64,
1206+ digest_b64);
1207+
1208+ OPENSSL_cleanse(digest_b64, strlen(digest_b64));
1209+ OPENSSL_free(digest_b64);
1210+
1211+ OPENSSL_cleanse(salt_b64, strlen(salt_b64));
1212+ OPENSSL_free(salt_b64);
1213+
1214+ return SSL_RC_OK;
1215+
1216+}
1217+
1218+gint check_password(const gchar *password, const gchar *password_hash) {
1219+
1220+ gint token_counter, rc;
1221+ guchar *salt;
1222+ gchar **tokens, *new_hash;
1223+ gsize salt_length;
1224+
1225+ rc = SSL_RC_ERROR;
1226+ tokens = g_strsplit(password_hash,
1227+ "$",
1228+ -1);
1229+ token_counter = 0;
1230+ while (*(tokens + token_counter) != NULL) {
1231+ ++token_counter;
1232+ }
1233+
1234+ if (token_counter != 4) {
1235+ debug_print("Invalid password hash...\n");
1236+ goto cleanup;
1237+ }
1238+
1239+ salt = g_base64_decode(*(tokens + 2), &salt_length);
1240+ if (salt_length != SALT_SIZE) {
1241+ debug_print("Salt size does not match\n");
1242+ goto cleanup;
1243+ }
1244+
1245+ if (generate_password_hash(&new_hash, password, salt) != SSL_RC_OK) {
1246+ debug_print("Password hash generation failed\n");
1247+ goto cleanup;
1248+ }
1249+
1250+ rc = g_strcmp0(password_hash, new_hash);
1251+
1252+cleanup:
1253+ g_free(salt);
1254+ g_free(new_hash);
1255+ g_strfreev(tokens);
1256+ return rc;
1257+
1258+}
1259+
1260 #endif /* USE_SSL */
1261diff --git a/libsylph/ssl.h b/libsylph/ssl.h
1262index a9f690d..4c0ccd1 100644
1263--- a/libsylph/ssl.h
1264+++ b/libsylph/ssl.h
1265@@ -32,9 +32,15 @@
1266 #include <openssl/pem.h>
1267 #include <openssl/ssl.h>
1268 #include <openssl/err.h>
1269+#include <openssl/evp.h>
1270+#include <openssl/rand.h>
1271
1272 #include "socket.h"
1273
1274+#define SSL_RC_OK 0
1275+#define SSL_RC_ERROR -1
1276+#define SSL_RC_WRONG_HASH_OR_KEY 1
1277+
1278 typedef enum {
1279 SSL_METHOD_SSLv23,
1280 SSL_METHOD_TLSv1
1281@@ -60,6 +66,26 @@ void ssl_done_socket (SockInfo *sockinfo);
1282
1283 void ssl_set_verify_func (SSLVerifyFunc func);
1284
1285+/* master password related code */
1286+gint encrypt_data(gchar **encrypted,
1287+ gint *length_encrypted,
1288+ const gchar *data,
1289+ const gchar *passphrase,
1290+ gint length_data,
1291+ guint min_data_length,
1292+ gboolean rnd_salt);
1293+
1294+gint decrypt_data(gchar **decrypted,
1295+ const gchar *data,
1296+ const gchar *passphrase,
1297+ gint length_data);
1298+
1299+gint generate_password_hash(gchar **password_hash,
1300+ const gchar *password,
1301+ const guchar *salt);
1302+
1303+gint check_password(const gchar *password, const gchar *password_hash);
1304+/* ---------------------------- */
1305 #endif /* USE_SSL */
1306
1307 #endif /* __SSL_H__ */
1308diff --git a/libsylph/utils.c b/libsylph/utils.c
1309index 6ecf328..3bbcbcf 100644
1310--- a/libsylph/utils.c
1311+++ b/libsylph/utils.c
1312@@ -4598,6 +4598,36 @@ gchar *input_query_password(const gchar *server, const gchar *user)
1313 return NULL;
1314 }
1315
1316+static QueryMasterPasswordFunc query_master_password_func = NULL;
1317+
1318+void set_input_query_master_password_func(QueryMasterPasswordFunc func)
1319+{
1320+ query_master_password_func = func;
1321+}
1322+
1323+gchar *input_query_master_password(void)
1324+{
1325+ if (query_master_password_func)
1326+ return query_master_password_func();
1327+ else
1328+ return NULL;
1329+}
1330+
1331+static SetNewPasswordFunc set_new_password_func = NULL;
1332+
1333+void set_input_set_new_password_func(SetNewPasswordFunc func)
1334+{
1335+ set_new_password_func = func;
1336+}
1337+
1338+gchar *input_set_new_password(guint max_attempts)
1339+{
1340+ if (set_new_password_func)
1341+ return set_new_password_func(max_attempts);
1342+ else
1343+ return NULL;
1344+}
1345+
1346 /* logging */
1347
1348 static FILE *log_fp = NULL;
1349diff --git a/libsylph/utils.h b/libsylph/utils.h
1350index 9ac65cf..ede55ff 100644
1351--- a/libsylph/utils.h
1352+++ b/libsylph/utils.h
1353@@ -202,6 +202,8 @@ typedef void (*ProgressFunc) (gint cur,
1354 gint total);
1355 typedef gchar * (*QueryPasswordFunc) (const gchar *server,
1356 const gchar *user);
1357+typedef gchar * (*QueryMasterPasswordFunc) (void);
1358+typedef gchar * (*SetNewPasswordFunc) (guint max_attempts);
1359 typedef void (*LogFunc) (const gchar *str);
1360 typedef void (*LogFlushFunc) (void);
1361
1362@@ -560,9 +562,12 @@ void progress_show (gint cur,
1363
1364 /* user input */
1365 void set_input_query_password_func (QueryPasswordFunc func);
1366-
1367 gchar *input_query_password (const gchar *server,
1368 const gchar *user);
1369+void set_input_query_master_password_func(QueryMasterPasswordFunc func);
1370+gchar *input_query_master_password(void);
1371+void set_input_set_new_password_func(SetNewPasswordFunc func);
1372+gchar *input_set_new_password(guint max_attempts);
1373
1374 /* logging */
1375 void set_log_file (const gchar *filename);
1376diff --git a/src/inputdialog.c b/src/inputdialog.c
1377index a601085..cdfb997 100644
1378--- a/src/inputdialog.c
1379+++ b/src/inputdialog.c
1380@@ -44,6 +44,7 @@
1381 #include "filesel.h"
1382 #include "prefs_common.h"
1383 #include "gtkutils.h"
1384+#include "masterpassword.h"
1385 #include "utils.h"
1386
1387 #define DIALOG_WIDTH 420
1388@@ -156,6 +157,57 @@ gchar *input_dialog_query_password(const gchar *server, const gchar *user)
1389 return pass;
1390 }
1391
1392+gchar *input_dialog_query_master_password(void)
1393+{
1394+ gchar *mp_input;
1395+
1396+ mp_input = input_dialog_with_invisible(_("Input password"),
1397+ _("Master password"),
1398+ NULL);
1399+
1400+ if (prefs_common.use_master_password &&
1401+ prefs_common.auto_unload_master_password)
1402+ {
1403+ g_timeout_add(1000 * 30, unload_master_password, NULL);
1404+ debug_print("Master password to be unloaded in 30 seconds\n");
1405+ }
1406+
1407+ return mp_input;
1408+}
1409+
1410+gchar *input_dialog_set_new_password(guint max_attempts)
1411+{
1412+ guint cnt;
1413+ gchar *pass1, *pass2;
1414+
1415+ if (max_attempts < 1)
1416+ return NULL;
1417+
1418+ for (cnt = 0; cnt < max_attempts; ++cnt) {
1419+ pass1 = input_dialog_with_invisible(
1420+ _("Input password"),
1421+ _("New password"),
1422+ NULL);
1423+ pass2 = input_dialog_with_invisible(
1424+ _("Input password"),
1425+ _("Confirm new password"),
1426+ NULL);
1427+
1428+ if (pass1 != NULL && pass2 != NULL && strcmp(pass1, pass2) == 0) {
1429+ cleanse_buffer(pass2, strlen(pass2));
1430+ g_free(pass2);
1431+ break;
1432+ }
1433+ cleanse_buffer(pass1, strlen(pass1));
1434+ cleanse_buffer(pass2, strlen(pass2));
1435+ g_free(pass2);
1436+ g_free(pass1);
1437+ pass1 = NULL;
1438+ }
1439+
1440+ return pass1;
1441+}
1442+
1443 gchar *input_dialog_with_filesel(const gchar *title, const gchar *message,
1444 const gchar *default_string,
1445 GtkFileChooserAction action)
1446diff --git a/src/inputdialog.h b/src/inputdialog.h
1447index 02bdda3..5364d1a 100644
1448--- a/src/inputdialog.h
1449+++ b/src/inputdialog.h
1450@@ -36,7 +36,8 @@ gchar *input_dialog_combo (const gchar *title,
1451 gboolean case_sensitive);
1452 gchar *input_dialog_query_password (const gchar *server,
1453 const gchar *user);
1454-
1455+gchar *input_dialog_query_master_password(void);
1456+gchar *input_dialog_set_new_password(guint max_attempts);
1457 gchar *input_dialog_with_filesel (const gchar *title,
1458 const gchar *message,
1459 const gchar *default_string,
1460diff --git a/src/main.c b/src/main.c
1461index 77d8192..97a4bd0 100644
1462--- a/src/main.c
1463+++ b/src/main.c
1464@@ -87,6 +87,7 @@
1465 #include "foldersel.h"
1466 #include "update_check.h"
1467 #include "colorlabel.h"
1468+#include "masterpassword.h"
1469
1470 #if USE_GPGME
1471 # include "rfc2015.h"
1472@@ -265,14 +266,54 @@ int main(int argc, char *argv[])
1473 set_ui_update_func(gtkut_events_flush);
1474 set_progress_func(main_window_progress_show);
1475 set_input_query_password_func(input_dialog_query_password);
1476+ set_input_set_new_password_func(input_dialog_set_new_password);
1477 #if USE_SSL
1478 ssl_init();
1479 ssl_set_verify_func(ssl_manager_verify_cert);
1480+ set_input_query_master_password_func(input_dialog_query_master_password);
1481 #endif
1482
1483 CHDIR_EXIT_IF_FAIL(get_home_dir(), 1);
1484
1485 prefs_common_read_config();
1486+ set_master_password(NULL);
1487+#if USE_SSL
1488+ if (prefs_common.use_master_password) {
1489+ if (prefs_common.master_password_hash != NULL) {
1490+ if (check_master_password_interactively(3) != MP_RC_OK) {
1491+ if (alertpanel(_("Master password"),
1492+ _("Invalid master password"),
1493+ GTK_STOCK_DISCARD,
1494+ GTK_STOCK_QUIT,
1495+ NULL) != G_ALERTDEFAULT) {
1496+ exit(1);
1497+ }
1498+ }
1499+ } else {
1500+ alertpanel_notice(
1501+ _("Master password enabled but not set. Setting one now"));
1502+ if (set_master_password_interactively(3) != MP_RC_OK) {
1503+ if (alertpanel(_("Master password"),
1504+ _("Unable to set master password"),
1505+ GTK_STOCK_DISCARD,
1506+ GTK_STOCK_QUIT,
1507+ NULL) != G_ALERTDEFAULT) {
1508+ exit(1);
1509+ }
1510+ }
1511+ }
1512+ }
1513+ /* security goal:
1514+ * if the master password is enabled and loaded on init,
1515+ * an attacker can potentially set use_master_password - disabled
1516+ * afterwards and then force Sylpheed to save account data - the result
1517+ * being passwords saved to accountrc in plain-text.
1518+ * possible solution:
1519+ * Do not allow the passwords to be stored in plain-text if Sylpheed
1520+ * was started with use_master_password - enabled.
1521+ */
1522+ master_password_enabled_on_init = prefs_common.use_master_password;
1523+#endif
1524 filter_set_addressbook_func(addressbook_has_address);
1525 filter_read_config();
1526 prefs_actions_read_config();
1527@@ -381,6 +422,13 @@ int main(int argc, char *argv[])
1528
1529 remote_command_exec();
1530
1531+#if USE_SSL
1532+ if (prefs_common.auto_unload_master_password && master_password_active()) {
1533+ debug_print("Auto unloading master password\n");
1534+ unload_master_password();
1535+ }
1536+#endif
1537+
1538 #if USE_UPDATE_CHECK
1539 if (prefs_common.auto_update_check)
1540 update_check(FALSE);
1541@@ -993,6 +1041,7 @@ void app_will_exit(gboolean force)
1542
1543 /* remove temporary files, close log file, socket cleanup */
1544 #if USE_SSL
1545+ unload_master_password();
1546 ssl_done();
1547 #endif
1548 syl_cleanup();
1549diff --git a/src/prefs_account_dialog.c b/src/prefs_account_dialog.c
1550index e9cba13..e215b8e 100644
1551--- a/src/prefs_account_dialog.c
1552+++ b/src/prefs_account_dialog.c
1553@@ -267,7 +267,7 @@ static PrefsUIData ui_data[] = {
1554 {"user_id", &basic.uid_entry,
1555 prefs_set_data_from_entry, prefs_set_entry},
1556 {"password", &basic.pass_entry,
1557- prefs_set_data_from_entry, prefs_set_entry},
1558+ prefs_set_data_from_epass_entry, prefs_set_entry},
1559
1560 /* Receive */
1561 {"use_apop_auth", &receive.use_apop_chkbtn,
1562@@ -313,7 +313,7 @@ static PrefsUIData ui_data[] = {
1563 {"smtp_user_id", &p_send.smtp_uid_entry,
1564 prefs_set_data_from_entry, prefs_set_entry},
1565 {"smtp_password", &p_send.smtp_pass_entry,
1566- prefs_set_data_from_entry, prefs_set_entry},
1567+ prefs_set_data_from_epass_entry, prefs_set_entry},
1568
1569 {"pop_before_smtp", &p_send.pop_bfr_smtp_chkbtn,
1570 prefs_set_data_from_toggle, prefs_set_toggle},
1571diff --git a/src/prefs_common_dialog.c b/src/prefs_common_dialog.c
1572index fa4be4e..119a056 100644
1573--- a/src/prefs_common_dialog.c
1574+++ b/src/prefs_common_dialog.c
1575@@ -216,6 +216,14 @@ static struct Privacy {
1576 } privacy;
1577 #endif
1578
1579+#if USE_SSL
1580+static struct MasterPassword {
1581+ GtkWidget *checkbtn_use_master_password;
1582+ GtkWidget *checkbtn_auto_unload_master_password;
1583+ GtkWidget *spinbtn_encrypted_password_min_length;
1584+} master_password;
1585+#endif
1586+
1587 static struct Interface {
1588 GtkWidget *checkbtn_always_show_msg;
1589 GtkWidget *checkbtn_always_mark_read;
1590@@ -563,6 +571,18 @@ static PrefsUIData ui_data[] = {
1591 prefs_set_data_from_toggle, prefs_set_toggle},
1592 #endif /* USE_GPGME */
1593
1594+#if USE_SSL
1595+ {"use_master_password", &master_password.checkbtn_use_master_password,
1596+ prefs_set_data_from_toggle, prefs_set_toggle},
1597+ {"auto_unload_master_password",
1598+ &master_password.checkbtn_auto_unload_master_password,
1599+ prefs_set_data_from_toggle, prefs_set_toggle},
1600+ {"encrypted_password_min_length",
1601+ &master_password.spinbtn_encrypted_password_min_length,
1602+ prefs_set_data_from_spinbtn,
1603+ prefs_set_spinbtn},
1604+#endif
1605+
1606 /* Interface */
1607 {"always_show_message_when_selected",
1608 &iface.checkbtn_always_show_msg,
1609@@ -693,6 +713,9 @@ static void prefs_junk_create (void);
1610 #if USE_GPGME
1611 static void prefs_privacy_create (void);
1612 #endif
1613+#if USE_SSL
1614+static void prefs_master_password_create (void);
1615+#endif
1616 static void prefs_details_create (void);
1617 static GtkWidget *prefs_other_create (void);
1618 static GtkWidget *prefs_extcmd_create (void);
1619@@ -852,6 +875,10 @@ static void prefs_common_create(void)
1620 #if USE_GPGME
1621 prefs_privacy_create();
1622 SET_NOTEBOOK_LABEL(dialog.notebook, _("Privacy"), page++);
1623+#endif
1624+#if USE_SSL
1625+ prefs_master_password_create();
1626+ SET_NOTEBOOK_LABEL(dialog.notebook, _("Master password"), page++);
1627 #endif
1628 prefs_details_create();
1629 SET_NOTEBOOK_LABEL(dialog.notebook, _("Details"), page++);
1630@@ -2648,6 +2675,93 @@ static void prefs_privacy_create(void)
1631 }
1632 #endif /* USE_GPGME */
1633
1634+#if USE_SSL
1635+static void prefs_master_password_create(void)
1636+{
1637+ GtkWidget *vbox_main;
1638+ GtkWidget *vbox_master_password_options;
1639+ GtkWidget *vbox_master_password_suboptions;
1640+ GtkWidget *hbox1;
1641+ GtkWidget *hbox_spc;
1642+ GtkWidget *label;
1643+ GtkWidget *checkbtn_use_master_password;
1644+ GtkWidget *checkbtn_auto_unload_master_password;
1645+ GtkWidget *spinbtn_encrypted_password_min_length;
1646+ GtkObject *spinbtn_encrypted_password_min_length_adj;
1647+
1648+ vbox_main = gtk_vbox_new (FALSE, VSPACING);
1649+ gtk_widget_show (vbox_main);
1650+ gtk_container_add (GTK_CONTAINER (dialog.notebook), vbox_main);
1651+ gtk_container_set_border_width (GTK_CONTAINER (vbox_main), VBOX_BORDER);
1652+
1653+ vbox_master_password_options = gtk_vbox_new (FALSE, 0);
1654+ gtk_widget_show (vbox_master_password_options);
1655+ gtk_box_pack_start (
1656+ GTK_BOX (vbox_main), vbox_master_password_options, FALSE, FALSE, 0);
1657+
1658+ PACK_CHECK_BUTTON (vbox_master_password_options,
1659+ checkbtn_use_master_password,
1660+ _("Use master password"));
1661+
1662+ vbox_master_password_suboptions = gtk_vbox_new (FALSE, VSPACING_NARROW);
1663+ gtk_widget_show (vbox_master_password_suboptions);
1664+ gtk_box_pack_start (GTK_BOX (vbox_master_password_options),
1665+ vbox_master_password_suboptions,
1666+ FALSE,
1667+ FALSE,
1668+ 0);
1669+
1670+ PACK_CHECK_BUTTON (vbox_master_password_suboptions,
1671+ checkbtn_auto_unload_master_password,
1672+ _("Automatically unload master password "
1673+ "after session initialization"));
1674+
1675+ hbox1 = gtk_hbox_new (FALSE, 8);
1676+ gtk_widget_show (hbox1);
1677+ gtk_box_pack_start (GTK_BOX (vbox_master_password_suboptions),
1678+ hbox1,
1679+ FALSE,
1680+ FALSE,
1681+ 0);
1682+
1683+ hbox_spc = gtk_hbox_new (FALSE, 0);
1684+ gtk_widget_show (hbox_spc);
1685+ gtk_box_pack_start (GTK_BOX (hbox1), hbox_spc, FALSE, FALSE, 0);
1686+ gtk_widget_set_size_request (hbox_spc, 12, -1);
1687+
1688+ label = gtk_label_new (_("Min. password length"));
1689+ gtk_widget_show (label);
1690+ gtk_box_pack_start (
1691+ GTK_BOX (hbox1), label, FALSE, FALSE, 0);
1692+
1693+ spinbtn_encrypted_password_min_length_adj = gtk_adjustment_new (
1694+ 32, 0, 99, 1, 5, 0);
1695+ spinbtn_encrypted_password_min_length = gtk_spin_button_new(
1696+ GTK_ADJUSTMENT (spinbtn_encrypted_password_min_length_adj), 1, 0);
1697+ gtk_widget_show (spinbtn_encrypted_password_min_length);
1698+ gtk_box_pack_start (GTK_BOX (hbox1),
1699+ spinbtn_encrypted_password_min_length,
1700+ FALSE,
1701+ FALSE,
1702+ 0);
1703+ gtk_spin_button_set_numeric (
1704+ GTK_SPIN_BUTTON (spinbtn_encrypted_password_min_length), TRUE);
1705+ gtk_widget_set_size_request (spinbtn_encrypted_password_min_length,
1706+ 64,
1707+ -1);
1708+
1709+ SET_TOGGLE_SENSITIVITY (checkbtn_use_master_password,
1710+ vbox_master_password_suboptions);
1711+
1712+ master_password.checkbtn_use_master_password
1713+ = checkbtn_use_master_password;
1714+ master_password.checkbtn_auto_unload_master_password
1715+ = checkbtn_auto_unload_master_password;
1716+ master_password.spinbtn_encrypted_password_min_length
1717+ = spinbtn_encrypted_password_min_length;
1718+}
1719+#endif /* USE_SSL */
1720+
1721 static void prefs_details_create(void)
1722 {
1723 GtkWidget *vbox1;
1724diff --git a/src/prefs_ui.c b/src/prefs_ui.c
1725index a3a8f74..2ab1d45 100644
1726--- a/src/prefs_ui.c
1727+++ b/src/prefs_ui.c
1728@@ -31,11 +31,13 @@
1729 #include <errno.h>
1730
1731 #include "prefs.h"
1732+#include "prefs_common.h"
1733 #include "prefs_ui.h"
1734 #include "menu.h"
1735 #include "codeconv.h"
1736 #include "utils.h"
1737 #include "gtkutils.h"
1738+#include "masterpassword.h"
1739
1740 typedef enum
1741 {
1742@@ -268,6 +270,65 @@ void prefs_set_data_from_entry(PrefParam *pparam)
1743 }
1744 }
1745
1746+void prefs_set_data_from_epass_entry(PrefParam *pparam)
1747+{
1748+#if USE_SSL
1749+ PrefsUIData *ui_data;
1750+ gchar **str;
1751+ const gchar *entry_str;
1752+
1753+ /* This is where decrypted passwords are encrypted and stored again */
1754+
1755+ /* master_password == NULL for any of the following reasons:
1756+ * - use_master_password is not enabled (we just save without encrypting)
1757+ * - master_password is auto unloaded (prompt for the master password)
1758+ * - undefined reason (refure to store the password)
1759+ */
1760+ if (!prefs_common.use_master_password) {
1761+ /* check if use_master_password was enabled when Sylpheed started */
1762+ if (!master_password_enabled_on_init) {
1763+ prefs_set_data_from_entry(pparam);
1764+ }
1765+ return;
1766+ } else if (master_password == NULL) {
1767+ if (!prefs_common.auto_unload_master_password) {
1768+ debug_print("Master password enabled, but not loaded for no "
1769+ "apparent reason. Not storing\n");
1770+ return;
1771+ } else {
1772+ if (check_master_password_interactively(3) != MP_RC_OK) {
1773+ debug_print("Failed to reload the master password\n");
1774+ return;
1775+ }
1776+ }
1777+ }
1778+
1779+ ui_data = (PrefsUIData *)pparam->ui_data;
1780+ g_return_if_fail(ui_data != NULL);
1781+ g_return_if_fail(*ui_data->widget != NULL);
1782+
1783+ entry_str = gtk_entry_get_text(GTK_ENTRY(*ui_data->widget));
1784+
1785+ switch (pparam->type) {
1786+ case P_STRING:
1787+ str = (gchar **)pparam->data;
1788+ g_free(*str);
1789+ if ((entry_str != NULL) && (!mpes_string_prefix(entry_str))) {
1790+ debug_print("Encrypting GTK password entry\n");
1791+ *str = encrypt_with_master_password(entry_str);
1792+ } else {
1793+ *str = entry_str[0] ? g_strdup(entry_str) : NULL;
1794+ }
1795+ break;
1796+ default:
1797+ g_warning("Invalid PrefType for GtkEntry widget: %d\n",
1798+ pparam->type);
1799+ }
1800+#else
1801+ prefs_set_data_from_entry(pparam);
1802+#endif
1803+}
1804+
1805 void prefs_set_entry(PrefParam *pparam)
1806 {
1807 PrefsUIData *ui_data;
1808diff --git a/src/prefs_ui.h b/src/prefs_ui.h
1809index cfdf353..f7bd903 100644
1810--- a/src/prefs_ui.h
1811+++ b/src/prefs_ui.h
1812@@ -164,6 +164,7 @@ void prefs_set_data_from_dialog (PrefParam *param);
1813 void prefs_set_dialog_to_default(PrefParam *param);
1814
1815 void prefs_set_data_from_entry (PrefParam *pparam);
1816+void prefs_set_data_from_epass_entry(PrefParam *pparam);
1817 void prefs_set_entry (PrefParam *pparam);
1818 void prefs_set_data_from_text (PrefParam *pparam);
1819 void prefs_set_text (PrefParam *pparam);
1820diff --git a/src/send_message.c b/src/send_message.c
1821index a8c2c7a..537c3c8 100644
1822--- a/src/send_message.c
1823+++ b/src/send_message.c
1824@@ -58,6 +58,7 @@
1825 #include "inc.h"
1826 #include "mainwindow.h"
1827 #include "summaryview.h"
1828+#include "masterpassword.h"
1829
1830 #define SMTP_PORT 25
1831 #if USE_SSL
1832@@ -648,13 +649,13 @@ static gint send_message_smtp(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp)
1833
1834 if (ac_prefs->smtp_userid) {
1835 smtp_session->user = g_strdup(ac_prefs->smtp_userid);
1836- if (ac_prefs->smtp_passwd)
1837- smtp_session->pass =
1838- g_strdup(ac_prefs->smtp_passwd);
1839- else if (ac_prefs->tmp_smtp_pass)
1840+ if (ac_prefs->smtp_passwd) {
1841+ smtp_session->pass = decrypt_with_master_password(
1842+ ac_prefs->smtp_passwd);
1843+ } else if (ac_prefs->tmp_smtp_pass) {
1844 smtp_session->pass =
1845 g_strdup(ac_prefs->tmp_smtp_pass);
1846- else {
1847+ } else {
1848 smtp_session->pass =
1849 input_query_password
1850 (ac_prefs->smtp_server,