[gcr/tintou/gtk4: 2/2] gcr-gtk4: Create GcrCertificateWidget




commit 14a959dbbc11cbdcc00c14e28db1c3b9bf12a1cc
Author: Corentin Noël <corentin noel collabora com>
Date:   Thu Oct 14 12:28:53 2021 +0200

    gcr-gtk4: Create GcrCertificateWidget

 gcr-gtk4/gcr-certificate-widget.c | 683 ++++++++++++++++++++++++++++++++++++++
 gcr-gtk4/gcr-certificate-widget.h |  30 ++
 gcr-gtk4/gcr-gtk4.h               |  15 +
 gcr-gtk4/gcr-renderer.c           |  16 +
 gcr-gtk4/gcr-renderer.h           |  25 ++
 gcr-gtk4/gcr-section.c            | 225 +++++++++++++
 gcr-gtk4/gcr-section.h            |  30 ++
 gcr-gtk4/libgcr-gtk4.map          |   8 +
 gcr-gtk4/meson.build              | 135 ++++++++
 gcr-gtk4/viewer/meson.build       |   7 +
 gcr-gtk4/viewer/viewer.c          |  93 ++++++
 gcr/frob-parser.c                 |   2 +-
 gcr/gcr-certificate-chain.c       |   4 +-
 gcr/gcr-simple-certificate.c      |  88 +++--
 gcr/gcr-simple-certificate.h      |  16 +-
 gcr/gcr.h                         |   1 +
 gcr/test-certificate-chain.c      |   3 +-
 gcr/test-certificate.c            |  24 +-
 gcr/test-pkcs11-certificate.c     |  10 +-
 gcr/test-simple-certificate.c     |  32 +-
 gcr/test-trust.c                  |   3 +-
 meson.build                       |   7 +
 meson_options.txt                 |   5 +
 ui/gcr-certificate-renderer.c     |  14 +-
 ui/gcr-viewer-tool.c              |   1 +
 25 files changed, 1397 insertions(+), 80 deletions(-)
---
diff --git a/gcr-gtk4/gcr-certificate-widget.c b/gcr-gtk4/gcr-certificate-widget.c
new file mode 100644
index 0000000..a1d69c6
--- /dev/null
+++ b/gcr-gtk4/gcr-certificate-widget.c
@@ -0,0 +1,683 @@
+/*
+ * 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-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..5301ce3
--- /dev/null
+++ b/gcr-gtk4/gcr-gtk4.h
@@ -0,0 +1,15 @@
+#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>
+#include <gcr-gtk4/gcr-renderer.h>
+
+#undef __GCR_GTK4_INSIDE_HEADER__
+
+#endif /* __GCR_GTK4_H__ */
diff --git a/gcr-gtk4/gcr-renderer.c b/gcr-gtk4/gcr-renderer.c
new file mode 100644
index 0000000..ebb4f4c
--- /dev/null
+++ b/gcr-gtk4/gcr-renderer.c
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2021 Collabora Ltd.
+ * Copyright Corentin Noël <corentin noel collabora com>
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <gcr-gtk4/gcr-renderer.h>
+
+G_DEFINE_INTERFACE (GcrRenderer, gcr_renderer, G_TYPE_OBJECT)
+
+static void
+gcr_renderer_default_init (GcrRendererInterface *iface)
+{
+  /* add properties and signals to the interface here */
+}
+
diff --git a/gcr-gtk4/gcr-renderer.h b/gcr-gtk4/gcr-renderer.h
new file mode 100644
index 0000000..498dca0
--- /dev/null
+++ b/gcr-gtk4/gcr-renderer.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 Collabora Ltd.
+ * Copyright Corentin Noël <corentin noel collabora com>
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GCR_RENDERER_H__
+#define __GCR_RENDERER_H__
+
+#include <glib-object.h>
+#include <gck/gck.h>
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_RENDERER gcr_renderer_get_type ()
+G_DECLARE_INTERFACE (GcrRenderer, gcr_renderer, GCK, RENDERER, GObject)
+
+struct _GcrRendererInterface
+{
+  GTypeInterface g_iface;
+};
+
+G_END_DECLS
+
+#endif /* __GCR_RENDERER_H__ */
diff --git a/gcr-gtk4/gcr-section.c b/gcr-gtk4/gcr-section.c
new file mode 100644
index 0000000..f888246
--- /dev/null
+++ b/gcr-gtk4/gcr-section.c
@@ -0,0 +1,225 @@
+#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_finalize (GObject *object)
+{
+       GcrSection *self = (GcrSection *)object;
+
+       G_OBJECT_CLASS (gcr_section_parent_class)->finalize (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->finalize = gcr_section_finalize;
+       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_append (GTK_LIST_BOX (self->listbox), row);
+}
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..e97e9ac
--- /dev/null
+++ b/gcr-gtk4/meson.build
@@ -0,0 +1,135 @@
+gcr_gtk4_headers_install_dir = 'gcr-@0@'.format(gcr_major_version) / 'gcr-gtk4'
+
+gcr_gtk4_public_sources = files(
+  'gcr-renderer.c',
+  'gcr-certificate-widget.c',
+)
+
+gcr_gtk4_private_sources = files(
+  'gcr-section.c',
+)
+
+gcr_gtk4_headers = files(
+  'gcr-renderer.h',
+  '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_basename = 'gcr-gtk4-@0@'.format(gcr_major_version)
+
+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-@0@'.format(gck_major_version),
+  'gcr-@0@'.format(gcr_major_version),
+  gtk4_dep,
+]
+pkgconfig.generate(gcr_gtk4_lib,
+  subdirs: 'gcr-@0@'.format(gcr_major_version),
+  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: '@0@'.format(gcr_major_version),
+    export_packages: gcr_gtk4_basename,
+    identifier_prefix: 'Gcr',
+    symbol_prefix: 'gcr',
+    packages: gcr_gtk4_deps,
+    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..b08ebe7
--- /dev/null
+++ b/gcr-gtk4/viewer/viewer.c
@@ -0,0 +1,93 @@
+/*
+ * 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
+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 = gcr_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/gcr/frob-parser.c b/gcr/frob-parser.c
index f071eb5..452bf43 100644
--- a/gcr/frob-parser.c
+++ b/gcr/frob-parser.c
@@ -43,7 +43,7 @@ dump_certificate (GckAttributes *attrs, const gchar *filename)
        if (!attr)
                return FALSE;
 
-       cert = gcr_simple_certificate_new_static (attr->value, attr->length);
+       cert = gcr_simple_certificate_new (g_bytes_new_static (attr->value, attr->length));
        subject = gcr_certificate_get_subject_dn (cert);
        g_print ("%s: parsed certificate: %s\n", filename, subject);
        g_free (subject);
diff --git a/gcr/gcr-certificate-chain.c b/gcr/gcr-certificate-chain.c
index 1793869..262ed07 100644
--- a/gcr/gcr-certificate-chain.c
+++ b/gcr/gcr-certificate-chain.c
@@ -173,9 +173,11 @@ prep_chain_private_thread_safe (GcrCertificateChainPrivate *orig, const gchar *p
 
                /* Otherwise copy the certificate data */
                } else {
+                       GBytes *bytes;
                        der = gcr_certificate_get_der_data (certificate, &n_der);
                        g_return_val_if_fail (der, NULL);
-                       safe = gcr_simple_certificate_new (der, n_der);
+                       bytes = g_bytes_new (der, n_der);
+                       safe = gcr_simple_certificate_new (bytes);
 
                        g_debug ("copying certificate so it's thread safe");
 
diff --git a/gcr/gcr-simple-certificate.c b/gcr/gcr-simple-certificate.c
index 61b9180..0862134 100644
--- a/gcr/gcr-simple-certificate.c
+++ b/gcr/gcr-simple-certificate.c
@@ -23,6 +23,7 @@
 #include "gcr-comparable.h"
 #include "gcr-internal.h"
 #include "gcr-simple-certificate.h"
+#include "gcr-parser.h"
 
 #include <string.h>
 
@@ -124,51 +125,86 @@ gcr_simple_certificate_iface_init (GcrCertificateIface *iface)
 
 /**
  * gcr_simple_certificate_new:
- * @data: (array length=n_data): the raw DER certificate data
- * @n_data: The length of @data
+ * @bytes: (transfer full): a #GBytes containing the raw DER certificate data
  *
- * Create a new #GcrSimpleCertificate for the raw DER data. The @data memory is
- * copied so you can dispose of it after this function returns.
+ * Create a new #GcrSimpleCertificate for the raw DER data. The @bytes memory is
+ * then owned by the returned object, use g_bytes_ref() before calling this
+ * function if you need to keep the #GBytes.
  *
  * Returns: (transfer full) (type Gcr.SimpleCertificate): a new #GcrSimpleCertificate
  */
 GcrCertificate *
-gcr_simple_certificate_new (const guchar *data,
-                            gsize n_data)
+gcr_simple_certificate_new (GBytes *bytes)
 {
        GcrSimpleCertificate *cert;
 
-       g_return_val_if_fail (data, NULL);
-       g_return_val_if_fail (n_data, NULL);
+       g_return_val_if_fail (bytes != NULL, NULL);
 
        cert = g_object_new (GCR_TYPE_SIMPLE_CERTIFICATE, NULL);
+       cert->bytes = bytes;
 
-       cert->bytes = g_bytes_new (data, n_data);
        return GCR_CERTIFICATE (cert);
+
+}
+
+static void
+on_parser_parsed (GcrParser *parser,
+                  gpointer user_data)
+{
+       GcrSimpleCertificate *self = user_data;
+       GckAttributes *attributes;
+       const GckAttribute *attr;
+
+       attributes = gcr_parser_get_parsed_attributes (parser);
+       attr = gck_attributes_find (attributes, CKA_VALUE);
+       self->bytes = g_bytes_new (attr->value, attr->length);
 }
 
-/**
- * gcr_simple_certificate_new_static: (skip)
- * @data: (array length=n_data): The raw DER certificate data
- * @n_data: The length of @data
- *
- * Create a new #GcrSimpleCertificate for the raw DER data. The @data memory is
- * not copied and must persist until the #GcrSimpleCertificate object is
- * destroyed.
- *
- * Returns: (transfer full) (type Gcr.SimpleCertificate): a new #GcrSimpleCertificate
- */
 GcrCertificate *
-gcr_simple_certificate_new_static (const guchar *data,
-                                   gsize n_data)
+gcr_simple_certificate_new_from_file (GFile         *file,
+                                      GCancellable  *cancellable,
+                                      GError       **error)
 {
        GcrSimpleCertificate *cert;
+       GcrParser *parser;
+       GBytes *bytes;
 
-       g_return_val_if_fail (data, NULL);
-       g_return_val_if_fail (n_data, NULL);
+       g_return_val_if_fail (G_IS_FILE (file), NULL);
+       g_return_val_if_fail (!error || !*error, NULL);
 
-       cert = g_object_new (GCR_TYPE_SIMPLE_CERTIFICATE, NULL);
 
-       cert->bytes = g_bytes_new_static (data, n_data);
+       bytes = g_file_load_bytes (file, cancellable, NULL, error);
+       if (!bytes) {
+               return NULL;
+       }
+
+       parser = gcr_parser_new ();
+       cert = g_object_new (GCR_TYPE_SIMPLE_CERTIFICATE, NULL);
+       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 GCR_CERTIFICATE (cert);
 }
+
+void
+gcr_simple_certificate_new_from_file_async (GFile               *file,
+                                            GCancellable        *cancellable,
+                                            GAsyncReadyCallback  callback,
+                                            gpointer             user_data)
+{
+
+}
+
+GcrCertificate *
+gcr_simple_certificate_new_from_file_finish (GAsyncResult  *res,
+                                             GError       **error)
+{
+       return NULL;
+}
diff --git a/gcr/gcr-simple-certificate.h b/gcr/gcr-simple-certificate.h
index 9ad042f..b82e9dd 100644
--- a/gcr/gcr-simple-certificate.h
+++ b/gcr/gcr-simple-certificate.h
@@ -25,6 +25,7 @@
 #define __GCR_SIMPLE_CERTIFICATE_H__
 
 #include <glib-object.h>
+#include <gio/gio.h>
 
 #include "gcr-certificate.h"
 
@@ -34,11 +35,16 @@ G_BEGIN_DECLS
 #define GCR_TYPE_SIMPLE_CERTIFICATE gcr_simple_certificate_get_type ()
 G_DECLARE_FINAL_TYPE (GcrSimpleCertificate, gcr_simple_certificate, GCR, SIMPLE_CERTIFICATE, GObject)
 
-GcrCertificate *gcr_simple_certificate_new        (const guchar *data,
-                                                   gsize n_data);
-
-GcrCertificate *gcr_simple_certificate_new_static (const guchar *data,
-                                                   gsize n_data);
+GcrCertificate *gcr_simple_certificate_new                 (GBytes  *bytes);
+GcrCertificate *gcr_simple_certificate_new_from_file       (GFile         *file,
+                                                            GCancellable  *cancellable,
+                                                            GError       **error);
+void            gcr_simple_certificate_new_from_file_async (GFile               *file,
+                                                            GCancellable        *cancellable,
+                                                            GAsyncReadyCallback  callback,
+                                                            gpointer             user_data);
+GcrCertificate *gcr_simple_certificate_new_from_file_finish (GAsyncResult  *res,
+                                                             GError       **error);
 
 G_END_DECLS
 
diff --git a/gcr/gcr.h b/gcr/gcr.h
index 5766c5a..2591295 100644
--- a/gcr/gcr.h
+++ b/gcr/gcr.h
@@ -38,6 +38,7 @@
 #include <gcr/gcr-column.h>
 #include <gcr/gcr-enum-types-base.h>
 #include <gcr/gcr-filter-collection.h>
+#include <gcr/gcr-fingerprint.h>
 #include <gcr/gcr-icons.h>
 #include <gcr/gcr-importer.h>
 #include <gcr/gcr-library.h>
diff --git a/gcr/test-certificate-chain.c b/gcr/test-certificate-chain.c
index 06347e5..ba4157d 100644
--- a/gcr/test-certificate-chain.c
+++ b/gcr/test-certificate-chain.c
@@ -167,8 +167,7 @@ setup (Test *test, gconstpointer unused)
        /* A self-signed certificate */
        if (!g_file_get_contents (SRCDIR "/gcr/fixtures/der-certificate.crt", &contents, &n_contents, NULL))
                g_assert_not_reached ();
-       test->cert_self = gcr_simple_certificate_new ((const guchar *)contents, n_contents);
-       g_free (contents);
+       test->cert_self = gcr_simple_certificate_new (g_bytes_new_take (contents, n_contents));
 
        /* A signed certificate */
        if (!g_file_get_contents (SRCDIR "/gcr/fixtures/dhansak-collabora.cer", &contents, &n_contents, NULL))
diff --git a/gcr/test-certificate.c b/gcr/test-certificate.c
index 2fed913..b3e4259 100644
--- a/gcr/test-certificate.c
+++ b/gcr/test-certificate.c
@@ -41,26 +41,22 @@ typedef struct {
 static void
 setup (Test *test, gconstpointer unused)
 {
-       gchar *contents;
-       gsize n_contents;
+       GFile *file;
 
-       if (!g_file_get_contents (SRCDIR "/gcr/fixtures/der-certificate.crt", &contents, &n_contents, NULL))
-               g_assert_not_reached ();
-       test->certificate = gcr_simple_certificate_new ((const guchar *)contents, n_contents);
+       file = g_file_new_for_path (SRCDIR "/gcr/fixtures/der-certificate.crt");
+       test->certificate = gcr_simple_certificate_new_from_file (file, NULL, NULL);
+       g_object_unref (file);
        g_assert (test->certificate);
-       g_free (contents);
 
-       if (!g_file_get_contents (SRCDIR "/gcr/fixtures/der-certificate-dsa.cer", &contents, &n_contents, 
NULL))
-               g_assert_not_reached ();
-       test->dsa_cert = gcr_simple_certificate_new ((const guchar *)contents, n_contents);
+       file = g_file_new_for_path (SRCDIR "/gcr/fixtures/der-certificate-dsa.cer");
+       test->dsa_cert = gcr_simple_certificate_new_from_file (file, NULL, NULL);
+       g_object_unref (file);
        g_assert (test->dsa_cert);
-       g_free (contents);
 
-       if (!g_file_get_contents (SRCDIR "/gcr/fixtures/dhansak-collabora.cer", &contents, &n_contents, NULL))
-               g_assert_not_reached ();
-       test->dhansak_cert = gcr_simple_certificate_new ((const guchar *)contents, n_contents);
+       file = g_file_new_for_path (SRCDIR "/gcr/fixtures/dhansak-collabora.cer");
+       test->dhansak_cert = gcr_simple_certificate_new_from_file (file, NULL, NULL);
+       g_object_unref (file);
        g_assert (test->dhansak_cert);
-       g_free (contents);
 }
 
 static void
diff --git a/gcr/test-pkcs11-certificate.c b/gcr/test-pkcs11-certificate.c
index 53003e6..9b70561 100644
--- a/gcr/test-pkcs11-certificate.c
+++ b/gcr/test-pkcs11-certificate.c
@@ -125,7 +125,7 @@ test_lookup_certificate_issuer (Test *test, gconstpointer unused)
        gconstpointer der;
        gsize n_der;
 
-       cert = gcr_simple_certificate_new_static (test->cert_data, test->n_cert_data);
+       cert = gcr_simple_certificate_new (g_bytes_new_static (test->cert_data, test->n_cert_data));
        g_assert (cert);
 
        /* Should be self-signed, so should find itself (added in setup) */
@@ -166,7 +166,7 @@ test_lookup_certificate_issuer_not_found (Test *test, gconstpointer unused)
        GcrCertificate *cert, *issuer;
        GError *error = NULL;
 
-       cert = gcr_simple_certificate_new_static (test->cert2_data, test->n_cert2_data);
+       cert = gcr_simple_certificate_new (g_bytes_new_static (test->cert2_data, test->n_cert2_data));
        g_assert (cert);
 
        /* Issuer shouldn't be found */
@@ -194,7 +194,7 @@ test_lookup_certificate_issuer_async (Test *test, gconstpointer unused)
        gconstpointer der;
        gsize n_der;
 
-       cert = gcr_simple_certificate_new_static (test->cert_data, test->n_cert_data);
+       cert = gcr_simple_certificate_new (g_bytes_new_static (test->cert_data, test->n_cert_data));
        g_assert (cert);
 
        /* Should be self-signed, so should find itself (added in setup) */
@@ -222,7 +222,7 @@ test_lookup_certificate_issuer_failure (Test *test, gconstpointer unused)
        GcrCertificate *cert, *issuer;
        GError *error = NULL;
 
-       cert = gcr_simple_certificate_new_static (test->cert_data, test->n_cert_data);
+       cert = gcr_simple_certificate_new (g_bytes_new_static (test->cert_data, test->n_cert_data));
        g_assert (cert);
 
        /* Make the lookup fail */
@@ -244,7 +244,7 @@ test_lookup_certificate_issuer_fail_async (Test *test, gconstpointer unused)
        GcrCertificate *cert, *issuer;
        GError *error = NULL;
 
-       cert = gcr_simple_certificate_new_static (test->cert_data, test->n_cert_data);
+       cert = gcr_simple_certificate_new (g_bytes_new_static (test->cert_data, test->n_cert_data));
        g_assert (cert);
 
        /* Make the lookup fail */
diff --git a/gcr/test-simple-certificate.c b/gcr/test-simple-certificate.c
index 945768e..d47bdbc 100644
--- a/gcr/test-simple-certificate.c
+++ b/gcr/test-simple-certificate.c
@@ -33,23 +33,25 @@
 #include <errno.h>
 
 typedef struct {
-       gpointer cert_data;
-       gsize n_cert_data;
+       GBytes *bytes;
 } Test;
 
 static void
 setup (Test *test, gconstpointer unused)
 {
-       if (!g_file_get_contents (SRCDIR "/gcr/fixtures/der-certificate.crt", (gchar**)&test->cert_data,
-                                 &test->n_cert_data, NULL))
+       gchar *contents;
+       gsize length;
+       if (!g_file_get_contents (SRCDIR "/gcr/fixtures/der-certificate.crt", &contents, &length, NULL))
                g_assert_not_reached ();
-       g_assert (test->cert_data);
+
+       test->bytes = g_bytes_new_take (contents, length);
+       g_assert (test->bytes);
 }
 
 static void
 teardown (Test *test, gconstpointer unused)
 {
-       g_free (test->cert_data);
+       g_clear_pointer (&test->bytes, g_bytes_unref);
 }
 
 static void
@@ -59,30 +61,34 @@ test_new (Test *test, gconstpointer unused)
        gconstpointer der;
        gsize n_der;
 
-       cert = gcr_simple_certificate_new (test->cert_data, test->n_cert_data);
+       cert = gcr_simple_certificate_new (test->bytes);
        g_assert (GCR_IS_SIMPLE_CERTIFICATE (cert));
 
        der = gcr_certificate_get_der_data (cert, &n_der);
        g_assert (der);
-       egg_assert_cmpmem (der, n_der, ==, test->cert_data, test->n_cert_data);
 
        g_object_unref (cert);
 }
 
 static void
-test_new_static (Test *test, gconstpointer unused)
+test_new_from_file (Test *test, gconstpointer unused)
 {
        GcrCertificate *cert;
+       GFile *file;
        gconstpointer der;
        gsize n_der;
+       gconstpointer ref_der;
+       gsize ref_n_der;
 
-       cert = gcr_simple_certificate_new_static (test->cert_data, test->n_cert_data);
+       file = g_file_new_for_path (SRCDIR "/gcr/fixtures/der-certificate.crt");
+       cert = gcr_simple_certificate_new_from_file (file, NULL, NULL);
        g_assert (GCR_IS_SIMPLE_CERTIFICATE (cert));
 
        der = gcr_certificate_get_der_data (cert, &n_der);
        g_assert (der);
-       egg_assert_cmpsize (n_der, ==, test->n_cert_data);
-       g_assert (der == test->cert_data); /* Must be same pointer */
+       ref_der = g_bytes_get_data (test->bytes, &ref_n_der);
+       egg_assert_cmpsize (n_der, ==, ref_n_der);
+       egg_assert_cmpmem (der, n_der, ==, ref_der, ref_n_der);
 
        g_object_unref (cert);
 }
@@ -94,7 +100,7 @@ main (int argc, char **argv)
        g_set_prgname ("test-simple-certificate");
 
        g_test_add ("/gcr/simple-certificate/new", Test, NULL, setup, test_new, teardown);
-       g_test_add ("/gcr/simple-certificate/new_static", Test, NULL, setup, test_new_static, teardown);
+       g_test_add ("/gcr/simple-certificate/new_from_file", Test, NULL, setup, test_new_from_file, teardown);
 
        return g_test_run ();
 }
diff --git a/gcr/test-trust.c b/gcr/test-trust.c
index 0af02f7..1999167 100644
--- a/gcr/test-trust.c
+++ b/gcr/test-trust.c
@@ -56,8 +56,7 @@ setup (Test *test, gconstpointer unused)
                g_assert_not_reached ();
        g_assert (contents);
 
-       test->certificate = gcr_simple_certificate_new ((const guchar *)contents, len);
-       g_free (contents);
+       test->certificate = gcr_simple_certificate_new (g_bytes_new_take (contents, len));
 
        rv = gck_mock_C_GetFunctionList (&f);
        gck_assert_cmprv (rv, ==, CKR_OK);
diff --git a/meson.build b/meson.build
index 6fa0ff4..9227148 100644
--- a/meson.build
+++ b/meson.build
@@ -70,6 +70,10 @@ if get_option('gtk')
   gtk_dep = dependency('gtk+-3.0', version: '>=' + gtk_min_version)
 endif
 
+if get_option('gtk4')
+  gtk4_dep = dependency('gtk4')
+endif
+
 # configuration
 conf = configuration_data()
 conf.set_quoted('VERSION', meson.project_version())
@@ -100,6 +104,9 @@ subdir('schema')
 if get_option('gtk')
   subdir('ui')
 endif
+if get_option('gtk4')
+  subdir('gcr-gtk4')
+endif
 if get_option('gtk_doc')
   subdir('docs')
 endif
diff --git a/meson_options.txt b/meson_options.txt
index f96a47c..fbc76f8 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -8,6 +8,11 @@ option('gtk',
   value: true,
   description: 'Build code that uses GTK+',
 )
+option('gtk4',
+  type: 'boolean',
+  value: true,
+  description: 'Build the Gtk4 library',
+)
 option('gtk_doc',
   type: 'boolean',
   value: true,
diff --git a/ui/gcr-certificate-renderer.c b/ui/gcr-certificate-renderer.c
index 65be5ba..ca5bcff 100644
--- a/ui/gcr-certificate-renderer.c
+++ b/ui/gcr-certificate-renderer.c
@@ -339,9 +339,7 @@ gcr_certificate_renderer_dispose (GObject *obj)
 {
        GcrCertificateRenderer *self = GCR_CERTIFICATE_RENDERER (obj);
 
-       if (self->pv->opt_cert)
-               g_object_unref (self->pv->opt_cert);
-       self->pv->opt_cert = NULL;
+       g_clear_object (&self->pv->opt_cert);
 
        G_OBJECT_CLASS (gcr_certificate_renderer_parent_class)->dispose (obj);
 }
@@ -351,14 +349,8 @@ gcr_certificate_renderer_finalize (GObject *obj)
 {
        GcrCertificateRenderer *self = GCR_CERTIFICATE_RENDERER (obj);
 
-       g_assert (!self->pv->opt_cert);
-
-       if (self->pv->opt_attrs)
-               gck_attributes_unref (self->pv->opt_attrs);
-       self->pv->opt_attrs = NULL;
-
-       g_free (self->pv->label);
-       self->pv->label = NULL;
+       g_clear_pointer (&self->pv->opt_attrs, gck_attributes_unref);
+       g_clear_pointer (&self->pv->label, g_free);
 
        G_OBJECT_CLASS (gcr_certificate_renderer_parent_class)->finalize (obj);
 }
diff --git a/ui/gcr-viewer-tool.c b/ui/gcr-viewer-tool.c
index 0117d70..e0a743b 100644
--- a/ui/gcr-viewer-tool.c
+++ b/ui/gcr-viewer-tool.c
@@ -124,3 +124,4 @@ main (int argc, char *argv[])
 
        return 0;
 }
+


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]