/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