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
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
25 #include "tls-certificate-info.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)
53 static void der_node_data_children_list_free(GSList
*children
);
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
);
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 */
77 der_parse(GBytes
*data_bytes
)
83 DerNodeData
*node
= NULL
;
85 data
= g_bytes_get_data(data_bytes
, &size
);
88 while (offset
< size
) {
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
);
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
")",
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
);
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
));
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
") "
154 for (i
= 0; i
< num_len_bytes
; ++i
) {
155 length
= length
<< 8;
156 length
|= *(data
+ offset
++);
159 /* Short-form length encoding */
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
);
175 node
->content
= g_bytes_new_from_bytes(data_bytes
,
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().
192 nodes
= g_slist_append(nodes
, node
);
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? */
207 der_parse_string(DerNodeData
*node
)
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
);
225 der_oid_value_copy(DerOIDValue
*data
)
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
);
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
);
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
);
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
)) {
271 der_parse_oid(DerNodeData
*node
)
273 const gchar
*oid_data
;
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 */
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
) {
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.
313 der_parse_name(DerNodeData
*name_node
)
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
;
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
) {
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
) {
346 /* Get the GSList item containing the ObjectID DerNode */
347 if ((child_list
= child_node
->children
) == NULL
) {
351 /* Get the DerNode containing the ObjectID */
352 if ((child_node
= child_list
->data
) == NULL
) {
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
) {
364 /* Get the DerNode containing the String-like value */
365 if ((child_node
= child_list
->data
) == NULL
) {
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
);
378 der_oid_value_free(value
);
379 der_oid_value_slist_free(ret
);
382 return g_slist_reverse(ret
);
386 der_parse_time(DerNodeData
*node
)
391 gint time_part_idx
= 0;
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
);
411 /* UTCTime: Skip the first part as it's calculated later */
413 } else if (length
== 15) {
414 /* Generalized Time */
415 /* TODO: Handle generalized time
416 * Maiku: None of the certificates I tested used this
419 g_return_val_if_reached(NULL
);
421 purple_debug_error("tls-certificate",
422 "Unrecognized time format (length: %i)",
430 while (c
- time
< length
) {
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'",
443 time_parts
[time_part_idx
++] =
444 g_ascii_digit_value(*c
) * 10 +
445 g_ascii_digit_value(*(c
+ 1));
450 if (time_parts
[1] >= 50) {
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)) */
478 GDateTime
*notBefore
;
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
;
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
);
510 purple_debug_warning("tls-certificate",
511 "Error parsing certificate");
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
);
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
);
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
);
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
);
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
);
576 /* Get certificate validity (notBefore) */
577 node
= g_slist_nth_data(valid_node
->children
, 0);
579 purple_debug_warning("tls-certificate",
580 "Error to parsing certificate valid "
582 purple_tls_certificate_info_free(info
);
586 info
->notBefore
= der_parse_time(node
);
588 /* Get certificate validity (notAfter) */
589 node
= g_slist_nth_data(valid_node
->children
, 1);
591 purple_debug_warning("tls-certificate",
592 "Error to parsing certificate valid "
594 purple_tls_certificate_info_free(info
);
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
);
610 info
->subject
= der_parse_name(node
);
613 der_node_data_children_list_free(nodes
);
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
);
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
);
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 */
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
,
666 g_hash_table_insert(ht
, "2.5.4.3", "CN");
668 g_hash_table_insert(ht
, "2.5.4.6", "C");
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).
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
;
697 purple_debug_error("tls-certificate",
698 "DerOIDValue data missing from GSList");
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
);
709 /* Remove trailing comma */
710 g_string_truncate(str
, str
->len
- 1);
712 return g_string_free(str
, FALSE
);
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
);
725 purple_tls_certificate_info_get_display_string(PurpleTlsCertificateInfo
*info
)
728 gchar
*issuer_name
= NULL
;
729 GByteArray
*sha1_bytes
;
730 gchar
*sha1_str
= NULL
;
731 gchar
*activation_time
;
732 gchar
*expiration_time
;
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
,
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"
757 "Fingerprint (SHA1): %s\n\n"
758 "Activation date: %s\n"
759 "Expiriation date: %s\n"),
766 g_free(subject_name
);
769 g_free(activation_time
);
770 g_free(expiration_time
);
775 /* TODO: Make better API for this? */
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
,
787 /* TODO: Make better API for this? */
789 purple_tls_certificate_get_fingerprint_sha1(GTlsCertificate
*certificate
)
792 GByteArray
*der
= NULL
;
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
);