pidgin: 60315e01: Fix GnuTLS validation of the CACert Chai...
darkrain42 at pidgin.im
darkrain42 at pidgin.im
Wed Jul 22 03:35:40 EDT 2009
-----------------------------------------------------------------
Revision: 60315e01b2e3725bb5d272284c3f11d31f18c114
Ancestor: 66dcef6295ae28ab7ae630616640aa789b819a9c
Author: darkrain42 at pidgin.im
Date: 2009-07-22T07:31:40
Branch: im.pidgin.pidgin
URL: http://d.pidgin.im/viewmtn/revision/info/60315e01b2e3725bb5d272284c3f11d31f18c114
Modified files:
ChangeLog.API libpurple/certificate.c
libpurple/certificate.h
ChangeLog:
Fix GnuTLS validation of the CACert Chain. Closes #4458.
If certificate validation fails partway through, check the last
validated certificate and, if it's in the CA store, consider the chain
validated. This allows GnuTLS to validate the CAcert Class 3 intermediate
without requiring us to accept MD5 signatures anywhere.
-------------- next part --------------
============================================================
--- ChangeLog.API 67b4d5fbb18237919c1678cde0b730511737978f
+++ ChangeLog.API 2004570ea33233779e05239c2cb444aa3c711271
@@ -26,6 +26,7 @@ version 2.6.0 (??/??/2009):
* purple_blist_set_ui_data
* purple_blist_node_get_ui_data
* purple_blist_node_set_ui_data
+ * purple_certificate_check_signature_chain_with_failing
* purple_chat_destroy
* purple_connection_get_protocol_data
* purple_connection_set_protocol_data
@@ -92,6 +93,7 @@ version 2.6.0 (??/??/2009):
* purple_blist_destroy
* purple_blist_new
* purple_buddy_get_local_alias
+ * purple_certificate_check_signature_chain
* purple_ip_address_is_valid
* purple_notify_user_info_remove_entry
* purple_set_blist
============================================================
--- libpurple/certificate.c 8646af2653a15c831619b6e2660b3898fd7b5504
+++ libpurple/certificate.c 042213bc90f61f1949af9d37fd777822e2ed7ecc
@@ -190,7 +190,8 @@ gboolean
}
gboolean
-purple_certificate_check_signature_chain(GList *chain)
+purple_certificate_check_signature_chain_with_failing(GList *chain,
+ PurpleCertificate **failing)
{
GList *cur;
PurpleCertificate *crt, *issuer;
@@ -200,6 +201,9 @@ purple_certificate_check_signature_chain
g_return_val_if_fail(chain, FALSE);
+ if (failing)
+ *failing = NULL;
+
uid = purple_certificate_get_unique_id((PurpleCertificate *) chain->data);
purple_debug_info("certificate",
"Checking signature chain for uid=%s\n",
@@ -239,6 +243,9 @@ purple_certificate_check_signature_chain
"...Not-yet-activated issuer %s will be valid at %s\n"
"Chain is INVALID\n", uid, ctime(&activation));
+ if (failing)
+ *failing = crt;
+
g_free(uid);
return FALSE;
}
@@ -250,6 +257,9 @@ purple_certificate_check_signature_chain
uid);
g_free(uid);
+ if (failing)
+ *failing = crt;
+
return FALSE;
}
@@ -268,6 +278,12 @@ purple_certificate_check_signature_chain
return TRUE;
}
+gboolean
+purple_certificate_check_signature_chain(GList *chain)
+{
+ return purple_certificate_check_signature_chain_with_failing(chain, NULL);
+}
+
PurpleCertificate *
purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename)
{
@@ -1312,11 +1328,10 @@ x509_tls_cached_unknown_peer(PurpleCerti
x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
{
PurpleCertificatePool *ca, *tls_peers;
- PurpleCertificate *end_crt, *ca_crt, *peer_crt;
+ PurpleCertificate *peer_crt;
+ PurpleCertificate *failing_crt;
GList *chain = vrq->cert_chain;
- GList *last;
- gchar *ca_id;
- GByteArray *last_fingerprint, *ca_fingerprint;
+ gboolean chain_validated = FALSE;
peer_crt = (PurpleCertificate *) chain->data;
@@ -1342,35 +1357,74 @@ x509_tls_cached_unknown_peer(PurpleCerti
return;
} /* if (self signed) */
+ /* Next, attempt to verify the last certificate against a CA */
+ ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca");
+
/* Next, check that the certificate chain is valid */
- if ( ! purple_certificate_check_signature_chain(chain) ) {
- /* TODO: Tell the user where the chain broke? */
- /* TODO: This error will hopelessly confuse any
- non-elite user. */
- gchar *secondary;
+ if (purple_certificate_check_signature_chain_with_failing(chain,
+ &failing_crt))
+ chain_validated = TRUE;
+ else {
+ /*
+ * Check if the failing certificate is in the CA store. If it is, then
+ * consider this fully validated. This works around issues with some
+ * prominent intermediate CAs whose signature is md5WithRSAEncryption.
+ * I'm looking at CACert Class 3 here. See #4458 for details.
+ */
+ if (ca) {
+ gchar *uid = purple_certificate_get_unique_id(failing_crt);
+ PurpleCertificate *ca_crt = purple_certificate_pool_retrieve(ca, uid);
+ if (ca_crt != NULL) {
+ GByteArray *failing_fpr;
+ GByteArray *ca_fpr;
+ failing_fpr = purple_certificate_get_fingerprint_sha1(failing_crt);
+ ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt);
+ if (byte_arrays_equal(failing_fpr, ca_fpr)) {
+ purple_debug_info("certificate/x509/tls_cached",
+ "Full chain verification failed (probably a bad "
+ "signature algorithm), but found the last "
+ "certificate %s in the CA pool.\n", uid);
+ chain_validated = TRUE;
+ }
- secondary = g_strdup_printf(_("The certificate chain presented"
- " for %s is not valid."),
- vrq->subject_name);
+ g_byte_array_free(failing_fpr, TRUE);
+ g_byte_array_free(ca_fpr, TRUE);
+ }
- /* TODO: Make this error either block the ensuing SSL
- connection error until the user dismisses this one, or
- stifle it. */
- purple_notify_error(NULL, /* TODO: Probably wrong. */
- _("SSL Certificate Error"),
- _("Invalid certificate chain"),
- secondary );
- g_free(secondary);
+ purple_certificate_destroy(ca_crt);
+ g_free(uid);
+ }
- /* Okay, we're done here */
- purple_certificate_verify_complete(vrq,
- PURPLE_CERTIFICATE_INVALID);
- return;
- } /* if (signature chain not good) */
+ /*
+ * If we get here, either the cert matched the stuff right above
+ * or it didn't, in which case we give up and complain to the user.
+ */
+ if (!chain_validated) {
+ /* TODO: Tell the user where the chain broke? */
+ /* TODO: This error will hopelessly confuse any
+ non-elite user. */
+ gchar *secondary;
- /* Next, attempt to verify the last certificate against a CA */
- ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca");
+ secondary = g_strdup_printf(_("The certificate chain presented"
+ " for %s is not valid."),
+ vrq->subject_name);
+ /* TODO: Make this error either block the ensuing SSL
+ connection error until the user dismisses this one, or
+ stifle it. */
+ purple_notify_error(NULL, /* TODO: Probably wrong. */
+ _("SSL Certificate Error"),
+ _("Invalid certificate chain"),
+ secondary );
+ g_free(secondary);
+
+ /* Okay, we're done here */
+ purple_certificate_verify_complete(vrq,
+ PURPLE_CERTIFICATE_INVALID);
+ return;
+ }
+ } /* if (signature chain not good) */
+
/* If, for whatever reason, there is no Certificate Authority pool
loaded, we will simply present it to the user for checking. */
if ( !ca ) {
@@ -1386,82 +1440,88 @@ x509_tls_cached_unknown_peer(PurpleCerti
return;
}
- last = g_list_last(chain);
- end_crt = (PurpleCertificate *) last->data;
+ if (!chain_validated) {
+ GByteArray *last_fpr, *ca_fpr;
+ PurpleCertificate *ca_crt, *end_crt;
+ gchar *ca_id;
- /* Attempt to look up the last certificate's issuer */
- ca_id = purple_certificate_get_issuer_unique_id(end_crt);
- purple_debug_info("certificate/x509/tls_cached",
- "Checking for a CA with DN=%s\n",
- ca_id);
- ca_crt = purple_certificate_pool_retrieve(ca, ca_id);
- if ( NULL == ca_crt ) {
- purple_debug_warning("certificate/x509/tls_cached",
- "Certificate Authority with DN='%s' not "
- "found. I'll prompt the user, I guess.\n",
+ end_crt = g_list_last(chain)->data;
+
+ /* Attempt to look up the last certificate's issuer */
+ ca_id = purple_certificate_get_issuer_unique_id(end_crt);
+ purple_debug_info("certificate/x509/tls_cached",
+ "Checking for a CA with DN=%s\n",
ca_id);
- g_free(ca_id);
- /* vrq will be completed by user_auth */
- x509_tls_cached_user_auth(vrq,_("The root certificate this "
- "one claims to be issued by "
- "is unknown to Pidgin."));
- return;
- }
+ ca_crt = purple_certificate_pool_retrieve(ca, ca_id);
+ if ( NULL == ca_crt ) {
+ purple_debug_warning("certificate/x509/tls_cached",
+ "Certificate Authority with DN='%s' not "
+ "found. I'll prompt the user, I guess.\n",
+ ca_id);
+ g_free(ca_id);
+ /* vrq will be completed by user_auth */
+ x509_tls_cached_user_auth(vrq,_("The root certificate this "
+ "one claims to be issued by "
+ "is unknown to Pidgin."));
+ return;
+ }
- g_free(ca_id);
+ g_free(ca_id);
- /*
- * Check the fingerprints; if they match, then this certificate *is* one
- * of the designated "trusted roots", and we don't need to verify the
- * signature. This is good because some of the older roots are self-signed
- * with bad hash algorithms that we don't want to allow in any other
- * circumstances (one of Verisign's root CAs is self-signed with MD2).
- *
- * If the fingerprints don't match, we'll fall back to checking the
- * signature.
- *
- * GnuTLS doesn't seem to include the final root in the verification
- * list, so this check will never succeed. NSS *does* include it in
- * the list, so here we are.
- */
- last_fingerprint = purple_certificate_get_fingerprint_sha1(end_crt);
- ca_fingerprint = purple_certificate_get_fingerprint_sha1(ca_crt);
+ /*
+ * Check the fingerprints; if they match, then this certificate *is* one
+ * of the designated "trusted roots", and we don't need to verify the
+ * signature. This is good because some of the older roots are self-signed
+ * with bad hash algorithms that we don't want to allow in any other
+ * circumstances (one of Verisign's root CAs is self-signed with MD2).
+ *
+ * If the fingerprints don't match, we'll fall back to checking the
+ * signature.
+ *
+ * GnuTLS doesn't seem to include the final root in the verification
+ * list, so this check will never succeed. NSS *does* include it in
+ * the list, so here we are.
+ */
+ last_fpr = purple_certificate_get_fingerprint_sha1(end_crt);
+ ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt);
- if ( !byte_arrays_equal(last_fingerprint, ca_fingerprint) &&
- !purple_certificate_signed_by(end_crt, ca_crt) )
- {
- /* TODO: If signed_by ever returns a reason, maybe mention
- that, too. */
- /* TODO: Also mention the CA involved. While I could do this
- now, a full DN is a little much with which to assault the
- user's poor, leaky eyes. */
- /* TODO: This error message makes my eyes cross, and I wrote it */
- gchar * secondary =
- g_strdup_printf(_("The certificate chain presented by "
- "%s does not have a valid digital "
- "signature from the Certificate "
- "Authority from which it claims to "
- "have a signature."),
- vrq->subject_name);
+ if ( !byte_arrays_equal(last_fpr, ca_fpr) &&
+ !purple_certificate_signed_by(end_crt, ca_crt) )
+ {
+ /* TODO: If signed_by ever returns a reason, maybe mention
+ that, too. */
+ /* TODO: Also mention the CA involved. While I could do this
+ now, a full DN is a little much with which to assault the
+ user's poor, leaky eyes. */
+ /* TODO: This error message makes my eyes cross, and I wrote it */
+ gchar * secondary =
+ g_strdup_printf(_("The certificate chain presented by "
+ "%s does not have a valid digital "
+ "signature from the Certificate "
+ "Authority from which it claims to "
+ "have a signature."),
+ vrq->subject_name);
- purple_notify_error(NULL, /* TODO: Probably wrong */
- _("SSL Certificate Error"),
- _("Invalid certificate authority"
- " signature"),
- secondary);
- g_free(secondary);
+ purple_notify_error(NULL, /* TODO: Probably wrong */
+ _("SSL Certificate Error"),
+ _("Invalid certificate authority"
+ " signature"),
+ secondary);
+ g_free(secondary);
- /* Signal "bad cert" */
- purple_certificate_verify_complete(vrq,
- PURPLE_CERTIFICATE_INVALID);
+ /* Signal "bad cert" */
+ purple_certificate_verify_complete(vrq,
+ PURPLE_CERTIFICATE_INVALID);
- g_byte_array_free(ca_fingerprint, TRUE);
- g_byte_array_free(last_fingerprint, TRUE);
- return;
- } /* if (CA signature not good) */
+ purple_certificate_destroy(ca_crt);
+ g_byte_array_free(ca_fpr, TRUE);
+ g_byte_array_free(last_fpr, TRUE);
+ return;
+ } /* if (CA signature not good) */
- g_byte_array_free(ca_fingerprint, TRUE);
- g_byte_array_free(last_fingerprint, TRUE);
+ g_byte_array_free(ca_fpr, TRUE);
+ g_byte_array_free(last_fpr, TRUE);
+ }
/* Last, check that the hostname matches */
if ( ! purple_certificate_check_subject_name(peer_crt,
============================================================
--- libpurple/certificate.h 905374a767740c19c2d68fe657aa98b3dfaca142
+++ libpurple/certificate.h 15d1462698743a031b64c8230aabcd5ea5ab2191
@@ -443,6 +443,28 @@ purple_certificate_signed_by(PurpleCerti
purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer);
/**
+ * Check that a certificate chain is valid and, if not, the failing certificate.
+ *
+ * Uses purple_certificate_signed_by() to verify that each PurpleCertificate
+ * in the chain carries a valid signature from the next. A single-certificate
+ * chain is considered to be valid.
+ *
+ * @param chain List of PurpleCertificate instances comprising the chain,
+ * in the order certificate, issuer, issuer's issuer, etc.
+ * @param failing A pointer to a PurpleCertificate*. If not NULL, if the
+ * chain fails to validate, this will be set to the
+ * certificate whose signature could not be validated.
+ * @return TRUE if the chain is valid. See description.
+ *
+ * @since 2.6.0
+ * @deprecated This function will become
+ * purple_certificate_check_signature_chain in 3.0.0
+ */
+gboolean
+purple_certificate_check_signature_chain_with_failing(GList *chain,
+ PurpleCertificate **failing);
+
+/**
* Check that a certificate chain is valid
*
* Uses purple_certificate_signed_by() to verify that each PurpleCertificate
@@ -453,6 +475,8 @@ purple_certificate_signed_by(PurpleCerti
* in the order certificate, issuer, issuer's issuer, etc.
* @return TRUE if the chain is valid. See description.
* @todo Specify which certificate in the chain caused a failure
+ * @deprecated This function will be removed in 3.0.0 and replaced with
+ * purple_certificate_check_signature_chain_with_failing
*/
gboolean
purple_certificate_check_signature_chain(GList *chain);
More information about the Commits
mailing list