diff options
author | Corentin Noël <corentin.noel@collabora.com> | 2021-12-07 12:29:33 +0100 |
---|---|---|
committer | Corentin Noël <tintou@noel.tf> | 2022-04-09 00:44:22 +0200 |
commit | d8c7ba55555acab2dbdeefda5b0decbb84d1d701 (patch) | |
tree | b09109b843ed7163c36e0d76145eb91bb4706508 | |
parent | ab94a01bbbcb90b360e1892e1e0153d1724a8028 (diff) | |
download | gcr-d8c7ba55555acab2dbdeefda5b0decbb84d1d701.tar.gz |
Create gcr-gtk4 library
Signed-off-by: Corentin Noël <corentin.noel@collabora.com>
-rw-r--r-- | .gitlab-ci.yml | 3 | ||||
-rw-r--r-- | docs/gcr-gtk4/gcr-gtk4.toml.in | 44 | ||||
-rw-r--r-- | docs/gcr-gtk4/meson.build | 31 | ||||
-rw-r--r-- | docs/meson.build | 12 | ||||
-rw-r--r-- | gcr-gtk3/meson.build | 4 | ||||
-rw-r--r-- | gcr-gtk4/gcr-certificate-widget.c | 684 | ||||
-rw-r--r-- | gcr-gtk4/gcr-certificate-widget.h | 30 | ||||
-rw-r--r-- | gcr-gtk4/gcr-gtk4.h | 20 | ||||
-rw-r--r-- | gcr-gtk4/gcr-section.c | 222 | ||||
-rw-r--r-- | gcr-gtk4/gcr-section.h | 30 | ||||
-rw-r--r-- | gcr-gtk4/libgcr-gtk4.map | 8 | ||||
-rw-r--r-- | gcr-gtk4/meson.build | 130 | ||||
-rw-r--r-- | gcr-gtk4/viewer/meson.build | 7 | ||||
-rw-r--r-- | gcr-gtk4/viewer/viewer.c | 139 | ||||
-rw-r--r-- | meson.build | 16 | ||||
-rw-r--r-- | meson_options.txt | 11 |
16 files changed, 1379 insertions, 12 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 25992f6..9e5b1ee 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: - deploy variables: - DEPENDENCIES: dbus-daemon diffutils gcc gi-docgen libsecret-devel meson ninja-build openssh openssh-clients python redhat-rpm-config systemd-devel + DEPENDENCIES: dbus-daemon diffutils gcc gi-docgen libsecret-devel meson ninja-build openssh openssh-clients python redhat-rpm-config systemd-devel gtk4-devel fedora:Werror: image: fedora:latest @@ -102,6 +102,7 @@ pages: - mv _build/docs/gck/gck-1 public/gck-2 - mv _build/docs/gcr/gcr-3 public/gcr-4 - mv _build/docs/gcr-gtk3/gcr-3-gtk3 public/gcr-4-gtk3 + - mv _build/docs/gcr-gtk4/gcr-3-gtk4 public/gcr-4-gtk4 artifacts: when: on_success paths: diff --git a/docs/gcr-gtk4/gcr-gtk4.toml.in b/docs/gcr-gtk4/gcr-gtk4.toml.in new file mode 100644 index 0000000..a42e5fb --- /dev/null +++ b/docs/gcr-gtk4/gcr-gtk4.toml.in @@ -0,0 +1,44 @@ +[library] +namespace = "GcrGtk" +version = "@GCR_VERSION@" +browse_url = "https://gitlab.gnome.org/GNOME/gcr/" +repository_url = "https://gitlab.gnome.org/GNOME/gcr.git" +authors = "GCR contributors" +license = "LGPL-2.0-or-later" +description = "Library providing GTK widgets for high level crypto" +dependencies = [ "GObject-2.0", "Gio-2.0", "Gtk-4.0", "Gck-@GCK_VERSION@", "Gcr-@GCR_VERSION@" ] +devhelp = true +search_index = true + + [dependencies."GObject-2.0"] + name = "GObject" + description = "The base type system library" + docs_url = "https://developer.gnome.org/gobject/stable" + + [dependencies."Gio-2.0"] + name = "Gio" + description = "GObject interfaces and objects" + docs_url = "https://developer.gnome.org/gio/stable" + + [dependencies."Gtk-4.0"] + name = "Gtk" + description = "The GTK UI toolkit" + docs_url = "https://docs.gtk.org/gtk4/" + + [dependencies."Gck-@GCK_VERSION@"] + name = "Gck" + description = "GObject bindings for PKCS#11" + docs_url = "https://gnome.pages.gitlab.gnome.org/gcr/gck-@GCK_VERSION@" + + [dependencies."Gcr-@GCR_VERSION@"] + name = "Gcr" + description = "GObject library for high level crypto parsing" + docs_url = "https://gnome.pages.gitlab.gnome.org/gcr/gcr-@GCR_VERSION@" + +[theme] +name = "basic" +show_index_summary = true +show_class_hierarchy = true + +[source-location] +base_url = "https://gitlab.gnome.org/GNOME/gcr/-/blob/master/" diff --git a/docs/gcr-gtk4/meson.build b/docs/gcr-gtk4/meson.build new file mode 100644 index 0000000..d3f34eb --- /dev/null +++ b/docs/gcr-gtk4/meson.build @@ -0,0 +1,31 @@ +gcr_gtk4_toml = configure_file( + input: 'gcr-gtk4.toml.in', + output: '@BASENAME@', + configuration: { + 'GCR_VERSION': gcr_api_version, + 'GCK_VERSION': gck_api_version, + }, +) + +gcr_gtk4_docs = custom_target('gcr-gtk4-docs', + input: gcr_gtk4_gir[0], + output: gcr_gtk4_basename, + command: [ + gi_docgen, + 'generate', + '--quiet', + '--fatal-warnings', + '--add-include-path=@0@'.format(build_root / 'gck'), + '--add-include-path=@0@'.format(build_root / 'gcr'), + '--config', gcr_gtk4_toml, + '--output-dir=@OUTPUT@', + '--no-namespace-dir', + '--content-dir=@0@'.format(meson.current_source_dir()), + '@INPUT@', + ], + depend_files: [ gcr_gtk4_toml ], + depends: [ gck_gir[0], gcr_gir[0] ], + build_by_default: true, + install: true, + install_dir: get_option('datadir') / 'doc', +) diff --git a/docs/meson.build b/docs/meson.build index aa1b11b..3ae63b0 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -2,11 +2,19 @@ gi_docgen = find_program('gi-docgen') subdir('gck') subdir('gcr') -subdir('gcr-gtk3') +gtk_docs_targets = [] +if get_option('gtk3') + subdir('gcr-gtk3') + gtk_docs_targets += gcr_gtk3_docs +endif +if get_option('gtk4') + subdir('gcr-gtk4') + gtk_docs_targets += gcr_gtk4_docs +endif # Create a pseudo target that build all docs at once alias_target('docs', gck_docs, gcr_docs, - gcr_gtk3_docs, + gtk_docs_targets, ) diff --git a/gcr-gtk3/meson.build b/gcr-gtk3/meson.build index b35fe01..6f3b76c 100644 --- a/gcr-gtk3/meson.build +++ b/gcr-gtk3/meson.build @@ -74,7 +74,7 @@ gcr_gtk3_deps = [ libegg_dep, gck_dep, gcr_dep, - gtk_dep, + gtk3_dep, ] gcr_gtk3_cflags = [ @@ -107,7 +107,7 @@ gcr_gtk3_pkgconf_deps = [ gobject_dep, gck_lib, gcr_lib, - gtk_dep, + gtk3_dep, ] pkgconfig.generate(gcr_gtk3_lib, diff --git a/gcr-gtk4/gcr-certificate-widget.c b/gcr-gtk4/gcr-certificate-widget.c new file mode 100644 index 0000000..58e3e55 --- /dev/null +++ b/gcr-gtk4/gcr-certificate-widget.c @@ -0,0 +1,684 @@ +/* + * Copyright 2021 Collabora Ltd. + * Copyright Corentin Noël <corentin.noel@collabora.com> + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include <glib/gi18n-lib.h> + +#include <gcr-gtk4/gcr-certificate-widget.h> +#include "gcr/gcr-certificate-extensions.h" +#include "gcr/gcr-fingerprint.h" +#include "gcr/gcr-oids.h" + +#include "gcr-section.h" + +#include "egg/egg-asn1x.h" +#include "egg/egg-asn1-defs.h" +#include "egg/egg-dn.h" +#include "egg/egg-oid.h" +#include "egg/egg-hex.h" + +struct _GcrCertificateWidget +{ + GtkWidget parent_instance; + + GcrCertificate *certificate; + GtkWidget *reveal_button; + GtkWidget *revealer; + GtkWidget *secondary_info; + GtkSizeGroup *size_group; +}; + +G_DEFINE_TYPE (GcrCertificateWidget, gcr_certificate_widget, GTK_TYPE_WIDGET) + +enum { + PROP_CERTIFICATE = 1, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; + +static void +gcr_certificate_widget_finalize (GObject *object) +{ + GtkWidget *child; + GcrCertificateWidget *self = (GcrCertificateWidget *)object; + + g_clear_object (&self->size_group); + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) { + gtk_widget_unparent (child); + } + + G_OBJECT_CLASS (gcr_certificate_widget_parent_class)->finalize (object); +} + +static void +gcr_certificate_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcrCertificateWidget *self = GCR_CERTIFICATE_WIDGET (object); + + switch (prop_id) + { + case PROP_CERTIFICATE: + g_value_set_object (value, gcr_certificate_widget_get_certificate (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gcr_certificate_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcrCertificateWidget *self = GCR_CERTIFICATE_WIDGET (object); + + switch (prop_id) + { + case PROP_CERTIFICATE: + gcr_certificate_widget_set_certificate (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcr_certificate_widget_class_init (GcrCertificateWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gcr_certificate_widget_finalize; + object_class->get_property = gcr_certificate_widget_get_property; + object_class->set_property = gcr_certificate_widget_set_property; + + obj_properties[PROP_CERTIFICATE] = + g_param_spec_object ("certificate", + "Certificate", + "Certificate to display.", + GCR_TYPE_CERTIFICATE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, + N_PROPERTIES, + obj_properties); + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); +} + +static void +on_reveal_button_clicked (GtkWidget *button, + GcrCertificateWidget *self) +{ + g_assert (GCR_IS_CERTIFICATE_WIDGET (self)); + gtk_widget_hide (button); + gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), TRUE); +} + +static void +gcr_certificate_widget_init (GcrCertificateWidget *self) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (gtk_widget_get_layout_manager (GTK_WIDGET (self))), GTK_ORIENTATION_VERTICAL); + self->reveal_button = gtk_button_new_with_label ("…"); + gtk_widget_set_halign (self->reveal_button, GTK_ALIGN_CENTER); + gtk_widget_insert_before (self->reveal_button, GTK_WIDGET (self), NULL); + self->secondary_info = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + self->revealer = g_object_new (GTK_TYPE_REVEALER, + "child", self->secondary_info, + "transition-type", GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN, + NULL); + gtk_widget_insert_after (self->revealer, GTK_WIDGET (self), self->reveal_button); + g_signal_connect (self->reveal_button, "clicked", G_CALLBACK (on_reveal_button_clicked), self); + self->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); +} + +static GtkWidget* +_gcr_certificate_widget_add_section (GcrCertificateWidget *self, + const gchar *title, + GIcon *icon, + gboolean important) +{ + GtkWidget *widget; + + g_assert (GCR_IS_CERTIFICATE_WIDGET (self)); + + if (icon) + widget = gcr_section_new_with_icon (title, icon); + else + widget = gcr_section_new (title); + + gtk_size_group_add_widget (self->size_group, widget); + if (important) + gtk_widget_insert_before (widget, GTK_WIDGET (self), self->reveal_button); + else + gtk_box_append (GTK_BOX (self->secondary_info), widget); + + return widget; +} + +static GtkWidget* +create_value_label (const gchar *label) +{ + return g_object_new (GTK_TYPE_LABEL, + "label", label, + "xalign", 1.0, + "halign", GTK_ALIGN_END, + "hexpand", TRUE, + "selectable", TRUE, + "wrap", TRUE, + NULL); +} + +static gchar* +calculate_label (GcrCertificateWidget *self) +{ + gchar *label; + + label = gcr_certificate_get_subject_cn (self->certificate); + if (label != NULL) + return label; + + return g_strdup (_("Certificate")); +} + +static void +on_parsed_dn_part (guint index, + GQuark oid, + GNode *value, + gpointer user_data) +{ + GtkWidget *section = user_data; + const gchar *attr; + const gchar *desc; + gchar *field = NULL; + gchar *display; + + attr = egg_oid_get_name (oid); + desc = egg_oid_get_description (oid); + + /* Combine them into something sane */ + if (attr && desc) { + if (strcmp (attr, desc) == 0) + field = g_strdup (attr); + else + field = g_strdup_printf ("%s (%s)", attr, desc); + } else if (!attr && !desc) { + field = g_strdup (""); + } else if (attr) { + field = g_strdup (attr); + } else if (desc) { + field = g_strdup (desc); + } else { + g_assert_not_reached (); + } + + display = egg_dn_print_value (oid, value); + if (display == NULL) + display = g_strdup (""); + + gcr_section_add_child (GCR_SECTION (section), field, create_value_label (display)); + + g_free (field); + g_free (display); +} + +static inline gchar * +hex_encode_bytes (GBytes *bytes) +{ + gsize size; + gconstpointer data = g_bytes_get_data (bytes, &size); + return egg_hex_encode_full (data, size, TRUE, " ", 1); +} + +static void +append_subject_public_key (GcrCertificateWidget *self, + GcrSection *section, + GNode *subject_public_key) +{ + guint key_nbits; + const gchar *text; + gchar *display; + GBytes *value; + guchar *raw; + gsize n_raw; + GQuark oid; + guint bits; + + key_nbits = gcr_certificate_get_key_size (self->certificate); + + oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (subject_public_key, + "algorithm", "algorithm", NULL)); + text = egg_oid_get_description (oid); + gcr_section_add_child (section, _("Key Algorithm"), create_value_label (text)); + + value = egg_asn1x_get_element_raw (egg_asn1x_node (subject_public_key, + "algorithm", "parameters", NULL)); + if (value) { + display = hex_encode_bytes (value); + gcr_section_add_child (section, _("Key Parameters"), create_value_label (display)); + g_clear_pointer (&display, g_free); + g_bytes_unref (value); + } + + if (key_nbits > 0) { + display = g_strdup_printf ("%u", key_nbits); + gcr_section_add_child (section, _("Key Size"), create_value_label (display)); + g_clear_pointer (&display, g_free); + } + + value = egg_asn1x_get_element_raw (subject_public_key); + raw = gcr_fingerprint_from_subject_public_key_info (g_bytes_get_data (value, NULL), + g_bytes_get_size (value), + G_CHECKSUM_SHA1, &n_raw); + g_bytes_unref (value); + display = egg_hex_encode_full (raw, n_raw, TRUE, " ", 1); + g_free (raw); + gcr_section_add_child (section, _("Key SHA1 Fingerprint"), create_value_label (text)); + g_clear_pointer (&display, g_free); + + value = egg_asn1x_get_bits_as_raw (egg_asn1x_node (subject_public_key, "subjectPublicKey", NULL), &bits); + display = egg_hex_encode_full (g_bytes_get_data (value, NULL), bits / 8, TRUE, " ", 1); + gcr_section_add_child (section, _("Public Key"), create_value_label (text)); + g_clear_pointer (&display, g_free); + g_bytes_unref (value); +} + +static GcrSection * +append_extension_basic_constraints (GcrCertificateWidget *self, + GBytes *data) +{ + GcrSection *section; + gboolean is_ca = FALSE; + gint path_len = -1; + gchar *number; + + if (!_gcr_certificate_extension_basic_constraints (data, &is_ca, &path_len)) + return NULL; + + section = GCR_SECTION (_gcr_certificate_widget_add_section (self, _("Basic Constraints"), NULL, FALSE)); + gcr_section_add_child (section, _("Certificate Authority"), create_value_label (is_ca ? _("Yes") : _("No"))); + + number = g_strdup_printf ("%d", path_len); + gcr_section_add_child (section, _("Max Path Length"), create_value_label (path_len < 0 ? _("Unlimited") : number)); + g_free (number); + + return section; +} + +static GcrSection * +append_extension_extended_key_usage (GcrCertificateWidget *self, + GBytes *data) +{ + GcrSection *section; + GQuark *oids; + GString *text; + guint i; + + oids = _gcr_certificate_extension_extended_key_usage (data); + if (oids == NULL) + return NULL; + + text = g_string_new (""); + for (i = 0; oids[i] != 0; i++) { + if (i > 0) + g_string_append_unichar (text, '\n'); + g_string_append (text, egg_oid_get_description (oids[i])); + } + + g_free (oids); + + section = GCR_SECTION (_gcr_certificate_widget_add_section (self, _("Extended Key Usage"), NULL, FALSE)); + gcr_section_add_child (section, _("Allowed Purposes"), create_value_label (text->str)); + + g_string_free (text, TRUE); + + return section; +} + +static GcrSection * +append_extension_subject_key_identifier (GcrCertificateWidget *self, + GBytes *data) +{ + GcrSection *section; + gpointer keyid; + gsize n_keyid; + + keyid = _gcr_certificate_extension_subject_key_identifier (data, &n_keyid); + if (keyid == NULL) + return NULL; + + section = GCR_SECTION (_gcr_certificate_widget_add_section (self, _("Subject Key Identifier"), NULL, FALSE)); + gchar *display = egg_hex_encode_full (keyid, n_keyid, TRUE, " ", 1); + g_free (keyid); + gcr_section_add_child (section, _("Key Identifier"), create_value_label (display)); + g_free (display); + + return section; +} + +static const struct { + guint usage; + const gchar *description; +} usage_descriptions[] = { + { GCR_KEY_USAGE_DIGITAL_SIGNATURE, N_("Digital signature") }, + { GCR_KEY_USAGE_NON_REPUDIATION, N_("Non repudiation") }, + { GCR_KEY_USAGE_KEY_ENCIPHERMENT, N_("Key encipherment") }, + { GCR_KEY_USAGE_DATA_ENCIPHERMENT, N_("Data encipherment") }, + { GCR_KEY_USAGE_KEY_AGREEMENT, N_("Key agreement") }, + { GCR_KEY_USAGE_KEY_CERT_SIGN, N_("Certificate signature") }, + { GCR_KEY_USAGE_CRL_SIGN, N_("Revocation list signature") }, + { GCR_KEY_USAGE_ENCIPHER_ONLY, N_("Encipher only") }, + { GCR_KEY_USAGE_DECIPHER_ONLY, N_("Decipher only") } +}; + +static GcrSection * +append_extension_key_usage (GcrCertificateWidget *self, + GBytes *data) +{ + GcrSection *section; + gulong key_usage; + GString *text; + guint i; + + if (!_gcr_certificate_extension_key_usage (data, &key_usage)) + return NULL; + + text = g_string_new (""); + + for (i = 0; i < G_N_ELEMENTS (usage_descriptions); i++) { + if (key_usage & usage_descriptions[i].usage) { + if (text->len > 0) + g_string_append_unichar (text, '\n'); + g_string_append (text, _(usage_descriptions[i].description)); + } + } + + section = GCR_SECTION (_gcr_certificate_widget_add_section (self, _("Key Usage"), NULL, FALSE)); + gcr_section_add_child (section, _("Usages"), create_value_label (text->str)); + + g_string_free (text, TRUE); + + return section; +} + +static GcrSection * +append_extension_subject_alt_name (GcrCertificateWidget *self, + GBytes *data) +{ + GcrSection *section; + GArray *general_names; + GcrGeneralName *general; + guint i; + + general_names = _gcr_certificate_extension_subject_alt_name (data); + if (general_names == NULL) + return FALSE; + + section = GCR_SECTION (_gcr_certificate_widget_add_section (self, _("Subject Alternative Names"), NULL, FALSE)); + + for (i = 0; i < general_names->len; i++) { + general = &g_array_index (general_names, GcrGeneralName, i); + if (general->display == NULL) { + gchar *display = hex_encode_bytes (general->raw); + gcr_section_add_child (section, general->description, create_value_label (display)); + g_free (display); + } else + gcr_section_add_child (section, general->description, create_value_label (general->display)); + } + + _gcr_general_names_free (general_names); + + return section; +} + +static GcrSection * +append_extension_hex (GcrCertificateWidget *self, + GQuark oid, + GBytes *value) +{ + GcrSection *section; + const gchar *text; + gchar *display; + + section = GCR_SECTION (_gcr_certificate_widget_add_section (self, _("Extension"), NULL, FALSE)); + + /* Extension type */ + text = egg_oid_get_description (oid); + gcr_section_add_child (section, _("Identifier"), create_value_label (text)); + display = hex_encode_bytes (value); + gcr_section_add_child (section, _("Value"), create_value_label (display)); + g_free (display); + + return section; +} + +static void +append_extension (GcrCertificateWidget *self, + GNode *node) +{ + GQuark oid; + GBytes *value; + gboolean critical; + GcrSection *section = NULL; + + /* Dig out the OID */ + oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (node, "extnID", NULL)); + g_return_if_fail (oid); + + /* Extension value */ + value = egg_asn1x_get_string_as_bytes (egg_asn1x_node (node, "extnValue", NULL)); + + /* The custom parsers */ + if (oid == GCR_OID_BASIC_CONSTRAINTS) + section = append_extension_basic_constraints (self, value); + else if (oid == GCR_OID_EXTENDED_KEY_USAGE) + section = append_extension_extended_key_usage (self, value); + else if (oid == GCR_OID_SUBJECT_KEY_IDENTIFIER) + section = append_extension_subject_key_identifier (self, value); + else if (oid == GCR_OID_KEY_USAGE) + section = append_extension_key_usage (self, value); + else if (oid == GCR_OID_SUBJECT_ALT_NAME) + section = append_extension_subject_alt_name (self, value); + + /* Otherwise the default raw display */ + if (!section) { + section = append_extension_hex (self, oid, value); + } + + /* Critical */ + if (section && egg_asn1x_get_boolean (egg_asn1x_node (node, "critical", NULL), &critical)) { + gcr_section_add_child (section, _("Critical"), create_value_label (critical ? _("Yes") : _("No"))); + } + + g_bytes_unref (value); +} + +/** + * gcr_certificate_widget_new: + * @certificate: (nullable): certificate to display, or %NULL + * + * Create a new certificate widget which displays a given certificate. + * + * Returns: (transfer full): a newly allocated #GcrCertificateWidget, which + * should be freed with g_object_unref() + */ +GtkWidget * +gcr_certificate_widget_new (GcrCertificate *certificate) +{ + return g_object_new (GCR_TYPE_CERTIFICATE_WIDGET, "certificate", certificate, NULL); +} + +/** + * gcr_certificate_widget_get_certificate: + * @self: The certificate widget + * + * Get the certificate displayed in the widget. + * + * Returns: (nullable) (transfer none): the certificate + */ +GcrCertificate * +gcr_certificate_widget_get_certificate (GcrCertificateWidget *self) +{ + g_return_val_if_fail (GCR_IS_CERTIFICATE_WIDGET (self), NULL); + return self->certificate; +} + +/** + * gcr_certificate_widget_set_certificate: + * @self: The certificate widget + * @certificate: (nullable): the certificate to display + * + * Set the certificate displayed in the widget + */ +void +gcr_certificate_widget_set_certificate (GcrCertificateWidget *self, GcrCertificate *certificate) +{ + GtkWidget *section, *label; + PangoAttrList *attributes; + gchar *display; + GIcon *icon; + GBytes *bytes, *number; + GNode *asn, *subject_public_key; + GQuark oid; + gconstpointer data; + gsize n_data; + GDate date; + gulong version; + guint bits, index; + + g_return_if_fail (GCR_IS_CERTIFICATE_WIDGET (self)); + + g_set_object (&self->certificate, certificate); + + data = gcr_certificate_get_der_data (self->certificate, &n_data); + if (!data) { + g_set_object (&self->certificate, NULL); + } + + icon = gcr_certificate_get_icon (self->certificate); + display = calculate_label (self); + section = _gcr_certificate_widget_add_section (self, display, icon, TRUE); + g_clear_pointer (&display, g_free); + g_object_unref (icon); + + bytes = g_bytes_new_static (data, n_data); + asn = egg_asn1x_create_and_decode (pkix_asn1_tab, "Certificate", bytes); + g_bytes_unref (bytes); + + display = egg_dn_read_part (egg_asn1x_node (asn, "tbsCertificate", "subject", "rdnSequence", NULL), "CN"); + gcr_section_add_child (GCR_SECTION (section), _("Identity"), create_value_label (display)); + g_clear_pointer (&display, g_free); + + display = egg_dn_read_part (egg_asn1x_node (asn, "tbsCertificate", "issuer", "rdnSequence", NULL), "CN"); + gcr_section_add_child (GCR_SECTION (section), _("Verified by"), create_value_label (display)); + g_clear_pointer (&display, g_free); + + if (egg_asn1x_get_time_as_date (egg_asn1x_node (asn, "tbsCertificate", "validity", "notAfter", NULL), &date)) { + display = g_malloc0 (128); + if (!g_date_strftime (display, 128, "%x", &date)) + g_return_if_reached (); + gcr_section_add_child (GCR_SECTION (section), _("Expires"), create_value_label (display)); + g_clear_pointer (&display, g_free); + } + + /* The subject */ + section = _gcr_certificate_widget_add_section (self, _("Subject Name"), NULL, FALSE); + egg_dn_parse (egg_asn1x_node (asn, "tbsCertificate", "subject", "rdnSequence", NULL), on_parsed_dn_part, section); + + /* The Issuer */ + section = _gcr_certificate_widget_add_section (self, _("Issuer Name"), NULL, FALSE); + egg_dn_parse (egg_asn1x_node (asn, "tbsCertificate", "issuer", "rdnSequence", NULL), on_parsed_dn_part, section); + + /* The Issued Parameters */ + section = _gcr_certificate_widget_add_section (self, _("Issued Certificate"), NULL, FALSE); + + if (!egg_asn1x_get_integer_as_ulong (egg_asn1x_node (asn, "tbsCertificate", "version", NULL), &version)) { + g_critical ("Unable to parse certificate version"); + } else { + display = g_strdup_printf ("%lu", version + 1); + gcr_section_add_child (GCR_SECTION (section), _("Version"), create_value_label (display)); + g_clear_pointer (&display, g_free); + } + + number = egg_asn1x_get_integer_as_raw (egg_asn1x_node (asn, "tbsCertificate", "serialNumber", NULL)); + if (!number) { + g_critical ("Unable to parse certificate serial number"); + } else { + display = hex_encode_bytes (number); + gcr_section_add_child (GCR_SECTION (section), _("Serial Number"), create_value_label (display)); + g_clear_pointer (&display, g_free); + g_bytes_unref (number); + } + + display = g_malloc0 (128); + if (egg_asn1x_get_time_as_date (egg_asn1x_node (asn, "tbsCertificate", "validity", "notBefore", NULL), &date)) { + if (!g_date_strftime (display, 128, "%x", &date)) + g_return_if_reached (); + gcr_section_add_child (GCR_SECTION (section), _("Not Valid Before"), create_value_label (display)); + } + if (egg_asn1x_get_time_as_date (egg_asn1x_node (asn, "tbsCertificate", "validity", "notAfter", NULL), &date)) { + if (!g_date_strftime (display, 128, "%x", &date)) + g_return_if_reached (); + gcr_section_add_child (GCR_SECTION (section), _("Not Valid After"), create_value_label (display)); + } + g_clear_pointer (&display, g_free); + + /* Fingerprints */ + section = _gcr_certificate_widget_add_section (self, _("Certificate Fingerprints"), NULL, FALSE); + display = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, bytes); + gcr_section_add_child (GCR_SECTION (section), "SHA1", create_value_label (display)); + g_clear_pointer (&display, g_free); + display = g_compute_checksum_for_bytes (G_CHECKSUM_MD5, bytes); + gcr_section_add_child (GCR_SECTION (section), "MD5", create_value_label (display)); + g_clear_pointer (&display, g_free); + + /* Public Key Info */ + section = _gcr_certificate_widget_add_section (self, _("Public Key Info"), NULL, FALSE); + subject_public_key = egg_asn1x_node (asn, "tbsCertificate", "subjectPublicKeyInfo", NULL); + append_subject_public_key (self, GCR_SECTION (section), subject_public_key); + + /* Extensions */ + for (index = 1; TRUE; ++index) { + GNode *extension = egg_asn1x_node (asn, "tbsCertificate", "extensions", index, NULL); + if (extension == NULL) + break; + append_extension (self, extension); + } + + /* Signature */ + section = _gcr_certificate_widget_add_section (self, _("Signature"), NULL, FALSE); + + oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (asn, "signatureAlgorithm", "algorithm", NULL)); + gcr_section_add_child (GCR_SECTION (section), _("Signature Algorithm"), create_value_label (egg_oid_get_description (oid))); + + bytes = egg_asn1x_get_element_raw (egg_asn1x_node (asn, "signatureAlgorithm", "parameters", NULL)); + if (bytes) { + display = hex_encode_bytes (bytes); + gcr_section_add_child (GCR_SECTION (section), _("Signature Parameters"), create_value_label (display)); + g_clear_pointer (&display, g_free); + g_bytes_unref (bytes); + } + + bytes = egg_asn1x_get_bits_as_raw (egg_asn1x_node (asn, "signature", NULL), &bits); + display = egg_hex_encode_full (g_bytes_get_data (bytes, NULL), bits / 8, TRUE, " ", 1); + g_bytes_unref (bytes); + label = create_value_label (display); + attributes = pango_attr_list_new (); + pango_attr_list_insert (attributes, pango_attr_family_new ("Monospace")); + gtk_label_set_attributes (GTK_LABEL (label), attributes); + pango_attr_list_unref (attributes); + gcr_section_add_child (GCR_SECTION (section), _("Signature"), label); + g_clear_pointer (&display, g_free); + + egg_asn1x_destroy (asn); +} diff --git a/gcr-gtk4/gcr-certificate-widget.h b/gcr-gtk4/gcr-certificate-widget.h new file mode 100644 index 0000000..fb36733 --- /dev/null +++ b/gcr-gtk4/gcr-certificate-widget.h @@ -0,0 +1,30 @@ +/* + * Copyright 2021 Collabora Ltd. + * Copyright Corentin Noël <corentin.noel@collabora.com> + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __GCR_CERTIFICATE_WIDGET_H__ +#define __GCR_CERTIFICATE_WIDGET_H__ + +#include <gck/gck.h> +#include <gcr/gcr.h> + +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GCR_TYPE_CERTIFICATE_WIDGET gcr_certificate_widget_get_type () +G_DECLARE_FINAL_TYPE (GcrCertificateWidget, gcr_certificate_widget, GCR, CERTIFICATE_WIDGET, GtkWidget) + +GtkWidget *gcr_certificate_widget_new (GcrCertificate *certificate); + +GcrCertificate *gcr_certificate_widget_get_certificate (GcrCertificateWidget *self); + +void gcr_certificate_widget_set_certificate (GcrCertificateWidget *self, + GcrCertificate *certificate); + +G_END_DECLS + +#endif /* __GCR_CERTIFICATE_WIDGET_H__ */ diff --git a/gcr-gtk4/gcr-gtk4.h b/gcr-gtk4/gcr-gtk4.h new file mode 100644 index 0000000..f94bf92 --- /dev/null +++ b/gcr-gtk4/gcr-gtk4.h @@ -0,0 +1,20 @@ +/* + * Copyright 2021 Collabora Ltd. + * Copyright Corentin Noël <corentin.noel@collabora.com> + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __GCR_GTK4_H__ +#define __GCR_GTK4_H__ + +#include <glib.h> + +#include <gcr/gcr.h> + +#define __GCR_GTK4_INSIDE_HEADER__ + +#include <gcr-gtk4/gcr-certificate-widget.h> + +#undef __GCR_GTK4_INSIDE_HEADER__ + +#endif /* __GCR_GTK4_H__ */ diff --git a/gcr-gtk4/gcr-section.c b/gcr-gtk4/gcr-section.c new file mode 100644 index 0000000..33aa1c3 --- /dev/null +++ b/gcr-gtk4/gcr-section.c @@ -0,0 +1,222 @@ +/* + * Copyright 2021 Collabora Ltd. + * Copyright Corentin Noël <corentin.noel@collabora.com> + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "gcr-section.h" + +struct _GcrSection +{ + GtkWidget parent_instance; + + GtkWidget *frame; + GtkWidget *label; + GtkWidget *image; + GtkWidget *listbox; + GtkSizeGroup *size_group; +}; + +G_DEFINE_TYPE (GcrSection, gcr_section, GTK_TYPE_WIDGET) + +enum { + PROP_TITLE = 1, + PROP_ICON, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; + +static void +gcr_section_dispose (GObject *object) +{ + GcrSection *self = (GcrSection *)object; + + g_clear_object (&self->size_group); + g_clear_pointer (&self->label, gtk_widget_unparent); + g_clear_pointer (&self->image, gtk_widget_unparent); + g_clear_pointer (&self->frame, gtk_widget_unparent); + + G_OBJECT_CLASS (gcr_section_parent_class)->dispose (object); +} + +static void +gcr_section_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcrSection *self = GCR_SECTION (object); + + switch (prop_id) + { + case PROP_TITLE: + g_value_set_string (value, gtk_label_get_label (GTK_LABEL (self->label))); + break; + case PROP_ICON: + g_value_set_object (value, gtk_image_get_gicon (GTK_IMAGE (self->image))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcr_section_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcrSection *self = GCR_SECTION (object); + + switch (prop_id) + { + case PROP_TITLE: + gtk_label_set_label (GTK_LABEL (self->label), g_value_get_string (value)); + break; + case PROP_ICON: + if (!self->image) + { + GtkLayoutManager *layout_manager; + GtkLayoutChild *child; + + self->image = gtk_image_new (); + gtk_widget_insert_after (self->image, GTK_WIDGET (self), self->label); + layout_manager = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + + child = gtk_layout_manager_get_layout_child (layout_manager, self->image); + g_object_set (G_OBJECT (child), + "column", 1, + NULL); + + child = gtk_layout_manager_get_layout_child (layout_manager, self->frame); + g_object_set (G_OBJECT (child), + "column-span", 2, + NULL); + } + + gtk_image_set_from_gicon (GTK_IMAGE (self->image), g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcr_section_class_init (GcrSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gcr_section_dispose; + object_class->get_property = gcr_section_get_property; + object_class->set_property = gcr_section_set_property; + obj_properties[PROP_TITLE] = + g_param_spec_string ("title", + "Title", + "The title of the section", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ICON] = + g_param_spec_object ("icon", + "Icon", + "The Icon to use", + G_TYPE_ICON, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, + N_PROPERTIES, + obj_properties); + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_GRID_LAYOUT); +} + +static void +gcr_section_init (GcrSection *self) +{ + GtkLayoutManager *layout_manager; + GtkLayoutChild *child; + + self->label = gtk_label_new (NULL); + g_object_set (G_OBJECT (self->label), + "margin-start", 12, + "margin-end", 12, + "margin-top", 8, + "margin-bottom", 8, + "xalign", 0.0, + "halign", GTK_ALIGN_START, + "hexpand", TRUE, + NULL); + gtk_style_context_add_class (gtk_widget_get_style_context (self->label), "caption-heading"); + self->listbox = gtk_list_box_new (); + gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->listbox), GTK_SELECTION_NONE); + self->frame = gtk_frame_new (NULL); + g_object_set (G_OBJECT (self->frame), + "child", self->listbox, + "hexpand", TRUE, + NULL); + self->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + gtk_widget_insert_after (self->label, GTK_WIDGET (self), NULL); + gtk_widget_insert_after (self->frame, GTK_WIDGET (self), self->label); + layout_manager = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + + child = gtk_layout_manager_get_layout_child (layout_manager, self->frame); + g_object_set (G_OBJECT (child), + "row", 1, + NULL); + g_object_set (self, + "margin-start", 12, + "margin-end", 12, + "margin-top", 8, + "margin-bottom", 8, + NULL); +} + +GtkWidget * +gcr_section_new (const gchar *title) +{ + return g_object_new (GCR_TYPE_SECTION, "title", title, NULL); +} + +GtkWidget * +gcr_section_new_with_icon (const gchar *title, + GIcon *icon) +{ + return g_object_new (GCR_TYPE_SECTION, "title", title, "icon", icon, NULL); +} + +void +gcr_section_add_child (GcrSection *self, + const gchar *description, + GtkWidget *child) +{ + GtkWidget *row, *box, *label; + g_return_if_fail (GCR_IS_SECTION (self)); + + row = gtk_list_box_row_new (); + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box); + label = gtk_label_new (description); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + g_object_set (label, + "margin-start", 12, + "margin-end", 12, + "margin-top", 8, + "margin-bottom", 8, + "xalign", 0.0, + "halign", GTK_ALIGN_START, + NULL); + gtk_style_context_add_class (gtk_widget_get_style_context (label), "caption"); + g_object_set (child, + "margin-start", 12, + "margin-end", 12, + "margin-top", 8, + "margin-bottom", 8, + "halign", GTK_ALIGN_END, + NULL); + gtk_size_group_add_widget (self->size_group, label); + gtk_box_append (GTK_BOX (box), label); + gtk_box_append (GTK_BOX (box), child); + gtk_list_box_insert (GTK_LIST_BOX (self->listbox), row, -1); +} diff --git a/gcr-gtk4/gcr-section.h b/gcr-gtk4/gcr-section.h new file mode 100644 index 0000000..9f275d9 --- /dev/null +++ b/gcr-gtk4/gcr-section.h @@ -0,0 +1,30 @@ +/* + * Copyright 2021 Collabora Ltd. + * Copyright Corentin Noël <corentin.noel@collabora.com> + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef __GCR_SECTION_H__ +#define __GCR_SECTION_H__ + +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GCR_TYPE_SECTION gcr_section_get_type() + +G_DECLARE_FINAL_TYPE (GcrSection, gcr_section, GCR, SECTION, GtkWidget) + +GtkWidget *gcr_section_new (const gchar *title); +GtkWidget *gcr_section_new_with_icon (const gchar *title, + GIcon *icon); + +void gcr_section_add_child (GcrSection *self, + const gchar *description, + GtkWidget *child); + +G_END_DECLS + +#endif /* __GCR_SECTION_H__ */ diff --git a/gcr-gtk4/libgcr-gtk4.map b/gcr-gtk4/libgcr-gtk4.map new file mode 100644 index 0000000..4a16138 --- /dev/null +++ b/gcr-gtk4/libgcr-gtk4.map @@ -0,0 +1,8 @@ +{ +global: + gcr_*; + _gcr_*; + SECMEM_*; +local: + *; +}; diff --git a/gcr-gtk4/meson.build b/gcr-gtk4/meson.build new file mode 100644 index 0000000..8b461c2 --- /dev/null +++ b/gcr-gtk4/meson.build @@ -0,0 +1,130 @@ +gcr_gtk4_headers_install_dir = gcr_headers_subdir / 'gcr-gtk4' + +gcr_gtk4_public_sources = files( + 'gcr-certificate-widget.c', +) + +gcr_gtk4_private_sources = files( + 'gcr-section.c', +) + +gcr_gtk4_headers = files( + 'gcr-certificate-widget.h', +) + + +gcr_gtk4_enums_gen = gnome.mkenums_simple('gcr-gtk4-enum-types', + sources: gcr_gtk4_headers, + install_header: true, + install_dir: get_option('includedir') / gcr_gtk4_headers_install_dir, +) + +gcr_gtk4_sources = [ + gcr_gtk4_private_sources, + gcr_gtk4_public_sources, + gcr_gtk4_enums_gen, +] + +gcr_gtk4_deps = [ + glib_deps, + p11kit_dep, + libegg_dep, + gck_dep, + gcr_dep, + gtk4_dep, +] + +gcr_gtk4_cflags = [ + '-DG_LOG_DOMAIN="GcrGtk4"', + '-DGCR_COMPILATION', + '-DGCR_API_SUBJECT_TO_CHANGE', + '-DGCK_API_SUBJECT_TO_CHANGE', + '-DP11_KIT_API_SUBJECT_TO_CHANGE', +] + +gcr_gtk4_symbolmap = meson.current_source_dir() / 'libgcr-gtk4.map' +gcr_gtk4_linkflags = cc.get_supported_link_arguments( + '-Wl,--version-script,@0@'.format(gcr_gtk4_symbolmap), +) + +gcr_gtk4_lib = shared_library(gcr_gtk4_basename, + gcr_gtk4_sources, + dependencies: gcr_gtk4_deps, + c_args: gcr_gtk4_cflags, + link_args: gcr_gtk4_linkflags, + link_depends: gcr_gtk4_symbolmap, + include_directories: config_h_dir, + version: gcr_soversion, + install: true, +) + +gcr_gtk4_pkgconf_deps = [ + glib_dep, + gio_dep, + gobject_dep, + gck_lib, + gcr_lib, + gtk4_dep, +] +pkgconfig.generate(gcr_gtk4_lib, + subdirs: gcr_headers_subdir, + requires: gcr_gtk4_pkgconf_deps, + description: 'Library providing Gtk4 widgets for high level crypto parsing and display', +) + +install_headers([gcr_gtk4_headers ], + subdir: gcr_gtk4_headers_install_dir, +) + +gcr_gtk4_dep = declare_dependency( + link_with: gcr_gtk4_lib, + sources: gcr_gtk4_enums_gen[1], # Make sure gcr-enum-types.h can be included +) + +if get_option('introspection') + gcr_gtk4_gir = gnome.generate_gir(gcr_gtk4_lib, + sources: [ gcr_gtk4_headers, gcr_gtk4_public_sources ], + namespace: 'GcrGtk4', + nsversion: gcr_api_version, + export_packages: gcr_gtk4_basename, + identifier_prefix: 'Gcr', + symbol_prefix: 'gcr', + includes: [ + 'GObject-2.0', + 'Gio-2.0', + 'Gtk-4.0', + gck_gir[0], + gcr_gir[0], + ], + header: 'gcr-gtk4/gcr-gtk4.h', + extra_args: [ + '-DGCR_COMPILATION', + '-DGCR_API_SUBJECT_TO_CHANGE', + ], + install: true, + ) + + gcr_gtk4_vapi = gnome.generate_vapi(gcr_gtk4_basename, + sources: gcr_gtk4_gir[0], + packages: [ + 'glib-2.0', + 'gio-2.0', + gck_vapi, + gcr_vapi, + 'gtk4' + ], + metadata_dirs: meson.current_source_dir(), + vapi_dirs: [ + build_root / 'gck', + build_root / 'gcr', + ], + gir_dirs: [ + build_root / 'gck', + build_root / 'gcr', + ], + install: true, + ) +endif + +# gcr-viewer-gtk4 +subdir('viewer') diff --git a/gcr-gtk4/viewer/meson.build b/gcr-gtk4/viewer/meson.build new file mode 100644 index 0000000..c33fae9 --- /dev/null +++ b/gcr-gtk4/viewer/meson.build @@ -0,0 +1,7 @@ +gcr_viewer = executable('gcr-viewer-gtk4', + 'viewer.c', + dependencies: [ gcr_gtk4_dep, gcr_gtk4_deps ], + c_args: gcr_gtk4_cflags, + include_directories: config_h_dir, + install: true, +) diff --git a/gcr-gtk4/viewer/viewer.c b/gcr-gtk4/viewer/viewer.c new file mode 100644 index 0000000..986cecd --- /dev/null +++ b/gcr-gtk4/viewer/viewer.c @@ -0,0 +1,139 @@ +/* + * Copyright 2011,2021 Collabora Ltd. + * Copyright Corentin Noël <corentin.noel@collabora.com> + * Copyright Stef Walter <stefw@collabora.co.uk> + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <gcr-gtk4/gcr-gtk4.h> + +#include "config.h" + +static gchar **remaining_args = NULL; + +static gboolean +print_version_and_exit (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + g_print("%s -- %s\n", _("GCR Certificate and Key Viewer"), VERSION); + exit (0); + return TRUE; +} + +static const GOptionEntry options[] = { + { "version", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + print_version_and_exit, N_("Show the application's version"), NULL}, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, + &remaining_args, NULL, N_("[file...]") }, + { NULL } +}; + +static void +on_parser_parsed (GcrParser *parser, + gpointer user_data) +{ + GcrCertificate **cert = user_data; + GckAttributes *attributes; + const GckAttribute *attr; + + attributes = gcr_parser_get_parsed_attributes (parser); + attr = gck_attributes_find (attributes, CKA_VALUE); + *cert = gcr_simple_certificate_new (attr->value, attr->length); +} + +GcrCertificate * +simple_certificate_new_from_file (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GcrCertificate *cert = NULL; + GcrParser *parser; + GBytes *bytes; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (!error || !*error, NULL); + + + bytes = g_file_load_bytes (file, cancellable, NULL, error); + if (!bytes) { + return NULL; + } + + parser = gcr_parser_new (); + g_signal_connect (parser, "parsed", G_CALLBACK (on_parser_parsed), &cert); + if (!gcr_parser_parse_bytes (parser, bytes, error)) { + g_bytes_unref (bytes); + g_object_unref (parser); + g_object_unref (cert); + return NULL; + } + + g_bytes_unref (bytes); + g_object_unref (parser); + return cert; +} + + +static void +activate (GtkApplication* app, + gpointer user_data) +{ + GtkWidget *window; + GtkWidget *box; + GtkWidget *scrolled; + GCancellable *cancellable = NULL; + + window = gtk_application_window_new (app); + gtk_window_set_title (GTK_WINDOW (window), "Window"); + gtk_window_set_default_size (GTK_WINDOW (window), 400, 300); + scrolled = gtk_scrolled_window_new (); + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + g_object_set (G_OBJECT (scrolled), + "child", box, + "hscrollbar-policy", GTK_POLICY_NEVER, + NULL); + + if (remaining_args) { + for (int i = 0; remaining_args[i] != NULL; ++i) { + GFile *file; + GError *error = NULL; + GcrCertificate *certificate; + GtkWidget *widget; + + file = g_file_new_for_commandline_arg (remaining_args[i]); + certificate = simple_certificate_new_from_file (file, cancellable, &error); + g_object_unref (file); + widget = gcr_certificate_widget_new (GCR_CERTIFICATE (certificate)); + g_object_unref (certificate); + gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); + gtk_box_append (GTK_BOX (box), widget); + } + + g_clear_pointer (&remaining_args, g_strfreev); + remaining_args = NULL; + } + + gtk_window_set_child (GTK_WINDOW (window), scrolled); + gtk_widget_show (window); +} + +int +main (int argc, + char **argv) +{ + GtkApplication *app; + int status; + + app = gtk_application_new ("org.gnome.GcrViewerGtk4", G_APPLICATION_FLAGS_NONE); + g_application_add_main_option_entries (G_APPLICATION (app), options); + g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); + status = g_application_run (G_APPLICATION (app), argc, argv); + g_object_unref (app); + + return status; +} diff --git a/meson.build b/meson.build index c6607b5..9d708b9 100644 --- a/meson.build +++ b/meson.build @@ -38,6 +38,7 @@ gck_basename = 'gck-@0@'.format(gck_api_version) gcr_base_basename = 'gcr-base-@0@'.format(gcr_api_version) gcr_basename = 'gcr-@0@'.format(gcr_api_version) gcr_gtk3_basename = 'gcr-@0@-gtk3'.format(gcr_api_version) +gcr_gtk4_basename = 'gcr-@0@-gtk4'.format(gcr_api_version) # Dependencies min_glib_version = '2.44' @@ -72,9 +73,13 @@ if libsystemd.found() and systemd.found() with_systemd = true endif -if get_option('gtk') - gtk_min_version = '3.22' - gtk_dep = dependency('gtk+-3.0', version: '>=' + gtk_min_version) +if get_option('gtk3') + gtk3_min_version = '3.22' + gtk3_dep = dependency('gtk+-3.0', version: '>=' + gtk3_min_version) +endif + +if get_option('gtk4') + gtk4_dep = dependency('gtk4') endif # configuration @@ -104,9 +109,12 @@ subdir('egg') subdir('gck') subdir('gcr') subdir('schema') -if get_option('gtk') +if get_option('gtk3') subdir('gcr-gtk3') endif +if get_option('gtk4') + subdir('gcr-gtk4') +endif subdir('tools') if get_option('gtk_doc') if not get_option('introspection') diff --git a/meson_options.txt b/meson_options.txt index f96a47c..6f4bc52 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -3,15 +3,20 @@ option('introspection', value: true, description: 'Build GObject Introspection (GIR) files', ) -option('gtk', +option('gtk3', type: 'boolean', value: true, - description: 'Build code that uses GTK+', + description: 'Enable the GTK3 library and utilities', +) +option('gtk4', + type: 'boolean', + value: true, + description: 'Enable the GTK4 library and utilities', ) option('gtk_doc', type: 'boolean', value: true, - description: 'Build the reference documentation (requires gtk-doc)', + description: 'Build the reference documentation (requires gi-docgen)', ) option('gpg_path', type: 'string', |