--- 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); -} -