/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see
*
*
* Authors:
* Chris Toshok
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include "pk11pub.h"
#include "hasht.h"
#include
#include "e-asn1-object.h"
#include "certificate-viewer.h"
#define CERTIFICATE_VIEWER_PRIV_KEY "CertificateViewerPriv-key"
typedef struct _CertificateViewerPriv
{
GtkWidget *issued_to_cn;
GtkWidget *issued_to_o;
GtkWidget *issued_to_ou;
GtkWidget *issued_to_serial;
GtkWidget *issued_by_cn;
GtkWidget *issued_by_o;
GtkWidget *issued_by_ou;
GtkWidget *validity_issued_on;
GtkWidget *validity_expires_on;
GtkWidget *fingerprints_sha1;
GtkWidget *fingerprints_md5;
GtkWidget *cert_hierarchy_treeview;
GtkWidget *cert_fields_treeview;
GtkWidget *cert_field_value_textview;
CERTCertificate *cert;
GSList *issuers;
GtkTextTag *monospace_tag;
} CertificateViewerPriv;
static void
free_priv_struct (gpointer ptr)
{
CertificateViewerPriv *priv = ptr;
GSList *iter;
if (!priv)
return;
if (priv->cert)
CERT_DestroyCertificate (priv->cert);
for (iter = priv->issuers; iter; iter = iter->next) {
CERTCertificate *cert = iter->data;
if (cert)
CERT_DestroyCertificate (cert);
}
g_slist_free (priv->issuers);
g_free (priv);
}
static void
begin_section (GtkGrid *add_to,
const gchar *caption,
gint *from_row,
gint for_rows)
{
GtkWidget *widget;
PangoAttribute *attr;
PangoAttrList *bold;
g_return_if_fail (add_to != NULL);
g_return_if_fail (caption != NULL);
g_return_if_fail (from_row != NULL);
bold = pango_attr_list_new ();
attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
pango_attr_list_insert (bold, attr);
widget = gtk_label_new (caption);
g_object_set (
G_OBJECT (widget),
"hexpand", TRUE,
"halign", GTK_ALIGN_START,
"justify", GTK_JUSTIFY_LEFT,
"attributes", bold,
"ellipsize", PANGO_ELLIPSIZE_NONE,
NULL);
pango_attr_list_unref (bold);
gtk_grid_attach (add_to, widget, 0, *from_row, 3, 1);
(*from_row)++;
widget = gtk_alignment_new (0.0, 0.0, 0.0, 0.0);
gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 12, 0);
gtk_grid_attach (add_to, widget, 0, *from_row, 1, for_rows);
}
static GtkWidget *
add_info_label (GtkGrid *add_to,
const gchar *caption,
gint *at_row)
{
GtkWidget *widget;
g_return_val_if_fail (add_to != NULL, NULL);
g_return_val_if_fail (at_row != NULL, NULL);
if (caption) {
widget = gtk_label_new (caption);
g_object_set (
G_OBJECT (widget),
"hexpand", FALSE,
"halign", GTK_ALIGN_START,
"justify", GTK_JUSTIFY_LEFT,
"ellipsize", PANGO_ELLIPSIZE_NONE,
NULL);
gtk_grid_attach (add_to, widget, 1, *at_row, 1, 1);
}
widget = gtk_label_new ("");
g_object_set (
G_OBJECT (widget),
"hexpand", TRUE,
"halign", GTK_ALIGN_START,
"justify", GTK_JUSTIFY_LEFT,
"ellipsize", PANGO_ELLIPSIZE_NONE,
"selectable", caption != NULL,
NULL);
gtk_grid_attach (add_to, widget, caption ? 2 : 1, *at_row, caption ? 1 : 2, 1);
(*at_row)++;
return widget;
}
static GtkWidget *
add_scrolled_window (GtkGrid *add_to,
const gchar *caption,
gint *at_row,
GtkWidget *add_widget)
{
GtkWidget *widget;
PangoAttribute *attr;
PangoAttrList *bold;
g_return_val_if_fail (add_to != NULL, NULL);
g_return_val_if_fail (caption != NULL, NULL);
g_return_val_if_fail (at_row != NULL, NULL);
bold = pango_attr_list_new ();
attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
pango_attr_list_insert (bold, attr);
widget = gtk_label_new (caption);
g_object_set (
G_OBJECT (widget),
"hexpand", TRUE,
"halign", GTK_ALIGN_START,
"justify", GTK_JUSTIFY_LEFT,
"attributes", bold,
"ellipsize", PANGO_ELLIPSIZE_NONE,
NULL);
pango_attr_list_unref (bold);
gtk_grid_attach (add_to, widget, 0, *at_row, 1, 1);
(*at_row)++;
widget = gtk_scrolled_window_new (NULL, NULL);
g_object_set (
G_OBJECT (widget),
"hexpand", TRUE,
"halign", GTK_ALIGN_FILL,
"vexpand", TRUE,
"valign", GTK_ALIGN_FILL,
"hscrollbar-policy", GTK_POLICY_AUTOMATIC,
"vscrollbar-policy", GTK_POLICY_AUTOMATIC,
"shadow-type", GTK_SHADOW_ETCHED_IN,
NULL);
gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (widget), add_widget);
gtk_grid_attach (add_to, widget, 0, *at_row, 1, 1);
(*at_row)++;
return add_widget;
}
#define FLAG_NONE (0)
#define FLAG_PORT_MEMORY (1 << 0)
#define FLAG_MARKUP (1 << 1)
static void
set_label_text (GtkWidget *label,
const gchar *not_part_markup,
gchar *text,
guint32 flags)
{
if (text) {
if ((flags & FLAG_MARKUP) != 0)
gtk_label_set_markup (GTK_LABEL (label), text);
else
gtk_label_set_text (GTK_LABEL (label), text);
if ((flags & FLAG_PORT_MEMORY) != 0)
PORT_Free (text);
else
g_free (text);
} else {
gtk_label_set_markup (GTK_LABEL (label), not_part_markup);
}
}
static void
get_cert_times (CERTCertificate *cert,
gchar **issued_on,
gchar **expires_on)
{
PRTime time_issued_on;
PRTime time_expires_on;
PRExplodedTime explodedTime;
struct tm exploded_tm;
gchar buf[128];
g_return_if_fail (cert != NULL);
g_return_if_fail (issued_on != NULL);
g_return_if_fail (expires_on != NULL);
if (SECSuccess != CERT_GetCertTimes (cert, &time_issued_on, &time_expires_on))
return;
PR_ExplodeTime (time_issued_on, PR_LocalTimeParameters, &explodedTime);
exploded_tm.tm_sec = explodedTime.tm_sec;
exploded_tm.tm_min = explodedTime.tm_min;
exploded_tm.tm_hour = explodedTime.tm_hour;
exploded_tm.tm_mday = explodedTime.tm_mday;
exploded_tm.tm_mon = explodedTime.tm_month;
exploded_tm.tm_year = explodedTime.tm_year - 1900;
e_utf8_strftime (buf, sizeof (buf), "%x", &exploded_tm);
*issued_on = g_strdup (buf);
PR_ExplodeTime (time_expires_on, PR_LocalTimeParameters, &explodedTime);
exploded_tm.tm_sec = explodedTime.tm_sec;
exploded_tm.tm_min = explodedTime.tm_min;
exploded_tm.tm_hour = explodedTime.tm_hour;
exploded_tm.tm_mday = explodedTime.tm_mday;
exploded_tm.tm_mon = explodedTime.tm_month;
exploded_tm.tm_year = explodedTime.tm_year - 1900;
e_utf8_strftime (buf, sizeof (buf), "%x", &exploded_tm);
*expires_on = g_strdup (buf);
}
static void
fill_general_page (CertificateViewerPriv *priv)
{
gchar *not_part_markup;
gchar *issued_on = NULL;
gchar *expires_on = NULL;
gchar *port_str;
guchar fingerprint[128];
SECItem fpItem;
g_return_if_fail (priv != NULL);
not_part_markup = g_strconcat ("<", _("Not part of certificate"), ">", NULL);
set_label_text (priv->issued_to_cn, not_part_markup, CERT_GetCommonName (&priv->cert->subject), FLAG_PORT_MEMORY);
set_label_text (priv->issued_to_o, not_part_markup, CERT_GetOrgName (&priv->cert->subject), FLAG_PORT_MEMORY);
set_label_text (priv->issued_to_ou, not_part_markup, CERT_GetOrgUnitName (&priv->cert->subject), FLAG_PORT_MEMORY);
set_label_text (priv->issued_to_serial, not_part_markup, CERT_Hexify (&priv->cert->serialNumber, TRUE), FLAG_PORT_MEMORY);
set_label_text (priv->issued_by_cn, not_part_markup, CERT_GetCommonName (&priv->cert->issuer), FLAG_PORT_MEMORY);
set_label_text (priv->issued_by_o, not_part_markup, CERT_GetOrgName (&priv->cert->issuer), FLAG_PORT_MEMORY);
set_label_text (priv->issued_by_ou, not_part_markup, CERT_GetOrgUnitName (&priv->cert->issuer), FLAG_PORT_MEMORY);
get_cert_times (priv->cert, &issued_on, &expires_on);
set_label_text (priv->validity_issued_on, not_part_markup, issued_on, FLAG_NONE);
set_label_text (priv->validity_expires_on, not_part_markup, expires_on, FLAG_NONE);
memset (fingerprint, 0, sizeof fingerprint);
PK11_HashBuf (
SEC_OID_SHA1, fingerprint,
priv->cert->derCert.data,
priv->cert->derCert.len);
fpItem.data = fingerprint;
fpItem.len = SHA1_LENGTH;
port_str = CERT_Hexify (&fpItem, TRUE);
set_label_text (priv->fingerprints_sha1, not_part_markup, g_strconcat ("", port_str, "", NULL), FLAG_MARKUP);
PORT_Free (port_str);
memset (fingerprint, 0, sizeof fingerprint);
PK11_HashBuf (
SEC_OID_MD5, fingerprint,
priv->cert->derCert.data,
priv->cert->derCert.len);
fpItem.data = fingerprint;
fpItem.len = MD5_LENGTH;
port_str = CERT_Hexify (&fpItem, TRUE);
set_label_text (priv->fingerprints_md5, not_part_markup, g_strconcat ("", port_str, "", NULL), FLAG_MARKUP);
PORT_Free (port_str);
g_free (not_part_markup);
}
static void
populate_fields_tree (CertificateViewerPriv *priv,
EASN1Object *asn1,
GtkTreeIter *root)
{
GtkTreeStore *fields_store;
GtkTreeIter new_iter;
if (!asn1)
return;
fields_store = GTK_TREE_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->cert_fields_treeview)));
/* first insert a node for the current asn1 */
gtk_tree_store_insert (fields_store, &new_iter, root, -1);
gtk_tree_store_set (
fields_store, &new_iter,
0, e_asn1_object_get_display_name (asn1),
1, asn1,
-1);
if (e_asn1_object_is_valid_container (asn1)) {
GList *children = e_asn1_object_get_children (asn1);
if (children) {
GList *iter;
for (iter = children; iter; iter = iter->next) {
EASN1Object *subasn1 = iter->data;
populate_fields_tree (priv, subasn1, &new_iter);
}
}
g_list_free_full (children, g_object_unref);
}
}
static void
hierarchy_selection_changed_cb (GtkTreeSelection *selection,
CertificateViewerPriv *priv)
{
GtkTreeIter iter;
GtkTreeModel *model;
if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
CERTCertificate *cert;
EASN1Object *asn1;
GtkTreeStore *fields_store;
gtk_tree_model_get (model, &iter, 1, &cert, -1);
if (!cert)
return;
/* display the cert's ASN1 structure */
asn1 = e_asn1_object_new_from_cert (cert);
/* wipe out the old model */
fields_store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_OBJECT);
gtk_tree_view_set_model (
GTK_TREE_VIEW (priv->cert_fields_treeview),
GTK_TREE_MODEL (fields_store));
/* populate the fields from the newly selected cert */
populate_fields_tree (priv, asn1, NULL);
gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->cert_fields_treeview));
if (asn1)
g_object_unref (asn1);
/* and blow away the field value */
gtk_text_buffer_set_text (
gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->cert_field_value_textview)),
"", 0);
}
}
static void
fields_selection_changed_cb (GtkTreeSelection *selection,
CertificateViewerPriv *priv)
{
GtkTreeIter iter;
GtkTreeModel *model;
if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
EASN1Object *asn1 = NULL;
const gchar *value = NULL;
GtkTextView *textview;
GtkTextBuffer *textbuffer;
gtk_tree_model_get (model, &iter, 1, &asn1, -1);
if (asn1)
value = e_asn1_object_get_display_value (asn1);
textview = GTK_TEXT_VIEW (priv->cert_field_value_textview);
textbuffer = gtk_text_view_get_buffer (textview);
gtk_text_buffer_set_text (textbuffer, "", 0);
if (value) {
GtkTextIter text_iter;
gtk_text_buffer_get_start_iter (textbuffer, &text_iter);
gtk_text_buffer_insert_with_tags (
textbuffer, &text_iter,
value, strlen (value),
priv->monospace_tag, NULL);
}
if (asn1)
g_object_unref (asn1);
}
}
static void
fill_details_page (CertificateViewerPriv *priv)
{
GSList *iter;
GtkTreeIter root;
GtkTreeSelection *selection;
gboolean root_set = FALSE;
GtkTreeStore *hierarchy_store;
g_return_if_fail (priv != NULL);
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->cert_hierarchy_treeview), FALSE);
hierarchy_store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
gtk_tree_view_set_model (
GTK_TREE_VIEW (priv->cert_hierarchy_treeview),
GTK_TREE_MODEL (hierarchy_store));
gtk_tree_view_insert_column_with_attributes (
GTK_TREE_VIEW (priv->cert_hierarchy_treeview),
-1, "Cert", gtk_cell_renderer_text_new (),
"text", 0, NULL);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->cert_hierarchy_treeview));
g_signal_connect (
selection, "changed",
G_CALLBACK (hierarchy_selection_changed_cb), priv);
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->cert_fields_treeview), FALSE);
gtk_tree_view_insert_column_with_attributes (
GTK_TREE_VIEW (priv->cert_fields_treeview),
-1, "Field", gtk_cell_renderer_text_new (),
"text", 0, NULL);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->cert_fields_treeview));
g_signal_connect (
selection, "changed",
G_CALLBACK (fields_selection_changed_cb), priv);
/* set the font of the field value viewer to be some fixed
* width font to the hex display looks nice. */
priv->monospace_tag = gtk_text_buffer_create_tag (
gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->cert_field_value_textview)),
"mono", "font", "Mono", NULL);
/* initially populate the hierarchy from the issuers' chain */
for (iter = priv->issuers; iter; iter = g_slist_next (iter)) {
CERTCertificate *cert = iter->data;
gchar *str;
GtkTreeIter new_iter;
if (!cert)
continue;
str = CERT_GetCommonName (&cert->subject);
gtk_tree_store_insert (hierarchy_store, &new_iter, root_set ? &root : NULL, -1);
gtk_tree_store_set (
hierarchy_store, &new_iter,
0, str ? str : cert->subjectName,
1, cert,
-1);
root = new_iter;
root_set = TRUE;
if (str)
PORT_Free (str);
}
gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->cert_hierarchy_treeview));
}
static gchar *
get_window_title (CERTCertificate *cert)
{
gchar *str;
g_return_val_if_fail (cert != NULL, NULL);
if (cert->nickname)
return g_strdup (cert->nickname);
str = CERT_GetCommonName (&cert->subject);
if (str) {
gchar *title;
title = g_strdup (str);
PORT_Free (str);
return title;
}
return cert->subjectName;
}
GtkWidget *
certificate_viewer_new (GtkWindow *parent,
const CERTCertificate *cert,
const GSList *issuers_chain_certs)
{
CertificateViewerPriv *priv;
GtkWidget *dialog, *notebook, *widget;
GtkGrid *grid;
gint row;
GSList *iter;
gchar *title;
g_return_val_if_fail (cert != NULL, NULL);
priv = g_new0 (CertificateViewerPriv, 1);
priv->cert = CERT_DupCertificate ((CERTCertificate *) cert);
priv->issuers = g_slist_copy ((GSList *) issuers_chain_certs);
/* root issuer first, then bottom down to certificate itself */
priv->issuers = g_slist_reverse (priv->issuers);
priv->issuers = g_slist_append (priv->issuers, priv->cert);
for (iter = priv->issuers; iter; iter = g_slist_next (iter)) {
iter->data = CERT_DupCertificate (iter->data);
}
title = get_window_title (priv->cert);
dialog = gtk_dialog_new_with_buttons (
title, parent,
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
NULL);
g_free (title);
g_object_set_data_full (G_OBJECT (dialog), CERTIFICATE_VIEWER_PRIV_KEY, priv, free_priv_struct);
notebook = gtk_notebook_new ();
gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), notebook);
gtk_container_set_border_width (GTK_CONTAINER (notebook), 12);
/* General page */
row = 0;
grid = GTK_GRID (gtk_grid_new ());
g_object_set (
G_OBJECT (grid),
"hexpand", TRUE,
"halign", GTK_ALIGN_FILL,
"vexpand", FALSE,
"valign", GTK_ALIGN_START,
"border-width", 12,
"row-spacing", 6,
"column-spacing", 6,
NULL);
begin_section (grid, _("This certificate has been verified for the following uses:"), &row, 4);
if (!priv->cert->keyUsagePresent || (priv->cert->keyUsage & certificateUsageSSLClient) != 0) {
widget = add_info_label (grid, NULL, &row);
gtk_label_set_text (GTK_LABEL (widget), _("SSL Client Certificate"));
}
if (!priv->cert->keyUsagePresent || (priv->cert->keyUsage & (certificateUsageSSLServer | certificateUsageSSLCA)) != 0) {
widget = add_info_label (grid, NULL, &row);
gtk_label_set_text (GTK_LABEL (widget), _("SSL Server Certificate"));
}
if (!priv->cert->keyUsagePresent || (priv->cert->keyUsage & certificateUsageEmailSigner) != 0) {
widget = add_info_label (grid, NULL, &row);
gtk_label_set_text (GTK_LABEL (widget), _("Email Signer Certificate"));
}
if (!priv->cert->keyUsagePresent || (priv->cert->keyUsage & certificateUsageEmailRecipient) != 0) {
widget = add_info_label (grid, NULL, &row);
gtk_label_set_text (GTK_LABEL (widget), _("Email Recipient Certificate"));
}
widget = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
g_object_set (
G_OBJECT (widget),
"hexpand", TRUE,
"halign", GTK_ALIGN_FILL,
"vexpand", FALSE,
"valign", GTK_ALIGN_START,
NULL);
gtk_grid_attach (grid, widget, 0, row, 3, 1);
row++;
begin_section (grid, _("Issued To"), &row, 4);
priv->issued_to_cn = add_info_label (grid, _("Common Name (CN)"), &row);
priv->issued_to_o = add_info_label (grid, _("Organization (O)"), &row);
priv->issued_to_ou = add_info_label (grid, _("Organizational Unit (OU)"), &row);
priv->issued_to_serial = add_info_label (grid, _("Serial Number"), &row);
begin_section (grid, _("Issued By"), &row, 3);
priv->issued_by_cn = add_info_label (grid, _("Common Name (CN)"), &row);
priv->issued_by_o = add_info_label (grid, _("Organization (O)"), &row);
priv->issued_by_ou = add_info_label (grid, _("Organizational Unit (OU)"), &row);
begin_section (grid, _("Validity"), &row, 2);
priv->validity_issued_on = add_info_label (grid, _("Issued On"), &row);
priv->validity_expires_on = add_info_label (grid, _("Expires On"), &row);
begin_section (grid, _("Fingerprints"), &row, 2);
priv->fingerprints_sha1 = add_info_label (grid, _("SHA1 Fingerprint"), &row);
priv->fingerprints_md5 = add_info_label (grid, _("MD5 Fingerprint"), &row);
widget = gtk_label_new (_("General"));
gtk_notebook_append_page (GTK_NOTEBOOK (notebook), GTK_WIDGET (grid), widget);
/* Details page */
row = 0;
grid = GTK_GRID (gtk_grid_new ());
g_object_set (
G_OBJECT (grid),
"hexpand", TRUE,
"halign", GTK_ALIGN_FILL,
"vexpand", TRUE,
"valign", GTK_ALIGN_FILL,
"border-width", 12,
"row-spacing", 6,
"column-spacing", 6,
NULL);
priv->cert_hierarchy_treeview = add_scrolled_window (
grid, _("Certificate Hierarchy"), &row, gtk_tree_view_new ());
priv->cert_fields_treeview = add_scrolled_window (
grid, _("Certificate Fields"), &row, gtk_tree_view_new ());
priv->cert_field_value_textview = add_scrolled_window (
grid, _("Field Value"), &row, gtk_text_view_new ());
widget = gtk_label_new (_("Details"));
gtk_notebook_append_page (GTK_NOTEBOOK (notebook), GTK_WIDGET (grid), widget);
gtk_widget_show_all (notebook);
fill_general_page (priv);
fill_details_page (priv);
return dialog;
}