about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorWilliam Wennerström <william@wstrm.dev>2020-07-20 22:49:50 +0200
committerWilliam Wennerström <william@wstrm.dev>2020-11-16 21:58:09 +0100
commit73f313b9212d652fecb13bcb82a0f162abb897a0 (patch)
tree79556fb956301732e66c58196e4b685e87718792
parentfb002a59b6dd0e2656987e9bdb72f6d3478d4e97 (diff)
downloadprofani-tty-73f313b9212d652fecb13bcb82a0f162abb897a0.tar.gz
Refactor OMEMO download into AESGCMDownload tool
-rw-r--r--Makefile.am2
-rw-r--r--src/command/cmd_funcs.c61
-rw-r--r--src/omemo/crypto.c8
-rw-r--r--src/omemo/crypto.h3
-rw-r--r--src/omemo/omemo.c106
-rw-r--r--src/omemo/omemo.h6
-rw-r--r--src/tools/aesgcm_download.c134
-rw-r--r--src/tools/aesgcm_download.h66
-rw-r--r--src/tools/aesgcm_upload.c0
-rw-r--r--src/tools/aesgcm_upload.h0
-rw-r--r--src/tools/http_download.c20
-rw-r--r--src/tools/http_download.h3
12 files changed, 386 insertions, 23 deletions
diff --git a/Makefile.am b/Makefile.am
index a2cf5598..5c34b35a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -45,6 +45,8 @@ core_sources = \
 	src/tools/http_upload.h \
 	src/tools/http_download.c \
 	src/tools/http_download.h \
+	src/tools/aesgcm_download.c \
+	src/tools/aesgcm_download.h \
 	src/tools/bookmark_ignore.c \
 	src/tools/bookmark_ignore.h \
 	src/tools/autocomplete.c src/tools/autocomplete.h \
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index b3914e0d..bf6d6843 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -69,6 +69,7 @@
 #include "event/client_events.h"
 #include "tools/http_upload.h"
 #include "tools/http_download.h"
+#include "tools/aesgcm_download.h"
 #include "tools/autocomplete.h"
 #include "tools/parser.h"
 #include "tools/bookmark_ignore.h"
@@ -9155,6 +9156,40 @@ cmd_url_open(ProfWin* window, const char* const command, gchar** args)
 }
 
 void
+_url_open_fallback_method(ProfWin* window, const char* url)
+{
+    /*
+    gboolean is_omemo_aesgcm = false;
+    gchar* scheme = g_uri_parse_scheme(url);
+    if (g_strcmp0(scheme, "aesgcm")) {
+        is_omemo_aesgcm = true;
+    }
+    free(scheme);
+
+    if (is_omemo_aesgcm) {
+        int tmpfd;
+        char* tmpname = NULL;
+        if ((tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmpname, NULL)) == -1) {
+            *err = "Unable to create temporary file for decryption stream.";
+            return NULL;
+        }
+        FILE* tmpfh = fdopen(tmpfd, "wb");
+
+        unsigned char* nonce;
+        unsigned char* key;
+        char* https_url = omemo_parse_aesgcm_url(url, nonce, key);
+
+        _url_save_fallback_method(window, https_url, tmpname);
+
+        int crypt_res = omemo_decrypt_file(tmpfh, 
+
+        remove(tmpname);
+        free(tmpname);
+    }
+    */
+}
+
+void
 _url_save_fallback_method(ProfWin* window, const char* url, const char* filename)
 {
     FILE* fh = fopen(filename, "wb");
@@ -9163,13 +9198,27 @@ _url_save_fallback_method(ProfWin* window, const char* url, const char* filename
         return;
     }
 
-    HTTPDownload* download = malloc(sizeof(HTTPDownload));
-    download->window = window;
-    download->url = strdup(url);
-    download->filehandle = fh;
+    gchar* scheme = g_uri_parse_scheme(url);
+
+    if (g_strcmp0(scheme, "aesgcm") == 0) {
+        AESGCMDownload* download = malloc(sizeof(AESGCMDownload));
+        download->window = window;
+        download->url = strdup(url);
+        download->filehandle = fh;
+
+        pthread_create(&(download->worker), NULL, &aesgcm_file_get, download);
+        aesgcm_download_add_download(download);
+    } else {
+        HTTPDownload* download = malloc(sizeof(HTTPDownload));
+        download->window = window;
+        download->url = strdup(url);
+        download->filehandle = fh;
+
+        pthread_create(&(download->worker), NULL, &http_file_get, download);
+        http_download_add_download(download);
+    }
 
-    pthread_create(&(download->worker), NULL, &http_file_get, download);
-    http_download_add_download(download);
+    free(scheme);
 }
 
 void
diff --git a/src/omemo/crypto.c b/src/omemo/crypto.c
index a9f72626..a05e160e 100644
--- a/src/omemo/crypto.c
+++ b/src/omemo/crypto.c
@@ -400,12 +400,12 @@ aes256gcm_crypt_file(FILE* in, FILE* out, off_t file_size,
         goto out;
     }
 
-    res = gcry_cipher_setkey(hd, key, AES256_GCM_KEY_LENGTH);
+    res = gcry_cipher_setkey(hd, key, OMEMO_AESGCM_KEY_LENGTH);
     if (res != GPG_ERR_NO_ERROR) {
         goto out;
     }
 
-    res = gcry_cipher_setiv(hd, nonce, AES256_GCM_NONCE_LENGTH);
+    res = gcry_cipher_setiv(hd, nonce, OMEMO_AESGCM_NONCE_LENGTH);
     if (res != GPG_ERR_NO_ERROR) {
         goto out;
     }
@@ -468,8 +468,8 @@ out:
 char*
 aes256gcm_create_secure_fragment(unsigned char* key, unsigned char* nonce)
 {
-    int key_size = AES256_GCM_KEY_LENGTH;
-    int nonce_size = AES256_GCM_NONCE_LENGTH;
+    int key_size = OMEMO_AESGCM_KEY_LENGTH;
+    int nonce_size = OMEMO_AESGCM_NONCE_LENGTH;
 
     char* fragment = gcry_malloc_secure((nonce_size + key_size) * 2 + 1);
 
diff --git a/src/omemo/crypto.h b/src/omemo/crypto.h
index f0090daf..c1d508b9 100644
--- a/src/omemo/crypto.h
+++ b/src/omemo/crypto.h
@@ -40,9 +40,6 @@
 #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.
diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c
index e08d3f06..e19b724e 100644
--- a/src/omemo/omemo.c
+++ b/src/omemo/omemo.c
@@ -62,6 +62,9 @@
 #include "xmpp/roster_list.h"
 #include "xmpp/xmpp.h"
 
+#define AESGCM_URL_NONCE_LEN (2 * OMEMO_AESGCM_NONCE_LENGTH)
+#define AESGCM_URL_KEY_LEN   (2 * OMEMO_AESGCM_KEY_LENGTH)
+
 static gboolean loaded;
 
 static void _generate_pre_keys(int count);
@@ -1664,12 +1667,12 @@ char*
 omemo_encrypt_file(FILE* in, FILE* out, off_t file_size, int* gcry_res)
 {
     unsigned char* key = gcry_random_bytes_secure(
-        AES256_GCM_KEY_LENGTH,
+        OMEMO_AESGCM_KEY_LENGTH,
         GCRY_VERY_STRONG_RANDOM);
 
     // Create nonce/IV with random bytes.
-    unsigned char nonce[AES256_GCM_NONCE_LENGTH];
-    gcry_create_nonce(nonce, AES256_GCM_NONCE_LENGTH);
+    unsigned char nonce[OMEMO_AESGCM_NONCE_LENGTH];
+    gcry_create_nonce(nonce, OMEMO_AESGCM_NONCE_LENGTH);
 
     char* fragment = aes256gcm_create_secure_fragment(key, nonce);
     *gcry_res = aes256gcm_crypt_file(in, out, file_size, key, nonce, true);
@@ -1684,7 +1687,96 @@ omemo_encrypt_file(FILE* in, FILE* out, off_t file_size, int* gcry_res)
     return fragment;
 }
 
-//int omemo_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);
-//}
+void
+_bytes_from_hex(const char* hex, size_t hex_size,
+                unsigned char* bytes, size_t bytes_size)
+{
+    const unsigned char ht[] = {
+        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567
+        0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>?
+        0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // @ABCDEFG
+    };
+    const size_t ht_size = sizeof(ht);
+
+    unsigned char b0;
+    unsigned char b1;
+
+    memset(bytes, 0, bytes_size);
+
+    for (int i = 0; (i < hex_size) && (i / 2 < bytes_size); i += 2) {
+        b0 = ((unsigned char)hex[i + 0] & 0x1f) ^ 0x10;
+        b1 = ((unsigned char)hex[i + 1] & 0x1f) ^ 0x10;
+
+        if (b0 <= ht_size && b1 <= ht_size) {
+            bytes[i / 2] = (unsigned char)(ht[b0] << 4) | ht[b1];
+        }
+    }
+}
+
+int
+omemo_decrypt_file(FILE* in, FILE* out, off_t file_size, const char* fragment)
+{
+    char nonce_hex[AESGCM_URL_NONCE_LEN];
+    char key_hex[AESGCM_URL_KEY_LEN];
+
+    const int nonce_pos = 0;
+    const int key_pos = AESGCM_URL_NONCE_LEN;
+
+    memcpy(nonce_hex, &(fragment[nonce_pos]), AESGCM_URL_NONCE_LEN);
+    memcpy(key_hex, &(fragment[key_pos]), AESGCM_URL_KEY_LEN);
+
+    unsigned char nonce[OMEMO_AESGCM_NONCE_LENGTH];
+    unsigned char* key = gcry_malloc_secure(OMEMO_AESGCM_KEY_LENGTH);
+
+    _bytes_from_hex(nonce_hex, AESGCM_URL_NONCE_LEN,
+                    nonce, OMEMO_AESGCM_NONCE_LENGTH);
+    _bytes_from_hex(key_hex, AESGCM_URL_KEY_LEN,
+                    key, OMEMO_AESGCM_KEY_LENGTH);
+
+    int crypt_res = aes256gcm_crypt_file(in, out, file_size, key, nonce, false);
+
+    gcry_free(key);
+
+    return crypt_res;
+}
+
+int
+omemo_parse_aesgcm_url(const char* aesgcm_url,
+                       char** https_url,
+                       char** fragment)
+{
+    CURLUcode ret;
+    CURLU* url = curl_url();
+
+    // Required to allow for the "aesgcm://" scheme that OMEMO Media Sharing
+    // uses.
+    unsigned int curl_flags = CURLU_NON_SUPPORT_SCHEME;
+
+    ret = curl_url_set(url, CURLUPART_URL, aesgcm_url, curl_flags);
+    if (ret) {
+        goto out;
+    }
+
+    ret = curl_url_get(url, CURLUPART_FRAGMENT, fragment, curl_flags);
+    if (ret) {
+        goto out;
+    }
+
+    if (strlen(*fragment) != AESGCM_URL_NONCE_LEN + AESGCM_URL_KEY_LEN) {
+        goto out;
+    }
+
+    ret = curl_url_set(url, CURLUPART_SCHEME, "https", curl_flags);
+    if (ret) {
+        goto out;
+    }
+
+    ret = curl_url_get(url, CURLUPART_URL, https_url, curl_flags);
+    if (ret) {
+        goto out;
+    }
+
+out:
+    curl_url_cleanup(url);
+    return ret;
+}
diff --git a/src/omemo/omemo.h b/src/omemo/omemo.h
index e875dadd..a0e89916 100644
--- a/src/omemo/omemo.h
+++ b/src/omemo/omemo.h
@@ -40,7 +40,9 @@
 #define OMEMO_ERR_UNSUPPORTED_CRYPTO -10000
 #define OMEMO_ERR_GCRYPT             -20000
 
-#define OMEMO_AESGCM_URL_SCHEME "aesgcm"
+#define OMEMO_AESGCM_NONCE_LENGTH 12
+#define OMEMO_AESGCM_KEY_LENGTH   32
+#define OMEMO_AESGCM_URL_SCHEME   "aesgcm"
 
 typedef enum {
     PROF_OMEMOPOLICY_MANUAL,
@@ -99,4 +101,6 @@ char* omemo_on_message_send(ProfWin* win, const char* const message, gboolean re
 char* omemo_on_message_recv(const char* const from, uint32_t sid, const unsigned char* const iv, size_t iv_len, GList* keys, const unsigned char* const payload, size_t payload_len, gboolean muc, gboolean* trusted);
 
 char* omemo_encrypt_file(FILE* in, FILE* out, off_t file_size, int* gcry_res);
+int omemo_decrypt_file(FILE* in, FILE* out, off_t file_size, const char* fragment);
 void omemo_free(void* a);
+int omemo_parse_aesgcm_url(const char* aesgcm_url, char** https_url, char** fragment);
diff --git a/src/tools/aesgcm_download.c b/src/tools/aesgcm_download.c
new file mode 100644
index 00000000..693eabe7
--- /dev/null
+++ b/src/tools/aesgcm_download.c
@@ -0,0 +1,134 @@
+/*
+ * aesgcm_download.c
+ * vim: expandtab:ts=4:sts=4:sw=4
+ *
+ * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2020 William Wennerström <william@wstrm.dev>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#define _GNU_SOURCE 1
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <curl/curl.h>
+#include <gio/gio.h>
+#include <pthread.h>
+#include <assert.h>
+
+#include "profanity.h"
+#include "event/client_events.h"
+#include "tools/aesgcm_download.h"
+#include "omemo/omemo.h"
+#include "config/preferences.h"
+#include "ui/ui.h"
+#include "ui/window.h"
+#include "common.h"
+
+#define FALLBACK_MSG ""
+
+void*
+aesgcm_file_get(void* userdata)
+{
+    AESGCMDownload* aesgcm_dl = (AESGCMDownload*)userdata;
+
+    char* https_url = NULL;
+    char* fragment = NULL;
+
+    if (omemo_parse_aesgcm_url(aesgcm_dl->url, &https_url, &fragment) != 0) {
+        http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->url,
+                                   "Download failed: Cannot parse URL.");
+        return NULL;
+    }
+
+    int tmpfd;
+    char* tmpname = NULL;
+    if ((tmpfd = g_file_open_tmp("profanity.XXXXXX", &tmpname, NULL)) == -1) {
+        http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->url,
+                                   "Downloading '%s' failed: Unable to create "
+                                   "temporary ciphertext file for writing.",
+                                   https_url);
+        return NULL;
+    }
+    FILE* tmpfh = fdopen(tmpfd, "wb");
+
+    // Remove the file once it is closed.
+    remove(tmpname);
+    free(tmpname);
+
+    HTTPDownload* http_dl = malloc(sizeof(HTTPDownload));
+    http_dl->window = aesgcm_dl->window;
+    http_dl->worker = aesgcm_dl->worker;
+    http_dl->url = https_url;
+    http_dl->filehandle = tmpfh;
+    http_dl->close = 0;
+
+    aesgcm_dl->http_dl = http_dl;
+
+    // TODO: Verify result.
+    http_file_get(http_dl);
+
+    // Force flush as the decrypt function will read from the same stream.
+    fflush(tmpfh);
+    rewind(tmpfh);
+
+    int crypt_res = omemo_decrypt_file(tmpfh, aesgcm_dl->filehandle,
+                                       http_dl->bytes_received, fragment);
+
+    fclose(tmpfh);
+
+    if (crypt_res != 0) {
+        http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->url,
+                                   "Downloading '%s' failed: Failed to decrypt"
+                                   "file.",
+                                   https_url);
+    }
+
+    fclose(aesgcm_dl->filehandle);
+
+    return NULL;
+}
+
+void
+aesgcm_download_cancel_processes(ProfWin* window)
+{
+    http_download_cancel_processes(window);
+}
+
+void
+aesgcm_download_add_download(AESGCMDownload* aesgcm_dl)
+{
+    http_download_add_download(aesgcm_dl->http_dl);
+}
diff --git a/src/tools/aesgcm_download.h b/src/tools/aesgcm_download.h
new file mode 100644
index 00000000..fc29a99e
--- /dev/null
+++ b/src/tools/aesgcm_download.h
@@ -0,0 +1,66 @@
+/*
+ * aesgcm_download.h
+ * vim: expandtab:ts=4:sts=4:sw=4
+ *
+ * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2020 William Wennerström <william@wstrm.dev>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#ifndef TOOLS_AESGCM_DOWNLOAD_H
+#define TOOLS_AESGCM_DOWNLOAD_H
+
+#ifdef PLATFORM_CYGWIN
+#define SOCKET int
+#endif
+
+#include <sys/select.h>
+#include <curl/curl.h>
+#include "tools/http_download.h"
+
+#include "ui/win_types.h"
+
+typedef struct aesgcm_download_t
+{
+    char* url;
+    FILE* filehandle;
+    ProfWin* window;
+    pthread_t worker;
+    HTTPDownload* http_dl;
+} AESGCMDownload;
+
+void* aesgcm_file_get(void* userdata);
+
+void aesgcm_download_cancel_processes(ProfWin* window);
+void aesgcm_download_add_download(AESGCMDownload* download);
+
+char* http_basename_from_url(const char* url);
+
+#endif
diff --git a/src/tools/aesgcm_upload.c b/src/tools/aesgcm_upload.c
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/tools/aesgcm_upload.c
diff --git a/src/tools/aesgcm_upload.h b/src/tools/aesgcm_upload.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/tools/aesgcm_upload.h
diff --git a/src/tools/http_download.c b/src/tools/http_download.c
index 09e6bb6e..a86af172 100644
--- a/src/tools/http_download.c
+++ b/src/tools/http_download.c
@@ -157,13 +157,12 @@ http_file_get(void* userdata)
     curl_easy_cleanup(curl);
     curl_global_cleanup();
 
-    if (download->filehandle) {
+    if (download->filehandle && download->close) {
         fclose(download->filehandle);
     }
 
     pthread_mutex_lock(&lock);
     g_free(cert_path);
-
     if (err) {
         char* msg;
         if (download->cancel) {
@@ -237,3 +236,20 @@ http_basename_from_url(const char* url)
 
     return filename;
 }
+
+void
+http_print_transfer_update(ProfWin* window, char* url,
+                           const char* fmt, ...)
+{
+    va_list args;
+
+    va_start(args, fmt);
+    char* msg;
+    if (vasprintf(&msg, fmt, args) == -1) {
+        msg = strdup(FALLBACK_MSG);
+    }
+    va_end(args);
+
+    win_print_http_transfer(window, msg, url);
+    free(msg);
+}
diff --git a/src/tools/http_download.h b/src/tools/http_download.h
index ba8b5023..797e1603 100644
--- a/src/tools/http_download.h
+++ b/src/tools/http_download.h
@@ -54,6 +54,7 @@ typedef struct http_download_t
     ProfWin* window;
     pthread_t worker;
     int cancel;
+    int close;
 } HTTPDownload;
 
 void* http_file_get(void* userdata);
@@ -62,5 +63,7 @@ void http_download_cancel_processes(ProfWin* window);
 void http_download_add_download(HTTPDownload* download);
 
 char* http_basename_from_url(const char* url);
+void http_print_transfer_update(ProfWin* window, char* url,
+                                const char* fmt, ...);
 
 #endif