Mon, 27 Nov 2017 20:55:41 -0600
Remove all of the #if 0'd blocks. All were 10 years old and don't seem necessary
/* * * purple * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "tls-certificate-info.h" #include "debug.h" #include "util.h" #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 DerOIDValue * der_oid_value_copy(DerOIDValue *data) { DerOIDValue *ret; g_return_val_if_fail(data != NULL, NULL); ret = g_new0(DerOIDValue, 1); ret->oid = g_strdup(data->oid); ret->value = g_strdup(data->value); return ret; } 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 = NULL; 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); memset(time_parts, 0, sizeof(time_parts)); 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; } static PurpleTlsCertificateInfo * purple_tls_certificate_info_copy(PurpleTlsCertificateInfo *info) { PurpleTlsCertificateInfo *ret; g_return_val_if_fail(info != NULL, NULL); ret = g_new0(PurpleTlsCertificateInfo, 1); ret->issuer = g_slist_copy_deep(info->issuer, (GCopyFunc)der_oid_value_copy, NULL); ret->notBefore = g_date_time_ref(info->notBefore); ret->notAfter = g_date_time_ref(info->notAfter); ret->subject = g_slist_copy_deep(info->subject, (GCopyFunc)der_oid_value_copy, NULL); return ret; } 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); } G_DEFINE_BOXED_TYPE(PurpleTlsCertificateInfo, purple_tls_certificate_info, purple_tls_certificate_info_copy, purple_tls_certificate_info_free); /* 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) { GChecksum *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 = g_checksum_new(G_CHECKSUM_SHA1); buf_size = g_checksum_type_get_length(G_CHECKSUM_SHA1); data = g_malloc(buf_size); g_checksum_update(hash, der->data, der->len); g_byte_array_unref(der); g_checksum_get_digest(hash, data, &buf_size); g_checksum_free(hash); return g_byte_array_new_take(data, buf_size); }