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




commit 052dbecf474ff1853a3a617e0f1780a72715c26a
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   | 531 ++---------------------------------
 gcr-gtk3/gcr-section.c              | 207 +++++++++-----
 gcr-gtk3/gcr-section.h              |   7 +-
 gcr-gtk4/gcr-certificate-widget.c   | 532 ++----------------------------------
 gcr-gtk4/gcr-section.c              | 205 +++++++++-----
 gcr-gtk4/gcr-section.h              |   7 +-
 gcr/gcr-certificate-field-private.h |  75 +++++
 gcr/gcr-certificate-field.c         | 261 ++++++++++++++++++
 gcr/gcr-certificate-field.h         |  33 +++
 gcr/gcr-certificate-section.c       | 211 ++++++++++++++
 gcr/gcr-certificate-section.h       |  35 +++
 gcr/gcr-certificate.c               | 436 +++++++++++++++++++++++++++++
 gcr/gcr-certificate.h               |   2 +
 gcr/gcr.h                           |   2 +
 gcr/meson.build                     |   4 +
 gcr/test-certificate.c              |  33 +++
 meson.build                         |   2 +-
 19 files changed, 1458 insertions(+), 1159 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 cf995d58..0ea05f8f 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,374 +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,
-                                    gboolean              important)
-{
-       GtkWidget *widget;
-
-       g_assert (GCR_IS_CERTIFICATE_WIDGET (self));
-
-       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"), 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"), 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"), 
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"), 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"), 
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"), 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
@@ -532,144 +166,35 @@ gcr_certificate_widget_get_certificate (GcrCertificateWidget *self)
 void
 gcr_certificate_widget_set_certificate (GcrCertificateWidget *self, GcrCertificate *certificate)
 {
-       GtkWidget *section, *label;
-       PangoAttrList *attributes;
-       gchar *display;
-       GBytes *bytes, *number;
-       GNode *asn, *subject_public_key;
-       GQuark oid;
-       gconstpointer data;
-       gsize n_data;
-       GDateTime *datetime;
-       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);
-       }
-
-       display = calculate_label (self);
-       section = _gcr_certificate_widget_add_section (self, 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);
-       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);
-
-       datetime = egg_asn1x_get_time_as_date_time (egg_asn1x_node (asn, "tbsCertificate", "validity", 
"notAfter", NULL));
-       if (datetime) {
-               display = g_date_time_format (datetime, "%x");
-               g_return_if_fail (display != NULL);
-               gcr_section_add_child (GCR_SECTION (section), _("Expires"), create_value_label (display));
-               g_clear_pointer (&display, g_free);
-               g_clear_pointer (&datetime, g_date_time_unref);
-       }
-
-       /* The subject */
-       section = _gcr_certificate_widget_add_section (self, _("Subject Name"), 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"), 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"), FALSE);
-
-       if (!egg_asn1x_get_integer_as_ulong (egg_asn1x_node (asn, "tbsCertificate", "version", NULL), 
&version)) {
-               g_critical ("Unable to parse certificate version");
-       } else {
-               display = g_strdup_printf ("%lu", version + 1);
-               gcr_section_add_child (GCR_SECTION (section), _("Version"), create_value_label (display));
-               g_clear_pointer (&display, g_free);
-       }
-
-       number = egg_asn1x_get_integer_as_raw (egg_asn1x_node (asn, "tbsCertificate", "serialNumber", NULL));
-       if (!number) {
-               g_critical ("Unable to parse certificate serial number");
-       } else {
-               display = hex_encode_bytes (number);
-               gcr_section_add_child (GCR_SECTION (section), _("Serial Number"), create_value_label 
(display));
-               g_clear_pointer (&display, g_free);
-               g_bytes_unref (number);
-       }
-
-       datetime = egg_asn1x_get_time_as_date_time (egg_asn1x_node (asn, "tbsCertificate", "validity", 
"notBefore", NULL));
-       if (datetime) {
-               display = g_date_time_format (datetime, "%x");
-               g_return_if_fail (display != NULL);
-               gcr_section_add_child (GCR_SECTION (section), _("Not Valid Before"), create_value_label 
(display));
-               g_clear_pointer (&display, g_free);
-               g_clear_pointer (&datetime, g_date_time_unref);
-       }
-       datetime = egg_asn1x_get_time_as_date_time (egg_asn1x_node (asn, "tbsCertificate", "validity", 
"notAfter", NULL));
-       if (datetime) {
-               display = g_date_time_format (datetime, "%x");
-               g_return_if_fail (display != NULL);
-               gcr_section_add_child (GCR_SECTION (section), _("Not Valid After"), create_value_label 
(display));
-               g_clear_pointer (&display, g_free);
-               g_clear_pointer (&datetime, g_date_time_unref);
+       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));
        }
 
-       /* Fingerprints */
-       section = _gcr_certificate_widget_add_section (self, _("Certificate Fingerprints"), 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);
+       g_list_free (children);
 
-       /* Public Key Info */
-       section = _gcr_certificate_widget_add_section (self, _("Public Key Info"), 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"), 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_flags (section) & GCR_CERTIFICATE_SECTION_IMPORTANT)
+                       gtk_box_pack_start (GTK_BOX (self->primary_info), widget, FALSE, FALSE, 0);
+               else
+                       gtk_box_pack_start (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) g_object_unref);
 }
diff --git a/gcr-gtk3/gcr-section.c b/gcr-gtk3/gcr-section.c
index bc6373ef..6b2ce0fb 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,21 +19,118 @@ struct _GcrSection
 G_DEFINE_TYPE (GcrSection, gcr_section, GTK_TYPE_GRID)
 
 enum {
-       PROP_TITLE = 1,
+       PROP_SECTION = 1,
        N_PROPERTIES
 };
 
 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
 
+static char*
+bytes_to_display (GBytes *bytes)
+{
+       const char *hexc = "0123456789ABCDEF";
+       GString *result;
+       const char *input;
+       gsize read_bytes;
+       gsize remaining_bytes;
+       guchar j;
+
+       g_return_val_if_fail (bytes != NULL, NULL);
+
+       input = g_bytes_get_data (bytes, &remaining_bytes);
+       result = g_string_sized_new (g_bytes_get_size (bytes) * 2 + 1);
+
+       for (read_bytes = 0; read_bytes < remaining_bytes; read_bytes++) {
+               if (read_bytes && (read_bytes % 1) == 0)
+                       g_string_append_c (result, ' ');
+
+               j = *(input) >> 4 & 0xf;
+               g_string_append_c (result, hexc[j]);
+
+               j = *(input++) & 0xf;
+               g_string_append_c (result, hexc[j]);
+       }
+
+       return g_string_free (result, FALSE);
+}
+
+GtkWidget *
+gcr_section_create_row (GObject *item,
+                        gpointer user_data)
+{
+       GcrCertificateField *field = (GcrCertificateField *) item;
+       GtkSizeGroup *size_group = user_data;
+       GtkWidget *row, *box, *label, *value;
+       GValue val = G_VALUE_INIT;
+       GType value_type;
+
+       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,
+                            "xalign", 1.0,
+                            "halign", GTK_ALIGN_END,
+                            "hexpand", TRUE,
+                            "selectable", TRUE,
+                            "wrap", TRUE,
+                            NULL);
+
+       value_type = gcr_certificate_field_get_value_type (field);
+       g_value_init (&val, value_type);
+       gcr_certificate_field_get_value (field, &val);
+       if (g_type_is_a (value_type, G_TYPE_STRING)) {
+               g_object_set (G_OBJECT (value), "label", g_value_get_string (&val), NULL);
+       } else if (g_type_is_a (value_type, G_TYPE_STRV)) {
+               char *lbl = g_strjoinv ("\n", (GStrv) g_value_get_boxed (&val));
+               g_object_set (G_OBJECT (value), "label", lbl, NULL);
+               g_free (lbl);
+       } else if (g_type_is_a (value_type, G_TYPE_BYTES)) {
+               PangoAttrList *attributes;
+               GBytes *bytes = g_value_get_boxed (&val);
+               char *lbl = bytes_to_display (bytes);
+               g_object_set (G_OBJECT (value), "label", lbl, NULL);
+               g_free (lbl);
+               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_value_unset (&val);
+
+       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_object (&self->section);
 
        G_OBJECT_CLASS (gcr_section_parent_class)->dispose (object);
 }
@@ -47,10 +143,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)));
+       switch (prop_id) {
+       case PROP_SECTION:
+               g_value_set_object (value, self->section);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -65,10 +160,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));
+       switch (prop_id) {
+       case PROP_SECTION:
+               g_assert (!self->section);
+               self->section = g_value_dup_object (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);
@@ -83,11 +183,11 @@ 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,
+       obj_properties[PROP_SECTION] =
+               g_param_spec_object ("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,
@@ -101,72 +201,33 @@ 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)
+gcr_section_new (GcrCertificateSection *section)
 {
-       return g_object_new (GCR_TYPE_SECTION, "title", title, 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_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 359716d5..55443ccc 100644
--- a/gcr-gtk3/gcr-section.h
+++ b/gcr-gtk3/gcr-section.h
@@ -10,17 +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);
-
-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 0acdc7b2..ce9e9490 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,373 +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,
-                                    gboolean              important)
-{
-       GtkWidget *widget;
-
-       g_assert (GCR_IS_CERTIFICATE_WIDGET (self));
-
-       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"), 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"), 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"), 
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"), 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"), 
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"), 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
@@ -539,144 +171,38 @@ gcr_certificate_widget_get_certificate (GcrCertificateWidget *self)
 void
 gcr_certificate_widget_set_certificate (GcrCertificateWidget *self, GcrCertificate *certificate)
 {
-       GtkWidget *section, *label;
-       PangoAttrList *attributes;
-       gchar *display;
-       GBytes *bytes, *number;
-       GNode *asn, *subject_public_key;
-       GQuark oid;
-       gconstpointer data;
-       gsize n_data;
-       GDateTime *datetime;
-       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);
+       if (!g_set_object (&self->certificate, certificate)) {
+               return;
        }
 
-       display = calculate_label (self);
-       section = _gcr_certificate_widget_add_section (self, 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);
-       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);
-
-       datetime = egg_asn1x_get_time_as_date_time (egg_asn1x_node (asn, "tbsCertificate", "validity", 
"notAfter", NULL));
-       if (datetime) {
-               display = g_date_time_format (datetime, "%x");
-               g_return_if_fail (display != NULL);
-               gcr_section_add_child (GCR_SECTION (section), _("Expires"), create_value_label (display));
-               g_clear_pointer (&display, g_free);
-               g_clear_pointer (&datetime, g_date_time_unref);
-       }
-
-       /* The subject */
-       section = _gcr_certificate_widget_add_section (self, _("Subject Name"), 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"), 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"), FALSE);
-
-       if (!egg_asn1x_get_integer_as_ulong (egg_asn1x_node (asn, "tbsCertificate", "version", NULL), 
&version)) {
-               g_critical ("Unable to parse certificate version");
-       } else {
-               display = g_strdup_printf ("%lu", version + 1);
-               gcr_section_add_child (GCR_SECTION (section), _("Version"), create_value_label (display));
-               g_clear_pointer (&display, g_free);
-       }
-
-       number = egg_asn1x_get_integer_as_raw (egg_asn1x_node (asn, "tbsCertificate", "serialNumber", NULL));
-       if (!number) {
-               g_critical ("Unable to parse certificate serial number");
-       } else {
-               display = hex_encode_bytes (number);
-               gcr_section_add_child (GCR_SECTION (section), _("Serial Number"), create_value_label 
(display));
-               g_clear_pointer (&display, g_free);
-               g_bytes_unref (number);
+       while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->secondary_info)))) {
+               gtk_widget_unparent (child);
        }
 
-       datetime = egg_asn1x_get_time_as_date_time (egg_asn1x_node (asn, "tbsCertificate", "validity", 
"notBefore", NULL));
-       if (datetime) {
-               display = g_date_time_format (datetime, "%x");
-               g_return_if_fail (display != NULL);
-               gcr_section_add_child (GCR_SECTION (section), _("Not Valid Before"), create_value_label 
(display));
-               g_clear_pointer (&display, g_free);
-               g_clear_pointer (&datetime, g_date_time_unref);
-       }
-       datetime = egg_asn1x_get_time_as_date_time (egg_asn1x_node (asn, "tbsCertificate", "validity", 
"notAfter", NULL));
-       if (datetime) {
-               display = g_date_time_format (datetime, "%x");
-               g_return_if_fail (display != NULL);
-               gcr_section_add_child (GCR_SECTION (section), _("Not Valid After"), create_value_label 
(display));
-               g_clear_pointer (&display, g_free);
-               g_clear_pointer (&datetime, g_date_time_unref);
+       while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->primary_info)))) {
+               gtk_widget_unparent (child);
        }
 
-       /* Fingerprints */
-       section = _gcr_certificate_widget_add_section (self, _("Certificate Fingerprints"), 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"), 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"), FALSE);
-
-       oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (asn, "signatureAlgorithm", "algorithm", NULL));
-       gcr_section_add_child (GCR_SECTION (section), _("Signature Algorithm"), create_value_label 
(egg_oid_get_description (oid)));
+       elements = gcr_certificate_get_interface_elements (certificate);
+       for (GList *l = elements; l != NULL; l = l->next) {
+               GcrCertificateSection *section = l->data;
+               GtkWidget *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_flags (section) & GCR_CERTIFICATE_SECTION_IMPORTANT)
+                       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) g_object_unref);
 }
diff --git a/gcr-gtk4/gcr-section.c b/gcr-gtk4/gcr-section.c
index ecd8a9cc..52d83263 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,21 +19,120 @@ struct _GcrSection
 G_DEFINE_TYPE (GcrSection, gcr_section, GTK_TYPE_WIDGET)
 
 enum {
-       PROP_TITLE = 1,
+       PROP_SECTION = 1,
        N_PROPERTIES
 };
 
 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
 
+static char*
+bytes_to_display (GBytes *bytes)
+{
+       const char *hexc = "0123456789ABCDEF";
+       GString *result;
+       const char *input;
+       gsize read_bytes;
+       gsize remaining_bytes;
+       guchar j;
+
+       g_return_val_if_fail (bytes != NULL, NULL);
+
+       input = g_bytes_get_data (bytes, &remaining_bytes);
+       result = g_string_sized_new (g_bytes_get_size (bytes) * 2 + 1);
+
+       for (read_bytes = 0; read_bytes < remaining_bytes; read_bytes++) {
+               if (read_bytes && (read_bytes % 1) == 0)
+                       g_string_append_c (result, ' ');
+
+               j = *(input) >> 4 & 0xf;
+               g_string_append_c (result, hexc[j]);
+
+               j = *(input++) & 0xf;
+               g_string_append_c (result, hexc[j]);
+       }
+
+       return g_string_free (result, FALSE);
+}
+
+GtkWidget *
+gcr_section_create_row (GObject *item,
+                        gpointer user_data)
+{
+       GcrCertificateField *field = (GcrCertificateField *) item;
+       GtkSizeGroup *size_group = user_data;
+       GtkWidget *row, *box, *label, *value;
+       GValue val = G_VALUE_INIT;
+       GType value_type;
+
+       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);
+       value_type = gcr_certificate_field_get_value_type (field);
+       g_value_init (&val, value_type);
+       gcr_certificate_field_get_value (field, &val);
+       if (g_type_is_a (value_type, G_TYPE_STRING)) {
+               g_object_set (G_OBJECT (value), "label", g_value_get_string (&val), NULL);
+       } else if (g_type_is_a (value_type, G_TYPE_STRV)) {
+               char *lbl = g_strjoinv ("\n", (GStrv) g_value_get_boxed (&val));
+               g_object_set (G_OBJECT (value), "label", lbl, NULL);
+               g_free (lbl);
+       } else if (g_type_is_a (value_type, G_TYPE_BYTES)) {
+               PangoAttrList *attributes;
+               GBytes *bytes = g_value_get_boxed (&val);
+               char *lbl = bytes_to_display (bytes);
+               g_object_set (G_OBJECT (value), "label", lbl, NULL);
+               g_free (lbl);
+               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_value_unset (&val);
+
+       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_object (&self->section);
 
        G_OBJECT_CLASS (gcr_section_parent_class)->dispose (object);
 }
@@ -49,8 +147,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)));
+       case PROP_SECTION:
+               g_value_set_object (value, self->section);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -67,8 +165,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));
+       case PROP_SECTION:
+               g_assert (!self->section);
+               self->section = g_value_dup_object (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);
@@ -84,11 +188,11 @@ 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,
+       obj_properties[PROP_SECTION] =
+               g_param_spec_object ("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,
@@ -106,76 +210,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);
-}
-
-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_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 0bc7c270..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,11 +18,7 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (GcrSection, gcr_section, GCR, SECTION, GtkWidget)
 
-GtkWidget *gcr_section_new (const gchar *title);
-
-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..bfd5bcf6
--- /dev/null
+++ b/gcr/gcr-certificate-field-private.h
@@ -0,0 +1,75 @@
+/*
+ * 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,
+                                                              GcrCertificateField   *field);
+GcrCertificateField   *_gcr_certificate_field_new_take_value (GcrCertificateSection *section,
+                                                              const char            *label,
+                                                              char                  *value);
+GcrCertificateField   *_gcr_certificate_field_new_take_values (GcrCertificateSection *section,
+                                                               const char            *label,
+                                                               GStrv                  value);
+GcrCertificateField   *_gcr_certificate_field_new_take_bytes (GcrCertificateSection *section,
+                                                              const char            *label,
+                                                              GBytes                *bytes);
+
+static inline void
+_gcr_certificate_section_new_field_take_value (GcrCertificateSection *section,
+                                               const char            *label,
+                                               char                  *value)
+{
+       GcrCertificateField *field = _gcr_certificate_field_new_take_value (section, label, value);
+       _gcr_certificate_section_append_field (section, field);
+       g_object_unref (field);
+}
+
+static inline void
+_gcr_certificate_section_new_field_take_values (GcrCertificateSection *section,
+                                                const char            *label,
+                                                GStrv                  values)
+{
+       GcrCertificateField *field = _gcr_certificate_field_new_take_values (section, label, values);
+       _gcr_certificate_section_append_field (section, field);
+       g_object_unref (field);
+}
+
+static inline void
+_gcr_certificate_section_new_field_take_bytes (GcrCertificateSection *section,
+                                               const char            *label,
+                                               GBytes                *bytes)
+{
+       GcrCertificateField *field = _gcr_certificate_field_new_take_bytes (section, label, bytes);
+       _gcr_certificate_section_append_field (section, field);
+       g_object_unref (field);
+}
+
+static inline void
+_gcr_certificate_section_new_field (GcrCertificateSection *section,
+                                    const char            *label,
+                                    const char            *value)
+{
+       _gcr_certificate_section_new_field_take_value (section, label, g_strdup (value));
+}
+
+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..3869e199
--- /dev/null
+++ b/gcr/gcr-certificate-field.c
@@ -0,0 +1,261 @@
+/*
+ * 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"
+#include "gcr-enum-types.h"
+
+struct _GcrCertificateField
+{
+       GObject parent_instance;
+
+       char *label;
+       GValue value;
+       GcrCertificateSection *section;
+};
+
+G_DEFINE_FINAL_TYPE (GcrCertificateField, gcr_certificate_field, G_TYPE_OBJECT)
+
+enum {
+       PROP_LABEL = 1,
+       PROP_VALUE,
+       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);
+       g_value_unset (&self->value);
+
+       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_boxed (value, &self->value);
+               break;
+       case PROP_SECTION:
+               g_value_set_object (value, self->section);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gcr_certificate_field_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+       GcrCertificateField *self = GCR_CERTIFICATE_FIELD (object);
+
+       switch (prop_id) {
+       case PROP_LABEL:
+               self->label = g_value_dup_string (value);
+               break;
+       case PROP_SECTION:
+               self->section = g_value_get_object (value);
+               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;
+       object_class->set_property = gcr_certificate_field_set_property;
+
+       obj_properties[PROP_LABEL] =
+               g_param_spec_string ("label",
+                                    "Label",
+                                    "Display name of the field.",
+                                    NULL,
+                                    G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+       obj_properties[PROP_VALUE] =
+               g_param_spec_boxed ("value",
+                                   "Value",
+                                   "Display name of the value.",
+                                   G_TYPE_VALUE,
+                                   G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+
+       obj_properties[PROP_SECTION] =
+               g_param_spec_object ("section",
+                                    "Section",
+                                    "The section it is included.",
+                                    GCR_TYPE_CERTIFICATE_SECTION,
+                                    G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+       g_object_class_install_properties (object_class,
+                                          N_PROPERTIES,
+                                          obj_properties);
+}
+
+static void
+gcr_certificate_field_init (GcrCertificateField *self)
+{
+}
+
+/**
+ * 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
+ * @value: (out caller-allocates): the `GValue` to fill
+ *
+ * Get the value of the field.
+ *
+ * The @value will have been initialized to the `GType` the value should be
+ * provided in.
+ *
+ * Returns: %TRUE if the value was set successfully.
+ */
+gboolean
+gcr_certificate_field_get_value (GcrCertificateField *self,
+                                 GValue              *value)
+{
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_FIELD (self), FALSE);
+       g_return_val_if_fail (G_IS_VALUE (value), FALSE);
+
+       if (G_VALUE_HOLDS (&self->value, G_VALUE_TYPE (value))) {
+               g_value_copy (&self->value, value);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+/**
+ * gcr_certificate_field_get_value_type:
+ * @self: the #GcrCertificateField
+ *
+ * Get the type associated with the value.
+ *
+ * Returns: The `GType` of the value
+ */
+GType
+gcr_certificate_field_get_value_type (GcrCertificateField *self)
+{
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_FIELD (self), FALSE);
+
+       return G_VALUE_TYPE (&self->value);
+}
+
+/**
+ * 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;
+}
+
+GcrCertificateField *
+_gcr_certificate_field_new_take_value (GcrCertificateSection *section,
+                                       const char            *label,
+                                       char                  *value)
+{
+       GcrCertificateField *self;
+
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_SECTION (section), NULL);
+       g_return_val_if_fail (label != NULL, NULL);
+       g_return_val_if_fail (value != NULL, NULL);
+
+       self = g_object_new (GCR_TYPE_CERTIFICATE_FIELD,
+                            "section", section,
+                            "label", label,
+                            NULL);
+       g_value_init (&self->value, G_TYPE_STRING);
+       g_value_take_string (&self->value, value);
+
+       return self;
+}
+
+GcrCertificateField *
+_gcr_certificate_field_new_take_values (GcrCertificateSection *section,
+                                        const char            *label,
+                                        GStrv                  values)
+{
+       GcrCertificateField *self;
+
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_SECTION (section), NULL);
+       g_return_val_if_fail (label != NULL, NULL);
+       g_return_val_if_fail (values != NULL, NULL);
+
+       self = g_object_new (GCR_TYPE_CERTIFICATE_FIELD,
+                            "section", section,
+                            "label", label,
+                            NULL);
+       g_value_init (&self->value, G_TYPE_STRV);
+       g_value_take_boxed (&self->value, values);
+
+       return self;
+}
+
+GcrCertificateField *
+_gcr_certificate_field_new_take_bytes (GcrCertificateSection *section,
+                                       const char            *label,
+                                       GBytes                *bytes)
+{
+       GcrCertificateField *self;
+
+       g_return_val_if_fail (GCR_IS_CERTIFICATE_SECTION (section), NULL);
+       g_return_val_if_fail (label != NULL, NULL);
+       g_return_val_if_fail (bytes != NULL, NULL);
+
+       self = g_object_new (GCR_TYPE_CERTIFICATE_FIELD,
+                            "section", section,
+                            "label", label,
+                            NULL);
+       g_value_init (&self->value, G_TYPE_BYTES);
+       g_value_take_boxed (&self->value, bytes);
+
+       return self;
+}
diff --git a/gcr/gcr-certificate-field.h b/gcr/gcr-certificate-field.h
new file mode 100644
index 00000000..946ccdc5
--- /dev/null
+++ b/gcr/gcr-certificate-field.h
@@ -0,0 +1,33 @@
+/*
+ * 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 <gcr/gcr-certificate-section.h>
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#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);
+gboolean                  gcr_certificate_field_get_value      (GcrCertificateField *self,
+                                                                GValue              *value);
+GType                     gcr_certificate_field_get_value_type (GcrCertificateField *self);
+GcrCertificateSection    *gcr_certificate_field_get_section    (GcrCertificateField *self);
+
+G_END_DECLS
+
+#endif /* __GCR_CERTIFICATE_FIELD_H__ */
diff --git a/gcr/gcr-certificate-section.c b/gcr/gcr-certificate-section.c
new file mode 100644
index 00000000..4741ca67
--- /dev/null
+++ b/gcr/gcr-certificate-section.c
@@ -0,0 +1,211 @@
+/*
+ * 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-section.h"
+#include "gcr-enum-types.h"
+
+struct _GcrCertificateSection {
+       GObject parent_instance;
+
+       char *label;
+       GcrCertificateSectionFlags flags;
+       GListStore *fields;
+};
+
+G_DEFINE_FINAL_TYPE (GcrCertificateSection, gcr_certificate_section, G_TYPE_OBJECT)
+
+enum {
+       PROP_LABEL = 1,
+       PROP_FIELDS,
+       PROP_FLAGS,
+       N_PROPERTIES
+};
+
+static GParamSpec *obj_properties [N_PROPERTIES];
+
+
+/**
+ * _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)
+{
+       return g_object_new (GCR_TYPE_CERTIFICATE_SECTION,
+                            "label", label,
+                            "flags", important ? GCR_CERTIFICATE_SECTION_IMPORTANT : 
GCR_CERTIFICATE_SECTION_NONE,
+                            NULL);
+}
+
+static void
+gcr_certificate_section_finalize (GObject *object)
+{
+       GcrCertificateSection *self = (GcrCertificateSection *)object;
+       g_clear_pointer (&self->label, g_free);
+
+       G_OBJECT_CLASS (gcr_certificate_section_parent_class)->finalize (object);
+}
+
+static void
+gcr_certificate_section_dispose (GObject *object)
+{
+       GcrCertificateSection *self = (GcrCertificateSection *)object;
+       g_clear_object (&self->fields);
+
+       G_OBJECT_CLASS (gcr_certificate_section_parent_class)->dispose (object);
+}
+
+static void
+gcr_certificate_section_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+       GcrCertificateSection *self = GCR_CERTIFICATE_SECTION (object);
+
+       switch (prop_id) {
+       case PROP_LABEL:
+               g_value_set_string (value, self->label);
+               break;
+       case PROP_FIELDS:
+               g_value_set_object (value, &self->fields);
+               break;
+       case PROP_FLAGS:
+               g_value_set_flags (value, self->flags);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gcr_certificate_section_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+       GcrCertificateSection *self = GCR_CERTIFICATE_SECTION (object);
+
+       switch (prop_id) {
+       case PROP_LABEL:
+               self->label = g_value_dup_string (value);
+               break;
+       case PROP_FLAGS:
+               self->flags = g_value_get_flags (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gcr_certificate_section_class_init (GcrCertificateSectionClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = gcr_certificate_section_finalize;
+       object_class->dispose = gcr_certificate_section_dispose;
+       object_class->get_property = gcr_certificate_section_get_property;
+       object_class->set_property = gcr_certificate_section_set_property;
+
+       obj_properties[PROP_LABEL] =
+               g_param_spec_string ("label",
+                                    "Label",
+                                    "Display name of the field.",
+                                    NULL,
+                                    G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+       obj_properties[PROP_FIELDS] =
+               g_param_spec_object ("fields",
+                                    "Fields",
+                                    "The list of fields.",
+                                    G_TYPE_LIST_MODEL,
+                                    G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
+
+       obj_properties[PROP_FLAGS] =
+               g_param_spec_flags ("flags",
+                                   "Flags",
+                                   "Flags defined for the section.",
+                                   GCR_TYPE_CERTIFICATE_SECTION_FLAGS,
+                                   GCR_CERTIFICATE_SECTION_NONE,
+                                   G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+       g_object_class_install_properties (object_class,
+                                          N_PROPERTIES,
+                                          obj_properties);
+}
+
+static void
+gcr_certificate_section_init (GcrCertificateSection *self)
+{
+       self->fields = g_list_store_new (GCR_TYPE_CERTIFICATE_FIELD);
+}
+
+/**
+ * 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_flags:
+ * @self: the #GcrCertificateSection
+ *
+ * Get the flags.
+ *
+ * Returns: the `GcrCertificateSectionFlags`
+ */
+GcrCertificateSectionFlags
+gcr_certificate_section_get_flags (GcrCertificateSection *self)
+{
+       g_return_val_if_fail (self != NULL, FALSE);
+
+       return self->flags;
+}
+
+/**
+ * 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);
+}
+
+void
+_gcr_certificate_section_append_field (GcrCertificateSection *section,
+                                       GcrCertificateField   *field)
+{
+       g_return_if_fail (GCR_IS_CERTIFICATE_SECTION (section));
+       g_return_if_fail (GCR_IS_CERTIFICATE_FIELD (field));
+
+       g_list_store_append (section->fields, field);
+}
+
diff --git a/gcr/gcr-certificate-section.h b/gcr/gcr-certificate-section.h
new file mode 100644
index 00000000..85068970
--- /dev/null
+++ b/gcr/gcr-certificate-section.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 Collabora Ltd.
+ * Copyright Corentin Noël <corentin noel collabora com>
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GCR_CERTIFICATE_SECTION_H__
+#define __GCR_CERTIFICATE_SECTION_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())
+
+G_DECLARE_FINAL_TYPE (GcrCertificateSection, gcr_certificate_section, GCR, CERTIFICATE_SECTION, GObject)
+
+typedef enum {
+       GCR_CERTIFICATE_SECTION_NONE = 0,
+       GCR_CERTIFICATE_SECTION_IMPORTANT = 1 << 0,
+} GcrCertificateSectionFlags;
+
+const char                 *gcr_certificate_section_get_label  (GcrCertificateSection *self);
+GListModel                 *gcr_certificate_section_get_fields (GcrCertificateSection *self);
+GcrCertificateSectionFlags  gcr_certificate_section_get_flags  (GcrCertificateSection *self);
+
+G_END_DECLS
+
+#endif /* __GCR_CERTIFICATE_SECTION_H__ */
diff --git a/gcr/gcr-certificate.c b/gcr/gcr-certificate.c
index fc2d9e09..97740c2f 100644
--- a/gcr/gcr-certificate.c
+++ b/gcr/gcr-certificate.c
@@ -21,6 +21,9 @@
 
 #include "gcr-certificate.h"
 #include "gcr-certificate-extensions.h"
+#include "gcr-certificate-field.h"
+#include "gcr-certificate-field-private.h"
+#include "gcr-fingerprint.h"
 #include "gcr-internal.h"
 #include "gcr-subject-public-key.h"
 
@@ -30,6 +33,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>
@@ -856,6 +860,438 @@ 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_new_field (section, _("Key Algorithm"), text);
+
+       value = egg_asn1x_get_element_raw (egg_asn1x_node (subject_public_key,
+                                                          "algorithm", "parameters", NULL));
+       if (value) {
+               _gcr_certificate_section_new_field_take_bytes (section,
+                                                              _("Key Parameters"),
+                                                              g_steal_pointer (&value));
+       }
+
+       if (key_nbits > 0) {
+               display = g_strdup_printf ("%u", key_nbits);
+               _gcr_certificate_section_new_field_take_value (section,
+                                                              _("Key Size"),
+                                                              g_steal_pointer (&display));
+       }
+
+       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);
+       _gcr_certificate_section_new_field_take_bytes (section,
+                                                      _("Key SHA1 Fingerprint"),
+                                                      g_bytes_new_take (raw, n_raw));
+
+       value = egg_asn1x_get_bits_as_raw (egg_asn1x_node (subject_public_key, "subjectPublicKey", NULL), 
&bits);
+       _gcr_certificate_section_new_field_take_bytes (section, _("Public Key"), g_steal_pointer (&value));
+}
+
+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_new_field (section, _("Certificate Authority"), is_ca ? _("Yes") : _("No"));
+
+       if (path_len < 0)
+               number = g_strdup (_("Unlimited"));
+       else
+               number = g_strdup_printf ("%d", path_len);
+
+       _gcr_certificate_section_new_field_take_value (section, _("Max Path Length"), g_steal_pointer 
(&number));
+
+       return section;
+}
+
+static GcrCertificateSection *
+append_extension_extended_key_usage (GBytes *data)
+{
+       GcrCertificateSection *section;
+       GQuark *oids;
+       GStrvBuilder *text;
+       guint i;
+
+       oids = _gcr_certificate_extension_extended_key_usage (data);
+       if (!oids)
+               return NULL;
+
+       text = g_strv_builder_new ();
+       for (i = 0; oids[i] != 0; i++) {
+               g_strv_builder_add (text, egg_oid_get_description (oids[i]));
+       }
+
+       g_free (oids);
+
+       section = _gcr_certificate_section_new (_("Extended Key Usage"), FALSE);
+       _gcr_certificate_section_new_field_take_values (section, _("Allowed Purposes"), g_strv_builder_end 
(text));
+       g_strv_builder_unref (text);
+
+       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_new_field_take_value (section, _("Key Identifier"), g_steal_pointer 
(&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 GcrCertificateSection *
+append_extension_key_usage (GBytes *data)
+{
+       GcrCertificateSection *section;
+       gulong key_usage;
+       GStrvBuilder *values;
+       guint i;
+
+       if (!_gcr_certificate_extension_key_usage (data, &key_usage))
+               return NULL;
+
+       values = g_strv_builder_new ();
+       for (i = 0; i < G_N_ELEMENTS (usage_descriptions); i++) {
+               if (key_usage & usage_descriptions[i].usage) {
+                       g_strv_builder_add (values, _(usage_descriptions[i].description));
+               }
+       }
+
+       section = _gcr_certificate_section_new (_("Key Usage"), FALSE);
+       _gcr_certificate_section_new_field_take_values (section, _("Usages"), g_strv_builder_end (values));
+       g_strv_builder_unref (values);
+
+       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_new_field_take_value (section, general->description, 
g_steal_pointer (&display));
+               } else
+                       _gcr_certificate_section_new_field (section, general->description, general->display);
+       }
+
+       _gcr_general_names_free (general_names);
+
+       return section;
+}
+
+static GcrCertificateSection *
+append_extension_hex (GQuark oid,
+                      GBytes *value)
+{
+       GcrCertificateSection *section;
+       const gchar *text;
+
+       section = _gcr_certificate_section_new (_("Extension"), FALSE);
+
+       /* Extension type */
+       text = egg_oid_get_description (oid);
+       _gcr_certificate_section_new_field (section, _("Identifier"), g_strdup (text));
+       _gcr_certificate_section_new_field_take_bytes (section, _("Value"), g_steal_pointer (&value));
+
+       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, g_steal_pointer (&value));
+       }
+
+       /* Critical */
+       if (section && egg_asn1x_get_boolean (egg_asn1x_node (node, "critical", NULL), &critical)) {
+               _gcr_certificate_section_new_field (section, _("Critical"), critical ? _("Yes") : _("No"));
+       }
+
+       g_clear_pointer (&value, g_bytes_unref);
+       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_new_field_take_value (section, label, g_steal_pointer (&display));
+       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;
+       GcrCertificateInfo *info;
+       GList *list = NULL;
+       gchar *display;
+       GBytes *bytes, *number;
+       GNode *subject_public_key;
+       GQuark oid;
+       GDateTime *datetime;
+       gulong version;
+       guint bits;
+
+       g_return_val_if_fail (GCR_IS_CERTIFICATE (self), NULL);
+
+       info = certificate_info_load (self);
+       g_return_val_if_fail (info != NULL, NULL);
+
+       display = gcr_certificate_get_subject_name (self);
+       if (!display)
+               display = g_strdup (_("Certificate"));
+
+       section = _gcr_certificate_section_new (display, TRUE);
+       g_clear_pointer (&display, g_free);
+
+       display = gcr_certificate_get_subject_cn (self);
+       _gcr_certificate_section_new_field_take_value (section, _("Identity"), g_steal_pointer (&display));
+
+       display = gcr_certificate_get_issuer_cn (self);
+       _gcr_certificate_section_new_field_take_value (section, _("Verified by"), g_steal_pointer (&display));
+
+       datetime = gcr_certificate_get_expiry_date (self);
+       if (datetime) {
+               display = g_date_time_format (datetime, "%x");
+               if (display)
+                       _gcr_certificate_section_new_field_take_value (section, _("Expires"), g_steal_pointer 
(&display));
+
+               g_clear_pointer (&datetime, g_date_time_unref);
+       }
+
+       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 (info->asn1, "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 (info->asn1, "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 (info->asn1, "tbsCertificate", "version", NULL), 
&version)) {
+               g_critical ("Unable to parse certificate version");
+       } else {
+               display = g_strdup_printf ("%lu", version + 1);
+               _gcr_certificate_section_new_field_take_value (section, _("Version"), g_steal_pointer 
(&display));
+       }
+
+       number = egg_asn1x_get_integer_as_raw (egg_asn1x_node (info->asn1, "tbsCertificate", "serialNumber", 
NULL));
+       if (!number) {
+               g_critical ("Unable to parse certificate serial number");
+       } else {
+               _gcr_certificate_section_new_field_take_bytes (section, _("Serial Number"), g_steal_pointer 
(&number));
+       }
+
+       datetime = gcr_certificate_get_issued_date (self);
+       if (datetime) {
+               display = g_date_time_format (datetime, "%x");
+               if (display)
+                       _gcr_certificate_section_new_field_take_value (section, _("Not Valid Before"), 
g_steal_pointer (&display));
+
+               g_clear_pointer (&datetime, g_date_time_unref);
+       }
+
+       datetime = gcr_certificate_get_expiry_date (self);
+       if (datetime) {
+               display = g_date_time_format (datetime, "%x");
+               if (display)
+                       _gcr_certificate_section_new_field_take_value (section, _("Not Valid After"), 
g_steal_pointer (&display));
+
+               g_clear_pointer (&datetime, g_date_time_unref);
+       }
+
+       list = g_list_prepend (list, g_steal_pointer (&section));
+
+       /* Fingerprints */
+       bytes = g_bytes_new_static (info->der, info->n_der);
+       section = _gcr_certificate_section_new (_("Certificate Fingerprints"), FALSE);
+       display = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, bytes);
+       _gcr_certificate_section_new_field_take_value (section, "SHA1", g_steal_pointer (&display));
+       display = g_compute_checksum_for_bytes (G_CHECKSUM_MD5, bytes);
+       _gcr_certificate_section_new_field_take_value (section, "MD5", g_steal_pointer (&display));
+       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 (info->asn1, "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 (info->asn1, "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 (info->asn1, "signatureAlgorithm", "algorithm", 
NULL));
+       _gcr_certificate_section_new_field (section, _("Signature Algorithm"), egg_oid_get_description (oid));
+
+       bytes = egg_asn1x_get_element_raw (egg_asn1x_node (info->asn1, "signatureAlgorithm", "parameters", 
NULL));
+       if (bytes) {
+               _gcr_certificate_section_new_field_take_bytes (section, _("Signature Parameters"), 
g_steal_pointer (&bytes));
+       }
+
+       bytes = egg_asn1x_get_bits_as_raw (egg_asn1x_node (info->asn1, "signature", NULL), &bits);
+       _gcr_certificate_section_new_field_take_bytes (section, _("Signature"), g_steal_pointer (&bytes));
+
+       list = g_list_prepend (list, g_steal_pointer (&section));
+
+       return g_list_reverse (list);
+}
+
 /* -----------------------------------------------------------------------------
  * MIXIN
  */
diff --git a/gcr/gcr-certificate.h b/gcr/gcr-certificate.h
index f0df3726..a764d124 100644
--- a/gcr/gcr-certificate.h
+++ b/gcr/gcr-certificate.h
@@ -120,6 +120,8 @@ gboolean            gcr_certificate_get_basic_constraints  (GcrCertificate *self
                                                             gboolean *is_ca,
                                                             gint *path_len);
 
+GList*              gcr_certificate_get_interface_elements (GcrCertificate *self);
+
 void                gcr_certificate_mixin_emit_notify      (GcrCertificate *self);
 
 void                gcr_certificate_mixin_class_init       (GObjectClass *object_class);
diff --git a/gcr/gcr.h b/gcr/gcr.h
index 17b4da02..b9705a8c 100644
--- a/gcr/gcr.h
+++ b/gcr/gcr.h
@@ -34,7 +34,9 @@
 
 #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-certificate-section.h>
 #include <gcr/gcr-enum-types.h>
 #include <gcr/gcr-fingerprint.h>
 #include <gcr/gcr-importer.h>
diff --git a/gcr/meson.build b/gcr/meson.build
index 6a1dd747..e67606e9 100644
--- a/gcr/meson.build
+++ b/gcr/meson.build
@@ -4,7 +4,9 @@ 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-certificate-section.c',
   'gcr-fingerprint.c',
   'gcr-importer.c',
   'gcr-import-interaction.c',
@@ -43,7 +45,9 @@ gcr_headers = files(
   'gcr.h',
   'gcr-certificate.h',
   'gcr-certificate-chain.h',
+  'gcr-certificate-field.h',
   'gcr-certificate-request.h',
+  'gcr-certificate-section.h',
   'gcr-fingerprint.h',
   'gcr-importer.h',
   'gcr-import-interaction.h',
diff --git a/gcr/test-certificate.c b/gcr/test-certificate.c
index 8bca0343..cdaafe81 100644
--- a/gcr/test-certificate.c
+++ b/gcr/test-certificate.c
@@ -266,6 +266,38 @@ 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;
+               GListModel *fields;
+
+               gcr_certificate_section_get_flags (section);
+               g_assert (gcr_certificate_section_get_label (section) != NULL);
+               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++) {
+                       GValue val = G_VALUE_INIT;
+                       GType value_type;
+                       GcrCertificateField *field = g_list_model_get_item (fields, i);
+                       g_assert (gcr_certificate_field_get_label (field) != NULL);
+                       value_type = gcr_certificate_field_get_value_type (field);
+                       g_value_init (&val, value_type);
+                       g_assert (gcr_certificate_field_get_value (field, &val));
+                       g_value_unset (&val);
+                       g_assert (gcr_certificate_field_get_section (field) == section);
+                       g_object_unref (field);
+               }
+       }
+
+       g_list_free_full (sections, (GDestroyNotify) g_object_unref);
+}
+
 static void
 test_subject_alt_name (void)
 {
@@ -346,6 +378,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);
 
diff --git a/meson.build b/meson.build
index 1c6db2aa..f66d0cc2 100644
--- a/meson.build
+++ b/meson.build
@@ -41,7 +41,7 @@ gcr_gtk3_basename = 'gcr-@0@-gtk3'.format(gcr_api_version)
 gcr_gtk4_basename = 'gcr-@0@-gtk4'.format(gcr_api_version)
 
 # Dependencies
-min_glib_version = '2.64'
+min_glib_version = '2.68'
 glib_dep = dependency('glib-2.0', version: '>=' + min_glib_version)
 gmodule_dep = dependency('gmodule-no-export-2.0', version: '>=' + min_glib_version)
 gthread_dep = dependency('gthread-2.0', version: '>=' + min_glib_version)


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