/pidgin/main: 14d1273cae74: Add TLS Certificate parsing API

Mike Ruprecht cmaiku at gmail.com
Thu Apr 7 13:36:20 EDT 2016


Changeset: 14d1273cae744a570a92570761665f4b02aa2f3f
Author:	 Mike Ruprecht <cmaiku at gmail.com>
Date:	 2016-03-29 22:51 -0500
Branch:	 purple-ssl-to-gio
URL: https://hg.pidgin.im/pidgin/main/rev/14d1273cae74

Description:

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.

diffstat:

 libpurple/tls-certificate.c |  751 ++++++++++++++++++++++++++++++++++++++++++++
 libpurple/tls-certificate.h |   64 +++
 2 files changed, 815 insertions(+), 0 deletions(-)

diffs (truncated from 839 to 300 lines):

diff --git a/libpurple/tls-certificate.c b/libpurple/tls-certificate.c
--- a/libpurple/tls-certificate.c
+++ b/libpurple/tls-certificate.c
@@ -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 @@ purple_tls_certificate_attach_to_socket_
 			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;



More information about the Commits mailing list