changeset 27734:d0654dea0575

certs: Clean up the code a little, since I made it hard to follow.
author Paul Aurich <paul@darkrain42.org>
date Thu, 30 Jul 2009 05:24:44 +0000
parents 82ac0bef7d89
children 29e07dedfacc
files libpurple/certificate.c
diffstat 1 files changed, 213 insertions(+), 198 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/certificate.c	Wed Jul 29 19:16:02 2009 +0000
+++ b/libpurple/certificate.c	Thu Jul 30 05:24:44 2009 +0000
@@ -1318,211 +1318,20 @@
 	g_byte_array_free(cached_fpr, TRUE);
 }
 
-/* For when we've never communicated with this party before */
-/* TODO: Need ways to specify possibly multiple problems with a cert, or at
-   least  reprioritize them. For example, maybe the signature ought to be
-   checked BEFORE the hostname checking?
-   Stu thinks we should check the signature before the name, so we do now.
-   The above TODO still stands. */
+/*
+ * This is called from two points in x509_tls_cached_unknown_peer below
+ * once we've verified the signature chain is valid. Now we need to verify
+ * the subject name of the certificate.
+ */
 static void
-x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
+x509_tls_cached_check_subject_name(PurpleCertificateVerificationRequest *vrq)
 {
-	PurpleCertificatePool *ca, *tls_peers;
+	PurpleCertificatePool *tls_peers;
 	PurpleCertificate *peer_crt;
-	PurpleCertificate *failing_crt;
 	GList *chain = vrq->cert_chain;
-	gboolean chain_validated = FALSE;
 
 	peer_crt = (PurpleCertificate *) chain->data;
 
-	/* TODO: Figure out a way to check for a bad signature, as opposed to
-	   "not self-signed" */
-	if ( purple_certificate_signed_by(peer_crt, peer_crt) ) {
-		gchar *msg;
-
-		purple_debug_info("certificate/x509/tls_cached",
-				  "Certificate for %s is self-signed.\n",
-				  vrq->subject_name);
-
-		/* Prompt the user to authenticate the certificate */
-		/* vrq will be completed by user_auth */
-		msg = g_strdup_printf(_("The certificate presented by \"%s\" "
-					"is self-signed. It cannot be "
-					"automatically checked."),
-				      vrq->subject_name);
-
-		x509_tls_cached_user_auth(vrq,msg);
-
-		g_free(msg);
-		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_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;
-				}
-
-				g_byte_array_free(failing_fpr, TRUE);
-				g_byte_array_free(ca_fpr, TRUE);
-			}
-
-			purple_certificate_destroy(ca_crt);
-			g_free(uid);
-		}
-
-		/*
-		 * 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;
-
-			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 ) {
-		purple_debug_error("certificate/x509/tls_cached",
-				   "No X.509 Certificate Authority pool "
-				   "could be found!\n");
-
-		/* vrq will be completed by user_auth */
-		x509_tls_cached_user_auth(vrq,_("You have no database of root "
-						"certificates, so this "
-						"certificate cannot be "
-						"validated."));
-		return;
-	}
-
-	if (!chain_validated) {
-		GByteArray *last_fpr, *ca_fpr;
-		PurpleCertificate *ca_crt, *end_crt;
-		gchar *ca_id;
-
-		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);
-		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);
-
-		/*
-		 * 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_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);
-
-			/* Signal "bad cert" */
-			purple_certificate_verify_complete(vrq,
-							   PURPLE_CERTIFICATE_INVALID);
-
-			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_fpr, TRUE);
-		g_byte_array_free(last_fpr, TRUE);
-	}
-
 	/* Last, check that the hostname matches */
 	if ( ! purple_certificate_check_subject_name(peer_crt,
 						     vrq->subject_name) ) {
@@ -1571,6 +1380,212 @@
 
 	/* Whew! Done! */
 	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
+
+}
+
+/* For when we've never communicated with this party before */
+/* TODO: Need ways to specify possibly multiple problems with a cert, or at
+   least  reprioritize them.
+ */
+static void
+x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
+{
+	PurpleCertificatePool *ca;
+	PurpleCertificate *peer_crt;
+	PurpleCertificate *ca_crt, *end_crt;
+	PurpleCertificate *failing_crt;
+	GList *chain = vrq->cert_chain;
+	GByteArray *last_fpr, *ca_fpr;
+	gchar *ca_id;
+
+	peer_crt = (PurpleCertificate *) chain->data;
+
+	/* TODO: Figure out a way to check for a bad signature, as opposed to
+	   "not self-signed" */
+	if ( purple_certificate_signed_by(peer_crt, peer_crt) ) {
+		gchar *msg;
+
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Certificate for %s is self-signed.\n",
+				  vrq->subject_name);
+
+		/* Prompt the user to authenticate the certificate */
+		/* vrq will be completed by user_auth */
+		msg = g_strdup_printf(_("The certificate presented by \"%s\" "
+					"is self-signed. It cannot be "
+					"automatically checked."),
+				      vrq->subject_name);
+
+		x509_tls_cached_user_auth(vrq,msg);
+
+		g_free(msg);
+		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_with_failing(chain,
+				&failing_crt))
+	{
+		gboolean chain_validated = FALSE;
+		/*
+		 * 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;
+				}
+
+				g_byte_array_free(failing_fpr, TRUE);
+				g_byte_array_free(ca_fpr, TRUE);
+			}
+
+			purple_certificate_destroy(ca_crt);
+			g_free(uid);
+		}
+
+		/*
+		 * 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) {
+			x509_tls_cached_check_subject_name(vrq);
+		} else {
+			/* TODO: Tell the user where the chain broke? */
+			/* TODO: This error will hopelessly confuse any
+			   non-elite user. */
+			gchar *secondary;
+
+			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 ) {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "No X.509 Certificate Authority pool "
+				   "could be found!\n");
+
+		/* vrq will be completed by user_auth */
+		x509_tls_cached_user_auth(vrq,_("You have no database of root "
+						"certificates, so this "
+						"certificate cannot be "
+						"validated."));
+		return;
+	}
+
+	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);
+	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);
+
+	/*
+	 * 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_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);
+
+		/* Signal "bad cert" */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+
+		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_fpr, TRUE);
+	g_byte_array_free(last_fpr, TRUE);
+
+	x509_tls_cached_check_subject_name(vrq);
 }
 
 static void