diff options
author | Corentin Noël <corentin.noel@collabora.com> | 2022-05-10 16:08:39 +0200 |
---|---|---|
committer | Niels De Graef <nielsdegraef@gmail.com> | 2022-08-07 20:32:12 +0000 |
commit | 46d67a18ba3e64297f95f7a900eeb9da24afd452 (patch) | |
tree | 6bea618a7022edc1210247344f0dba9c5bfd54e7 /gcr | |
parent | e934b5bb7642023b5a880a71e00997fccc12a262 (diff) | |
download | gcr-46d67a18ba3e64297f95f7a900eeb9da24afd452.tar.gz |
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).
Diffstat (limited to 'gcr')
-rw-r--r-- | gcr/gcr-certificate-field-private.h | 75 | ||||
-rw-r--r-- | gcr/gcr-certificate-field.c | 261 | ||||
-rw-r--r-- | gcr/gcr-certificate-field.h | 32 | ||||
-rw-r--r-- | gcr/gcr-certificate-section.c | 211 | ||||
-rw-r--r-- | gcr/gcr-certificate-section.h | 36 | ||||
-rw-r--r-- | gcr/gcr-certificate.c | 435 | ||||
-rw-r--r-- | gcr/gcr-certificate.h | 2 | ||||
-rw-r--r-- | gcr/gcr.h | 2 | ||||
-rw-r--r-- | gcr/meson.build | 4 | ||||
-rw-r--r-- | gcr/test-certificate.c | 33 |
10 files changed, 1091 insertions, 0 deletions
diff --git a/gcr/gcr-certificate-field-private.h b/gcr/gcr-certificate-field-private.h new file mode 100644 index 0000000..bfd5bcf --- /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 0000000..3869e19 --- /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 0000000..42e3f82 --- /dev/null +++ b/gcr/gcr-certificate-field.h @@ -0,0 +1,32 @@ +/* + * 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/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 0000000..4741ca6 --- /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 0000000..92267fc --- /dev/null +++ b/gcr/gcr-certificate-section.h @@ -0,0 +1,36 @@ +/* + * 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/gcr-enum-types.h> + +#include <glib-object.h> +#include <gio/gio.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 fc2d9e0..a11cf1e 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,437 @@ 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) { + _gcr_certificate_section_new_field_take_bytes (section, general->description, g_bytes_ref (general->raw)); + } 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 (§ion)); + + /* 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 (§ion)); + + /* 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 (§ion)); + + /* 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 (§ion)); + + /* 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 (§ion)); + + /* 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 (§ion)); + + /* 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 (§ion)); + } + + /* 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 (§ion)); + + return g_list_reverse (list); +} + /* ----------------------------------------------------------------------------- * MIXIN */ diff --git a/gcr/gcr-certificate.h b/gcr/gcr-certificate.h index f0df372..a764d12 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); @@ -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 6a1dd74..e67606e 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 8bca034..cdaafe8 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); |