From 1bb2a9d33e8cd193c21f9995fb7852c7562cafcf Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Tue, 27 Feb 2018 21:52:12 +0100 Subject: First prototype --- libsylph/account.c | 7 + libsylph/defs.h | 3 + libsylph/imap.c | 12 +- libsylph/prefs_account.c | 1 + libsylph/prefs_account.h | 2 + libsylph/ssl.c | 377 +++++++++++++++++++++++++++++++++++++++++++++++ libsylph/ssl.h | 20 +++ src/send_message.c | 20 ++- 8 files changed, 436 insertions(+), 6 deletions(-) diff --git a/libsylph/account.c b/libsylph/account.c index 2431bf3..de0d6f0 100644 --- a/libsylph/account.c +++ b/libsylph/account.c @@ -51,6 +51,10 @@ void account_read_config_all(void) FILE *fp; gchar buf[PREFSBUFSIZE]; PrefsAccount *ac_prefs; +#if USE_SSL + gchar *master_password; + master_password = input_query_password("Sylpheed", "Master password"); +#endif debug_print(_("Reading all config for each account...\n")); @@ -79,6 +83,9 @@ void account_read_config_all(void) for (cur = ac_label_list; cur != NULL; cur = cur->next) { ac_prefs = prefs_account_new(); prefs_account_read_config(ac_prefs, (gchar *)cur->data); +#if USE_SSL + ac_prefs->master_password = master_password; +#endif account_list = g_list_append(account_list, ac_prefs); if (ac_prefs->is_default) cur_account = ac_prefs; diff --git a/libsylph/defs.h b/libsylph/defs.h index 9e3f82b..67325bf 100644 --- a/libsylph/defs.h +++ b/libsylph/defs.h @@ -58,6 +58,9 @@ #define DISPLAY_HEADER_RC "dispheaderrc" #define MENU_RC "menurc" #define ACTIONS_RC "actionsrc" +#ifdef USE_SSL +#define SECURE_RC "securerc" +#endif #define COMMAND_HISTORY "command_history" #define TEMPLATE_DIR "templates" #define TMP_DIR "tmp" diff --git a/libsylph/imap.c b/libsylph/imap.c index aa0d737..a61dc2b 100644 --- a/libsylph/imap.c +++ b/libsylph/imap.c @@ -707,7 +707,17 @@ static gint imap_session_connect(IMAPSession *session) log_message(_("creating IMAP4 connection to %s:%d ...\n"), SESSION(session)->server, SESSION(session)->port); - pass = account->passwd; + if (account->master_password != NULL) { + if (decrypt_data(&pass, + account->passwd, + account->master_password, + strlen(account->passwd)) != RC_OK) { + session_destroy(session); + return -1; + } + } else { + pass = account->passwd; + } if (!pass) pass = account->tmp_pass; if (!pass) { diff --git a/libsylph/prefs_account.c b/libsylph/prefs_account.c index 1aecba9..a16ba3d 100644 --- a/libsylph/prefs_account.c +++ b/libsylph/prefs_account.c @@ -218,6 +218,7 @@ void prefs_account_read_config(PrefsAccount *ac_prefs, const gchar *label) g_free(rcpath); *ac_prefs = tmp_ac_prefs; + ac_prefs->master_password = NULL; while (*p && !g_ascii_isdigit(*p)) p++; id = atoi(p); if (id < 0) g_warning("wrong account id: %d\n", id); diff --git a/libsylph/prefs_account.h b/libsylph/prefs_account.h index ad899f8..5239034 100644 --- a/libsylph/prefs_account.h +++ b/libsylph/prefs_account.h @@ -187,6 +187,8 @@ struct _PrefsAccount /* Compose */ gchar *sig_names[10]; gchar *sig_texts[10]; + + gchar *master_password; }; PrefsAccount *prefs_account_new (void); diff --git a/libsylph/ssl.c b/libsylph/ssl.c index 8413925..5a76b8b 100644 --- a/libsylph/ssl.c +++ b/libsylph/ssl.c @@ -32,6 +32,11 @@ #include "ssl.h" #include "ssl_hostname_validation.h" +#define SALT_SIZE 16 +#define CIPHER EVP_aes_256_cfb() +#define KEY_HASH EVP_sha256() +#define DIGEST_HASH EVP_sha256() + static SSL_CTX *ssl_ctx_SSLv23 = NULL; static SSL_CTX *ssl_ctx_TLSv1 = NULL; @@ -402,4 +407,376 @@ void ssl_set_verify_func(SSLVerifyFunc func) verify_ui_func = func; } +/* master password related functions */ +static gint secure_derive_key(guchar *key, + gint length_key, + const gchar *passphrase, + const guchar *salt) { + + guint length_buffer, length_hash; + guchar *buffer, *ptr_hash; + + EVP_MD_CTX *mdctx; + + OPENSSL_cleanse(key, length_key); + + length_buffer = SALT_SIZE + strlen(passphrase); + buffer = OPENSSL_malloc(length_buffer); + OPENSSL_cleanse(buffer, length_buffer); + + memcpy(buffer, salt, SALT_SIZE); + memcpy(buffer + SALT_SIZE, passphrase, strlen(passphrase)); + + mdctx = EVP_MD_CTX_create(); + EVP_DigestInit_ex(mdctx, KEY_HASH, NULL); + EVP_DigestUpdate(mdctx, buffer, length_buffer); + OPENSSL_cleanse(buffer, length_buffer); + + ptr_hash = OPENSSL_malloc(EVP_MD_size(KEY_HASH)); + EVP_DigestFinal_ex(mdctx, ptr_hash, &length_hash); + + memcpy(key, + ptr_hash, + (length_hash > length_key) ? length_key : length_hash); + + OPENSSL_cleanse(ptr_hash, length_hash); + OPENSSL_free(ptr_hash); + OPENSSL_free(buffer); + EVP_MD_CTX_destroy(mdctx); + + return RC_OK; + +} + + +gint encrypt_data(gchar **encrypted, + gint *length_encrypted, + const gchar *data, + const gchar *passphrase, + gint length_data, + guint min_data_length, + gboolean rnd_salt) { + + gint crypt_buffer_cnt, rc; + guint key_size, length_hash; + guint length_cleartext, length_ciphertext, length_total; + guchar salt[SALT_SIZE]; + guchar *ciphertext_buffer, *total_buffer; + /* sensitive buffers and counters */ + guint length_data_payload, length_padding; + gchar str_data_size[3]; + guchar *data_payload_buffer, *hash_buffer, *padding_buffer, *key; + guchar *cleartext_buffer; + + EVP_CIPHER_CTX *ctx; + EVP_MD_CTX *mdctx; + + rc = RC_ERROR; + + if (length_data < 1) { + return -1; + } + if (rnd_salt) { + if (RAND_bytes(salt, SALT_SIZE) != 1) { + debug_print("Random problems...\n"); + goto cleanup; + } + } else { + strncpy((gchar *)salt, "FOR TESTING ONLY", SALT_SIZE); + } + + key_size = EVP_CIPHER_key_length(CIPHER); + key = OPENSSL_malloc(key_size); + OPENSSL_cleanse(key, key_size); + if (secure_derive_key(key, + key_size, + passphrase, + salt) != RC_OK) { + OPENSSL_cleanse(key, key_size); + debug_print("Could not generate secure key\n"); + goto cleanup; + } + + /* prepare the data-buffer */ + length_padding = 0; + if (length_data < min_data_length) { + g_snprintf(str_data_size, 3, "%02d", length_data); + length_padding = min_data_length - length_data; + padding_buffer = OPENSSL_malloc(length_padding); + OPENSSL_cleanse(padding_buffer, length_padding); + if (RAND_bytes(padding_buffer, length_padding) != 1) { + debug_print("Random problems...\n"); + goto cleanup; + } + } else { + g_snprintf(str_data_size, 3, "-1"); + } + + length_data_payload = 2 + length_data + length_padding; + data_payload_buffer = OPENSSL_malloc(length_data_payload); + OPENSSL_cleanse(data_payload_buffer, length_data_payload); + + memcpy(data_payload_buffer, str_data_size, 2); + memcpy(data_payload_buffer + 2, data, length_data); + if (length_padding > 0) { + memcpy(data_payload_buffer + (2 + length_data), + padding_buffer, + length_padding); + } + + mdctx = EVP_MD_CTX_create(); + EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL); + EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload); + + length_hash = EVP_MD_size(DIGEST_HASH); + hash_buffer = OPENSSL_malloc(length_hash); + OPENSSL_cleanse(hash_buffer, length_hash); + EVP_DigestFinal_ex(mdctx, hash_buffer, NULL); + + length_cleartext = length_hash + length_data_payload; + + cleartext_buffer = OPENSSL_malloc(length_cleartext); + OPENSSL_cleanse(cleartext_buffer, length_cleartext); + + /* assemble cleartext-buffer */ + memcpy(cleartext_buffer, hash_buffer, length_hash); + memcpy(cleartext_buffer + length_hash, + data_payload_buffer, + length_data_payload); + OPENSSL_cleanse(data_payload_buffer, length_data_payload); /* sensitive */ + + /* encryption */ + if (!(ctx = EVP_CIPHER_CTX_new())) { + debug_print("New ctx failed\n"); + goto cleanup; + } + + length_ciphertext = 0; + if (EVP_EncryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) { + debug_print("EVP_EncryptInit_ex failed\n"); + goto cleanup; + } + + ciphertext_buffer = OPENSSL_malloc(length_cleartext); + + crypt_buffer_cnt = 0; + while(1) { + if (EVP_EncryptUpdate(ctx, + ciphertext_buffer + length_ciphertext, + &crypt_buffer_cnt, + cleartext_buffer + length_ciphertext, + 1) != 1) { /* one byte at a time */ + debug_print("EVP_EncryptUpdate failed\n"); + goto cleanup; + } + + if (crypt_buffer_cnt != 1) { /* paranoia */ + debug_print("The sizes of enc and dec text do not correspond\n"); + goto cleanup; + } + + ++length_ciphertext; + + if (length_ciphertext >= length_cleartext) { + break; + } + } + /* No padding required for the CFB mode */ + + OPENSSL_cleanse(cleartext_buffer, length_cleartext); /* sensitive */ + length_total = SALT_SIZE + length_ciphertext; + total_buffer = OPENSSL_malloc(length_total); + + memcpy(total_buffer, salt, SALT_SIZE); + memcpy(total_buffer + SALT_SIZE, ciphertext_buffer, length_ciphertext); + + *encrypted = g_base64_encode(total_buffer, length_total); + *length_encrypted = strlen(*encrypted); + + rc = RC_OK; + +cleanup: + /* key */ + OPENSSL_cleanse(key, key_size); + OPENSSL_free(key); + /* cleartext buffer */ + OPENSSL_cleanse(cleartext_buffer, length_cleartext); + OPENSSL_free(cleartext_buffer); + /* payload buffer */ + OPENSSL_cleanse(data_payload_buffer, length_data_payload); + OPENSSL_free(data_payload_buffer); + /* hash */ + OPENSSL_cleanse(hash_buffer, length_hash); + OPENSSL_free(hash_buffer); + /* padding */ + if (length_padding > 0) { + OPENSSL_cleanse(padding_buffer, length_padding); + OPENSSL_free(padding_buffer); + } + + OPENSSL_cleanse(str_data_size, 3); /* paranoia */ + + /* ciphertext buffer */ + OPENSSL_free(ciphertext_buffer); + + /* total buffer */ + OPENSSL_free(total_buffer); + + length_data_payload = 0; + length_padding = 0; + + EVP_CIPHER_CTX_free(ctx); + EVP_MD_CTX_destroy(mdctx); + + return rc; + +} + + +gint decrypt_data(gchar **decrypted, + const gchar *data, + const gchar *passphrase, + gint length_data) { + + gint rc; /* return code */ + gint decrypt_buffer_cnt, length_ciphertext, length_decrypted; + guint key_size, length_hash, length_cleartext; + gsize length_total; + guchar salt[SALT_SIZE]; + guchar *ciphertext_buffer, *total_buffer; + /* sensitive buffers and counters */ + gint data_size; + guint length_data_payload; + gchar str_data_size[3]; + guchar *data_payload_buffer, *hash_buffer, *key; + guchar *cleartext_buffer; + + EVP_CIPHER_CTX *ctx; + EVP_MD_CTX *mdctx; + + rc = -1; + + if (length_data < 1) { + return -1; + } + + total_buffer = g_base64_decode(data, &length_total); + length_ciphertext = length_total - SALT_SIZE; + ciphertext_buffer = OPENSSL_malloc(length_ciphertext); + OPENSSL_cleanse(ciphertext_buffer, length_ciphertext); + + memcpy(salt, total_buffer, SALT_SIZE); + memcpy(ciphertext_buffer, total_buffer + SALT_SIZE, length_ciphertext); + + key_size = EVP_CIPHER_key_length(CIPHER); + + /* decryption */ + if(!(ctx = EVP_CIPHER_CTX_new())) { + debug_print("New ctx failed\n"); + goto cleanup; + } + + key = OPENSSL_malloc(key_size); + OPENSSL_cleanse(key, key_size); + if (secure_derive_key(key, + key_size, + passphrase, + salt) != 0) { + OPENSSL_cleanse(key, key_size); + debug_print("Could not generate secure key\n"); + goto cleanup; + } + + if (EVP_DecryptInit_ex(ctx, CIPHER, NULL, key, salt) != 1) { + debug_print("EVP_DecryptInit_ex failed\n"); + goto cleanup; + } + OPENSSL_cleanse(key, key_size); /* highly sensitive */ + cleartext_buffer = OPENSSL_malloc(length_ciphertext); + OPENSSL_cleanse(cleartext_buffer, length_ciphertext); + + length_cleartext = 0; + decrypt_buffer_cnt = 0; + + while(1) { + if (EVP_DecryptUpdate(ctx, + cleartext_buffer + length_cleartext, + &decrypt_buffer_cnt, + ciphertext_buffer + length_cleartext, + 1) != 1) { /* one byte at a time */ + debug_print("EVP_EncryptUpdate failed\n"); + goto cleanup; + } + + if (decrypt_buffer_cnt != 1) { /* paranoia */ + debug_print("The sizes of enc and dec text do not correspond"); + goto cleanup; + } + + ++length_cleartext; + + if (length_cleartext >= length_ciphertext) { + break; + } + } + + + length_hash = EVP_MD_size(DIGEST_HASH); + hash_buffer = OPENSSL_malloc(length_hash); + length_data_payload = length_cleartext - length_hash; + data_payload_buffer = OPENSSL_malloc(length_data_payload); + OPENSSL_cleanse(data_payload_buffer, length_data_payload); + + memcpy(data_payload_buffer, + cleartext_buffer + length_hash, + length_data_payload); + + mdctx = EVP_MD_CTX_create(); + EVP_DigestInit_ex(mdctx, DIGEST_HASH, NULL); + EVP_DigestUpdate(mdctx, data_payload_buffer, length_data_payload); + EVP_DigestFinal_ex(mdctx, hash_buffer, &length_hash); + + if (strncmp((const gchar*) hash_buffer, + (const gchar*) cleartext_buffer, + length_hash) != 0) { + debug_print("Invalid hash\n"); + rc = RC_WRONG_HASH_OR_KEY; + goto cleanup; + } + + memcpy(str_data_size, cleartext_buffer + length_hash, 2); + data_size = atoi(str_data_size); + + if (data_size < 0) { + length_decrypted = length_data_payload - 2; + } else { + length_decrypted = data_size; + } + *decrypted = OPENSSL_malloc(length_decrypted); + memcpy(*decrypted, data_payload_buffer + 2, length_decrypted); + + rc = RC_OK; + +cleanup: + + /* key */ + OPENSSL_cleanse(key, key_size); + OPENSSL_free(key); + /* payload buffer */ + OPENSSL_cleanse(data_payload_buffer, length_data_payload); + OPENSSL_free(data_payload_buffer); + /* hash */ + OPENSSL_cleanse(hash_buffer, length_hash); + OPENSSL_free(hash_buffer); + /* ciphertext */ + OPENSSL_free(ciphertext_buffer); + OPENSSL_free(total_buffer); + + EVP_CIPHER_CTX_free(ctx); + EVP_MD_CTX_destroy(mdctx); + + return rc; + +} + #endif /* USE_SSL */ diff --git a/libsylph/ssl.h b/libsylph/ssl.h index a9f690d..94ab4b7 100644 --- a/libsylph/ssl.h +++ b/libsylph/ssl.h @@ -32,9 +32,15 @@ #include #include #include +#include +#include #include "socket.h" +#define RC_OK 0 +#define RC_ERROR -1 +#define RC_WRONG_HASH_OR_KEY 1 + typedef enum { SSL_METHOD_SSLv23, SSL_METHOD_TLSv1 @@ -60,6 +66,20 @@ void ssl_done_socket (SockInfo *sockinfo); void ssl_set_verify_func (SSLVerifyFunc func); +/* master password related code */ +gint encrypt_data(gchar **encrypted, + gint *length_encrypted, + const gchar *data, + const gchar *passphrase, + gint length_data, + guint min_data_length, + gboolean rnd_salt); + +gint decrypt_data(gchar **decrypted, + const gchar *data, + const gchar *passphrase, + gint length_data); +/* ---------------------------- */ #endif /* USE_SSL */ #endif /* __SSL_H__ */ diff --git a/src/send_message.c b/src/send_message.c index a8c2c7a..695246a 100644 --- a/src/send_message.c +++ b/src/send_message.c @@ -648,13 +648,23 @@ static gint send_message_smtp(PrefsAccount *ac_prefs, GSList *to_list, FILE *fp) if (ac_prefs->smtp_userid) { smtp_session->user = g_strdup(ac_prefs->smtp_userid); - if (ac_prefs->smtp_passwd) - smtp_session->pass = - g_strdup(ac_prefs->smtp_passwd); - else if (ac_prefs->tmp_smtp_pass) + if (ac_prefs->smtp_passwd) { + if(ac_prefs->master_password != NULL) { + if (decrypt_data(&(smtp_session->pass), + ac_prefs->smtp_passwd, + ac_prefs->master_password, + strlen(ac_prefs->smtp_passwd)) != RC_OK) { + session_destroy(session); + return -1; + } + } else { + smtp_session->pass = + g_strdup(ac_prefs->smtp_passwd); + } + } else if (ac_prefs->tmp_smtp_pass) { smtp_session->pass = g_strdup(ac_prefs->tmp_smtp_pass); - else { + } else { smtp_session->pass = input_query_password (ac_prefs->smtp_server, -- cgit v1.3