From 73f313b9212d652fecb13bcb82a0f162abb897a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Wennerstr=C3=B6m?= Date: Mon, 20 Jul 2020 22:49:50 +0200 Subject: [PATCH] Refactor OMEMO download into AESGCMDownload tool --- Makefile.am | 2 + src/command/cmd_funcs.c | 61 ++++++++++++++-- src/omemo/crypto.c | 8 +-- src/omemo/crypto.h | 3 - src/omemo/omemo.c | 106 ++++++++++++++++++++++++++-- src/omemo/omemo.h | 6 +- src/tools/aesgcm_download.c | 134 ++++++++++++++++++++++++++++++++++++ src/tools/aesgcm_download.h | 66 ++++++++++++++++++ src/tools/aesgcm_upload.c | 0 src/tools/aesgcm_upload.h | 0 src/tools/http_download.c | 20 +++++- src/tools/http_download.h | 3 + 12 files changed, 386 insertions(+), 23 deletions(-) create mode 100644 src/tools/aesgcm_download.c create mode 100644 src/tools/aesgcm_download.h create mode 100644 src/tools/aesgcm_upload.c create mode 100644 src/tools/aesgcm_upload.h 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" @@ -9154,6 +9155,40 @@ cmd_url_open(ProfWin* window, const char* const command, gchar** args) return TRUE; } +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) { @@ -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); - pthread_create(&(download->worker), NULL, &http_file_get, download); - http_download_add_download(download); + 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); + } + + 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 + * Copyright (C) 2020 William Wennerström + * + * 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * Copyright (C) 2020 William Wennerström + * + * 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 . + * + * 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 +#include +#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 diff --git a/src/tools/aesgcm_upload.h b/src/tools/aesgcm_upload.h new file mode 100644 index 00000000..e69de29b 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