changeset 30050:c35fd54ec64b

Implement reading multiple certificates from a single "bundle" of certificates in one file. This is most helpful for systems where system CA certs are installed as a bundle. Please test, preferably after removing any cached certificates from ~/.purple/certificates/x509/tls_peers/
author Stu Tomlinson <stu@nosnilmot.com>
date Sat, 27 Mar 2010 03:55:09 +0000
parents 55a807c06fbb
children 3b24193663bc
files ChangeLog.API libpurple/certificate.c libpurple/certificate.h libpurple/plugins/ssl/ssl-gnutls.c libpurple/plugins/ssl/ssl-nss.c
diffstat 5 files changed, 158 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Sat Mar 27 02:11:31 2010 +0000
+++ b/ChangeLog.API	Sat Mar 27 03:55:09 2010 +0000
@@ -24,6 +24,9 @@
 		* sent-attention conversation signal
 		* got-attention conversation signal
 		* PurpleMood struct in status.h
+		* purple_certificates_import for importing multiple certificates from
+		  a single file (and corresponding import_certificates member of
+		  PurpleCertificateScheme struct)
 
 	Pidgin:
 		Added:
--- a/libpurple/certificate.c	Sat Mar 27 02:11:31 2010 +0000
+++ b/libpurple/certificate.c	Sat Mar 27 03:55:09 2010 +0000
@@ -377,6 +377,16 @@
 	return (scheme->import_certificate)(filename);
 }
 
+GSList *
+purple_certificates_import(PurpleCertificateScheme *scheme, const gchar *filename)
+{
+	g_return_val_if_fail(scheme, NULL);
+	g_return_val_if_fail(scheme->import_certificates, NULL);
+	g_return_val_if_fail(filename, NULL);
+
+	return (scheme->import_certificates)(filename);
+}
+
 gboolean
 purple_certificate_export(const gchar *filename, PurpleCertificate *crt)
 {
@@ -800,8 +810,9 @@
 	PurpleCertificateScheme *x509;
 	GDir *certdir;
 	const gchar *entry;
-	GPatternSpec *pempat;
+	GPatternSpec *pempat, *crtpat;
 	GList *iter = NULL;
+	GSList *crts = NULL;
 
 	if (x509_ca_initialized) return TRUE;
 
@@ -817,6 +828,7 @@
 
 	/* Use a glob to only read .pem files */
 	pempat = g_pattern_spec_new("*.pem");
+	crtpat = g_pattern_spec_new("*.crt");
 
 	/* Populate the certificates pool from the search path(s) */
 	for (iter = x509_ca_paths; iter; iter = iter->next) {
@@ -830,32 +842,39 @@
 			gchar *fullpath;
 			PurpleCertificate *crt;
 
-			if ( !g_pattern_match_string(pempat, entry) ) {
+			if (!g_pattern_match_string(pempat, entry) && !g_pattern_match_string(crtpat, entry)) {
 				continue;
 			}
 
 			fullpath = g_build_filename(iter->data, entry, NULL);
 
 			/* TODO: Respond to a failure in the following? */
-			crt = purple_certificate_import(x509, fullpath);
+			crts = purple_certificates_import(x509, fullpath);
 
-			if (x509_ca_quiet_put_cert(crt)) {
-				purple_debug_info("certificate/x509/ca",
-						  "Loaded %s\n",
-						  fullpath);
-			} else {
-				purple_debug_error("certificate/x509/ca",
-						  "Failed to load %s\n",
-						  fullpath);
+			while (crts && crts->data) {
+				crt = crts->data;
+				if (x509_ca_quiet_put_cert(crt)) {
+					gchar *name;
+					name = purple_certificate_get_subject_name(crt);
+					purple_debug_info("certificate/x509/ca",
+							  "Loaded %s from %s\n",
+							  name ? name : "(unknown)", fullpath);
+				} else {
+					purple_debug_error("certificate/x509/ca",
+							  "Failed to load certificate from %s\n",
+							  fullpath);
+				}
+				purple_certificate_destroy(crt);
+				crts = g_slist_delete_link(crts, crts);
 			}
 
-			purple_certificate_destroy(crt);
 			g_free(fullpath);
 		}
 		g_dir_close(certdir);
 	}
 
 	g_pattern_spec_free(pempat);
+	g_pattern_spec_free(crtpat);
 
 	purple_debug_info("certificate/x509/ca",
 			  "Lazy init completed.\n");
--- a/libpurple/certificate.h	Sat Mar 27 02:11:31 2010 +0000
+++ b/libpurple/certificate.h	Sat Mar 27 03:55:09 2010 +0000
@@ -250,10 +250,17 @@
 	/** Retrieve the certificate activation/expiration times */
 	gboolean (* get_times)(PurpleCertificate *crt, time_t *activation, time_t *expiration);
 
+	/** Imports certificates from a file
+	 *
+	 *  @param filename   File to import the certificates from
+	 *  @return           GSList of pointers to the newly allocated Certificate structs
+	 *                    or NULL on failure.
+	 */
+	GSList * (* import_certificates)(const gchar * filename);
+
 	void (*_purple_reserved1)(void);
 	void (*_purple_reserved2)(void);
 	void (*_purple_reserved3)(void);
-	void (*_purple_reserved4)(void);
 };
 
 /** A set of operations used to provide logic for verifying a Certificate's
@@ -492,6 +499,16 @@
 purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename);
 
 /**
+ * Imports a list of PurpleCertificates from a file
+ *
+ * @param scheme      Scheme to import under
+ * @param filename    File path to import from
+ * @return Pointer to a GSList of new PurpleCertificates, or NULL on failure
+ */
+GSList *
+purple_certificates_import(PurpleCertificateScheme *scheme, const gchar *filename);
+
+/**
  * Exports a PurpleCertificate to a file
  *
  * @param filename    File to export the certificate to
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Sat Mar 27 02:11:31 2010 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Sat Mar 27 03:55:09 2010 +0000
@@ -548,6 +548,55 @@
 	return crt;
 }
 
+/** Imports a number of PEM-formatted X.509 certificates from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A newly allocated GSList of Certificate structures of the x509_gnutls scheme
+ */
+static GSList *
+x509_importcerts_from_file(const gchar * filename)
+{
+	PurpleCertificate *crt;  /* Certificate being constructed */
+	gchar *buf;        /* Used to load the raw file data */
+	gchar *begin, *end;
+	GSList *crts = NULL;
+	gsize buf_sz;      /* Size of the above */
+	gnutls_datum dt; /* Struct to pass down to GnuTLS */
+
+	purple_debug_info("gnutls",
+			  "Attempting to load X.509 certificates from %s\n",
+			  filename);
+
+	/* Next, we'll simply yank the entire contents of the file
+	   into memory */
+	/* TODO: Should I worry about very large files here? */
+	g_return_val_if_fail(
+		g_file_get_contents(filename,
+			    &buf,
+			    &buf_sz,
+			    NULL      /* No error checking for now */
+		),
+		NULL);
+
+	begin = buf;
+	while((end = strstr(begin, "-----END CERTIFICATE-----")) != NULL) {
+		end += sizeof("-----END CERTIFICATE-----")-1;
+		/* Load the datum struct */
+		dt.data = (unsigned char *) begin;
+		dt.size = (end-begin);
+
+		/* Perform the conversion; files should be in PEM format */
+		crt = x509_import_from_datum(dt, GNUTLS_X509_FMT_PEM);
+		crts = g_slist_prepend(crts, crt);
+		begin = end;
+	}
+
+	/* Cleanup */
+	g_free(buf);
+
+	return crts;
+}
+
 /**
  * Exports a PEM-formatted X.509 certificate to the specified file.
  * @param filename Filename to export to. Format will be PEM
@@ -964,10 +1013,10 @@
 	x509_common_name,                /* Subject name */
 	x509_check_name,                 /* Check subject name */
 	x509_times,                      /* Activation/Expiration time */
+	x509_importcerts_from_file,      /* Multiple certificates import function */
 
 	NULL,
 	NULL,
-	NULL,
 	NULL
 
 };
--- a/libpurple/plugins/ssl/ssl-nss.c	Sat Mar 27 02:11:31 2010 +0000
+++ b/libpurple/plugins/ssl/ssl-nss.c	Sat Mar 27 03:55:09 2010 +0000
@@ -530,7 +530,7 @@
 /** Imports a PEM-formatted X.509 certificate from the specified file.
  * @param filename Filename to import from. Format is PEM
  *
- * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ * @return A newly allocated Certificate structure of the x509_nss scheme
  */
 static PurpleCertificate *
 x509_import_from_file(const gchar *filename)
@@ -575,6 +575,60 @@
 	return crt;
 }
 
+/** Imports a number of PEM-formatted X.509 certificates from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A GSList of newly allocated Certificate structures of the x509_nss scheme
+ */
+static GSList *
+x509_importcerts_from_file(const gchar *filename)
+{
+	gchar *rawcert, *begin, *end;
+	gsize len = 0;
+	GSList *crts = NULL;
+	CERTCertificate *crt_dat;
+	PurpleCertificate *crt;
+
+	g_return_val_if_fail(filename != NULL, NULL);
+
+	purple_debug_info("nss/x509",
+			  "Loading certificate from %s\n",
+			  filename);
+
+	/* Load the raw data up */
+	if (!g_file_get_contents(filename,
+				 &rawcert, &len,
+				 NULL)) {
+		purple_debug_error("nss/x509", "Unable to read certificate file.\n");
+		return NULL;
+	}
+
+	if (len == 0) {
+		purple_debug_error("nss/x509",
+				"Certificate file has no contents!\n");
+		if (rawcert)
+			g_free(rawcert);
+		return NULL;
+	}
+
+	begin = rawcert;
+	while((end = strstr(begin, "-----END CERTIFICATE-----")) != NULL) {
+		end += sizeof("-----END CERTIFICATE-----")-1;
+		/* Decode the certificate */
+		crt_dat = CERT_DecodeCertFromPackage(begin, (end-begin));
+
+		g_return_val_if_fail(crt_dat != NULL, NULL);
+
+		crt = g_new0(PurpleCertificate, 1);
+		crt->scheme = &x509_nss;
+		crt->data = crt_dat;
+		crts = g_slist_prepend(crts, crt);
+		begin = end;
+	}
+	g_free(rawcert);
+
+	return crts;
+}
 /**
  * Exports a PEM-formatted X.509 certificate to the specified file.
  * @param filename Filename to export to. Format will be PEM
@@ -874,10 +928,10 @@
 	x509_common_name,                /* Subject name */
 	x509_check_name,                 /* Check subject name */
 	x509_times,                      /* Activation/Expiration time */
+	x509_importcerts_from_file,      /* Multiple certificate import function */
 
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };