From 3370418d71de255c832da97113543e554ec0e86b Mon Sep 17 00:00:00 2001 From: William Wennerström Date: Thu, 11 Jun 2020 22:50:36 +0200 Subject: Initial /sendfile OMEMO encryption --- src/command/cmd_funcs.c | 133 ++++++++++++++++++++++++++++++++++++------------ src/omemo/crypto.c | 99 +++++++++++++++++++++++++++++++++++ src/omemo/crypto.h | 18 +++++-- src/tools/http_upload.c | 20 ++++---- src/tools/http_upload.h | 10 ++-- src/xmpp/iq.c | 4 +- 6 files changed, 230 insertions(+), 54 deletions(-) (limited to 'src') diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 0837f630..c37cc51b 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -73,6 +73,7 @@ #include "plugins/plugins.h" #include "ui/ui.h" #include "ui/window_list.h" +#include "omemo/crypto.h" #include "xmpp/xmpp.h" #include "xmpp/connection.h" #include "xmpp/contact.h" @@ -4809,7 +4810,9 @@ gboolean cmd_sendfile(ProfWin* window, const char* const command, gchar** args) { jabber_conn_status_t conn_status = connection_get_status(); - char* filename = args[0]; + char *filename = args[0]; + char *filepath = NULL; + unsigned char *key = NULL; // expand ~ to $HOME if (filename[0] == '~' && filename[1] == '/') { @@ -4820,45 +4823,103 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args) filename = strdup(filename); } + filepath = strdup(filename); + if (conn_status != JABBER_CONNECTED) { cons_show("You are not currently connected."); - free(filename); - return TRUE; + goto out; } if (window->type != WIN_CHAT && window->type != WIN_PRIVATE && window->type != WIN_MUC) { cons_show_error("Unsupported window for file transmission."); - free(filename); - return TRUE; + goto out; } switch (window->type) { - case WIN_MUC: - { - ProfMucWin* mucwin = (ProfMucWin*)window; - assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK); + case WIN_MUC: + case WIN_CHAT: + { + ProfChatWin *chatwin = (ProfChatWin*)window; + assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); - // only omemo, no pgp/otr available in MUCs - if (mucwin->is_omemo && !prefs_get_boolean(PREF_OMEMO_SENDFILE)) { - cons_show_error("Uploading unencrypted files disabled. See /omemo sendfile, /otr sendfile, /pgp sendfile."); - win_println(window, THEME_ERROR, "-", "Sending encrypted files via http_upload is not possible yet."); - free(filename); - return TRUE; - } - break; - } - case WIN_CHAT: - { - ProfChatWin* chatwin = (ProfChatWin*)window; - assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK); + if (chatwin->is_omemo && !prefs_get_boolean(PREF_OMEMO_SENDFILE)) { + int tmpfd; + GError *err = NULL; + char *tmppath = NULL; - if ((chatwin->is_omemo && !prefs_get_boolean(PREF_OMEMO_SENDFILE)) - || (chatwin->pgp_send && !prefs_get_boolean(PREF_PGP_SENDFILE)) - || (chatwin->is_otr && !prefs_get_boolean(PREF_OTR_SENDFILE))) { - cons_show_error("Uploading unencrypted files disabled. See /omemo sendfile, /otr sendfile, /pgp sendfile."); - win_println(window, THEME_ERROR, "-", "Sending encrypted files via http_upload is not possible yet."); - free(filename); - return TRUE; + tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmppath, &err); + if (err != NULL) { + cons_show_error("Unable to create temporary file for encrypted transfer."); + win_println(window, THEME_ERROR, "-", "Unable to create temporary file for encrypted transfer."); + goto out; + } + + struct stat tmpst; + if (fstat(tmpfd, &tmpst)) { + cons_show_error("Cannot determine file size."); + win_println(window, THEME_ERROR, "-", "Cannot determine file size."); + goto out; + } + + FILE *tmpfile = fdopen(tmpfd, "wb"); + if (tmpfile == NULL) { + cons_show_error("Unable to open temporary file."); + win_println(window, THEME_ERROR, "-", "Unable to open temporary file."); + goto out; + } + + FILE *infile = fopen(filepath, "rb"); + if (infile == NULL) { + cons_show_error("Unable to open file."); + win_println(window, THEME_ERROR, "-", "Unable to open file."); + close(tmpfd); + goto out; + } + + int crypt_res = GPG_ERR_NO_ERROR; + + // TODO(wstrm): Move these to omemo/crypto.c + unsigned char nonce[AES256_GCM_NONCE_LENGTH]; + key = gcry_malloc_secure(AES256_GCM_KEY_LENGTH); + if (key == NULL) { + cons_show_error("Cannot allocate secure memory for encryption."); + win_println(window, THEME_ERROR, "-", "Cannot allocate secure memory for encryption."); + goto out; + } + + key = gcry_random_bytes_secure(AES256_GCM_KEY_LENGTH, GCRY_VERY_STRONG_RANDOM); + gcry_create_nonce(nonce, AES256_GCM_NONCE_LENGTH); + + crypt_res = aes256gcm_encrypt_file(infile, tmpfile, tmpst.st_size, key, nonce); + + if (crypt_res != 0) { + cons_show_error("Failed to encrypt file."); + win_println(window, THEME_ERROR, "-", "Failed to encrypt file."); + goto out; + } + + free(filepath); + filepath = tmppath; + + break; + } + + if ((chatwin->pgp_send && !prefs_get_boolean(PREF_PGP_SENDFILE)) + || (chatwin->is_otr && !prefs_get_boolean(PREF_OTR_SENDFILE))) { + cons_show_error("Uploading unencrypted files disabled. See /omemo sendfile, /otr sendfile, /pgp sendfile."); + win_println(window, THEME_ERROR, "-", "Sending encrypted files via http_upload is not possible yet."); + goto out; + } + break; + } + case WIN_PRIVATE: + { + // We don't support encryption in private MUC windows. + break; + } + default: + cons_show_error("Unsupported window for file transmission."); + goto out; } break; } @@ -4875,14 +4936,12 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args) if (access(filename, R_OK) != 0) { cons_show_error("Uploading '%s' failed: File not found!", filename); - free(filename); - return TRUE; + goto out; } if (!is_regular_file(filename)) { cons_show_error("Uploading '%s' failed: Not a file!", filename); - free(filename); - return TRUE; + goto out; } HTTPUpload* upload = malloc(sizeof(HTTPUpload)); @@ -4894,6 +4953,14 @@ cmd_sendfile(ProfWin* window, const char* const command, gchar** args) iq_http_upload_request(upload); +out: + if (key != NULL) + gcry_free(key); + if (filename != NULL) + free(filename); + if (filepath != NULL) + free(filepath); + return TRUE; } diff --git a/src/omemo/crypto.c b/src/omemo/crypto.c index 380551ad..6d6ba519 100644 --- a/src/omemo/crypto.c +++ b/src/omemo/crypto.c @@ -41,6 +41,9 @@ #include "omemo/omemo.h" #include "omemo/crypto.h" +#define AES256_GCM_TAG_LENGTH 16 +#define AES256_GCM_BUFFER_SIZE 1024 + int omemo_crypto_init(void) { @@ -373,3 +376,99 @@ out: gcry_cipher_close(hd); return res; } + +int aes256gcm_crypt_file(FILE *in, FILE *out, off_t file_size, + unsigned char key[], unsigned char nonce[], bool encrypt) { + + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { + fputs("libgcrypt has not been initialized\n", stderr); + abort(); + } + + if (!encrypt) { + file_size -= AES256_GCM_TAG_LENGTH; + } + + gcry_error_t res; + gcry_cipher_hd_t hd; + + res = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, + GCRY_CIPHER_SECURE); + if (res != GPG_ERR_NO_ERROR) { + goto out; + } + + res = gcry_cipher_setkey(hd, key, AES256_GCM_KEY_LENGTH); + if (res != GPG_ERR_NO_ERROR) { + goto out; + } + + res = gcry_cipher_setiv(hd, nonce, AES256_GCM_NONCE_LENGTH); + if (res != GPG_ERR_NO_ERROR) { + goto out; + } + + unsigned char buffer[AES256_GCM_BUFFER_SIZE]; + + int bytes = 0; + off_t bytes_read = 0, bytes_available = 0, read_size = 0; + while (bytes_read < file_size) { + bytes_available = file_size - bytes_read; + if (!bytes_available) { + break; + } + + if (bytes_available < AES256_GCM_BUFFER_SIZE) { + read_size = bytes_available; + gcry_cipher_final(hd); // Signal last round of bytes. + } else { + read_size = AES256_GCM_BUFFER_SIZE; + } + + bytes = fread(buffer, 1, read_size, in); + bytes_read += bytes; + + if (encrypt) { + res = gcry_cipher_encrypt(hd, buffer, bytes, NULL, 0); + } else { + res = gcry_cipher_decrypt(hd, buffer, bytes, NULL, 0); + } + + if (res != GPG_ERR_NO_ERROR) { + goto out; + } + + fwrite(buffer, 1, bytes, out); + } + + unsigned char tag[AES256_GCM_TAG_LENGTH]; + + if (encrypt) { + // Append authentication tag at the end of the file. + res = gcry_cipher_gettag(hd, tag, AES256_GCM_TAG_LENGTH); + if (res != GPG_ERR_NO_ERROR) { + goto out; + } + + fwrite(tag, 1, AES256_GCM_TAG_LENGTH, out); + + } else { + // Read and verify authentication tag stored at the end of the file. + bytes = fread(tag, 1, AES256_GCM_TAG_LENGTH, in); + res = gcry_cipher_checktag(hd, tag, bytes); + } + +out: + gcry_cipher_close(hd); + return res; +} + +int aes256gcm_encrypt_file(FILE *in, FILE *out, off_t file_size, + unsigned char key[], unsigned char nonce[]) { + return aes256gcm_crypt_file(in, out, file_size, key, nonce, true); +} + +int aes256gcm_decrypt_file(FILE *in, FILE *out, off_t file_size, + unsigned char key[], unsigned char nonce[]) { + return aes256gcm_crypt_file(in, out, file_size, key, nonce, false); +} diff --git a/src/omemo/crypto.h b/src/omemo/crypto.h index 4fb6283e..916486b7 100644 --- a/src/omemo/crypto.h +++ b/src/omemo/crypto.h @@ -32,12 +32,16 @@ * source files in the program, then also delete it here. * */ +#include #include #define AES128_GCM_KEY_LENGTH 16 #define AES128_GCM_IV_LENGTH 12 #define AES128_GCM_TAG_LENGTH 16 +#define AES256_GCM_KEY_LENGTH 32 +#define AES256_GCM_NONCE_LENGTH 12 + int omemo_crypto_init(void); /** * Callback for a secure random number generator. @@ -176,7 +180,13 @@ int aes128gcm_encrypt(unsigned char* ciphertext, size_t* ciphertext_len, const unsigned char* const plaintext, size_t plaintext_len, const unsigned char* const iv, const unsigned char* const key); -int aes128gcm_decrypt(unsigned char* plaintext, - size_t* plaintext_len, const unsigned char* const ciphertext, - size_t ciphertext_len, const unsigned char* const iv, size_t iv_len, - const unsigned char* const key, const unsigned char* const tag); +int aes128gcm_decrypt(unsigned char *plaintext, + size_t *plaintext_len, const unsigned char *const ciphertext, + size_t ciphertext_len, const unsigned char *const iv, size_t iv_len, + const unsigned char *const key, const unsigned char *const tag); + +int aes256gcm_encrypt_file(FILE *in, FILE *out, off_t file_size, + unsigned char key[], unsigned char nonce[]); + +int aes256gcm_decrypt_file(FILE *in, FILE *out, off_t file_size, + unsigned char key[], unsigned char nonce[]); diff --git a/src/tools/http_upload.c b/src/tools/http_upload.c index 312fad46..68de57ed 100644 --- a/src/tools/http_upload.c +++ b/src/tools/http_upload.c @@ -146,7 +146,7 @@ http_file_put(void* userdata) pthread_mutex_lock(&lock); char* msg; - if (asprintf(&msg, "Uploading '%s': 0%%", upload->filename) == -1) { + if (asprintf(&msg, "Uploading '%s': 0%%", upload->filepath) == -1) { msg = strdup(FALLBACK_MSG); } win_print_http_upload(upload->window, msg, upload->put_url); @@ -186,8 +186,8 @@ http_file_put(void* userdata) curl_easy_setopt(curl, CURLOPT_USERAGENT, "profanity"); - if (!(fd = fopen(upload->filename, "rb"))) { - if (asprintf(&err, "failed to open '%s'", upload->filename) == -1) { + if (!(fd = fopen(upload->filepath, "rb"))) { + if (asprintf(&err, "failed to open '%s'", upload->filepath) == -1) { err = NULL; } goto end; @@ -294,6 +294,7 @@ end: pthread_mutex_unlock(&lock); free(upload->filename); + free(upload->filepath); free(upload->mime_type); free(upload->get_url); free(upload->put_url); @@ -303,18 +304,18 @@ end: } char* -file_mime_type(const char* const file_name) +file_mime_type(const char* const filepath) { char* out_mime_type; char file_header[FILE_HEADER_BYTES]; - FILE* fd; - if (!(fd = fopen(file_name, "rb"))) { + FILE *fd; + if (!(fd = fopen(filepath, "rb"))) { return strdup(FALLBACK_MIMETYPE); } size_t file_header_size = fread(file_header, 1, FILE_HEADER_BYTES, fd); fclose(fd); - char* content_type = g_content_type_guess(file_name, (unsigned char*)file_header, file_header_size, NULL); + char *content_type = g_content_type_guess(filepath, (unsigned char*)file_header, file_header_size, NULL); if (content_type != NULL) { char* mime_type = g_content_type_get_mime_type(content_type); out_mime_type = strdup(mime_type); @@ -326,11 +327,10 @@ file_mime_type(const char* const file_name) return out_mime_type; } -off_t -file_size(const char* const filename) +off_t file_size(const char* const filepath) { struct stat st; - stat(filename, &st); + stat(filepath, &st); return st.st_size; } diff --git a/src/tools/http_upload.h b/src/tools/http_upload.h index 3838a5e8..cbab96ed 100644 --- a/src/tools/http_upload.h +++ b/src/tools/http_upload.h @@ -45,9 +45,9 @@ #include "ui/win_types.h" -typedef struct http_upload_t -{ - char* filename; +typedef struct http_upload_t { + char *filename; + char *filepath; off_t filesize; curl_off_t bytes_sent; char* mime_type; @@ -60,8 +60,8 @@ typedef struct http_upload_t void* http_file_put(void* userdata); -char* file_mime_type(const char* const file_name); -off_t file_size(const char* const file_name); +char* file_mime_type(const char* const filepath); +off_t file_size(const char* const filepath); void http_upload_cancel_processes(ProfWin* window); void http_upload_add_upload(HTTPUpload* upload); diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c index 07acdf14..1c7d16e7 100644 --- a/src/xmpp/iq.c +++ b/src/xmpp/iq.c @@ -2401,9 +2401,9 @@ _http_upload_response_id_handler(xmpp_stanza_t* const stanza, void* const userda if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) { char* error_message = stanza_get_error_message(stanza); if (from) { - cons_show_error("Uploading '%s' failed for %s: %s", upload->filename, from, error_message); + cons_show_error("Uploading '%s' failed for %s: %s", upload->filepath, from, error_message); } else { - cons_show_error("Uploading '%s' failed: %s", upload->filename, error_message); + cons_show_error("Uploading '%s' failed: %s", upload->filepath, error_message); } free(error_message); return 0; -- cgit 1.4.1-2-gfad0