|
|
|
|
/*
|
|
|
|
|
* Yorkie - A task-oriented GnuPG frontend
|
|
|
|
|
* Copyright (C) 2020 Damien Goutte-Gattat
|
|
|
|
|
*
|
|
|
|
|
* This program 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.
|
|
|
|
|
*
|
|
|
|
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include "decrypt.h"
|
|
|
|
|
|
|
|
|
|
#include <glib.h>
|
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
|
|
|
|
|
|
#include <gpgme.h>
|
|
|
|
|
|
|
|
|
|
#include "error.h"
|
|
|
|
|
#include "gpgutil.h"
|
|
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Context structure and associated functions. */
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
gpgme_ctx_t gpgme;
|
|
|
|
|
gpgme_decrypt_result_t decrypt_result;
|
|
|
|
|
gpgme_verify_result_t verify_result;
|
|
|
|
|
|
|
|
|
|
const char *original_filename;
|
|
|
|
|
char *output_filename;
|
|
|
|
|
char *output_basename;
|
|
|
|
|
gboolean output_filename_userset;
|
|
|
|
|
|
|
|
|
|
gboolean signed_only;
|
|
|
|
|
} yki_decrypt_data_t;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
set_default_output(yki_decrypt_data_t *yki)
|
|
|
|
|
{
|
|
|
|
|
char *dirname;
|
|
|
|
|
|
|
|
|
|
if ( yki->output_filename_userset )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
g_free(yki->output_filename);
|
|
|
|
|
g_free(yki->output_basename);
|
|
|
|
|
|
|
|
|
|
if ( yki->decrypt_result && yki->decrypt_result->file_name )
|
|
|
|
|
yki->output_basename = g_strdup(yki->decrypt_result->file_name);
|
|
|
|
|
else if ( yki->verify_result && yki->verify_result->file_name )
|
|
|
|
|
yki->output_basename = g_strdup(yki->verify_result->file_name);
|
|
|
|
|
else if ( g_str_has_suffix(yki->original_filename, ".asc") ||
|
|
|
|
|
g_str_has_suffix(yki->original_filename, ".gpg") ) {
|
|
|
|
|
char *basename = g_path_get_basename(yki->original_filename);
|
|
|
|
|
yki->output_basename = g_strndup(basename, strlen(basename) - 4);
|
|
|
|
|
g_free(basename);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
yki->output_basename = g_strdup("cleartext");
|
|
|
|
|
|
|
|
|
|
dirname = g_path_get_dirname(yki->original_filename);
|
|
|
|
|
yki->output_filename = g_build_filename(dirname, yki->output_basename, NULL);
|
|
|
|
|
g_free(dirname);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* GUI stuff. */
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
output_clicked(GtkButton *button, gpointer data)
|
|
|
|
|
{
|
|
|
|
|
yki_decrypt_data_t *yki = (yki_decrypt_data_t *)data;
|
|
|
|
|
GtkWidget *dlg;
|
|
|
|
|
|
|
|
|
|
dlg = gtk_file_chooser_dialog_new("Save as",
|
|
|
|
|
NULL,
|
|
|
|
|
GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
|
|
|
"_Cancel", GTK_RESPONSE_CANCEL,
|
|
|
|
|
"_OK", GTK_RESPONSE_ACCEPT,
|
|
|
|
|
NULL);
|
|
|
|
|
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dlg), yki->output_filename);
|
|
|
|
|
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dlg), yki->output_basename);
|
|
|
|
|
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dlg), TRUE);
|
|
|
|
|
|
|
|
|
|
if ( gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_ACCEPT ) {
|
|
|
|
|
g_free(yki->output_filename);
|
|
|
|
|
g_free(yki->output_basename);
|
|
|
|
|
|
|
|
|
|
yki->output_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg));
|
|
|
|
|
yki->output_filename_userset = TRUE;
|
|
|
|
|
yki->output_basename = g_path_get_basename(yki->output_filename);
|
|
|
|
|
|
|
|
|
|
gtk_button_set_label(button, yki->output_basename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gtk_widget_destroy(dlg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GtkDialog *
|
|
|
|
|
create_decrypt_dialog(yki_decrypt_data_t *yki)
|
|
|
|
|
{
|
|
|
|
|
GtkBuilder *builder;
|
|
|
|
|
GObject *widget;
|
|
|
|
|
|
|
|
|
|
builder = gtk_builder_new_from_resource("/org/incenp/Yorkie/yki-decrypt.ui");
|
|
|
|
|
|
|
|
|
|
widget = gtk_builder_get_object(builder, "btnOutput");
|
|
|
|
|
g_signal_connect(widget, "clicked", G_CALLBACK(output_clicked), yki);
|
|
|
|
|
gtk_button_set_label(GTK_BUTTON(widget), yki->output_basename);
|
|
|
|
|
gtk_button_set_always_show_image(GTK_BUTTON(widget), TRUE);
|
|
|
|
|
|
|
|
|
|
if ( ! yki->signed_only )
|
|
|
|
|
gtk_label_set_label(GTK_LABEL(gtk_builder_get_object(builder, "lblDecrypt")),
|
|
|
|
|
"The message was successfully decrypted.");
|
|
|
|
|
|
|
|
|
|
if ( yki->verify_result && yki->verify_result->signatures ) {
|
|
|
|
|
GtkCellRenderer *renderer;
|
|
|
|
|
GtkTreeViewColumn *column;
|
|
|
|
|
GtkListStore *store;
|
|
|
|
|
gpgme_signature_t signature;
|
|
|
|
|
GtkTreeIter iter;
|
|
|
|
|
|
|
|
|
|
gtk_label_set_label(GTK_LABEL(gtk_builder_get_object(builder, "lblSign")),
|
|
|
|
|
"The message was signed.");
|
|
|
|
|
|
|
|
|
|
widget = gtk_builder_get_object(builder, "trvSignatures");
|
|
|
|
|
store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(widget)));
|
|
|
|
|
|
|
|
|
|
renderer = gtk_cell_renderer_toggle_new();
|
|
|
|
|
column = gtk_tree_view_column_new_with_attributes("Valid signature", renderer, "active", 0, NULL);
|
|
|
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(widget), column);
|
|
|
|
|
|
|
|
|
|
renderer = gtk_cell_renderer_text_new();
|
|
|
|
|
column = gtk_tree_view_column_new_with_attributes("Issuer", renderer, "text", 1, NULL);
|
|
|
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(widget), column);
|
|
|
|
|
|
|
|
|
|
signature = yki->verify_result->signatures;
|
|
|
|
|
while ( signature ) {
|
|
|
|
|
gpgme_key_t key;
|
|
|
|
|
|
|
|
|
|
gpgme_get_key(yki->gpgme, signature->fpr, &key, 0);
|
|
|
|
|
|
|
|
|
|
gtk_list_store_append(store, &iter);
|
|
|
|
|
gtk_list_store_set(store, &iter,
|
|
|
|
|
0, signature->status == 0,
|
|
|
|
|
1, key ? key->uids->name : signature->fpr,
|
|
|
|
|
-1);
|
|
|
|
|
|
|
|
|
|
gpgme_key_release(key);
|
|
|
|
|
|
|
|
|
|
signature = signature->next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GTK_DIALOG(gtk_builder_get_object(builder, "dlgDecrypt"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Crypto helper functions. */
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
is_file_encrypted(gpgme_data_t buffer, GError **error)
|
|
|
|
|
{
|
|
|
|
|
int ret;
|
|
|
|
|
gpgme_data_type_t type;
|
|
|
|
|
|
|
|
|
|
type = gpgme_data_identify(buffer, 0);
|
|
|
|
|
switch ( type ) {
|
|
|
|
|
case GPGME_DATA_TYPE_INVALID:
|
|
|
|
|
case GPGME_DATA_TYPE_UNKNOWN:
|
|
|
|
|
default:
|
|
|
|
|
yki_error(error, YKI_ERROR_UNKNOWN_DATA, "Cannot decrypt or verify");
|
|
|
|
|
ret = 0;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GPGME_DATA_TYPE_PGP_KEY:
|
|
|
|
|
case GPGME_DATA_TYPE_X509_CERT:
|
|
|
|
|
case GPGME_DATA_TYPE_PKCS12:
|
|
|
|
|
yki_error(error, YKI_ERROR_UNENCRYPTED_DATA, "Cannot decrypt or verify");
|
|
|
|
|
ret = 0;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GPGME_DATA_TYPE_PGP_SIGNATURE:
|
|
|
|
|
yki_error(error, YKI_ERROR_NOT_IMPLEMENTED, "Cannot verify detached signature");
|
|
|
|
|
ret = 0;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GPGME_DATA_TYPE_PGP_SIGNED:
|
|
|
|
|
case GPGME_DATA_TYPE_PGP_ENCRYPTED:
|
|
|
|
|
case GPGME_DATA_TYPE_PGP_OTHER:
|
|
|
|
|
ret = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Public interface. */
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
yki_decrypt(gpgme_ctx_t gpgme, const char *file, GError **error)
|
|
|
|
|
{
|
|
|
|
|
yki_decrypt_data_t yki = { 0 };
|
|
|
|
|
gpgutil_file_t in = { 0 }, out = { 0 };
|
|
|
|
|
GtkDialog *dlg;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
g_assert(gpgme && file);
|
|
|
|
|
|
|
|
|
|
yki.gpgme = gpgme;
|
|
|
|
|
yki.original_filename = file;
|
|
|
|
|
|
|
|
|
|
ret = gpgutil_open_file(file, "r", &in, error);
|
|
|
|
|
|
|
|
|
|
if ( ret )
|
|
|
|
|
ret = is_file_encrypted(in.buffer, error);
|
|
|
|
|
|
|
|
|
|
if ( ret )
|
|
|
|
|
ret = gpgutil_open_file(NULL, NULL, &out, error);
|
|
|
|
|
|
|
|
|
|
if ( ret ) {
|
|
|
|
|
gpgme_error_t gerr;
|
|
|
|
|
|
|
|
|
|
gerr = gpgme_op_decrypt_verify(gpgme, in.buffer, out.buffer);
|
|
|
|
|
yki.decrypt_result = gpgme_op_decrypt_result(gpgme);
|
|
|
|
|
yki.verify_result = gpgme_op_verify_result(gpgme);
|
|
|
|
|
|
|
|
|
|
set_default_output(&yki);
|
|
|
|
|
|
|
|
|
|
if ( gpgme_err_code(gerr) == GPG_ERR_NO_DATA ) {
|
|
|
|
|
yki.signed_only = TRUE;
|
|
|
|
|
gerr = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( gerr )
|
|
|
|
|
yki_error_gpgme(error, YKI_ERROR_GPGME_CRYPTO, gerr);
|
|
|
|
|
|
|
|
|
|
ret = gpgme_err_code(gerr) == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( ret ) {
|
|
|
|
|
dlg = create_decrypt_dialog(&yki);
|
|
|
|
|
ret = gtk_dialog_run(dlg) == GTK_RESPONSE_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( ret && g_file_test(yki.output_filename, G_FILE_TEST_EXISTS) && ! yki.output_filename_userset )
|
|
|
|
|
ret = yki_confirm_overwrite(yki.output_filename);
|
|
|
|
|
|
|
|
|
|
if ( ret )
|
|
|
|
|
ret = gpgutil_copy_buffer(&out, yki.output_filename, error);
|
|
|
|
|
|
|
|
|
|
gpgutil_close_file(&in, NULL);
|
|
|
|
|
gpgutil_close_file(&out, NULL);
|
|
|
|
|
|
|
|
|
|
g_free(yki.output_filename);
|
|
|
|
|
g_free(yki.output_basename);
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|