Browse Source

Implement decrypt/verify task.

Add a draft implementation of the -d, --decrypt command (which is
used both to decrypt and/or to verify). The core functionality is
there, but the UI could probably be better, both in terms of
aesthetics and in terms of informations shown about the decrypted
or verified file.
master
parent
commit
be308c97de
  1. 6
      src/Makefile.am
  2. 305
      src/decrypt.c
  3. 36
      src/decrypt.h
  4. 3
      src/errors.h
  5. 13
      src/gpgutil.c
  6. 1
      src/resources.xml
  7. 238
      src/yki-decrypt.ui
  8. 3
      src/yki.c

6
src/Makefile.am

@ -1,7 +1,9 @@
bin_PROGRAMS = yki
yki_SOURCES = yki.c gpgutil.c gpgutil.h error.c error.h errors.h \
encrypt.c encrypt.h yki-encrypt.ui resources.xml
encrypt.c encrypt.h yki-encrypt.ui \
decrypt.c decrypt.h yki-decrypt.ui \
resources.xml
nodist_yki_SOURCES = resources.c
@ -9,6 +11,6 @@ BUILT_SOURCES = resources.c
CLEANFILES = resources.c
resources.c: resources.xml yki-encrypt.ui
resources.c: resources.xml yki-encrypt.ui yki-decrypt.ui
$(GLIB_COMPILE_RESOURCES) --target=$@ --generate-source \
--sourcedir=$(top_srcdir)/src $<

305
src/decrypt.c

@ -0,0 +1,305 @@
/*
* 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 <errno.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <gpgme.h>
#include "error.h"
#include "gpgutil.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;
}
static int
write_decrypted_output(gpgutil_file_t *src, const char *filename, GError **error)
{
int ret;
gpgutil_file_t dest = { 0 };
ret = gpgme_data_seek(src->buffer, 0, SEEK_SET) != -1;
if ( ret )
ret = gpgutil_open_file(filename, "w", &dest, error);
if ( ret ) {
char buffer[512];
size_t len;
while ( ret && (len = gpgme_data_read(src->buffer, buffer, sizeof(buffer))) > 0 )
ret = gpgme_data_write(dest.buffer, buffer, len) != -1;
if ( ret )
ret = len != - 1;
}
if ( ! ret )
yki_error(error, YKI_ERROR_IO, "Cannot write to output file: %s", strerror(errno));
gpgutil_close_file(&dest, NULL);
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 )
ret = write_decrypted_output(&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;
}

36
src/decrypt.h

@ -0,0 +1,36 @@
/*
* 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/>.
*/
#ifndef ICP20200509_DECRYPT_H
#define ICP20200509_DECRYPT_H
#include <glib.h>
#include <gpgme.h>
#ifdef __cpluscplus
extern "C" {
#endif
int
yki_decrypt(gpgme_ctx_t, const char *, GError **);
#ifdef __cplusplus
}
#endif
#endif /* !ICP20200509_DECRYPT_H */

3
src/errors.h

@ -6,3 +6,6 @@ DEFINE_YKI_ERROR(4, GPGME_CRYPTO, "Cannot perform cryptographic operation")
DEFINE_YKI_ERROR(5, GPGME_KEYLIST, "Cannot list keys")
DEFINE_YKI_ERROR(6, INVALID_COMMAND, "Invalid command line")
DEFINE_YKI_ERROR(7, NOT_IMPLEMENTED, "Feature not implemented")
DEFINE_YKI_ERROR(8, UNKNOWN_DATA, "The file contains unrecognized data")
DEFINE_YKI_ERROR(9, UNENCRYPTED_DATA, "The file is not an encrypted or signed message")
DEFINE_YKI_ERROR(10, IO, "I/O error")

13
src/gpgutil.c

@ -53,23 +53,27 @@ gpgutil_open_file(const char *name,
{
gpgme_error_t gerr;
g_assert(name && mode && file);
g_assert(file);
gerr = 0;
file->buffer = NULL;
file->file = NULL;
if ( ! (file->file = fopen(name, mode)) )
if ( name )
file->file = fopen(name, mode);
else
file->file = tmpfile();
if ( ! file )
gerr = gpgme_error_from_errno(errno);
if ( ! gerr )
gerr = gpgme_data_new_from_stream(&file->buffer, file->file);
if ( ! gerr )
if ( ! gerr && name )
gerr = gpgme_data_set_file_name(file->buffer, name);
if ( gerr ) {
if ( file->buffer) {
if ( file->buffer ) {
gpgme_data_release(file->buffer);
file->buffer = NULL;
}
@ -85,6 +89,7 @@ gpgutil_open_file(const char *name,
return gpgme_err_code(gerr) == 0;
}
int
gpgutil_close_file(gpgutil_file_t *file, GError **error)
{

1
src/resources.xml

@ -2,5 +2,6 @@
<gresources>
<gresource prefix="/org/incenp/Yorkie">
<file compressed="true">yki-encrypt.ui</file>
<file compressed="true">yki-decrypt.ui</file>
</gresource>
</gresources>

238
src/yki-decrypt.ui

@ -0,0 +1,238 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.24"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-save-as</property>
</object>
<object class="GtkListStore" id="lstSignatures">
<columns>
<!-- column-name Valid -->
<column type="gboolean"/>
<!-- column-name User ID -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkDialog" id="dlgDecrypt">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="btnCancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnOK">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkFrame" id="frame1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<child>
<object class="GtkLabel" id="lblDecrypt">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">The message was not encrypted.</property>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Decryption status&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="frame2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="lblSign">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">The message was not signed.</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTreeView" id="trvSignatures">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">lstSignatures</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Signature status&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="frame3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">12</property>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Output: </property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnOutput">
<property name="label" translatable="yes">button</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="image">image1</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Output&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="cancel">btnCancel</action-widget>
<action-widget response="ok">btnOK</action-widget>
</action-widgets>
</object>
</interface>

3
src/yki.c

@ -29,6 +29,7 @@
#include "error.h"
#include "encrypt.h"
#include "decrypt.h"
#include "gpgutil.h"
@ -86,7 +87,7 @@ main(int argc, char **argv)
if ( encrypt )
yki_encrypt(gpgme, files[0], &error);
else
g_set_error(&error, YKI_ERROR, YKI_ERROR_NOT_IMPLEMENTED, "Cannot decrypt");
yki_decrypt(gpgme, files[0], &error);
}
if ( error )

Loading…
Cancel
Save