A task-oriented GnuPG frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

277 lines
8.1 KiB

/*
* 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;
}