/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