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

Mike Ruprecht <[email protected]>
Newsgroups gmane.comp.gnome.gaim.cvs
Message-ID <[email protected]>
Changeset: 14d1273cae744a570a92570761665f4b02aa2f3f
Author:	 Mike Ruprecht <[email protected]>
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;

_______________________________________________
Commits mailing list
[email protected]
https://pidgin.im/cgi-bin/mailman/listinfo/commits
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.