about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorWilliam Wennerström <william@wstrm.dev>2020-06-11 22:50:36 +0200
committerWilliam Wennerström <william@wstrm.dev>2020-11-16 21:58:07 +0100
commit3370418d71de255c832da97113543e554ec0e86b (patch)
tree0a364790895088cb0415b4ff31bfd40a588735bc /src
parent35aecd425fad6697e9cf72832cb287a156ec7942 (diff)
downloadprofani-tty-3370418d71de255c832da97113543e554ec0e86b.tar.gz
Initial /sendfile OMEMO encryption
Diffstat (limited to 'src')
-rw-r--r--src/command/cmd_funcs.c133
-rw-r--r--src/omemo/crypto.c99
-rw-r--r--src/omemo/crypto.h18
-rw-r--r--src/tools/http_upload.c20
-rw-r--r--src/tools/http_upload.h10
-rw-r--r--src/xmpp/iq.c4
6 files changed, 230 insertions, 54 deletions
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 <stdio.h>
 #include <signal/signal_protocol_types.h>
 
 #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;