Merged default into xdg-dirs
[pidgin-git.git] / libpurple / tls-certificate-info.c
blob6db4ac3e7b8247c014e900545aa5baabbaa0acce
1 /*
3 * purple
5 * Purple is the legal property of its developers, whose names are too numerous
6 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * source distribution.
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include "internal.h"
25 #include "tls-certificate-info.h"
26 #include "debug.h"
27 #include "util.h"
29 #define DER_TYPE_CLASS(type) (type & 0xc0)
31 #define DER_TYPE_CLASS_UNIVERSAL 0x00
32 #define DER_TYPE_CLASS_APPLICATION 0x40
33 #define DER_TYPE_CLASS_CONTEXT_SPECIFIC 0x80
34 #define DER_TYPE_CLASS_PRIVATE 0xc0
36 #define DER_TYPE_TAG(type) (type & 0x1f)
38 #define DER_TYPE_IS_CONSTRUCTED(type) ((type & 0x20) ? TRUE : FALSE)
40 #define DER_TYPE_TAG_IS_LONG_FORM(type) (DER_TYPE_TAG(type) == 0x1f)
42 #define DER_LENGTH_IS_LONG_FORM(byte) ((byte & 0x80) ? TRUE : FALSE)
43 #define DER_LENGTH_LONG_FORM_SIZE(byte) (byte & 0x7f)
45 typedef struct {
46 guint8 type_class;
47 gboolean constructed;
48 guint type;
49 GBytes *content;
50 GSList *children;
51 } DerNodeData;
53 static void der_node_data_children_list_free(GSList *children);
55 static void
56 der_node_data_free(DerNodeData *node_data)
58 g_return_if_fail(node_data != NULL);
60 g_clear_pointer(&node_data->content, g_bytes_unref);
61 g_clear_pointer(&node_data->children,
62 der_node_data_children_list_free);
64 g_free(node_data);
67 static void
68 der_node_data_children_list_free(GSList *children)
70 g_return_if_fail(children != NULL);
72 g_slist_free_full(children, (GDestroyNotify)der_node_data_free);
75 /* Parses DER encoded data into a GSList of DerNodeData instances */
76 static GSList *
77 der_parse(GBytes *data_bytes)
79 const guint8 *data;
80 gsize size = 0;
81 gsize offset = 0;
82 GSList *nodes = NULL;
83 DerNodeData *node = NULL;
85 data = g_bytes_get_data(data_bytes, &size);
87 /* Parse data */
88 while (offset < size) {
89 guint8 byte;
90 gsize length;
92 /* Parse type */
94 byte = *(data + offset++);
95 node = g_new0(DerNodeData, 1);
96 node->type_class = DER_TYPE_CLASS(byte);
97 node->constructed = DER_TYPE_IS_CONSTRUCTED(byte);
99 if (DER_TYPE_TAG_IS_LONG_FORM(byte)) {
100 /* Long-form type encoding */
101 /* TODO: Handle long-form encoding.
102 * Maiku: The certificates I tested didn't do this.
104 g_return_val_if_reached(NULL);
105 } else {
106 /* Short-form type encoding */
107 node->type = DER_TYPE_TAG(byte);
110 /* Parse content length */
112 if (offset >= size) {
113 purple_debug_error("tls-certificate",
114 "Not enough remaining data when "
115 "parsing DER chunk length byte: "
116 "read (%" G_GSIZE_FORMAT ") "
117 "available: ""(%" G_GSIZE_FORMAT ")",
118 offset, size);
119 break;
122 byte = *(data + offset++);
124 if (DER_LENGTH_IS_LONG_FORM(byte)) {
125 /* Long-form length encoding */
126 guint num_len_bytes = DER_LENGTH_LONG_FORM_SIZE(byte);
127 guint i;
129 /* Guard against overflowing the integer */
130 if (num_len_bytes > sizeof(guint)) {
131 purple_debug_error("tls-certificate",
132 "Number of long-form length "
133 "bytes greater than guint "
134 "size: %u > %" G_GSIZE_FORMAT,
135 num_len_bytes, sizeof(guint));
136 break;
139 /* Guard against reading past the end of the buffer */
140 if (offset + num_len_bytes > size) {
141 purple_debug_error("tls-certificate",
142 "Not enough remaining data "
143 "when parsing DER chunk "
144 "long-form length bytes: "
145 "read (%" G_GSIZE_FORMAT ") "
146 "available: ""(%"
147 G_GSIZE_FORMAT ")",
148 offset, size);
149 break;
152 length = 0;
154 for (i = 0; i < num_len_bytes; ++i) {
155 length = length << 8;
156 length |= *(data + offset++);
158 } else {
159 /* Short-form length encoding */
160 length = byte;
163 /* Parse content */
165 if (offset + length > size) {
166 purple_debug_error("tls-certificate",
167 "Not enough remaining data when "
168 "parsing DER chunk content: "
169 "content size (%" G_GSIZE_FORMAT ") "
170 "available: ""(%" G_GSIZE_FORMAT ")",
171 length, size - offset);
172 break;
175 node->content = g_bytes_new_from_bytes(data_bytes,
176 offset, length);
177 offset += length;
179 /* Maybe recurse */
180 if (node->constructed) {
181 node->children = der_parse(node->content);
183 if (node->children == NULL) {
184 /* No children on a constructed type
185 * should an error. If this happens, it
186 * outputs debug info inside der_parse().
188 break;
192 nodes = g_slist_append(nodes, node);
193 node = NULL;
196 if (node != NULL) {
197 /* There was an error. Free parsing data. */
198 der_node_data_free(node);
199 g_clear_pointer(&nodes, der_node_data_children_list_free);
200 /* FIXME: Report error to calling function ala GError? */
203 return nodes;
206 static gchar *
207 der_parse_string(DerNodeData *node)
209 const gchar *str;
210 gsize length = 0;
212 g_return_val_if_fail(node != NULL, NULL);
213 g_return_val_if_fail(node->content != NULL, NULL);
215 str = g_bytes_get_data(node->content, &length);
216 return g_strndup(str, length);
219 typedef struct {
220 gchar *oid;
221 gchar *value;
222 } DerOIDValue;
224 static DerOIDValue *
225 der_oid_value_copy(DerOIDValue *data)
227 DerOIDValue *ret;
229 g_return_val_if_fail(data != NULL, NULL);
231 ret = g_new0(DerOIDValue, 1);
232 ret->oid = g_strdup(data->oid);
233 ret->value = g_strdup(data->value);
234 return ret;
237 static void
238 der_oid_value_free(DerOIDValue *data)
240 g_return_if_fail(data != NULL);
242 g_clear_pointer(&data->oid, g_free);
243 g_clear_pointer(&data->value, g_free);
245 g_free(data);
248 static void
249 der_oid_value_slist_free(GSList *list)
251 g_return_if_fail(list != NULL);
253 g_slist_free_full(list, (GDestroyNotify)der_oid_value_free);
256 static const gchar *
257 der_oid_value_slist_get_value_by_oid(GSList *list, const gchar *oid)
259 for (; list != NULL; list = g_slist_next(list)) {
260 DerOIDValue *value = list->data;
262 if (!strcmp(oid, value->oid)) {
263 return value->value;
267 return NULL;
270 static gchar *
271 der_parse_oid(DerNodeData *node)
273 const gchar *oid_data;
274 gsize length = 0;
275 gsize offset = 0;
276 guint8 byte;
277 GString *ret;
279 g_return_val_if_fail(node != NULL, NULL);
280 g_return_val_if_fail(node->content != NULL, NULL);
282 oid_data = g_bytes_get_data(node->content, &length);
283 /* Most OIDs used for certificates aren't larger than 9 bytes */
284 ret = g_string_sized_new(9);
286 /* First byte is encoded as num1 * 40 + num2 */
287 if (length > 0) {
288 byte = *(oid_data + offset++);
289 g_string_append_printf(ret, "%u.%u", byte / 40, byte % 40);
292 /* Subsequent numbers are in base 128 format (the most
293 * significant bit being set adds another 7 bits to the number)
295 while (offset < length) {
296 guint value = 0;
298 do {
299 byte = *(oid_data + offset++);
300 value = (value << 7) + (byte & 0x7f);
301 } while (byte & 0x80 && offset < length);
303 g_string_append_printf(ret, ".%u", value);
306 return g_string_free(ret, FALSE);
309 /* Parses X.509 Issuer and Subject name structures
310 * into a GSList of DerOIDValue.
312 static GSList *
313 der_parse_name(DerNodeData *name_node)
315 GSList *list;
316 GSList *ret = NULL;
317 DerOIDValue *value = NULL;
319 g_return_val_if_fail(name_node != NULL, NULL);
321 /* Iterate over items in the name sequence */
322 list = name_node->children;
324 while (list != NULL) {
325 DerNodeData *child_node;
326 GSList *child_list;
328 value = g_new(DerOIDValue, 1);
330 /* Each item in the name sequence is a set containing
331 * a sequence of an ObjectID and a String-like value
334 /* Get the DerNode containing set data */
335 if ((child_node = g_slist_nth_data(list, 0)) == NULL) {
336 break;
339 /* Get the DerNode containing its sequence data */
340 if (child_node == NULL ||
341 (child_node = g_slist_nth_data(
342 child_node->children, 0)) == NULL) {
343 break;
346 /* Get the GSList item containing the ObjectID DerNode */
347 if ((child_list = child_node->children) == NULL) {
348 break;
351 /* Get the DerNode containing the ObjectID */
352 if ((child_node = child_list->data) == NULL) {
353 break;
356 /* Parse ObjectID */
357 value->oid = der_parse_oid(child_node);
359 /* Get the GSList item containing the String-like value */
360 if ((child_list = g_slist_next(child_list)) == NULL) {
361 break;
364 /* Get the DerNode containing the String-like value */
365 if ((child_node = child_list->data) == NULL) {
366 break;
369 /* Parse String-like value */
370 value->value = der_parse_string(child_node);
372 ret = g_slist_prepend(ret, value);
373 list = g_slist_next(list);
374 value = NULL;
377 if (value != NULL) {
378 der_oid_value_free(value);
379 der_oid_value_slist_free(ret);
382 return g_slist_reverse(ret);
385 static GDateTime *
386 der_parse_time(DerNodeData *node)
388 gchar *time;
389 gchar *c;
390 gint time_parts[7];
391 gint time_part_idx = 0;
392 int length;
394 g_return_val_if_fail(node != NULL, NULL);
395 g_return_val_if_fail(node->content != NULL, NULL);
397 memset(time_parts, 0, sizeof(time_parts));
399 time = der_parse_string(node);
401 /* For the purposes of X.509
402 * UTCTime format is "YYMMDDhhmmssZ" (YY >= 50 ? 19YY : 20YY) and
403 * GeneralizedTime format is "YYYYMMDDhhmmssZ"
404 * According to RFC2459, they both are GMT, which is weird
405 * considering one is named UTC, but for the purposes of display,
406 * for which this is used, it shouldn't matter.
409 length = strlen(time);
410 if (length == 13) {
411 /* UTCTime: Skip the first part as it's calculated later */
412 time_part_idx = 1;
413 } else if (length == 15) {
414 /* Generalized Time */
415 /* TODO: Handle generalized time
416 * Maiku: None of the certificates I tested used this
418 g_free(time);
419 g_return_val_if_reached(NULL);
420 } else {
421 purple_debug_error("tls-certificate",
422 "Unrecognized time format (length: %i)",
423 length);
424 g_free(time);
425 return NULL;
428 c = time;
430 while (c - time < length) {
431 if (*c == 'Z') {
432 break;
435 if (!g_ascii_isdigit(*c) || !g_ascii_isdigit(*(c + 1))) {
436 purple_debug_error("tls-certificate",
437 "Error parsing time. next characters "
438 "aren't both digits: '%c%c'",
439 *c, *(c + 1));
440 break;
443 time_parts[time_part_idx++] =
444 g_ascii_digit_value(*c) * 10 +
445 g_ascii_digit_value(*(c + 1));
446 c += 2;
449 if (length == 13) {
450 if (time_parts[1] >= 50) {
451 time_parts[0] = 19;
452 } else {
453 time_parts[0] = 20;
457 return g_date_time_new_utc(
458 time_parts[0] * 100 + time_parts[1], /* year */
459 time_parts[2], /* month */
460 time_parts[3], /* day */
461 time_parts[4], /* hour */
462 time_parts[5], /* minute */
463 time_parts[6]); /* seconds */
466 /* This structure contains the data which is in an X.509 certificate.
467 * Only the values actually parsed/used are here. The remaining commented
468 * out values are informative placeholders for the remaining data that
469 * could be in a standard certificate.
471 struct _PurpleTlsCertificateInfo {
472 GTlsCertificate *cert;
474 /* version (Optional, defaults to version 1 (version = value + 1)) */
475 /* serialNumber */
476 /* signature */
477 GSList *issuer;
478 GDateTime *notBefore;
479 GDateTime *notAfter;
480 GSList *subject;
481 /* subjectPublicKeyInfo */
482 /* issuerUniqueIdentifier (Optional, requires version 2 or 3) */
483 /* subjectUniqueIdentifier (Optional, requires version 2 or 3) */
484 /* extensions (Optional, requires version 3) */
487 /* TODO: Make better API for this? */
488 PurpleTlsCertificateInfo *
489 purple_tls_certificate_get_info(GTlsCertificate *certificate)
491 GByteArray *der_array = NULL;
492 GBytes *root;
493 GSList *nodes;
494 DerNodeData *node;
495 DerNodeData *cert_node;
496 DerNodeData *valid_node;
497 PurpleTlsCertificateInfo *info;
499 g_return_val_if_fail(G_IS_TLS_CERTIFICATE(certificate), NULL);
501 /* Get raw bytes from DER formatted certificate */
502 g_object_get(certificate, "certificate", &der_array, NULL);
504 /* Parse raw bytes into DerNode tree */
505 root = g_byte_array_free_to_bytes(der_array);
506 nodes = der_parse(root);
507 g_bytes_unref(root);
509 if (nodes == NULL) {
510 purple_debug_warning("tls-certificate",
511 "Error parsing certificate");
512 return NULL;
515 /* Set up PurpleTlsCertificateInfo struct with initial data */
516 info = g_new0(PurpleTlsCertificateInfo, 1);
517 info->cert = g_object_ref(certificate);
519 /* Get certificate root sequence GSList item */
520 node = g_slist_nth_data(nodes, 0);
521 if (node == NULL || node->children == NULL) {
522 purple_debug_warning("tls-certificate",
523 "Error parsing certificate root node");
524 purple_tls_certificate_info_free(info);
525 return NULL;
528 /* Get certificate sequence GSList DerNode */
529 cert_node = g_slist_nth_data(node->children, 0);
530 if (cert_node == NULL || cert_node->children == NULL) {
531 purple_debug_warning("tls-certificate",
532 "Error to parsing certificate node");
533 purple_tls_certificate_info_free(info);
534 return NULL;
537 /* Check for optional certificate version */
539 node = g_slist_nth_data(cert_node->children, 0);
540 if (node == NULL || node->children == NULL) {
541 purple_debug_warning("tls-certificate",
542 "Error to parsing certificate version node");
543 purple_tls_certificate_info_free(info);
544 return NULL;
547 if (node->type_class != DER_TYPE_CLASS_CONTEXT_SPECIFIC) {
548 /* Include optional version so indices work right */
549 /* TODO: Actually set default version value? */
550 cert_node->children =
551 g_slist_prepend(cert_node->children, NULL);
554 /* Get certificate issuer */
556 node = g_slist_nth_data(cert_node->children, 3);
557 if (node == NULL || node->children == NULL) {
558 purple_debug_warning("tls-certificate",
559 "Error to parsing certificate issuer node");
560 purple_tls_certificate_info_free(info);
561 return NULL;
564 info->issuer = der_parse_name(node);
566 /* Get certificate validity */
568 valid_node = g_slist_nth_data(cert_node->children, 4);
569 if (valid_node == NULL || valid_node->children == NULL) {
570 purple_debug_warning("tls-certificate",
571 "Error to parsing certificate validity node");
572 purple_tls_certificate_info_free(info);
573 return NULL;
576 /* Get certificate validity (notBefore) */
577 node = g_slist_nth_data(valid_node->children, 0);
578 if (node == NULL) {
579 purple_debug_warning("tls-certificate",
580 "Error to parsing certificate valid "
581 "notBefore node");
582 purple_tls_certificate_info_free(info);
583 return NULL;
586 info->notBefore = der_parse_time(node);
588 /* Get certificate validity (notAfter) */
589 node = g_slist_nth_data(valid_node->children, 1);
590 if (node == NULL) {
591 purple_debug_warning("tls-certificate",
592 "Error to parsing certificate valid "
593 "notAfter node");
594 purple_tls_certificate_info_free(info);
595 return NULL;
598 info->notAfter = der_parse_time(node);
600 /* Get certificate subject */
602 node = g_slist_nth_data(cert_node->children, 5);
603 if (node == NULL || node->children == NULL) {
604 purple_debug_warning("tls-certificate",
605 "Error to parsing certificate subject node");
606 purple_tls_certificate_info_free(info);
607 return NULL;
610 info->subject = der_parse_name(node);
612 /* Clean up */
613 der_node_data_children_list_free(nodes);
615 return info;
618 static PurpleTlsCertificateInfo *
619 purple_tls_certificate_info_copy(PurpleTlsCertificateInfo *info)
621 PurpleTlsCertificateInfo *ret;
623 g_return_val_if_fail(info != NULL, NULL);
625 ret = g_new0(PurpleTlsCertificateInfo, 1);
626 ret->issuer = g_slist_copy_deep(info->issuer,
627 (GCopyFunc)der_oid_value_copy, NULL);
628 ret->notBefore = g_date_time_ref(info->notBefore);
629 ret->notAfter = g_date_time_ref(info->notAfter);
630 ret->subject = g_slist_copy_deep(info->subject,
631 (GCopyFunc)der_oid_value_copy, NULL);
633 return ret;
636 void
637 purple_tls_certificate_info_free(PurpleTlsCertificateInfo *info)
639 g_return_if_fail(info != NULL);
641 g_clear_object(&info->cert);
643 g_clear_pointer(&info->issuer, der_oid_value_slist_free);
644 g_clear_pointer(&info->notBefore, g_date_time_unref);
645 g_clear_pointer(&info->notAfter, g_date_time_unref);
646 g_clear_pointer(&info->subject, der_oid_value_slist_free);
648 g_free(info);
651 G_DEFINE_BOXED_TYPE(PurpleTlsCertificateInfo, purple_tls_certificate_info,
652 purple_tls_certificate_info_copy,
653 purple_tls_certificate_info_free);
655 /* Looks up the relative distinguished name (RDN) from an ObjectID */
656 static const gchar *
657 lookup_rdn_name_by_oid(const gchar *oid)
659 static GHashTable *ht = NULL;
661 if (G_UNLIKELY(ht == NULL)) {
662 ht = g_hash_table_new_full(g_str_hash, g_str_equal,
663 NULL, NULL);
665 /* commonName */
666 g_hash_table_insert(ht, "2.5.4.3", "CN");
667 /* countryName */
668 g_hash_table_insert(ht, "2.5.4.6", "C");
669 /* localityName */
670 g_hash_table_insert(ht, "2.5.4.7", "L");
671 /* stateOrProvinceName */
672 g_hash_table_insert(ht, "2.5.4.8", "ST");
673 /* organizationName */
674 g_hash_table_insert(ht, "2.5.4.10", "O");
675 /* organizationalUnitName */
676 g_hash_table_insert(ht, "2.5.4.11", "OU");
679 return g_hash_table_lookup(ht, oid);
682 /* Makes a distinguished name (DN) from
683 * a list of relative distinguished names (RDN).
684 * Order matters.
686 static gchar *
687 make_dn_from_oid_value_slist(GSList *list)
689 GString *str = g_string_new(NULL);
691 for (; list != NULL; list = g_slist_next(list)) {
692 DerOIDValue *value = list->data;
693 const gchar *name;
694 gchar *new_value;
696 if (value == NULL) {
697 purple_debug_error("tls-certificate",
698 "DerOIDValue data missing from GSList");
699 continue;
702 name = lookup_rdn_name_by_oid(value->oid);
703 /* Escape commas in value as that's the DN separator */
704 new_value = purple_strreplace(value->value, ",", "\\,");
705 g_string_append_printf(str, "%s=%s,", name, new_value);
706 g_free(new_value);
709 /* Remove trailing comma */
710 g_string_truncate(str, str->len - 1);
712 return g_string_free(str, FALSE);
715 static gchar *
716 purple_tls_certificate_info_get_issuer_dn(PurpleTlsCertificateInfo *info)
718 g_return_val_if_fail(info != NULL, NULL);
719 g_return_val_if_fail(info->issuer != NULL, NULL);
721 return make_dn_from_oid_value_slist(info->issuer);
724 gchar *
725 purple_tls_certificate_info_get_display_string(PurpleTlsCertificateInfo *info)
727 gchar *subject_name;
728 gchar *issuer_name = NULL;
729 GByteArray *sha1_bytes;
730 gchar *sha1_str = NULL;
731 gchar *activation_time;
732 gchar *expiration_time;
733 gchar *ret;
735 g_return_val_if_fail(info != NULL, NULL);
737 /* Getting the commonName of a CA supposedly doesn't work, but we
738 * shouldn't be dealing with those here anyway.
740 subject_name = purple_tls_certificate_info_get_subject_name(info);
742 issuer_name = purple_tls_certificate_info_get_issuer_dn(info);
744 sha1_bytes = purple_tls_certificate_get_fingerprint_sha1(info->cert);
745 if (sha1_bytes != NULL) {
746 sha1_str = purple_base16_encode_chunked(sha1_bytes->data,
747 sha1_bytes->len);
748 g_byte_array_unref(sha1_bytes);
751 activation_time = g_date_time_format(info->notBefore, "%c");
752 expiration_time = g_date_time_format(info->notAfter, "%c");
754 ret = g_strdup_printf(
755 _("Common name: %s\n\n"
756 "Issued by: %s\n\n"
757 "Fingerprint (SHA1): %s\n\n"
758 "Activation date: %s\n"
759 "Expiriation date: %s\n"),
760 subject_name,
761 issuer_name,
762 sha1_str,
763 activation_time,
764 expiration_time);
766 g_free(subject_name);
767 g_free(issuer_name);
768 g_free(sha1_str);
769 g_free(activation_time);
770 g_free(expiration_time);
772 return ret;
775 /* TODO: Make better API for this? */
776 gchar *
777 purple_tls_certificate_info_get_subject_name(PurpleTlsCertificateInfo *info)
779 g_return_val_if_fail(info != NULL, NULL);
780 g_return_val_if_fail(info->subject != NULL, NULL);
782 /* commonName component of the subject */
783 return g_strdup(der_oid_value_slist_get_value_by_oid(info->subject,
784 "2.5.4.3"));
787 /* TODO: Make better API for this? */
788 GByteArray *
789 purple_tls_certificate_get_fingerprint_sha1(GTlsCertificate *certificate)
791 GChecksum *hash;
792 GByteArray *der = NULL;
793 guint8 *data = NULL;
794 gsize buf_size = 0;
796 g_return_val_if_fail(G_IS_TLS_CERTIFICATE(certificate), NULL);
798 g_object_get(certificate, "certificate", &der, NULL);
800 g_return_val_if_fail(der != NULL, NULL);
802 hash = g_checksum_new(G_CHECKSUM_SHA1);
804 buf_size = g_checksum_type_get_length(G_CHECKSUM_SHA1);
805 data = g_malloc(buf_size);
807 g_checksum_update(hash, der->data, der->len);
808 g_byte_array_unref(der);
810 g_checksum_get_digest(hash, data, &buf_size);
811 g_checksum_free(hash);
813 return g_byte_array_new_take(data, buf_size);