Add TLS Certificate parsing API purple-ssl-to-gio

Tue, 29 Mar 2016 22:51:43 -0500

author
Mike Ruprecht <cmaiku@gmail.com>
date
Tue, 29 Mar 2016 22:51:43 -0500
branch
purple-ssl-to-gio
changeset 37622
14d1273cae74
parent 37621
2a2f1068e0f0
child 37623
53718d3c53f0

Add TLS Certificate parsing API

This patch adds X.509 certificate parsing API. It takes the bytes
from a GTlsCertificate and parses information such as subject name,
SHA-1 hash, and similar. GTlsCertificate parses the certificates
internally, so these functions are only used for displaying to the
user.

UIs could conceivably use a library such as libgcr directly instead,
but this is here, at least for now, until such an alternative is
used, if at all.

libpurple/tls-certificate.c file | annotate | diff | comparison | revisions
libpurple/tls-certificate.h file | annotate | diff | comparison | revisions
--- a/libpurple/tls-certificate.c	Tue Feb 16 17:48:03 2016 -0600
+++ b/libpurple/tls-certificate.c	Tue Mar 29 22:51:43 2016 -0500
@@ -23,6 +23,7 @@
 
 #include "internal.h"
 #include "tls-certificate.h"
+#include "ciphers/sha1hash.h"
 #include "debug.h"
 #include "util.h"
 
@@ -277,3 +278,753 @@
 			socket_client_event_cb, NULL, NULL);
 }
 
+#define DER_TYPE_CLASS(type)		(type & 0xc0)
+
+#define DER_TYPE_CLASS_UNIVERSAL	0x00
+#define DER_TYPE_CLASS_APPLICATION	0x40
+#define DER_TYPE_CLASS_CONTEXT_SPECIFIC	0x80
+#define DER_TYPE_CLASS_PRIVATE		0xc0
+
+#define DER_TYPE_TAG(type) (type & 0x1f)
+
+#define DER_TYPE_IS_CONSTRUCTED(type) ((type & 0x20) ? TRUE : FALSE)
+
+#define DER_TYPE_TAG_IS_LONG_FORM(type) (DER_TYPE_TAG(type) == 0x1f)
+
+#define DER_LENGTH_IS_LONG_FORM(byte) ((byte & 0x80) ? TRUE : FALSE)
+#define DER_LENGTH_LONG_FORM_SIZE(byte) (byte & 0x7f)
+
+typedef struct {
+	guint8 type_class;
+	gboolean constructed;
+	guint type;
+	GBytes *content;
+	GSList *children;
+} DerNodeData;
+
+static void der_node_data_children_list_free(GSList *children);
+
+static void
+der_node_data_free(DerNodeData *node_data)
+{
+	g_return_if_fail(node_data != NULL);
+
+	g_clear_pointer(&node_data->content, g_bytes_unref);
+	g_clear_pointer(&node_data->children,
+			der_node_data_children_list_free);
+
+	g_free(node_data);
+}
+
+static void
+der_node_data_children_list_free(GSList *children)
+{
+	g_return_if_fail(children != NULL);
+
+	g_slist_free_full(children, (GDestroyNotify)der_node_data_free);
+}
+
+/* Parses DER encoded data into a GSList of DerNodeData instances */
+static GSList *
+der_parse(GBytes *data_bytes)
+{
+	const guint8 *data;
+	gsize size = 0;
+	gsize offset = 0;
+	GSList *nodes = NULL;
+	DerNodeData *node = NULL;
+
+	data = g_bytes_get_data(data_bytes, &size);
+
+	/* Parse data */
+	while (offset < size) {
+		guint8 byte;
+		gsize length;
+
+		/* Parse type */
+
+		byte = *(data + offset++);
+		node = g_new0(DerNodeData, 1);
+		node->type_class = DER_TYPE_CLASS(byte);
+		node->constructed = DER_TYPE_IS_CONSTRUCTED(byte);
+
+		if (DER_TYPE_TAG_IS_LONG_FORM(byte)) {
+			/* Long-form type encoding */
+			/* TODO: Handle long-form encoding.
+			 * Maiku: The certificates I tested didn't do this.
+			 */
+			g_return_val_if_reached(NULL);
+		} else {
+			/* Short-form type encoding */
+			node->type = DER_TYPE_TAG(byte);
+		}
+
+		/* Parse content length */
+
+		if (offset >= size) {
+			purple_debug_error("tls-certificate",
+					"Not enough remaining data when "
+					"parsing DER chunk length byte: "
+					"read (%" G_GSIZE_FORMAT ") "
+					"available: ""(%" G_GSIZE_FORMAT ")",
+					offset, size);
+			break;
+		}
+
+		byte = *(data + offset++);
+
+		if (DER_LENGTH_IS_LONG_FORM(byte)) {
+			/* Long-form length encoding */
+			guint num_len_bytes = DER_LENGTH_LONG_FORM_SIZE(byte);
+			guint i;
+
+			/* Guard against overflowing the integer */
+			if (num_len_bytes > sizeof(guint)) {
+				purple_debug_error("tls-certificate",
+						"Number of long-form length "
+						"bytes greater than guint "
+						"size: %u > %" G_GSIZE_FORMAT,
+						num_len_bytes, sizeof(guint));
+				break;
+			}
+
+			/* Guard against reading past the end of the buffer */
+			if (offset + num_len_bytes > size) {
+				purple_debug_error("tls-certificate",
+						"Not enough remaining data "
+						"when parsing DER chunk "
+						"long-form length bytes: "
+						"read (%" G_GSIZE_FORMAT ") "
+						"available: ""(%"
+						G_GSIZE_FORMAT ")",
+						offset, size);
+				break;
+			}
+
+			length = 0;
+
+			for (i = 0; i < num_len_bytes; ++i) {
+				length = length << 8;
+				length |= *(data + offset++);
+			}
+		} else {
+			/* Short-form length encoding */
+			length = byte;
+		}
+		
+		/* Parse content */
+
+		if (offset + length > size) {
+			purple_debug_error("tls-certificate",
+					"Not enough remaining data when "
+					"parsing DER chunk content: "
+					"content size (%" G_GSIZE_FORMAT ") "
+					"available: ""(%" G_GSIZE_FORMAT ")",
+					length, size - offset);
+			break;
+		}
+
+		node->content = g_bytes_new_from_bytes(data_bytes,
+				offset, length);
+		offset += length;
+
+		/* Maybe recurse */
+		if (node->constructed) {
+			node->children = der_parse(node->content);
+
+			if (node->children == NULL) {
+				/* No children on a constructed type
+				 * should an error. If this happens, it
+				 * outputs debug info inside der_parse().
+				 */
+				break;
+			}
+		}
+
+		nodes = g_slist_append(nodes, node);
+		node = NULL;
+	}
+
+	if (node != NULL) {
+		/* There was an error. Free parsing data. */
+		der_node_data_free(node);
+		g_clear_pointer(&nodes, der_node_data_children_list_free);
+		/* FIXME: Report error to calling function ala GError? */
+	}
+
+	return nodes;
+}
+
+static gchar *
+der_parse_string(DerNodeData *node)
+{
+	const gchar *str;
+	gsize length = 0;
+
+	g_return_val_if_fail(node != NULL, NULL);
+	g_return_val_if_fail(node->content != NULL, NULL);
+
+	str = g_bytes_get_data(node->content, &length);
+	return g_strndup(str, length);
+}
+
+typedef struct {
+	gchar *oid;
+	gchar *value;
+} DerOIDValue;
+
+static void
+der_oid_value_free(DerOIDValue *data)
+{
+	g_return_if_fail(data != NULL);
+
+	g_clear_pointer(&data->oid, g_free);
+	g_clear_pointer(&data->value, g_free);
+
+	g_free(data);
+}
+
+static void
+der_oid_value_slist_free(GSList *list)
+{
+	g_return_if_fail(list != NULL);
+
+	g_slist_free_full(list, (GDestroyNotify)der_oid_value_free);
+}
+
+static const gchar *
+der_oid_value_slist_get_value_by_oid(GSList *list, const gchar *oid)
+{
+	for (; list != NULL; list = g_slist_next(list)) {
+		DerOIDValue *value = list->data;
+
+		if (!strcmp(oid, value->oid)) {
+			return value->value;
+		}
+	}
+
+	return NULL;
+}
+
+static gchar *
+der_parse_oid(DerNodeData *node)
+{
+	const gchar *oid_data;
+	gsize length = 0;
+	gsize offset = 0;
+	guint8 byte;
+	GString *ret;
+
+	g_return_val_if_fail(node != NULL, NULL);
+	g_return_val_if_fail(node->content != NULL, NULL);
+
+	oid_data = g_bytes_get_data(node->content, &length);
+	/* Most OIDs used for certificates aren't larger than 9 bytes */
+	ret = g_string_sized_new(9);
+
+	/* First byte is encoded as num1 * 40 + num2 */
+	if (length > 0) {
+		byte = *(oid_data + offset++);
+		g_string_append_printf(ret, "%u.%u", byte / 40, byte % 40);
+	}
+
+	/* Subsequent numbers are in base 128 format (the most
+	 * significant bit being set adds another 7 bits to the number)
+	 */
+	while (offset < length) {
+		guint value = 0;
+
+		do {
+			byte = *(oid_data + offset++);
+			value = (value << 7) + (byte & 0x7f);
+		} while (byte & 0x80 && offset < length);
+
+		g_string_append_printf(ret, ".%u", value);
+	}
+
+	return g_string_free(ret, FALSE);
+}
+
+/* Parses X.509 Issuer and Subject name structures
+ * into a GSList of DerOIDValue.
+ */
+static GSList *
+der_parse_name(DerNodeData *name_node)
+{
+	GSList *list;
+	GSList *ret = NULL;
+	DerOIDValue *value;
+
+	g_return_val_if_fail(name_node != NULL, NULL);
+
+	/* Iterate over items in the name sequence */
+	list = name_node->children;
+
+	while (list != NULL) {
+		DerNodeData *child_node;
+		GSList *child_list;
+
+		value = g_new(DerOIDValue, 1);
+
+		/* Each item in the name sequence is a set containing
+		 * a sequence of an ObjectID and a String-like value
+		 */
+
+		/* Get the DerNode containing set data */
+		if ((child_node = g_slist_nth_data(list, 0)) == NULL) {
+			break;
+		}
+
+		/* Get the DerNode containing its sequence data */
+		if (child_node == NULL ||
+				(child_node = g_slist_nth_data(
+				child_node->children, 0)) == NULL) {
+			break;
+		}
+
+		/* Get the GSList item containing the ObjectID DerNode  */
+		if ((child_list = child_node->children) == NULL) {
+			break;
+		}
+
+		/* Get the DerNode containing the ObjectID */
+		if ((child_node = child_list->data) == NULL) {
+			break;
+		}
+
+		/* Parse ObjectID */
+		value->oid = der_parse_oid(child_node);
+
+		/* Get the GSList item containing the String-like value */
+		if ((child_list = g_slist_next(child_list)) == NULL) {
+			break;
+		}
+
+		/* Get the DerNode containing the String-like value */
+		if ((child_node = child_list->data) == NULL) {
+			break;
+		}
+
+		/* Parse String-like value */
+		value->value = der_parse_string(child_node);
+
+		ret = g_slist_prepend(ret, value);
+		list = g_slist_next(list);
+		value = NULL;
+	}
+
+	if (value != NULL) {
+		der_oid_value_free(value);
+		der_oid_value_slist_free(ret);
+	}
+
+	return g_slist_reverse(ret);
+}
+
+static GDateTime *
+der_parse_time(DerNodeData *node)
+{
+	gchar *time;
+	gchar *c;
+	gint time_parts[7];
+	gint time_part_idx = 0;
+	int length;
+
+	g_return_val_if_fail(node != NULL, NULL);
+	g_return_val_if_fail(node->content != NULL, NULL);
+
+	time = der_parse_string(node);
+
+	/* For the purposes of X.509
+	 * UTCTime format is "YYMMDDhhmmssZ" (YY >= 50 ? 19YY : 20YY) and
+	 * GeneralizedTime format is "YYYYMMDDhhmmssZ"
+	 * According to RFC2459, they both are GMT, which is weird
+	 * considering one is named UTC, but for the purposes of display,
+	 * for which this is used, it shouldn't matter.
+	 */
+
+	length = strlen(time);
+	if (length == 13) {
+		/* UTCTime: Skip the first part as it's calculated later */
+		time_part_idx = 1;
+	} else if (length == 15) {
+		/* Generalized Time */
+		/* TODO: Handle generalized time
+		 * Maiku: None of the certificates I tested used this
+		 */
+		g_free(time);
+		g_return_val_if_reached(NULL);
+	} else {
+		purple_debug_error("tls-certificate",
+				"Unrecognized time format (length: %i)",
+				length);
+		g_free(time);
+		return NULL;
+	}
+
+	c = time;
+
+	while (c - time < length) {
+		if (*c == 'Z') {
+			break;
+		}
+
+		if (!g_ascii_isdigit(*c) || !g_ascii_isdigit(*(c + 1))) {
+			purple_debug_error("tls-certificate",
+					"Error parsing time. next characters "
+					"aren't both digits: '%c%c'",
+					*c, *(c + 1));
+			break;
+		}
+
+		time_parts[time_part_idx++] =
+				g_ascii_digit_value(*c) * 10 +
+				g_ascii_digit_value(*(c + 1));
+		c += 2;
+	}
+
+	if (length == 13) {
+		if (time_parts[1] >= 50) {
+			time_parts[0] = 19;
+		} else {
+			time_parts[0] = 20;
+		}
+	}
+
+	return g_date_time_new_utc(
+			time_parts[0] * 100 + time_parts[1],	/* year */
+			time_parts[2],				/* month */
+			time_parts[3],				/* day */
+			time_parts[4],				/* hour */	
+			time_parts[5],				/* minute */
+			time_parts[6]);				/* seconds */
+}
+
+/* This structure contains the data which is in an X.509 certificate.
+ * Only the values actually parsed/used are here. The remaining commented
+ * out values are informative placeholders for the remaining data that
+ * could be in a standard certificate.
+ */
+struct _PurpleTlsCertificateInfo {
+	GTlsCertificate *cert;
+
+	/* version (Optional, defaults to version 1 (version = value + 1)) */
+	/* serialNumber */
+	/* signature */
+	GSList *issuer;
+	GDateTime *notBefore;
+	GDateTime *notAfter;
+	GSList *subject;
+	/* subjectPublicKeyInfo */
+	/* issuerUniqueIdentifier (Optional, requires version 2 or 3) */
+	/* subjectUniqueIdentifier (Optional, requires version 2 or 3) */
+	/* extensions (Optional, requires version 3) */
+};
+
+/* TODO: Make better API for this? */
+PurpleTlsCertificateInfo *
+purple_tls_certificate_get_info(GTlsCertificate *certificate)
+{
+	GByteArray *der_array = NULL;
+	GBytes *root;
+	GSList *nodes;
+	DerNodeData *node;
+	DerNodeData *cert_node;
+	DerNodeData *valid_node;
+	PurpleTlsCertificateInfo *info;
+
+	g_return_val_if_fail(G_IS_TLS_CERTIFICATE(certificate), NULL);
+
+	/* Get raw bytes from DER formatted certificate */
+	g_object_get(certificate, "certificate", &der_array, NULL);
+
+	/* Parse raw bytes into DerNode tree */
+	root = g_byte_array_free_to_bytes(der_array);
+	nodes = der_parse(root);
+	g_bytes_unref(root);
+
+	if (nodes == NULL) {
+		purple_debug_warning("tls-certificate",
+				"Error parsing certificate");
+		return NULL;
+	}
+
+	/* Set up PurpleTlsCertificateInfo struct with initial data */
+	info = g_new0(PurpleTlsCertificateInfo, 1);
+	info->cert = g_object_ref(certificate);
+
+	/* Get certificate root sequence GSList item */
+	node = g_slist_nth_data(nodes, 0);
+	if (node == NULL || node->children == NULL) {
+		purple_debug_warning("tls-certificate",
+				"Error parsing certificate root node");
+		purple_tls_certificate_info_free(info);
+		return NULL;
+	}
+
+	/* Get certificate sequence GSList DerNode */
+	cert_node = g_slist_nth_data(node->children, 0);
+	if (cert_node == NULL || cert_node->children == NULL) {
+		purple_debug_warning("tls-certificate",
+				"Error to parsing certificate node");
+		purple_tls_certificate_info_free(info);
+		return NULL;
+	}
+
+	/* Check for optional certificate version */
+
+	node = g_slist_nth_data(cert_node->children, 0);
+	if (node == NULL || node->children == NULL) {
+		purple_debug_warning("tls-certificate",
+				"Error to parsing certificate version node");
+		purple_tls_certificate_info_free(info);
+		return NULL;
+	}
+
+	if (node->type_class != DER_TYPE_CLASS_CONTEXT_SPECIFIC) {
+		/* Include optional version so indices work right */
+		/* TODO: Actually set default version value? */
+		cert_node->children =
+				g_slist_prepend(cert_node->children, NULL);
+	}
+
+	/* Get certificate issuer */
+
+	node = g_slist_nth_data(cert_node->children, 3);
+	if (node == NULL || node->children == NULL) {
+		purple_debug_warning("tls-certificate",
+				"Error to parsing certificate issuer node");
+		purple_tls_certificate_info_free(info);
+		return NULL;
+	}
+
+	info->issuer = der_parse_name(node);
+
+	/* Get certificate validity */
+
+	valid_node = g_slist_nth_data(cert_node->children, 4);
+	if (valid_node == NULL || valid_node->children == NULL) {
+		purple_debug_warning("tls-certificate",
+				"Error to parsing certificate validity node");
+		purple_tls_certificate_info_free(info);
+		return NULL;
+	}
+
+	/* Get certificate validity (notBefore) */
+	node = g_slist_nth_data(valid_node->children, 0);
+	if (node == NULL) {
+		purple_debug_warning("tls-certificate",
+				"Error to parsing certificate valid "
+				"notBefore node");
+		purple_tls_certificate_info_free(info);
+		return NULL;
+	}
+
+	info->notBefore = der_parse_time(node);
+
+	/* Get certificate validity (notAfter) */
+	node = g_slist_nth_data(valid_node->children, 1);
+	if (node == NULL) {
+		purple_debug_warning("tls-certificate",
+				"Error to parsing certificate valid "
+				"notAfter node");
+		purple_tls_certificate_info_free(info);
+		return NULL;
+	}
+
+	info->notAfter = der_parse_time(node);
+
+	/* Get certificate subject */
+
+	node = g_slist_nth_data(cert_node->children, 5);
+	if (node == NULL || node->children == NULL) {
+		purple_debug_warning("tls-certificate",
+				"Error to parsing certificate subject node");
+		purple_tls_certificate_info_free(info);
+		return NULL;
+	}
+
+	info->subject = der_parse_name(node);
+
+	/* Clean up */
+	der_node_data_children_list_free(nodes);
+
+	return info;
+}
+
+void
+purple_tls_certificate_info_free(PurpleTlsCertificateInfo *info)
+{
+	g_return_if_fail(info != NULL);
+
+	g_clear_object(&info->cert);
+
+	g_clear_pointer(&info->issuer, der_oid_value_slist_free);
+	g_clear_pointer(&info->notBefore, g_date_time_unref);
+	g_clear_pointer(&info->notAfter, g_date_time_unref);
+	g_clear_pointer(&info->subject, der_oid_value_slist_free);
+
+	g_free(info);
+}
+
+/* Looks up the relative distinguished name (RDN) from an ObjectID */
+static const gchar *
+lookup_rdn_name_by_oid(const gchar *oid)
+{
+	static GHashTable *ht = NULL;
+
+	if (G_UNLIKELY(ht == NULL)) {
+		ht = g_hash_table_new_full(g_str_hash, g_str_equal,
+				NULL, NULL);
+
+		/* commonName */
+		g_hash_table_insert(ht, "2.5.4.3", "CN");
+		/* countryName */
+		g_hash_table_insert(ht, "2.5.4.6", "C");
+		/* localityName */
+		g_hash_table_insert(ht, "2.5.4.7", "L");
+		/* stateOrProvinceName */
+		g_hash_table_insert(ht, "2.5.4.8", "ST");
+		/* organizationName */
+		g_hash_table_insert(ht, "2.5.4.10", "O");
+		/* organizationalUnitName */
+		g_hash_table_insert(ht, "2.5.4.11", "OU");
+	}
+
+	return g_hash_table_lookup(ht, oid);
+}
+
+/* Makes a distinguished name (DN) from
+ * a list of relative distinguished names (RDN).
+ * Order matters.
+ */
+static gchar *
+make_dn_from_oid_value_slist(GSList *list)
+{
+	GString *str = g_string_new(NULL);
+
+	for (; list != NULL; list = g_slist_next(list)) {
+		DerOIDValue *value = list->data;
+		const gchar *name;
+		gchar *new_value;
+
+		if (value == NULL) {
+			purple_debug_error("tls-certificate",
+					"DerOIDValue data missing from GSList");
+			continue;
+		}
+
+		name = lookup_rdn_name_by_oid(value->oid);
+		/* Escape commas in value as that's the DN separator */
+		new_value = purple_strreplace(value->value, ",", "\\,");
+		g_string_append_printf(str, "%s=%s,", name, new_value);
+		g_free(new_value);
+	}
+
+	/* Remove trailing comma */
+	g_string_truncate(str, str->len - 1);
+
+	return g_string_free(str, FALSE);
+}
+
+static gchar *
+purple_tls_certificate_info_get_issuer_dn(PurpleTlsCertificateInfo *info)
+{
+	g_return_val_if_fail(info != NULL, NULL);
+	g_return_val_if_fail(info->issuer != NULL, NULL);
+
+	return make_dn_from_oid_value_slist(info->issuer);
+}
+
+gchar *
+purple_tls_certificate_info_get_display_string(PurpleTlsCertificateInfo *info)
+{
+	gchar *subject_name;
+	gchar *issuer_name = NULL;
+	GByteArray *sha1_bytes;
+	gchar *sha1_str = NULL;
+	gchar *activation_time;
+	gchar *expiration_time;
+	gchar *ret;
+
+	g_return_val_if_fail(info != NULL, NULL);
+
+	/* Getting the commonName of a CA supposedly doesn't work, but we
+	 * shouldn't be dealing with those here anyway.
+	 */
+	subject_name = purple_tls_certificate_info_get_subject_name(info);
+
+	issuer_name = purple_tls_certificate_info_get_issuer_dn(info);
+
+	sha1_bytes = purple_tls_certificate_get_fingerprint_sha1(info->cert);
+	if (sha1_bytes != NULL) {
+		sha1_str = purple_base16_encode_chunked(sha1_bytes->data,
+				sha1_bytes->len);
+		g_byte_array_unref(sha1_bytes);
+	}
+
+	activation_time = g_date_time_format(info->notBefore, "%c");
+	expiration_time = g_date_time_format(info->notAfter, "%c");
+
+	ret = g_strdup_printf(
+			_("Common name: %s\n\n"
+			  "Issued by: %s\n\n"
+			  "Fingerprint (SHA1): %s\n\n"
+			  "Activation date: %s\n"
+			  "Expiriation date: %s\n"),
+			subject_name,
+			issuer_name,
+			sha1_str,
+			activation_time,
+			expiration_time);
+
+	g_free(subject_name);
+	g_free(issuer_name);
+	g_free(sha1_str);
+	g_free(activation_time);
+	g_free(expiration_time);
+
+	return ret;
+}
+
+/* TODO: Make better API for this? */
+gchar *
+purple_tls_certificate_info_get_subject_name(PurpleTlsCertificateInfo *info)
+{
+	g_return_val_if_fail(info != NULL, NULL);
+	g_return_val_if_fail(info->subject != NULL, NULL);
+
+	/* commonName component of the subject */
+	return g_strdup(der_oid_value_slist_get_value_by_oid(info->subject,
+			"2.5.4.3"));
+}
+
+/* TODO: Make better API for this? */
+GByteArray *
+purple_tls_certificate_get_fingerprint_sha1(GTlsCertificate *certificate)
+{
+	PurpleHash *hash;
+	GByteArray *der = NULL;
+	guint8 *data = NULL;
+	gsize buf_size = 0;
+
+	g_return_val_if_fail(G_IS_TLS_CERTIFICATE(certificate), NULL);
+
+	g_object_get(certificate, "certificate", &der, NULL);
+
+	g_return_val_if_fail(der != NULL, NULL);
+
+	hash = purple_sha1_hash_new();
+
+	buf_size = purple_hash_get_digest_size(hash);
+	data = g_malloc(buf_size);
+
+	purple_hash_append(hash, der->data, der->len);
+	g_byte_array_unref(der);
+
+	purple_hash_digest(hash, data, buf_size);
+	g_object_unref(hash);
+
+	return g_byte_array_new_take(data, buf_size);
+}
+
--- a/libpurple/tls-certificate.h	Tue Feb 16 17:48:03 2016 -0600
+++ b/libpurple/tls-certificate.h	Tue Mar 29 22:51:43 2016 -0500
@@ -118,6 +118,70 @@
 gpointer
 purple_tls_certificate_attach_to_socket_client(GSocketClient *client);
 
+
+/**
+ * PurpleTlsCertificateInfo
+ *
+ * An opaque structure to contain parsed certificate info, which
+ * can subsequently be accessed by purple_tls_certificate_info_*
+ * functions.
+ */
+typedef struct _PurpleTlsCertificateInfo PurpleTlsCertificateInfo;
+
+/**
+ * purple_tls_certificate_get_info:
+ * @certificate: Certificate from which to parse the info
+ *
+ * Returns a #PurpleTlsCertificateInfo containing parsed information
+ * of the certificate.
+ *
+ * Returns: #PurpleTlsCertificateInfo parsed from the certificate
+ */
+PurpleTlsCertificateInfo *
+purple_tls_certificate_get_info(GTlsCertificate *certificate);
+
+/**
+ * purple_tls_certificate_info_free:
+ * @info: #PurpleTlsCertificateInfo object to free
+ *
+ * Frees @info.
+ */
+void
+purple_tls_certificate_info_free(PurpleTlsCertificateInfo *info);
+
+/**
+ * purple_tls_certificate_info_get_display_string:
+ * @info: #PurpleTlsCertificateInfo from which to generate a display string
+ *
+ * Generates a user readable string to display information from @info
+ *
+ * Returns: A user readable string suitable to display to the user
+ */
+gchar *
+purple_tls_certificate_info_get_display_string(PurpleTlsCertificateInfo *info);
+
+/**
+ * purple_tls_certificate_get_subject_name:
+ * @certificate: Certificate from which to get the subject name
+ *
+ * Returns the common subject name of the cert
+ *
+ * Returns: The subject name of the cert
+ */
+gchar *
+purple_tls_certificate_info_get_subject_name(PurpleTlsCertificateInfo *info);
+
+/**
+ * purple_tls_certificate_get_fingerprint_sha1:
+ * @certificate: Certificate from which to get the SHA1 fingerprint
+ *
+ * Returns the SHA1 fingerprint of the cert
+ *
+ * Returns: The SHA1 fingerprint of the cert
+ */
+GByteArray *
+purple_tls_certificate_get_fingerprint_sha1(GTlsCertificate *certificate);
+
 G_END_DECLS
 
 #endif /* _PURPLE_TLS_CERTIFICATE_H */

mercurial