changeset 28404:15833e4f2e09

merge of '0696d14c26cfe65de7a3f68a2b9d14b283f88e1f' and '256d39c0fd5169819199dd5a2586b6b2b144280c'
author Paul Aurich <paul@darkrain42.org>
date Sat, 22 Aug 2009 05:38:56 +0000
parents b9e28b2a119b (current diff) b341ae89f5ce (diff)
children 2c85f44113b4
files
diffstat 1 files changed, 226 insertions(+), 178 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/certificate.c	Sat Aug 22 04:44:11 2009 +0000
+++ b/libpurple/certificate.c	Sat Aug 22 05:38:56 2009 +0000
@@ -43,6 +43,92 @@
 /** List of registered Pools */
 static GList *cert_pools = NULL;
 
+/*
+ * TODO: Merge this with PurpleCertificateVerificationStatus for 3.0.0 */
+typedef enum {
+	PURPLE_CERTIFICATE_UNKNOWN_ERROR = -1,
+
+	/* Not an error */
+	PURPLE_CERTIFICATE_NO_PROBLEMS = 0,
+
+	/* Non-fatal */
+	PURPLE_CERTIFICATE_NON_FATALS_MASK = 0x0000FFFF,
+
+	/* The certificate is self-signed. */
+	PURPLE_CERTIFICATE_SELF_SIGNED = 0x01,
+
+	/* The CA is not in libpurple's pool of certificates. */
+	PURPLE_CERTIFICATE_CA_UNKNOWN = 0x02,
+
+	/* The current time is before the certificate's specified
+	 * activation time.
+	 */
+	PURPLE_CERTIFICATE_NOT_ACTIVATED = 0x04,
+
+	/* The current time is after the certificate's specified expiration time */
+	PURPLE_CERTIFICATE_EXPIRED = 0x08,
+
+	/* The certificate's subject name doesn't match the expected */
+	PURPLE_CERTIFICATE_NAME_MISMATCH = 0x10,
+
+	/* No CA pool was found. This shouldn't happen... */
+	PURPLE_CERTIFICATE_NO_CA_POOL = 0x20,
+
+	/* Fatal */
+	PURPLE_CERTIFICATE_FATALS_MASK = 0xFFFF0000,
+
+	/* The signature chain could not be validated. Due to limitations in the
+	 * the current API, this also indicates one of the CA certificates in the
+	 * chain is expired (or not yet activated). FIXME 3.0.0 */
+	PURPLE_CERTIFICATE_INVALID_CHAIN = 0x10000,
+
+	/* The signature has been revoked. */
+	PURPLE_CERTIFICATE_REVOKED = 0x20000,
+
+	PURPLE_CERTIFICATE_LAST = 0x40000,
+} PurpleCertificateInvalidityFlags;
+
+static const gchar *
+invalidity_reason_to_string(PurpleCertificateInvalidityFlags flag)
+{
+	switch (flag) {
+		case PURPLE_CERTIFICATE_SELF_SIGNED:
+			return _("The certificate is self-signed and cannot be "
+			         "automatically checked.");
+			break;
+		case PURPLE_CERTIFICATE_CA_UNKNOWN:
+			return _("The root certificate this one claims to be issued by is "
+			         "unknown to Pidgin.");
+			break;
+		case PURPLE_CERTIFICATE_NOT_ACTIVATED:
+			return _("The certificate has not yet been activated.");
+			break;
+		case PURPLE_CERTIFICATE_EXPIRED:
+			return _("The certificate has expired and should not be "
+			         "considered valid.");
+			break;
+		case PURPLE_CERTIFICATE_NAME_MISMATCH:
+			/* Translators: "domain" refers to a DNS domain (e.g. talk.google.com) */
+			return _("The certificate presented is not valid for this "
+			         "domain.");
+			break;
+		case PURPLE_CERTIFICATE_NO_CA_POOL:
+			return _("You have no database of root certificates, so "
+			         "this certificate cannot be validated.");
+			break;
+		case PURPLE_CERTIFICATE_INVALID_CHAIN:
+			return _("The certificate chain presented is invalid.");
+			break;
+		case PURPLE_CERTIFICATE_REVOKED:
+			return _("The certificate has been revoked.");
+			break;
+		case PURPLE_CERTIFICATE_UNKNOWN_ERROR:
+		default:
+			return _("An unknown certificate error occurred.");
+			break;
+	}
+}
+
 void
 purple_certificate_verify (PurpleCertificateVerifier *verifier,
 			   const gchar *subject_name, GList *cert_chain,
@@ -1265,10 +1351,104 @@
 }
 
 static void
-x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq);
+x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq,
+                             PurpleCertificateInvalidityFlags flags);
 
 static void
-x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq)
+x509_tls_cached_complete(PurpleCertificateVerificationRequest *vrq,
+                         PurpleCertificateInvalidityFlags flags)
+{
+	PurpleCertificatePool *tls_peers;
+	PurpleCertificate *peer_crt = vrq->cert_chain->data;
+
+	if (flags & PURPLE_CERTIFICATE_FATALS_MASK) {
+		/* TODO: Also print any other warnings? */
+		const gchar *error;
+		gchar *tmp, *secondary;
+
+		if (flags & PURPLE_CERTIFICATE_INVALID_CHAIN)
+			error = invalidity_reason_to_string(PURPLE_CERTIFICATE_INVALID_CHAIN);
+		else if (flags & PURPLE_CERTIFICATE_REVOKED)
+			error = invalidity_reason_to_string(PURPLE_CERTIFICATE_REVOKED);
+		else
+			error = invalidity_reason_to_string(PURPLE_CERTIFICATE_UNKNOWN_ERROR);
+
+		tmp = g_strdup_printf(_("The certificate for %s could not be validated."),
+					vrq->subject_name);
+		secondary = g_strconcat(tmp, " ", error, NULL);
+		g_free(tmp);
+
+		purple_notify_error(NULL, /* TODO: Probably wrong. */
+					_("SSL Certificate Error"),
+					_("Unable to validate certificate"),
+					secondary);
+		g_free(secondary);
+
+		purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID);
+		return;
+	} else if (flags & PURPLE_CERTIFICATE_NON_FATALS_MASK) {
+		/* Non-fatal error. Prompt the user. */
+		gchar *tmp;
+		GString *errors;
+		guint32 i = 1;
+
+		tmp = g_strdup_printf(_("The certificate for %s could not be validated."),
+					vrq->subject_name);
+		errors = g_string_new(tmp);
+		g_free(tmp);
+
+		errors = g_string_append_c(errors, '\n');
+
+		/* Special case a name mismatch because we want to display the two names... */
+		if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) {
+			gchar *sn = purple_certificate_get_subject_name(peer_crt);
+
+			g_string_append_printf(errors, _("The certificate claims to be "
+						"from \"%s\" instead. This could mean that you are "
+						"not connecting to the service you believe you are."),
+						sn);
+			g_free(sn);
+
+			flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
+		}
+
+		while (i != PURPLE_CERTIFICATE_LAST) {
+			if (flags & i) {
+				errors = g_string_append_c(errors, '\n');
+				g_string_append(errors, invalidity_reason_to_string(i));
+			}
+
+			i <<= 1;
+		}
+
+		x509_tls_cached_user_auth(vrq, errors->str);
+		g_string_free(errors, TRUE);
+		return;
+	}
+
+	/* If we reach this point, the certificate is good. */
+
+	/* Look up the local cache and store it there for future use */
+	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
+						 "tls_peers");
+	if (tls_peers) {
+		if (!purple_certificate_pool_contains(tls_peers, vrq->subject_name) &&
+		        !purple_certificate_pool_store(tls_peers,vrq->subject_name,
+		                                       peer_crt)) {
+			purple_debug_error("certificate/x509/tls_cached",
+			                   "FAILED to cache peer certificate\n");
+		}
+	} else {
+		purple_debug_error("certificate/x509/tls_cached",
+		                   "Unable to locate tls_peers certificate cache.\n");
+	}
+
+	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
+}
+
+static void
+x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq,
+                              PurpleCertificateInvalidityFlags flags)
 {
 	/* TODO: Looking this up by name over and over is expensive.
 	   Fix, please! */
@@ -1291,7 +1471,7 @@
 				   "Lookup failed on cached certificate!\n"
 				   "Falling back to full verification.\n");
 		/* vrq now becomes the problem of unknown_peer */
-		x509_tls_cached_unknown_peer(vrq);
+		x509_tls_cached_unknown_peer(vrq, flags);
 		return;
 	}
 
@@ -1302,14 +1482,12 @@
 	if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) {
 		purple_debug_info("certificate/x509/tls_cached",
 				  "Peer cert matched cached\n");
-		/* vrq is now finished */
-		purple_certificate_verify_complete(vrq,
-						   PURPLE_CERTIFICATE_VALID);
+		x509_tls_cached_complete(vrq, flags);
 	} else {
 		purple_debug_error("certificate/x509/tls_cached",
 				  "Peer cert did NOT match cached\n");
 		/* vrq now becomes the problem of the user */
-		x509_tls_cached_unknown_peer(vrq);
+		x509_tls_cached_unknown_peer(vrq, flags);
 	}
 
 	purple_certificate_destroy(cached_crt);
@@ -1324,9 +1502,8 @@
  */
 static void
 x509_tls_cached_check_subject_name(PurpleCertificateVerificationRequest *vrq,
-                                   gboolean had_ca_pool)
+                                   PurpleCertificateInvalidityFlags flags)
 {
-	PurpleCertificatePool *tls_peers;
 	PurpleCertificate *peer_crt;
 	GList *chain = vrq->cert_chain;
 
@@ -1337,77 +1514,14 @@
 						     vrq->subject_name) ) {
 		gchar *sn = purple_certificate_get_subject_name(peer_crt);
 
+		flags |= PURPLE_CERTIFICATE_NAME_MISMATCH;
 		purple_debug_error("certificate/x509/tls_cached",
 				  "Name mismatch: Certificate given for %s "
 				  "has a name of %s\n",
 				  vrq->subject_name, sn);
-
-		if (had_ca_pool) {
-			/* Prompt the user to authenticate the certificate */
-			/* TODO: Provide the user with more guidance about why he is
-			   being prompted */
-			/* vrq will be completed by user_auth */
-			gchar *msg;
-			msg = g_strdup_printf(_("The certificate presented by \"%s\" "
-						"claims to be from \"%s\" instead.  "
-						"This could mean that you are not "
-						"connecting to the service you "
-						"believe you are."),
-					      vrq->subject_name, sn);
-
-			x509_tls_cached_user_auth(vrq, msg);
-			g_free(msg);
-		} else {
-			/* Had no CA pool, so couldn't verify the chain *and*
-			 * the subject name isn't valid.
-			 * I think this is bad enough to warrant a fatal error. It's
-			 * not likely anyway...
-			 */
-			purple_notify_error(NULL, /* TODO: Probably wrong. */
-						_("SSL Certificate Error"),
-						_("Invalid certificate chain"),
-						_("You have no database of root certificates, so "
-						"this certificate cannot be validated."));
-		}
-
-		g_free(sn);
-		return;
-	} /* if (name mismatch) */
-
-	if (!had_ca_pool) {
-		/* The subject name is correct, but we weren't able to verify the
-		 * chain because there was no pool of root CAs found. Prompt the user
-		 * to validate it.
-		 */
-
-		/* 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 we reach this point, the certificate is good. */
-	/* Look up the local cache and store it there for future use */
-	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
-						 "tls_peers");
-
-	if (tls_peers) {
-		if (!purple_certificate_pool_store(tls_peers,vrq->subject_name,
-						   peer_crt) ) {
-			purple_debug_error("certificate/x509/tls_cached",
-					   "FAILED to cache peer certificate\n");
-		}
-	} else {
-		purple_debug_error("certificate/x509/tls_cached",
-				   "Unable to locate tls_peers certificate "
-				   "cache.\n");
-	}
-
-	/* Whew! Done! */
-	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
-
+	x509_tls_cached_complete(vrq, flags);
 }
 
 /* For when we've never communicated with this party before */
@@ -1415,7 +1529,8 @@
    least  reprioritize them.
  */
 static void
-x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
+x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq,
+                             PurpleCertificateInvalidityFlags flags)
 {
 	PurpleCertificatePool *ca;
 	PurpleCertificate *peer_crt;
@@ -1430,22 +1545,13 @@
 	/* 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;
+		flags |= PURPLE_CERTIFICATE_SELF_SIGNED;
 
 		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);
+		x509_tls_cached_check_subject_name(vrq, flags);
 		return;
 	} /* if (self signed) */
 
@@ -1491,32 +1597,11 @@
 		 * 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, TRUE);
-		} else {
+		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);
+			flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
 
-			/* 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);
-		}
-
+		x509_tls_cached_check_subject_name(vrq, flags);
 		return;
 	} /* if (signature chain not good) */
 
@@ -1527,7 +1612,9 @@
 				   "No X.509 Certificate Authority pool "
 				   "could be found!\n");
 
-		x509_tls_cached_check_subject_name(vrq, FALSE);
+		flags |= PURPLE_CERTIFICATE_NO_CA_POOL;
+
+		x509_tls_cached_check_subject_name(vrq, flags);
 		return;
 	}
 
@@ -1540,15 +1627,15 @@
 			  ca_id);
 	ca_crt = purple_certificate_pool_retrieve(ca, ca_id);
 	if ( NULL == ca_crt ) {
+		flags |= PURPLE_CERTIFICATE_CA_UNKNOWN;
+
 		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."));
+
+		x509_tls_cached_check_subject_name(vrq, flags);
 		return;
 	}
 
@@ -1579,36 +1666,15 @@
 		/* 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) */
+		flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
+	}
 
 	g_byte_array_free(ca_fpr, TRUE);
 	g_byte_array_free(last_fpr, TRUE);
 
-	x509_tls_cached_check_subject_name(vrq, TRUE);
+	purple_certificate_destroy(ca_crt);
+
+	x509_tls_cached_check_subject_name(vrq, flags);
 }
 
 static void
@@ -1617,6 +1683,7 @@
 	const gchar *tls_peers_name = "tls_peers"; /* Name of local cache */
 	PurpleCertificatePool *tls_peers;
 	time_t now, activation, expiration;
+	PurpleCertificateInvalidityFlags flags = PURPLE_CERTIFICATE_NO_PROBLEMS;
 	gboolean ret;
 
 	g_return_if_fail(vrq);
@@ -1632,39 +1699,21 @@
 	now = time(NULL);
 	ret = purple_certificate_get_times(vrq->cert_chain->data, &activation,
 	                                   &expiration);
-	if (!ret || now > expiration || now < activation) {
-		gchar *secondary;
-
-		if (!ret) {
-			purple_debug_error("certificate/x509/tls_cached",
-					"Failed to get validity times for certificate %s\n",
-					vrq->subject_name);
-			secondary = g_strdup_printf(_("Failed to validate expiration time "
-					"for %s"), vrq->subject_name);
-		} else if (now > expiration) {
-			purple_debug_error("certificate/x509/tls_cached",
-					"Certificate %s expired at %s\n",
-					vrq->subject_name, ctime(&expiration));
-			secondary = g_strdup_printf(_("The certificate for %s is expired."),
-					vrq->subject_name);
-		} else {
-			purple_debug_error("certificate/x509/tls_cached",
-					"Certificate %s is not yet valid, will be at %s\n",
-					vrq->subject_name, ctime(&activation));
-			secondary = g_strdup_printf(_("The certificate for %s should not "
-					"yet be in use."), vrq->subject_name);
-		}
-
-		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 (!ret) {
+		flags |= PURPLE_CERTIFICATE_EXPIRED | PURPLE_CERTIFICATE_NOT_ACTIVATED;
+		purple_debug_error("certificate/x509/tls_cached",
+				"Failed to get validity times for certificate %s\n",
+				vrq->subject_name);
+	} else if (now > expiration) {
+		flags |= PURPLE_CERTIFICATE_EXPIRED;
+		purple_debug_error("certificate/x509/tls_cached",
+				"Certificate %s expired at %s\n",
+				vrq->subject_name, ctime(&expiration));
+	} else if (now < activation) {
+		flags |= PURPLE_CERTIFICATE_NOT_ACTIVATED;
+		purple_debug_error("certificate/x509/tls_cached",
+				"Certificate %s is not yet valid, will be at %s\n",
+				vrq->subject_name, ctime(&activation));
 	}
 
 	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,tls_peers_name);
@@ -1674,9 +1723,8 @@
 				   "Couldn't find local peers cache %s\n",
 				   tls_peers_name);
 
-
 		/* vrq now becomes the problem of unknown_peer */
-		x509_tls_cached_unknown_peer(vrq);
+		x509_tls_cached_unknown_peer(vrq, flags);
 		return;
 	}
 
@@ -1687,12 +1735,12 @@
 		purple_debug_info("certificate/x509/tls_cached",
 				  "...Found cached cert\n");
 		/* vrq is now the responsibility of cert_in_cache */
-		x509_tls_cached_cert_in_cache(vrq);
+		x509_tls_cached_cert_in_cache(vrq, flags);
 	} else {
 		purple_debug_warning("certificate/x509/tls_cached",
 				  "...Not in cache\n");
 		/* vrq now becomes the problem of unknown_peer */
-		x509_tls_cached_unknown_peer(vrq);
+		x509_tls_cached_unknown_peer(vrq, flags);
 	}
 }