libpurple/tls-certificate.c

branch
purple-ssl-to-gio
changeset 37632
4879ef4db7cf
parent 37627
a1591cb7c087
child 38870
575e41bfc15e
--- a/libpurple/tls-certificate.c	Thu Mar 31 20:18:02 2016 -0500
+++ b/libpurple/tls-certificate.c	Thu Apr 07 01:29:49 2016 -0500
@@ -23,7 +23,6 @@
 
 #include "internal.h"
 #include "tls-certificate.h"
-#include "ciphers/sha1hash.h"
 #include "debug.h"
 #include "util.h"
 
@@ -389,753 +388,3 @@
 			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);
-}
-

mercurial