[gcr/tintou/section-field] gcr: Add GcrCertificateSection/Field




commit c63749dd38b1e6c14e783270ae38856bab8143da
Author: Corentin Noël <corentin noel collabora com>
Date:   Tue May 10 16:08:39 2022 +0200

    gcr: Add GcrCertificateSection/Field
    
    Allows to use the same code-path for both GTK3 and GTK4.
    Also allow any library-user to reimplement it with the toolkit of its choice or
    to adapt it when a different styling is required (e.g. with libadwaita).

 egg/egg-hex.c                       |  27 ++
 egg/egg-hex.h                       |   7 +
 gcr-gtk3/gcr-certificate-widget.c   | 534 +++---------------------------------
 gcr-gtk3/gcr-section.c              | 195 ++++++-------
 gcr-gtk3/gcr-section.h              |   9 +-
 gcr-gtk4/gcr-certificate-widget.c   | 534 ++----------------------------------
 gcr-gtk4/gcr-section.c              | 211 +++++++-------
 gcr-gtk4/gcr-section.h              |   9 +-
 gcr/gcr-certificate-field-private.h |  38 +++
 gcr/gcr-certificate-field.c         | 405 +++++++++++++++++++++++++++
 gcr/gcr-certificate-field.h         |  46 ++++
 gcr/gcr-certificate.c               | 454 ++++++++++++++++++++++++++++++
 gcr/gcr-certificate.h               |   2 +
 gcr/gcr.h                           |   1 +
 gcr/meson.build                     |   2 +
 gcr/test-certificate.c              |  32 +++
 16 files changed, 1270 insertions(+), 1236 deletions(-)
---
diff --git a/egg/egg-hex.c b/egg/egg-hex.c
index d7d05fce..193468f3 100644
--- a/egg/egg-hex.c
+++ b/egg/egg-hex.c
@@ -153,3 +153,30 @@ egg_hex_encode_full (gconstpointer data,
        return g_string_free (result, FALSE);
 }
 
+
+gchar*
+egg_hex_encode_bytes (GBytes *bytes)
+{
+       gsize size;
+       gconstpointer data;
+
+       g_return_val_if_fail (bytes != NULL, NULL);
+
+       data = g_bytes_get_data (bytes, &size);
+       return egg_hex_encode (data, size);
+}
+
+gchar*
+egg_hex_encode_bytes_full (GBytes *bytes,
+                           gboolean upper_case,
+                           const gchar *delim,
+                           guint group)
+{
+       gsize size;
+       gconstpointer data;
+
+       g_return_val_if_fail (bytes != NULL, NULL);
+
+       data = g_bytes_get_data (bytes, &size);
+       return egg_hex_encode_full (data, size, upper_case, delim, group);
+}
diff --git a/egg/egg-hex.h b/egg/egg-hex.h
index d5baea64..b1a8c1a4 100644
--- a/egg/egg-hex.h
+++ b/egg/egg-hex.h
@@ -41,4 +41,11 @@ gchar*                egg_hex_encode_full                    (gconstpointer data
                                                               const gchar *delim,
                                                               guint group);
 
+gchar*                egg_hex_encode_bytes                   (GBytes *bytes);
+
+gchar*                egg_hex_encode_bytes_full              (GBytes *bytes,
+                                                              gboolean upper_case,
+                                                              const gchar *delim,
+                                                              guint group);
+
 #endif /* EGG_HEX_H_ */
diff --git a/gcr-gtk3/gcr-certificate-widget.c b/gcr-gtk3/gcr-certificate-widget.c
index e5fefdc2..de683862 100644
--- a/gcr-gtk3/gcr-certificate-widget.c
+++ b/gcr-gtk3/gcr-certificate-widget.c
@@ -6,21 +6,9 @@
 
 #include "config.h"
 
-#include <glib/gi18n-lib.h>
-
-#include <gcr-gtk3/gcr-certificate-widget.h>
-#include "gcr/gcr-certificate-extensions.h"
-#include "gcr/gcr-fingerprint.h"
-#include "gcr/gcr-oids.h"
-
+#include "gcr-certificate-widget.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
 {
        GtkBox parent_instance;
@@ -28,6 +16,7 @@ struct _GcrCertificateWidget
        GcrCertificate *certificate;
        GtkWidget *reveal_button;
        GtkWidget *revealer;
+       GtkWidget *primary_info;
        GtkWidget *secondary_info;
        GtkSizeGroup *size_group;
 };
@@ -124,378 +113,19 @@ gcr_certificate_widget_init (GcrCertificateWidget *self)
        gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
        self->reveal_button = gtk_button_new_with_label ("…");
        gtk_widget_set_halign (self->reveal_button, GTK_ALIGN_CENTER);
-   gtk_box_pack_end (GTK_BOX (self), self->reveal_button, FALSE, TRUE, 0);
+       gtk_box_pack_end (GTK_BOX (self), self->reveal_button, FALSE, TRUE, 0);
+       self->primary_info = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
        self->secondary_info = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+       gtk_box_pack_start (GTK_BOX (self), self->primary_info, FALSE, TRUE, 0);
        self->revealer = g_object_new (GTK_TYPE_REVEALER,
-                                      "child", self->secondary_info,
-                                      "transition-type", GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN,
-                                      NULL);
-   gtk_box_pack_end (GTK_BOX (self), self->revealer, FALSE, TRUE, 0);
+                                      "child", self->secondary_info,
+                                      "transition-type", GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN,
+                                      NULL);
+       gtk_box_pack_end (GTK_BOX (self), self->revealer, FALSE, TRUE, 0);
        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_box_pack_start (GTK_BOX (self), widget, FALSE, TRUE, 0);
-       else
-               gtk_container_add (GTK_CONTAINER (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
@@ -536,142 +166,42 @@ gcr_certificate_widget_get_certificate (GcrCertificateWidget *self)
 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;
+       GList* elements, *l, *children;
 
        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);
+       children = gtk_container_get_children (GTK_CONTAINER (self->secondary_info));
+       for (l = children; l != NULL; l = l->next) {
+               gtk_widget_destroy (GTK_WIDGET (l->data));
        }
 
-       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);
+       g_list_free (children);
 
-       /* 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);
+       children = gtk_container_get_children (GTK_CONTAINER (self->primary_info));
+       for (l = children; l != NULL; l = l->next) {
+               gtk_widget_destroy (GTK_WIDGET (l->data));
        }
 
-       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);
-       }
+       g_list_free (children);
 
-       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);
+       g_set_object (&self->certificate, certificate);
+       if (!certificate) {
+               return;
        }
 
-       /* Signature */
-       section = _gcr_certificate_widget_add_section (self, _("Signature"), NULL, FALSE);
+       elements = gcr_certificate_get_interface_elements (certificate);
+       for (l = elements; l != NULL; l = l->next) {
+               GcrCertificateSection *section = l->data;
+               GtkWidget *widget;
 
-       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)));
+               widget = gcr_section_new (section);
 
-       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);
+               gtk_size_group_add_widget (self->size_group, widget);
+               if (gcr_certificate_section_get_important (section))
+                       gtk_box_pack_end (GTK_BOX (self->primary_info), widget, FALSE, FALSE, 0);
+               else
+                       gtk_box_pack_end (GTK_BOX (self->secondary_info), widget, FALSE, FALSE, 0);
        }
 
-       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);
+       g_list_free_full (elements, (GDestroyNotify) gcr_certificate_section_unref);
 }
diff --git a/gcr-gtk3/gcr-section.c b/gcr-gtk3/gcr-section.c
index 24c15023..8e1c44d7 100644
--- a/gcr-gtk3/gcr-section.c
+++ b/gcr-gtk3/gcr-section.c
@@ -10,9 +10,8 @@ struct _GcrSection
 {
        GtkGrid parent_instance;
 
-       GtkWidget *frame;
+       GcrCertificateSection *section;
        GtkWidget *label;
-       GtkWidget *image;
        GtkWidget *listbox;
        GtkSizeGroup *size_group;
 };
@@ -20,22 +19,73 @@ struct _GcrSection
 G_DEFINE_TYPE (GcrSection, gcr_section, GTK_TYPE_GRID)
 
 enum {
-       PROP_TITLE = 1,
-       PROP_ICON,
+       PROP_SECTION = 1,
        N_PROPERTIES
 };
 
 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
 
+GtkWidget *
+gcr_section_create_row (GObject *item,
+                        gpointer user_data)
+{
+       GcrCertificateField *field = (GcrCertificateField *) item;
+       GtkSizeGroup *size_group = user_data;
+       GtkWidget *row, *box, *label, *value;
+
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_FIELD (field), NULL);
+
+       row = gtk_list_box_row_new ();
+       gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
+       box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+       gtk_container_add (GTK_CONTAINER (row), box);
+       label = gtk_label_new (gcr_certificate_field_get_label (field));
+       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");
+       value = g_object_new (GTK_TYPE_LABEL,
+                            "label", gcr_certificate_field_get_value (field),
+                            "xalign", 1.0,
+                            "halign", GTK_ALIGN_END,
+                            "hexpand", TRUE,
+                            "selectable", TRUE,
+                            "wrap", TRUE,
+                            NULL);
+       if (gcr_certificate_field_is_bytes (field)) {
+               PangoAttrList *attributes;
+               attributes = pango_attr_list_new ();
+               pango_attr_list_insert (attributes, pango_attr_family_new ("Monospace"));
+               gtk_label_set_attributes (GTK_LABEL (value), attributes);
+               pango_attr_list_unref (attributes);
+       }
+
+       g_object_set (value,
+                     "margin-start", 12,
+                     "margin-end", 12,
+                     "margin-top", 8,
+                     "margin-bottom", 8,
+                     "halign", GTK_ALIGN_END,
+                     NULL);
+       gtk_size_group_add_widget (size_group, label);
+       gtk_container_add (GTK_CONTAINER (box), label);
+       gtk_container_add (GTK_CONTAINER (box), value);
+       return row;
+}
+
 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_clear_pointer (&self->section, gcr_certificate_section_unref);
 
        G_OBJECT_CLASS (gcr_section_parent_class)->dispose (object);
 }
@@ -48,17 +98,9 @@ gcr_section_get_property (GObject    *object,
 {
        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:
-               {
-                       GIcon *icon;
-                       gtk_image_get_gicon (GTK_IMAGE (self->image), &icon, NULL);
-                       g_value_set_object (value, icon);
-               }
+       switch (prop_id) {
+       case PROP_SECTION:
+               g_value_set_boxed (value, self->section);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -73,20 +115,15 @@ gcr_section_set_property (GObject      *object,
 {
        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)
-               {
-                       self->image = gtk_image_new ();
-                       gtk_grid_attach (GTK_GRID (self), self->image, 1, 0, 1, 1);
-                       gtk_container_child_set (GTK_CONTAINER (self), self->frame, "width", 2, NULL);
-               }
-
-               gtk_image_set_from_gicon (GTK_IMAGE (self->image), g_value_get_object (value), 
GTK_ICON_SIZE_BUTTON);
+       switch (prop_id) {
+       case PROP_SECTION:
+               g_assert (!self->section);
+               self->section = g_value_dup_boxed (value);
+               gtk_label_set_label (GTK_LABEL (self->label), gcr_certificate_section_get_label 
(self->section));
+               gtk_list_box_bind_model (GTK_LIST_BOX (self->listbox),
+                                        gcr_certificate_section_get_fields (self->section),
+                                        (GtkListBoxCreateWidgetFunc) gcr_section_create_row,
+                                        self->size_group, NULL);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -101,19 +138,12 @@ gcr_section_class_init (GcrSectionClass *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);
+       obj_properties[PROP_SECTION] =
+               g_param_spec_boxed ("section",
+                                   "Section",
+                                   "The section object",
+                                   GCR_TYPE_CERTIFICATE_SECTION,
+                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 
        g_object_class_install_properties (object_class,
                                           N_PROPERTIES,
@@ -126,79 +156,34 @@ gcr_section_init (GcrSection *self)
        gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
        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,
+                     "margin-start", 12,
+                     "margin-end", 12,
+                     "margin-top", 8,
+                     "margin-bottom", 8,
                      "xalign", 0.0,
-                     "halign", GTK_ALIGN_START,
-                     "hexpand", TRUE,
+                     "halign", GTK_ALIGN_START,
+                     "hexpand", TRUE,
                      NULL);
        gtk_style_context_add_class (gtk_widget_get_style_context (self->label), "heading");
        gtk_style_context_add_class (gtk_widget_get_style_context (self->label), "h4");
        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_container_add (GTK_CONTAINER (self), self->label);
-   gtk_container_add (GTK_CONTAINER (self), self->frame);
+       gtk_style_context_add_class (gtk_widget_get_style_context (self->listbox), "frame");
+       gtk_container_add (GTK_CONTAINER (self), self->label);
+       gtk_container_add (GTK_CONTAINER (self), self->listbox);
 
        g_object_set (self,
-                     "margin-start", 12,
-                     "margin-end", 12,
-                     "margin-top", 8,
-                     "margin-bottom", 8,
+                     "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)
+gcr_section_new (GcrCertificateSection *section)
 {
-       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_container_add (GTK_CONTAINER (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_container_add (GTK_CONTAINER (box), label);
-       gtk_container_add (GTK_CONTAINER (box), child);
-       gtk_container_add (GTK_CONTAINER (self->listbox), row);
+       return g_object_new (GCR_TYPE_SECTION, "section", section, NULL);
 }
diff --git a/gcr-gtk3/gcr-section.h b/gcr-gtk3/gcr-section.h
index 69950cc9..55443ccc 100644
--- a/gcr-gtk3/gcr-section.h
+++ b/gcr-gtk3/gcr-section.h
@@ -10,19 +10,14 @@
 #include <glib.h>
 #include <glib-object.h>
 #include <gtk/gtk.h>
+#include <gcr/gcr.h>
 
 G_BEGIN_DECLS
 
 #define GCR_TYPE_SECTION gcr_section_get_type()
 G_DECLARE_FINAL_TYPE (GcrSection, gcr_section, GCR, SECTION, GtkGrid)
 
-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);
+GtkWidget *gcr_section_new (GcrCertificateSection *section);
 
 G_END_DECLS
 
diff --git a/gcr-gtk4/gcr-certificate-widget.c b/gcr-gtk4/gcr-certificate-widget.c
index 58e3e55e..ee51f956 100644
--- a/gcr-gtk4/gcr-certificate-widget.c
+++ b/gcr-gtk4/gcr-certificate-widget.c
@@ -6,26 +6,15 @@
 
 #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-certificate-widget.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 *primary_info;
        GtkWidget *reveal_button;
        GtkWidget *revealer;
        GtkWidget *secondary_info;
@@ -63,8 +52,7 @@ gcr_certificate_widget_get_property (GObject    *object,
 {
        GcrCertificateWidget *self = GCR_CERTIFICATE_WIDGET (object);
 
-       switch (prop_id)
-       {
+       switch (prop_id) {
        case PROP_CERTIFICATE:
                g_value_set_object (value, gcr_certificate_widget_get_certificate (self));
                break;
@@ -82,8 +70,7 @@ gcr_certificate_widget_set_property (GObject      *object,
 {
        GcrCertificateWidget *self = GCR_CERTIFICATE_WIDGET (object);
 
-       switch (prop_id)
-       {
+       switch (prop_id) {
        case PROP_CERTIFICATE:
                gcr_certificate_widget_set_certificate (self, g_value_get_object (value));
                break;
@@ -132,377 +119,18 @@ gcr_certificate_widget_init (GcrCertificateWidget *self)
        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->primary_info = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
        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);
+                                      "child", self->secondary_info,
+                                      "transition-type", GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN,
+                                      NULL);
+       gtk_widget_insert_after (self->primary_info, GTK_WIDGET (self), 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
@@ -543,142 +171,40 @@ gcr_certificate_widget_get_certificate (GcrCertificateWidget *self)
 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;
+       GtkWidget *child;
+       GList* elements;
 
        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);
+       if (!g_set_object (&self->certificate, certificate)) {
+               return;
        }
 
-       /* 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);
+       while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->secondary_info)))) {
+               gtk_widget_unparent (child);
        }
 
-       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);
+       while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->primary_info)))) {
+               gtk_widget_unparent (child);
        }
 
-       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);
+       if (!certificate) {
+               return;
        }
 
-       /* Signature */
-       section = _gcr_certificate_widget_add_section (self, _("Signature"), NULL, FALSE);
+       elements = gcr_certificate_get_interface_elements (certificate);
+       for (GList *l = elements; l != NULL; l = l->next) {
+               GcrCertificateSection *section = l->data;
+               GtkWidget *widget;
 
-       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)));
+               widget = gcr_section_new (section);
 
-       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);
+               gtk_size_group_add_widget (self->size_group, widget);
+               if (gcr_certificate_section_get_important (section))
+                       gtk_box_append (GTK_BOX (self->primary_info), widget);
+               else
+                       gtk_box_append (GTK_BOX (self->secondary_info), widget);
        }
 
-       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);
+       g_list_free_full (elements, (GDestroyNotify) gcr_certificate_section_unref);
 }
diff --git a/gcr-gtk4/gcr-section.c b/gcr-gtk4/gcr-section.c
index 33aa1c31..c8e900e7 100644
--- a/gcr-gtk4/gcr-section.c
+++ b/gcr-gtk4/gcr-section.c
@@ -10,9 +10,8 @@ struct _GcrSection
 {
        GtkWidget parent_instance;
 
-       GtkWidget *frame;
+       GcrCertificateSection *section;
        GtkWidget *label;
-       GtkWidget *image;
        GtkWidget *listbox;
        GtkSizeGroup *size_group;
 };
@@ -20,22 +19,83 @@ struct _GcrSection
 G_DEFINE_TYPE (GcrSection, gcr_section, GTK_TYPE_WIDGET)
 
 enum {
-       PROP_TITLE = 1,
-       PROP_ICON,
+       PROP_SECTION = 1,
        N_PROPERTIES
 };
 
 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
 
+GtkWidget *
+gcr_section_create_row (GObject *item,
+                        gpointer user_data)
+{
+       GcrCertificateField *field = (GcrCertificateField *) item;
+       GtkSizeGroup *size_group = user_data;
+       GtkWidget *row, *box, *label, *value;
+
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_FIELD (field), NULL);
+
+       row = gtk_list_box_row_new ();
+       gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
+       gtk_widget_set_focusable (row, FALSE);
+       box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+       gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box);
+       label = gtk_label_new (gcr_certificate_field_get_label (field));
+       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");
+       value = g_object_new (GTK_TYPE_LABEL,
+                             "xalign", 1.0,
+                             "halign", GTK_ALIGN_END,
+                             "hexpand", TRUE,
+                             "selectable", TRUE,
+                             "wrap", TRUE,
+                             NULL);
+       if (gcr_certificate_field_is_single(field)) {
+               g_object_set (G_OBJECT (value), "label", gcr_certificate_field_get_value (field), NULL);
+       } else {
+               char *val = g_strjoinv ("\n", gcr_certificate_field_get_values (field));
+               g_object_set (G_OBJECT (value), "label", val, NULL);
+               g_free (val);
+       }
+
+       if (gcr_certificate_field_is_bytes (field)) {
+               PangoAttrList *attributes;
+               attributes = pango_attr_list_new ();
+               pango_attr_list_insert (attributes, pango_attr_family_new ("Monospace"));
+               gtk_label_set_attributes (GTK_LABEL (value), attributes);
+               pango_attr_list_unref (attributes);
+       }
+
+       g_object_set (value,
+                     "margin-start", 12,
+                     "margin-end", 12,
+                     "margin-top", 8,
+                     "margin-bottom", 8,
+                     "halign", GTK_ALIGN_END,
+                     NULL);
+       gtk_size_group_add_widget (size_group, label);
+       gtk_box_append (GTK_BOX (box), label);
+       gtk_box_append (GTK_BOX (box), value);
+       return row;
+}
+
 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_clear_pointer (&self->listbox, gtk_widget_unparent);
+       g_clear_object (&self->size_group);
+       g_clear_pointer (&self->section, gcr_certificate_section_unref);
 
        G_OBJECT_CLASS (gcr_section_parent_class)->dispose (object);
 }
@@ -50,11 +110,8 @@ gcr_section_get_property (GObject    *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)));
+       case PROP_SECTION:
+               g_value_set_boxed (value, self->section);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -71,31 +128,14 @@ gcr_section_set_property (GObject      *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));
+       case PROP_SECTION:
+               g_assert (!self->section);
+               self->section = g_value_dup_boxed (value);
+               gtk_label_set_label (GTK_LABEL (self->label), gcr_certificate_section_get_label 
(self->section));
+               gtk_list_box_bind_model (GTK_LIST_BOX (self->listbox),
+                                        gcr_certificate_section_get_fields (self->section),
+                                        (GtkListBoxCreateWidgetFunc) gcr_section_create_row,
+                                        self->size_group, NULL);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -111,19 +151,12 @@ gcr_section_class_init (GcrSectionClass *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);
+       obj_properties[PROP_SECTION] =
+               g_param_spec_boxed ("section",
+                                   "Section",
+                                   "The section object",
+                                   GCR_TYPE_CERTIFICATE_SECTION,
+                                   G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 
        g_object_class_install_properties (object_class,
                                           N_PROPERTIES,
@@ -140,83 +173,39 @@ gcr_section_init (GcrSection *self)
 
        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,
+                     "margin-start", 12,
+                     "margin-end", 12,
+                     "margin-top", 8,
+                     "margin-bottom", 8,
                      "xalign", 0.0,
-                     "halign", GTK_ALIGN_START,
-                     "hexpand", TRUE,
+                     "halign", GTK_ALIGN_START,
+                     "hexpand", TRUE,
                      NULL);
+       gtk_style_context_add_class (gtk_widget_get_style_context (self->label), "title");
        gtk_style_context_add_class (gtk_widget_get_style_context (self->label), "caption-heading");
        self->listbox = gtk_list_box_new ();
+       gtk_style_context_add_class (gtk_widget_get_style_context (self->listbox), "frame");
+       gtk_style_context_add_class (gtk_widget_get_style_context (self->listbox), "rich-list");
        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);
+       gtk_widget_insert_after (self->listbox, 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);
+       child = gtk_layout_manager_get_layout_child (layout_manager, self->listbox);
        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,
+                     "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)
+gcr_section_new (GcrCertificateSection *section)
 {
-       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);
+       return g_object_new (GCR_TYPE_SECTION, "section", section, NULL);
 }
diff --git a/gcr-gtk4/gcr-section.h b/gcr-gtk4/gcr-section.h
index 9f275d92..c341ae94 100644
--- a/gcr-gtk4/gcr-section.h
+++ b/gcr-gtk4/gcr-section.h
@@ -10,6 +10,7 @@
 #include <glib.h>
 #include <glib-object.h>
 #include <gtk/gtk.h>
+#include <gcr/gcr.h>
 
 G_BEGIN_DECLS
 
@@ -17,13 +18,7 @@ G_BEGIN_DECLS
 
 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);
+GtkWidget *gcr_section_new (GcrCertificateSection *section);
 
 G_END_DECLS
 
diff --git a/gcr/gcr-certificate-field-private.h b/gcr/gcr-certificate-field-private.h
new file mode 100644
index 00000000..3434ab1c
--- /dev/null
+++ b/gcr/gcr-certificate-field-private.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 Collabora Ltd.
+ * Copyright Corentin Noël <corentin noel collabora com>
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GCR_CERTIFICATE_FIELD_PRIVATE_H__
+#define __GCR_CERTIFICATE_FIELD_PRIVATE_H__
+
+#if !defined (__GCR_INSIDE_HEADER__) && !defined (GCR_COMPILATION)
+#error "Only <gcr/gcr.h> can be included directly."
+#endif
+
+#include "gcr-types.h"
+#include "gcr-certificate-field.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+GcrCertificateSection * _gcr_certificate_section_new (const char *label,
+                                                      gboolean    important);
+void _gcr_certificate_section_append_field (GcrCertificateSection *section,
+                                            const char            *label,
+                                            const char            *value,
+                                            gboolean               is_bytes);
+void _gcr_certificate_section_append_field_take_value (GcrCertificateSection *section,
+                                                       const char            *label,
+                                                       char                  *value,
+                                                       gboolean               is_bytes);
+void _gcr_certificate_section_append_field_take_values (GcrCertificateSection *section,
+                                                        const char            *label,
+                                                        GStrv                  value,
+                                                        gboolean               is_bytes);
+
+G_END_DECLS
+
+#endif /* __GCR_CERTIFICATE_FIELD_PRIVATE_H__ */
diff --git a/gcr/gcr-certificate-field.c b/gcr/gcr-certificate-field.c
new file mode 100644
index 00000000..18fb1d5e
--- /dev/null
+++ b/gcr/gcr-certificate-field.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright 2021 Collabora Ltd.
+ * Copyright Corentin Noël <corentin noel collabora com>
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "gcr-certificate-field.h"
+#include "gcr-certificate-field-private.h"
+
+struct _GcrCertificateSection {
+       gatomicrefcount ref_count;
+
+       char *label;
+       gboolean important;
+       GListStore *fields;
+};
+
+G_DEFINE_BOXED_TYPE (GcrCertificateSection, gcr_certificate_section, gcr_certificate_section_ref, 
gcr_certificate_section_unref)
+
+struct _GcrCertificateField
+{
+       GObject parent_instance;
+
+       char *label;
+       union {
+               char *value;
+               GStrv values;
+       };
+       gboolean is_bytes : 1;
+       gboolean is_single : 1;
+       GcrCertificateSection *section;
+};
+
+G_DEFINE_FINAL_TYPE (GcrCertificateField, gcr_certificate_field, G_TYPE_OBJECT)
+
+enum {
+       PROP_LABEL = 1,
+       PROP_VALUE,
+       PROP_VALUES,
+       PROP_IS_BYTES,
+       PROP_IS_SINGLE,
+       PROP_SECTION,
+       N_PROPERTIES
+};
+
+static GParamSpec *obj_properties [N_PROPERTIES];
+
+static void
+gcr_certificate_field_finalize (GObject *object)
+{
+       GcrCertificateField *self = (GcrCertificateField *)object;
+
+       g_clear_pointer (&self->label, g_free);
+       if (self->is_single)
+               g_clear_pointer (&self->value, g_free);
+       else
+               g_clear_pointer (&self->values, g_strfreev);
+
+       G_OBJECT_CLASS (gcr_certificate_field_parent_class)->finalize (object);
+}
+
+static void
+gcr_certificate_field_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+       GcrCertificateField *self = GCR_CERTIFICATE_FIELD (object);
+
+       switch (prop_id) {
+       case PROP_LABEL:
+               g_value_set_string (value, self->label);
+               break;
+       case PROP_VALUE:
+               g_value_set_string (value, self->value);
+               break;
+       case PROP_VALUES:
+               g_value_set_boxed (value, self->values);
+               break;
+       case PROP_IS_BYTES:
+               g_value_set_boolean (value, self->is_bytes);
+               break;
+       case PROP_IS_SINGLE:
+               g_value_set_boolean (value, self->is_single);
+               break;
+       case PROP_SECTION:
+               g_value_set_boxed (value, self->section);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gcr_certificate_field_class_init (GcrCertificateFieldClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = gcr_certificate_field_finalize;
+       object_class->get_property = gcr_certificate_field_get_property;
+
+       obj_properties[PROP_LABEL] =
+               g_param_spec_string ("label",
+                                    "Label",
+                                    "Display name of the field.",
+                                    NULL,
+                                    G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+
+       obj_properties[PROP_VALUE] =
+               g_param_spec_string ("value",
+                                    "Value",
+                                    "Display name of the value.",
+                                    NULL,
+                                    G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+
+       obj_properties[PROP_VALUES] =
+               g_param_spec_boxed ("values",
+                                    "Values",
+                                    "Display name of the values.",
+                                    G_TYPE_STRV,
+                                    G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+
+       obj_properties[PROP_IS_BYTES] =
+               g_param_spec_boolean ("is-bytes",
+                                     "Is Bytes",
+                                     "Whether the value is representing bytes.",
+                                     FALSE,
+                                     G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+
+       obj_properties[PROP_IS_SINGLE] =
+               g_param_spec_boolean ("is-single",
+                                     "Is Single",
+                                     "Whether the value is a single value or an array",
+                                     TRUE,
+                                     G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+
+       obj_properties[PROP_SECTION] =
+               g_param_spec_boxed ("section",
+                                   "Section",
+                                   "The section it is included.",
+                                   GCR_TYPE_CERTIFICATE_SECTION,
+                                   G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+
+       g_object_class_install_properties (object_class,
+                                          N_PROPERTIES,
+                                          obj_properties);
+}
+
+static void
+gcr_certificate_field_init (GcrCertificateField *self)
+{
+}
+
+/**
+ * _gcr_certificate_section_new:
+ * @label: the user-displayable label of the section
+ * @important: whether this section should be visible by default
+ *
+ * Create a new certificate section usable in user interfaces.
+ *
+ * Returns: (transfer full): a new #GcrCertificateSection
+ */
+GcrCertificateSection *
+_gcr_certificate_section_new (const char *label,
+                              gboolean    important)
+{
+       GcrCertificateSection *self = g_new(GcrCertificateSection, 1);
+       g_atomic_ref_count_init (&self->ref_count);
+       self->fields = g_list_store_new (GCR_TYPE_CERTIFICATE_FIELD);
+       self->label = g_strdup (label);
+       self->important = important;
+
+       return self;
+}
+
+GcrCertificateSection *
+gcr_certificate_section_ref (GcrCertificateSection *self)
+{
+       g_return_val_if_fail (self != NULL, NULL);
+
+       g_atomic_ref_count_inc (&self->ref_count);
+
+       return self;
+}
+
+void
+gcr_certificate_section_unref (GcrCertificateSection *self)
+{
+       g_return_if_fail (self != NULL);
+
+       if (g_atomic_ref_count_dec (&self->ref_count)) {
+               g_clear_pointer (&self->label, g_free);
+               g_clear_object (&self->fields);
+               g_free (self);
+       }
+}
+
+/**
+ * gcr_certificate_section_get_label:
+ * @self: the #GcrCertificateSection
+ *
+ * Get the displayable label of the section.
+ *
+ * Returns: the displayable label of the section
+ */
+const char *
+gcr_certificate_section_get_label (GcrCertificateSection *self)
+{
+       g_return_val_if_fail (self != NULL, NULL);
+
+       return self->label;
+}
+
+/**
+ * gcr_certificate_section_get_important:
+ * @self: the #GcrCertificateSection
+ *
+ * Get whether this section should be visible by default.
+ *
+ * Returns: whether this section should be visible by default
+ */
+gboolean
+gcr_certificate_section_get_important (GcrCertificateSection *self)
+{
+       g_return_val_if_fail (self != NULL, FALSE);
+
+       return self->important;
+}
+
+/**
+ * gcr_certificate_section_get_fields:
+ * @self: the #GcrCertificateSection
+ *
+ * Get the list of all the fields in this section.
+ *
+ * Returns: (transfer none): a #GListModel of #GcrCertificateField
+ */
+GListModel *
+gcr_certificate_section_get_fields (GcrCertificateSection *self)
+{
+       g_return_val_if_fail (self != NULL, NULL);
+
+       return G_LIST_MODEL (self->fields);
+}
+
+/**
+ * gcr_certificate_field_get_label:
+ * @self: the #GcrCertificateField
+ *
+ * Get the display label of the field.
+ *
+ * Returns: the display label of the field
+ */
+const char *
+gcr_certificate_field_get_label (GcrCertificateField *self)
+{
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_FIELD (self), NULL);
+
+       return self->label;
+}
+
+/**
+ * gcr_certificate_field_get_value:
+ * @self: the #GcrCertificateField
+ *
+ * Get the value of the field.
+ *
+ * It is a programming mistake to call this on non single field.
+ *
+ * Returns: the value of the field
+ */
+const char *
+gcr_certificate_field_get_value (GcrCertificateField *self)
+{
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_FIELD (self), NULL);
+       g_return_val_if_fail (self->is_single, NULL);
+
+       return self->value;
+}
+
+/**
+ * gcr_certificate_field_get_values:
+ * @self: the #GcrCertificateField
+ *
+ * Get the values of the field.
+ *
+ * It is a programming mistake to call this on single field.
+ *
+ * Returns: (transfer none): the values of the field
+ */
+const GStrv
+gcr_certificate_field_get_values (GcrCertificateField *self)
+{
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_FIELD (self), NULL);
+       g_return_val_if_fail (!self->is_single, NULL);
+
+       return self->values;
+}
+
+/**
+ * gcr_certificate_field_is_bytes:
+ * @self: the #GcrCertificateField
+ *
+ * Get whether the value is representing a byte sequence.
+ *
+ * Returns: %TRUE if this is representing bytes, %FALSE otherwise
+ */
+gboolean
+gcr_certificate_field_is_bytes (GcrCertificateField *self)
+{
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_FIELD (self), FALSE);
+
+       return self->is_bytes ? TRUE : FALSE;
+}
+
+/**
+ * gcr_certificate_field_is_single:
+ * @self: the #GcrCertificateField
+ *
+ * Get whether the value is a single value.
+ *
+ * Returns: %TRUE if the value is single, %FALSE otherwise
+ */
+gboolean
+gcr_certificate_field_is_single (GcrCertificateField *self)
+{
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_FIELD (self), FALSE);
+
+       return self->is_single ? TRUE : FALSE;
+}
+
+/**
+ * gcr_certificate_field_get_section:
+ * @self: the #GcrCertificateField
+ *
+ * Get the parent #GcrCertificateSection.
+ *
+ * Returns: (transfer none): the parent #GcrCertificateSection
+ */
+GcrCertificateSection *
+gcr_certificate_field_get_section (GcrCertificateField *self)
+{
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_FIELD (self), NULL);
+
+       return self->section;
+}
+
+void
+_gcr_certificate_section_append_field (GcrCertificateSection *section,
+                                       const char            *label,
+                                       const char            *value,
+                                       gboolean               is_bytes)
+{
+       g_return_if_fail (section != NULL);
+       g_return_if_fail (label != NULL);
+       g_return_if_fail (value != NULL);
+
+       _gcr_certificate_section_append_field_take_value (section, label, g_strdup (value), is_bytes);
+}
+
+void
+_gcr_certificate_section_append_field_take_value (GcrCertificateSection *section,
+                                                  const char            *label,
+                                                  char                  *value,
+                                                  gboolean               is_bytes)
+{
+       GcrCertificateField *self;
+
+       g_return_if_fail (section != NULL);
+       g_return_if_fail (label != NULL);
+       g_return_if_fail (value != NULL);
+
+       self = g_object_new (GCR_TYPE_CERTIFICATE_FIELD, NULL);
+       self->section = section;
+       self->label = g_strdup (label);
+       self->value = value;
+       self->is_bytes = is_bytes;
+       self->is_single = TRUE;
+       g_list_store_append (section->fields, self);
+
+       g_object_unref (self);
+}
+
+void
+_gcr_certificate_section_append_field_take_values (GcrCertificateSection *section,
+                                                   const char            *label,
+                                                   GStrv                  values,
+                                                   gboolean               is_bytes)
+{
+       GcrCertificateField *self;
+
+       g_return_if_fail (section != NULL);
+       g_return_if_fail (label != NULL);
+       g_return_if_fail (values != NULL);
+
+       self = g_object_new (GCR_TYPE_CERTIFICATE_FIELD, NULL);
+       self->section = section;
+       self->label = g_strdup (label);
+       self->values = values;
+       self->is_bytes = is_bytes;
+       self->is_single = FALSE;
+       g_list_store_append (section->fields, self);
+
+       g_object_unref (self);
+}
diff --git a/gcr/gcr-certificate-field.h b/gcr/gcr-certificate-field.h
new file mode 100644
index 00000000..3bc2685b
--- /dev/null
+++ b/gcr/gcr-certificate-field.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 Collabora Ltd.
+ * Copyright Corentin Noël <corentin noel collabora com>
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GCR_CERTIFICATE_FIELD_H__
+#define __GCR_CERTIFICATE_FIELD_H__
+
+#if !defined (__GCR_INSIDE_HEADER__) && !defined (GCR_COMPILATION)
+#error "Only <gcr/gcr.h> can be included directly."
+#endif
+
+#include "gcr-types.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_CERTIFICATE_SECTION (gcr_certificate_section_get_type ())
+
+typedef struct _GcrCertificateSection GcrCertificateSection;
+
+GType                  gcr_certificate_section_get_type      (void) G_GNUC_CONST;
+GcrCertificateSection *gcr_certificate_section_ref           (GcrCertificateSection *self);
+void                   gcr_certificate_section_unref         (GcrCertificateSection *self);
+const char            *gcr_certificate_section_get_label     (GcrCertificateSection *self);
+gboolean               gcr_certificate_section_get_important (GcrCertificateSection *self);
+GListModel            *gcr_certificate_section_get_fields    (GcrCertificateSection *self);
+
+#define GCR_TYPE_CERTIFICATE_FIELD (gcr_certificate_field_get_type ())
+
+G_DECLARE_FINAL_TYPE (GcrCertificateField, gcr_certificate_field, GCR, CERTIFICATE_FIELD, GObject)
+
+const char            *gcr_certificate_field_get_label   (GcrCertificateField *self);
+const char            *gcr_certificate_field_get_value   (GcrCertificateField *self);
+const GStrv            gcr_certificate_field_get_values  (GcrCertificateField *self);
+gboolean               gcr_certificate_field_is_bytes    (GcrCertificateField *self);
+gboolean               gcr_certificate_field_is_single   (GcrCertificateField *self);
+GcrCertificateSection *gcr_certificate_field_get_section (GcrCertificateField *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GcrCertificateSection, gcr_certificate_section_unref)
+
+G_END_DECLS
+
+#endif /* __GCR_CERTIFICATE_FIELD_H__ */
diff --git a/gcr/gcr-certificate.c b/gcr/gcr-certificate.c
index 714f4a96..0144d8c7 100644
--- a/gcr/gcr-certificate.c
+++ b/gcr/gcr-certificate.c
@@ -21,7 +21,10 @@
 
 #include "gcr-certificate.h"
 #include "gcr-certificate-extensions.h"
+#include "gcr-certificate-field.h"
+#include "gcr-certificate-field-private.h"
 #include "gcr-comparable.h"
+#include "gcr-fingerprint.h"
 #include "gcr-icons.h"
 #include "gcr-internal.h"
 #include "gcr-subject-public-key.h"
@@ -32,6 +35,7 @@
 #include "egg/egg-asn1-defs.h"
 #include "egg/egg-dn.h"
 #include "egg/egg-hex.h"
+#include "egg/egg-oid.h"
 
 #include <string.h>
 #include <glib/gi18n-lib.h>
@@ -1040,6 +1044,456 @@ gcr_certificate_get_basic_constraints (GcrCertificate *self,
        return TRUE;
 }
 
+static void
+append_subject_public_key (GcrCertificate        *self,
+                           GcrCertificateSection *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);
+
+       oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (subject_public_key,
+                                                         "algorithm", "algorithm", NULL));
+       text = egg_oid_get_description (oid);
+       _gcr_certificate_section_append_field (section, _("Key Algorithm"), text, FALSE);
+
+       value = egg_asn1x_get_element_raw (egg_asn1x_node (subject_public_key,
+                                                          "algorithm", "parameters", NULL));
+       if (value) {
+               display = egg_hex_encode_bytes_full (value, TRUE, " ", 1);
+               _gcr_certificate_section_append_field_take_value (section,
+                                                                 _("Key Parameters"),
+                                                                 g_steal_pointer (&display),
+                                                                 TRUE);
+       }
+
+       g_clear_pointer (&value, g_bytes_unref);
+
+       if (key_nbits > 0) {
+               display = g_strdup_printf ("%u", key_nbits);
+               _gcr_certificate_section_append_field_take_value (section,
+                                                                 _("Key Size"),
+                                                                 g_steal_pointer (&display),
+                                                                 FALSE);
+       }
+
+       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_clear_pointer (&value, g_bytes_unref);
+       display = egg_hex_encode_full (raw, n_raw, TRUE, " ", 1);
+       g_clear_pointer (&raw, g_free);
+       _gcr_certificate_section_append_field_take_value (section,
+                                                         _("Key SHA1 Fingerprint"),
+                                                         g_steal_pointer (&display),
+                                                         TRUE);
+
+       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_certificate_section_append_field_take_value (section, _("Public Key"), g_steal_pointer 
(&display), TRUE);
+       g_clear_pointer (&value, g_bytes_unref);
+}
+
+static GcrCertificateSection *
+append_extension_basic_constraints (GBytes *data)
+{
+       GcrCertificateSection *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_certificate_section_new (_("Basic Constraints"), FALSE);
+       _gcr_certificate_section_append_field (section, _("Certificate Authority"), is_ca ? _("Yes") : 
_("No"), FALSE);
+
+       if (path_len < 0)
+               number = g_strdup (_("Unlimited"));
+       else
+               number = g_strdup_printf ("%d", path_len);
+
+       _gcr_certificate_section_append_field_take_value (section, _("Max Path Length"), g_steal_pointer 
(&number), FALSE);
+
+       return section;
+}
+
+static GcrCertificateSection *
+append_extension_extended_key_usage (GBytes *data)
+{
+       GcrCertificateSection *section;
+       GQuark *oids;
+       GString *text;
+       guint i;
+
+       oids = _gcr_certificate_extension_extended_key_usage (data);
+       if (!oids)
+               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_certificate_section_new (_("Extended Key Usage"), FALSE);
+       _gcr_certificate_section_append_field_take_value (section, _("Allowed Purposes"), g_string_free 
(text, FALSE), FALSE);
+
+       return section;
+}
+
+static GcrCertificateSection *
+append_extension_subject_key_identifier (GBytes *data)
+{
+       GcrCertificateSection *section;
+       gpointer keyid;
+       gsize n_keyid;
+
+       keyid = _gcr_certificate_extension_subject_key_identifier (data, &n_keyid);
+       if (!keyid)
+               return NULL;
+
+       section = _gcr_certificate_section_new (_("Subject Key Identifier"), FALSE);
+       gchar *display = egg_hex_encode_full (keyid, n_keyid, TRUE, " ", 1);
+       g_free (keyid);
+       _gcr_certificate_section_append_field_take_value (section, _("Key Identifier"), g_steal_pointer 
(&display), FALSE);
+
+       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 GcrCertificateSection *
+append_extension_key_usage (GBytes *data)
+{
+       GcrCertificateSection *section;
+       gulong key_usage;
+       char* values[G_N_ELEMENTS (usage_descriptions) + 1];
+       guint i, index = 0;
+
+       if (!_gcr_certificate_extension_key_usage (data, &key_usage))
+               return NULL;
+
+       for (i = 0; i < G_N_ELEMENTS (usage_descriptions); i++) {
+               if (key_usage & usage_descriptions[i].usage) {
+                       values[index++] = _(usage_descriptions[i].description);
+               }
+       }
+
+       values[index] = NULL;
+       section = _gcr_certificate_section_new (_("Key Usage"), FALSE);
+       _gcr_certificate_section_append_field_take_values (section, _("Usages"), g_strdupv (values), FALSE);
+
+       return section;
+}
+
+static GcrCertificateSection *
+append_extension_subject_alt_name (GBytes *data)
+{
+       GcrCertificateSection *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_certificate_section_new (_("Subject Alternative Names"), FALSE);
+
+       for (i = 0; i < general_names->len; i++) {
+               general = &g_array_index (general_names, GcrGeneralName, i);
+               if (general->display == NULL) {
+                       gchar *display = egg_hex_encode_bytes_full (general->raw, TRUE, " ", 1);
+                       _gcr_certificate_section_append_field_take_value (section, general->description, 
g_steal_pointer (&display), FALSE);
+               } else
+                       _gcr_certificate_section_append_field (section, general->description, 
general->display, FALSE);
+       }
+
+       _gcr_general_names_free (general_names);
+
+       return section;
+}
+
+static GcrCertificateSection *
+append_extension_hex (GQuark oid,
+                      GBytes *value)
+{
+       GcrCertificateSection *section;
+       const gchar *text;
+       gchar *display;
+
+       section = _gcr_certificate_section_new (_("Extension"), FALSE);
+
+       /* Extension type */
+       text = egg_oid_get_description (oid);
+       _gcr_certificate_section_append_field (section, _("Identifier"), g_strdup (text), FALSE);
+       display = egg_hex_encode_bytes_full (value, TRUE, " ", 1);
+       _gcr_certificate_section_append_field_take_value (section, _("Value"), g_steal_pointer (&display), 
TRUE);
+
+       return section;
+}
+
+static GcrCertificateSection *
+append_extension (GcrCertificate *self,
+                  GNode *node)
+{
+       GQuark oid;
+       GBytes *value;
+       gboolean critical;
+       GcrCertificateSection *section = NULL;
+
+       /* Dig out the OID */
+       oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (node, "extnID", NULL));
+       g_return_val_if_fail (oid, NULL);
+
+       /* 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 (value);
+       else if (oid == GCR_OID_EXTENDED_KEY_USAGE)
+               section = append_extension_extended_key_usage (value);
+       else if (oid == GCR_OID_SUBJECT_KEY_IDENTIFIER)
+               section = append_extension_subject_key_identifier (value);
+       else if (oid == GCR_OID_KEY_USAGE)
+               section = append_extension_key_usage (value);
+       else if (oid == GCR_OID_SUBJECT_ALT_NAME)
+               section = append_extension_subject_alt_name (value);
+
+       /* Otherwise the default raw display */
+       if (!section) {
+               section = append_extension_hex (oid, value);
+       }
+
+       /* Critical */
+       if (section && egg_asn1x_get_boolean (egg_asn1x_node (node, "critical", NULL), &critical)) {
+               _gcr_certificate_section_append_field (section, _("Critical"), critical ? _("Yes") : _("No"), 
FALSE);
+       }
+
+       g_bytes_unref (value);
+       return section;
+}
+
+static void
+on_parsed_dn_part (guint index,
+                   GQuark oid,
+                   GNode *value,
+                   gpointer user_data)
+{
+       GcrCertificateSection *section = user_data;
+       const gchar *attr;
+       const gchar *desc;
+       gchar *label, *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)
+                       label = g_strdup (attr);
+               else
+                       label = g_strdup_printf ("%s (%s)", attr, desc);
+       } else if (!attr && !desc) {
+               label = g_strdup ("");
+       } else if (attr) {
+               label = g_strdup (attr);
+       } else if (desc) {
+               label = g_strdup (desc);
+       } else {
+               g_assert_not_reached ();
+       }
+
+       display = egg_dn_print_value (oid, value);
+       if (!display)
+               display = g_strdup ("");
+
+       _gcr_certificate_section_append_field_take_value (section, label, g_steal_pointer (&display), FALSE);
+       g_clear_pointer (&label, g_free);
+}
+
+/**
+ * gcr_certificate_get_interface_elements:
+ * @self: the #GcrCertificate
+ *
+ * Get the list of sections from the certificate that can be shown to the user
+ * interface.
+ *
+ * Returns: (element-type GcrCertificateSection) (transfer full): A #GList of
+ * #GcrCertificateSection
+ */
+GList *
+gcr_certificate_get_interface_elements (GcrCertificate *self)
+{
+       GcrCertificateSection *section;
+       GList *list = NULL;
+       gchar *display;
+       GBytes *bytes, *number;
+       GNode *asn, *subject_public_key;
+       GQuark oid;
+       gconstpointer data;
+       gsize n_data;
+       GDate date;
+       gulong version;
+       guint bits;
+
+       g_return_val_if_fail (GCR_IS_CERTIFICATE (self), NULL);
+
+       data = gcr_certificate_get_der_data (self, &n_data);
+       g_return_val_if_fail (data != NULL, NULL);
+
+       display = gcr_certificate_get_subject_cn (self);
+       if (!display)
+               display = g_strdup (_("Certificate"));
+
+       section = _gcr_certificate_section_new (display, TRUE);
+       g_clear_pointer (&display, g_free);
+
+       bytes = g_bytes_new_static (data, n_data);
+       asn = egg_asn1x_create_and_decode (pkix_asn1_tab, "Certificate", bytes);
+
+       display = egg_dn_read_part (egg_asn1x_node (asn, "tbsCertificate", "subject", "rdnSequence", NULL), 
"CN");
+       _gcr_certificate_section_append_field_take_value (section, _("Identity"), g_steal_pointer (&display), 
FALSE);
+
+       display = egg_dn_read_part (egg_asn1x_node (asn, "tbsCertificate", "issuer", "rdnSequence", NULL), 
"CN");
+       _gcr_certificate_section_append_field_take_value (section, _("Verified by"), g_steal_pointer 
(&display), FALSE);
+
+       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)) {
+                       _gcr_certificate_section_append_field_take_value (section, _("Expires"), 
g_steal_pointer (&display), FALSE);
+               } else {
+                       g_clear_pointer (&display, g_free);
+               }
+       }
+
+       list = g_list_prepend (list, g_steal_pointer (&section));
+
+       /* The subject */
+       section = _gcr_certificate_section_new (_("Subject Name"), FALSE);
+       egg_dn_parse (egg_asn1x_node (asn, "tbsCertificate", "subject", "rdnSequence", NULL), 
on_parsed_dn_part, section);
+
+       list = g_list_prepend (list, g_steal_pointer (&section));
+
+       /* The Issuer */
+       section = _gcr_certificate_section_new (_("Issuer Name"), FALSE);
+       egg_dn_parse (egg_asn1x_node (asn, "tbsCertificate", "issuer", "rdnSequence", NULL), 
on_parsed_dn_part, section);
+
+       list = g_list_prepend (list, g_steal_pointer (&section));
+
+       /* The Issued Parameters */
+       section = _gcr_certificate_section_new (_("Issued Certificate"), 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_certificate_section_append_field_take_value (section, _("Version"), g_steal_pointer 
(&display), FALSE);
+       }
+
+       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 = egg_hex_encode_bytes_full (number, TRUE, " ", 1);
+               _gcr_certificate_section_append_field_take_value (section, _("Serial Number"), 
g_steal_pointer (&display), TRUE);
+       }
+
+       if (egg_asn1x_get_time_as_date (egg_asn1x_node (asn, "tbsCertificate", "validity", "notBefore", 
NULL), &date)) {
+               display = g_malloc0 (128);
+               if (g_date_strftime (display, 128, "%x", &date))
+                       _gcr_certificate_section_append_field_take_value (section, _("Not Valid Before"), 
g_steal_pointer (&display), FALSE);
+               else
+                       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))
+                       _gcr_certificate_section_append_field_take_value (section, _("Not Valid After"), 
g_steal_pointer (&display), FALSE);
+               else
+                       g_clear_pointer (&display, g_free);
+       }
+
+       list = g_list_prepend (list, g_steal_pointer (&section));
+
+       /* Fingerprints */
+       section = _gcr_certificate_section_new (_("Certificate Fingerprints"), FALSE);
+       display = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, bytes);
+       _gcr_certificate_section_append_field_take_value (section, "SHA1", g_steal_pointer (&display), FALSE);
+       display = g_compute_checksum_for_bytes (G_CHECKSUM_MD5, bytes);
+       _gcr_certificate_section_append_field_take_value (section, "MD5", g_steal_pointer (&display), FALSE);
+       g_clear_pointer (&bytes, g_bytes_unref);
+
+       list = g_list_prepend (list, g_steal_pointer (&section));
+
+       /* Public Key Info */
+       section = _gcr_certificate_section_new (_("Public Key Info"), FALSE);
+       subject_public_key = egg_asn1x_node (asn, "tbsCertificate", "subjectPublicKeyInfo", NULL);
+       append_subject_public_key (self, section, subject_public_key);
+
+       list = g_list_prepend (list, g_steal_pointer (&section));
+
+       /* Extensions */
+       for (guint extension_num = 1; TRUE; ++extension_num) {
+               GNode *extension = egg_asn1x_node (asn, "tbsCertificate", "extensions", extension_num, NULL);
+               if (extension == NULL)
+                       break;
+               section = append_extension (self, extension);
+               if (section)
+                       list = g_list_prepend (list, g_steal_pointer (&section));
+       }
+
+       /* Signature */
+       section = _gcr_certificate_section_new (_("Signature"), FALSE);
+
+       oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (asn, "signatureAlgorithm", "algorithm", NULL));
+       _gcr_certificate_section_append_field (section, _("Signature Algorithm"), egg_oid_get_description 
(oid), FALSE);
+
+       bytes = egg_asn1x_get_element_raw (egg_asn1x_node (asn, "signatureAlgorithm", "parameters", NULL));
+       if (bytes) {
+               display = egg_hex_encode_bytes_full (bytes, TRUE, " ", 1);
+               _gcr_certificate_section_append_field_take_value (section, _("Signature Parameters"), 
g_steal_pointer (&display), TRUE);
+       }
+
+       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_clear_pointer (&bytes, g_bytes_unref);
+       _gcr_certificate_section_append_field_take_value (section, _("Signature"), g_steal_pointer 
(&display), TRUE);
+
+       list = g_list_prepend (list, g_steal_pointer (&section));
+
+       egg_asn1x_destroy (asn);
+       return g_list_reverse (list);
+}
+
 /* -----------------------------------------------------------------------------
  * MIXIN
  */
diff --git a/gcr/gcr-certificate.h b/gcr/gcr-certificate.h
index 12a12752..aa2aca43 100644
--- a/gcr/gcr-certificate.h
+++ b/gcr/gcr-certificate.h
@@ -132,6 +132,8 @@ gboolean            gcr_certificate_get_basic_constraints  (GcrCertificate *self
                                                             gboolean *is_ca,
                                                             gint *path_len);
 
+GList*              gcr_certificate_get_interface_elements (GcrCertificate *self);
+
 #define GCR_CERTIFICATE_MIXIN_IMPLEMENT_COMPARABLE() \
        G_IMPLEMENT_INTERFACE (GCR_TYPE_COMPARABLE, gcr_certificate_mixin_comparable_init)
 
diff --git a/gcr/gcr.h b/gcr/gcr.h
index aaa2982d..3bd8b3ae 100644
--- a/gcr/gcr.h
+++ b/gcr/gcr.h
@@ -34,6 +34,7 @@
 
 #include <gcr/gcr-certificate.h>
 #include <gcr/gcr-certificate-chain.h>
+#include <gcr/gcr-certificate-field.h>
 #include <gcr/gcr-certificate-request.h>
 #include <gcr/gcr-column.h>
 #include <gcr/gcr-enum-types.h>
diff --git a/gcr/meson.build b/gcr/meson.build
index 88428eed..95819fe5 100644
--- a/gcr/meson.build
+++ b/gcr/meson.build
@@ -4,6 +4,7 @@ gcr_headers_install_dir = gcr_headers_subdir / 'gcr'
 gcr_public_sources = files(
   'gcr-certificate.c',
   'gcr-certificate-chain.c',
+  'gcr-certificate-field.c',
   'gcr-certificate-request.c',
   'gcr-comparable.c',
   'gcr-fingerprint.c',
@@ -47,6 +48,7 @@ gcr_headers = files(
   'gcr.h',
   'gcr-certificate.h',
   'gcr-certificate-chain.h',
+  'gcr-certificate-field.h',
   'gcr-certificate-request.h',
   'gcr-column.h',
   'gcr-comparable.h',
diff --git a/gcr/test-certificate.c b/gcr/test-certificate.c
index 2fed913a..95b6b9d6 100644
--- a/gcr/test-certificate.c
+++ b/gcr/test-certificate.c
@@ -266,6 +266,37 @@ test_basic_constraints (Test *test,
        g_assert (path_len == -1);
 }
 
+static void
+test_interface_elements (Test *test,
+                         gconstpointer unused)
+{
+       GList* sections = gcr_certificate_get_interface_elements (test->dsa_cert);
+       for (GList *l = sections; l != NULL; l = l->next) {
+               GcrCertificateSection *section = l->data;
+               gboolean important = gcr_certificate_section_get_important (section);
+               g_assert (important == TRUE || important == FALSE);
+               g_assert (gcr_certificate_section_get_label (section) != NULL);
+               GListModel *fields = gcr_certificate_section_get_fields (section);
+               g_assert (fields != NULL);
+               g_assert (g_list_model_get_item_type (fields) == GCR_TYPE_CERTIFICATE_FIELD);
+               for (guint i = 0; i < g_list_model_get_n_items (fields); i++) {
+                       GcrCertificateField *field = g_list_model_get_item (fields, i);
+                       g_assert (gcr_certificate_field_get_label (field) != NULL);
+                       gboolean single = gcr_certificate_field_is_single (field);
+                       g_assert (single == TRUE || single == FALSE);
+                       if (single)
+                               g_assert (gcr_certificate_field_get_value (field) != NULL);
+                       else
+                               g_assert (gcr_certificate_field_get_values (field) != NULL);
+                       gboolean bytes = gcr_certificate_field_is_bytes (field);
+                       g_assert (bytes == TRUE || bytes == FALSE);
+                       g_assert (gcr_certificate_field_get_section (field) == section);
+               }
+       }
+
+       g_list_free_full (sections, (GDestroyNotify) gcr_certificate_section_unref);
+}
+
 static void
 test_subject_alt_name (void)
 {
@@ -346,6 +377,7 @@ main (int argc, char **argv)
        g_test_add ("/gcr/certificate/key_size", Test, NULL, setup, test_certificate_key_size, teardown);
        g_test_add ("/gcr/certificate/is_issuer", Test, NULL, setup, test_certificate_is_issuer, teardown);
        g_test_add ("/gcr/certificate/basic_constraints", Test, NULL, setup, test_basic_constraints, 
teardown);
+       g_test_add ("/gcr/certificate/interface_elements", Test, NULL, setup, test_interface_elements, 
teardown);
        g_test_add_func ("/gcr/certificate/subject_alt_name", test_subject_alt_name);
        g_test_add_func ("/gcr/certificate/key_usage", test_key_usage);
 


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