[network-manager-openvpn/NETWORKMANAGER_0_7] core: add support for PKCS#12 certificates and keys (bgo #534219)



commit 3fdf0a608c55aff58717153ed1c06d2dbf02c07e
Author: Huzaifa S. Sidhpurwala <huzaifas redhat com>
Date:   Mon Mar 1 11:21:08 2010 -0800

    core: add support for PKCS#12 certificates and keys (bgo #534219)
    
    Allow PKCS#12 files to be used.  openvpn requires that all of
    CA, client cert, and private key be in the same PKCS12 file, so the
    UI widgets make sure that either all cert file chooser buttons
    contain the selected PKCS#12 file, or none of them do.
    
    This should also clean up handling of PEM private keys so that if
    the key is unencrypted, the user is not asked for a password.
    
    (heavily cleaned up and refactored by dcbw)

 Makefile.am                           |    2 +-
 auth-dialog/Makefile.am               |    3 +-
 auth-dialog/main.c                    |   38 +--------
 common/Makefile.am                    |   14 +++
 common/utils.c                        |   87 +++++++++++++++++++
 common/utils.h                        |   32 +++++++
 configure.ac                          |    1 +
 properties/Makefile.am                |    3 +-
 properties/auth-helpers.c             |  153 +++++++++++++++++++++++++--------
 properties/auth-helpers.h             |    2 +-
 properties/import-export.c            |   24 ++++--
 properties/tests/conf/Makefile.am     |    3 +-
 properties/tests/conf/pkcs12.ovpn     |   17 ++++
 properties/tests/test-import-export.c |  114 ++++++++++++++++++++++++-
 src/Makefile.am                       |   47 +++++-----
 src/nm-openvpn-service.c              |   90 +++++++++++---------
 16 files changed, 483 insertions(+), 147 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index e86d11f..9fe9ddb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
 AUTOMAKE_OPTIONS = foreign
 
-SUBDIRS = src
+SUBDIRS = common src
 
 if WITH_GNOME
 SUBDIRS += common-gnome auth-dialog properties po
diff --git a/auth-dialog/Makefile.am b/auth-dialog/Makefile.am
index ef0e675..5a748e3 100644
--- a/auth-dialog/Makefile.am
+++ b/auth-dialog/Makefile.am
@@ -24,6 +24,7 @@ nm_openvpn_auth_dialog_SOURCES =			\
 nm_openvpn_auth_dialog_LDADD =			\
 	$(GTK_LIBS)				\
 	$(GCONF_LIBS)				\
-	$(top_builddir)/common-gnome/libnm-openvpn-common-gnome.la
+	$(top_builddir)/common-gnome/libnm-openvpn-common-gnome.la \
+	$(top_builddir)/common/libnm-openvpn-common.la
 
 CLEANFILES = *~
diff --git a/auth-dialog/main.c b/auth-dialog/main.c
index 699d601..45ed544 100644
--- a/auth-dialog/main.c
+++ b/auth-dialog/main.c
@@ -36,6 +36,7 @@
 #include <nm-setting-connection.h>
 
 #include "common-gnome/keyring-helpers.h"
+#include "common/utils.h"
 #include "src/nm-openvpn-service.h"
 #include "gnome-two-password-dialog.h"
 
@@ -50,40 +51,6 @@ typedef struct {
 	char *certpass;
 } PasswordsInfo;
 
-#define PROC_TYPE_TAG "Proc-Type: 4,ENCRYPTED"
-
-/** Checks if a key is encrypted
- * The key file is read and it is checked if it contains a line reading
- * Proc-Type: 4,ENCRYPTED
- * This is defined in RFC 1421 (PEM)
- * @param filename the path to the file
- * @return returns true if the key is encrypted, false otherwise
- */
-static gboolean
-pem_is_encrypted (const char *filename)
-{
-	GIOChannel *pem_chan;
-	char       *str = NULL;
-	gboolean encrypted = FALSE;
-
-	pem_chan = g_io_channel_new_file (filename, "r", NULL);
-	if (!pem_chan)
-		return FALSE;
-
-	while (g_io_channel_read_line (pem_chan, &str, NULL, NULL, NULL) != G_IO_STATUS_EOF) {
-		if (strncmp (str, PROC_TYPE_TAG, strlen (PROC_TYPE_TAG)) == 0) {
-			encrypted = TRUE;
-			break;
-		}
-		g_free (str);
-	}
-
-	g_io_channel_shutdown (pem_chan, FALSE, NULL);
-	g_io_channel_unref (pem_chan);
-	return encrypted;
-}
-
-
 static void
 clear_secrets (PasswordsInfo *info)
 {
@@ -289,8 +256,7 @@ get_password_types (PasswordsInfo *info)
 		key = g_strdup_printf ("%s/%s/%s", connection_path, NM_SETTING_VPN_SETTING_NAME,
 		                       NM_OPENVPN_KEY_KEY);
 		str = gconf_client_get_string (gconf_client, key, NULL);
-		if (str)
-			info->need_certpass = pem_is_encrypted (str);
+		info->need_certpass = (is_pkcs12 (str) || is_encrypted_pem (str));
 		g_free (str);
 		g_free (key);
 	} else if (!strcmp (connection_type, NM_OPENVPN_CONTYPE_STATIC_KEY)) {
diff --git a/common/Makefile.am b/common/Makefile.am
new file mode 100644
index 0000000..c16eba8
--- /dev/null
+++ b/common/Makefile.am
@@ -0,0 +1,14 @@
+noinst_LTLIBRARIES=libnm-openvpn-common.la
+
+libnm_openvpn_common_la_CPPFLAGS = \
+	$(NETWORK_MANAGER_CFLAGS) \
+	-DG_DISABLE_DEPRECATED \
+        -I$(top_srcdir)/src/
+
+libnm_openvpn_common_la_SOURCES= \
+	utils.c \
+	utils.h
+
+libnm_openvpn_common_gnome_la_LIBADD = \
+	$(NETWORK_MANAGER_LIBS)
+
diff --git a/common/utils.c b/common/utils.c
new file mode 100644
index 0000000..7574801
--- /dev/null
+++ b/common/utils.c
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * Dan Williams <dcbw redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2010 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <nm-setting-8021x.h>
+#include "utils.h"
+
+gboolean
+is_pkcs12 (const char *filepath)
+{
+	NMSetting8021xCKFormat ck_format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+	NMSetting8021x *s_8021x;
+
+	if (!filepath || !strlen (filepath))
+		return FALSE;
+
+	if (!g_file_test (filepath, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
+		return FALSE;
+
+	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+	g_return_val_if_fail (s_8021x != NULL, FALSE);
+
+	nm_setting_802_1x_set_private_key (s_8021x,
+	                                   filepath,
+	                                   NULL,
+	                                   NM_SETTING_802_1X_CK_SCHEME_PATH,
+	                                   &ck_format,
+	                                   NULL);
+	g_object_unref (s_8021x);
+
+	return (ck_format == NM_SETTING_802_1X_CK_FORMAT_PKCS12);
+}
+
+#define PROC_TYPE_TAG "Proc-Type: 4,ENCRYPTED"
+
+/** Checks if a key is encrypted
+ * The key file is read and it is checked if it contains a line reading
+ * Proc-Type: 4,ENCRYPTED
+ * This is defined in RFC 1421 (PEM)
+ * @param filename the path to the file
+ * @return returns true if the key is encrypted, false otherwise
+ */
+gboolean
+is_encrypted_pem (const char *filename)
+{
+	GIOChannel *pem_chan;
+	char *str = NULL;
+	gboolean encrypted = FALSE;
+
+	if (!filename || !strlen (filename))
+		return FALSE;
+
+	pem_chan = g_io_channel_new_file (filename, "r", NULL);
+	if (!pem_chan)
+		return FALSE;
+
+	while (g_io_channel_read_line (pem_chan, &str, NULL, NULL, NULL) != G_IO_STATUS_EOF) {
+		if (strncmp (str, PROC_TYPE_TAG, strlen (PROC_TYPE_TAG)) == 0) {
+			encrypted = TRUE;
+			break;
+		}
+		g_free (str);
+	}
+
+	g_io_channel_shutdown (pem_chan, FALSE, NULL);
+	g_io_channel_unref (pem_chan);
+	return encrypted;
+}
+
diff --git a/common/utils.h b/common/utils.h
new file mode 100644
index 0000000..5e6033e
--- /dev/null
+++ b/common/utils.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * Dan Williams <dcbw redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2010 Red Hat, Inc.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <glib.h>
+
+gboolean is_pkcs12 (const char *filepath);
+
+gboolean is_encrypted_pem (const char *filename);
+
+#endif  /* UTILS_H */
+
diff --git a/configure.ac b/configure.ac
index 03aa692..8db1521 100644
--- a/configure.ac
+++ b/configure.ac
@@ -118,6 +118,7 @@ esac
 AC_CONFIG_FILES([
 Makefile
 src/Makefile
+common/Makefile
 common-gnome/Makefile
 auth-dialog/Makefile
 properties/Makefile
diff --git a/properties/Makefile.am b/properties/Makefile.am
index 6044378..77b9e5d 100644
--- a/properties/Makefile.am
+++ b/properties/Makefile.am
@@ -35,7 +35,8 @@ libnm_openvpn_properties_la_LIBADD =    \
         $(GTK_LIBS)                     \
         $(GCONF_LIBS)                   \
         $(NETWORK_MANAGER_LIBS)         \
-        $(top_builddir)/common-gnome/libnm-openvpn-common-gnome.la
+        $(top_builddir)/common-gnome/libnm-openvpn-common-gnome.la \
+        $(top_builddir)/common/libnm-openvpn-common.la
 
 libnm_openvpn_properties_la_LDFLAGS =   \
         -avoid-version
diff --git a/properties/auth-helpers.c b/properties/auth-helpers.c
index 7279550..6c5ad22 100644
--- a/properties/auth-helpers.c
+++ b/properties/auth-helpers.c
@@ -35,11 +35,13 @@
 #include <glib/gi18n-lib.h>
 #include <gnome-keyring-memory.h>
 #include <nm-setting-connection.h>
+#include <nm-setting-8021x.h>
 
 #include "auth-helpers.h"
 #include "nm-openvpn.h"
 #include "src/nm-openvpn-service.h"
 #include "common-gnome/keyring-helpers.h"
+#include "common/utils.h"
 
 static void
 show_password (GtkToggleButton *togglebutton, GtkEntry *password_entry)
@@ -129,6 +131,47 @@ fill_vpn_passwords (GladeXML *xml,
 	}
 }
 
+static void
+tls_cert_changed_cb (GtkWidget *widget, GtkWidget *next_widget)
+{
+	GtkFileChooser *this, *next;
+	char *fname, *next_fname;
+
+	/* If the just-changed file chooser is a PKCS#12 file, then all of the
+	 * TLS filechoosers have to be PKCS#12.  But if it just changed to something
+	 * other than a PKCS#12 file, then clear out the other file choosers.
+	 *
+	 * Basically, all the choosers have to contain PKCS#12 files, or none of
+	 * them can, because PKCS#12 files contain everything required for the TLS
+	 * connection (CA, client cert, private key).
+	 */
+
+	this = GTK_FILE_CHOOSER (widget);
+	next = GTK_FILE_CHOOSER (next_widget);
+
+	fname = gtk_file_chooser_get_filename (this);
+	if (is_pkcs12 (fname)) {
+		/* Make sure all choosers have this PKCS#12 file */
+		next_fname = gtk_file_chooser_get_filename (next);
+		if (!next_fname || strcmp (fname, next_fname)) {
+			/* Next chooser was different, make it the same as the first */
+			gtk_file_chooser_set_filename (next, fname);
+		}
+		g_free (fname);
+		g_free (next_fname);
+		return;
+	}
+	g_free (fname);
+
+	/* Just-chosen file isn't PKCS#12 or no file was chosen, so clear out other
+	 * file selectors that have PKCS#12 files in them.
+	 */
+	next_fname = gtk_file_chooser_get_filename (next);
+	if (is_pkcs12 (next_fname))
+		gtk_file_chooser_set_filename (next, NULL);
+	g_free (next_fname);
+}
+
 void
 tls_pw_init_auth_widget (GladeXML *xml,
                          GtkSizeGroup *group,
@@ -138,7 +181,7 @@ tls_pw_init_auth_widget (GladeXML *xml,
                          ChangedCallback changed_cb,
                          gpointer user_data)
 {
-	GtkWidget *widget;
+	GtkWidget *widget, *ca, *cert, *key;
 	const char *value;
 	char *tmp;
 	GtkFileFilter *filter;
@@ -149,59 +192,68 @@ tls_pw_init_auth_widget (GladeXML *xml,
 	g_return_if_fail (prefix != NULL);
 
 	tmp = g_strdup_printf ("%s_ca_cert_chooser", prefix);
-	widget = glade_xml_get_widget (xml, tmp);
+	ca = glade_xml_get_widget (xml, tmp);
 	g_free (tmp);
 
-	gtk_size_group_add_widget (group, widget);
-	filter = tls_file_chooser_filter_new ();
-	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget), filter);
-	gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), TRUE);
-	gtk_file_chooser_button_set_title (GTK_FILE_CHOOSER_BUTTON (widget),
+	gtk_size_group_add_widget (group, ca);
+	if (!strcmp (contype, NM_OPENVPN_CONTYPE_TLS) || !strcmp (contype, NM_OPENVPN_CONTYPE_PASSWORD_TLS))
+		filter = tls_file_chooser_filter_new (TRUE);
+	else
+		filter = tls_file_chooser_filter_new (FALSE);
+
+	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (ca), filter);
+	gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (ca), TRUE);
+	gtk_file_chooser_button_set_title (GTK_FILE_CHOOSER_BUTTON (ca),
 	                                   _("Choose a Certificate Authority certificate..."));
-	g_signal_connect (G_OBJECT (widget), "selection-changed", G_CALLBACK (changed_cb), user_data);
+	g_signal_connect (G_OBJECT (ca), "selection-changed", G_CALLBACK (changed_cb), user_data);
 
 	if (s_vpn) {
 		value = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CA);
 		if (value && strlen (value))
-			gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (widget), value);
+			gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (ca), value);
 	}
 
 	if (!strcmp (contype, NM_OPENVPN_CONTYPE_TLS) || !strcmp (contype, NM_OPENVPN_CONTYPE_PASSWORD_TLS)) {
 		tmp = g_strdup_printf ("%s_user_cert_chooser", prefix);
-		widget = glade_xml_get_widget (xml, tmp);
+		cert = glade_xml_get_widget (xml, tmp);
 		g_free (tmp);
 
-		gtk_size_group_add_widget (group, widget);
-		filter = tls_file_chooser_filter_new ();
-		gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget), filter);
-		gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), TRUE);
-		gtk_file_chooser_button_set_title (GTK_FILE_CHOOSER_BUTTON (widget),
+		gtk_size_group_add_widget (group, cert);
+		filter = tls_file_chooser_filter_new (TRUE);
+		gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (cert), filter);
+		gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (cert), TRUE);
+		gtk_file_chooser_button_set_title (GTK_FILE_CHOOSER_BUTTON (cert),
 		                                   _("Choose your personal certificate..."));
-		g_signal_connect (G_OBJECT (widget), "selection-changed", G_CALLBACK (changed_cb), user_data);
+		g_signal_connect (G_OBJECT (cert), "selection-changed", G_CALLBACK (changed_cb), user_data);
 
 		if (s_vpn) {
 			value = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CERT);
 			if (value && strlen (value))
-				gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (widget), value);
+				gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (cert), value);
 		}
 
 		tmp = g_strdup_printf ("%s_private_key_chooser", prefix);
-		widget = glade_xml_get_widget (xml, tmp);
+		key = glade_xml_get_widget (xml, tmp);
 		g_free (tmp);
 
-		gtk_size_group_add_widget (group, widget);
-		filter = tls_file_chooser_filter_new ();
-		gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget), filter);
-		gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), TRUE);
-		gtk_file_chooser_button_set_title (GTK_FILE_CHOOSER_BUTTON (widget),
+		gtk_size_group_add_widget (group, key);
+		filter = tls_file_chooser_filter_new (TRUE);
+		gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (key), filter);
+		gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (key), TRUE);
+		gtk_file_chooser_button_set_title (GTK_FILE_CHOOSER_BUTTON (key),
 		                                   _("Choose your private key..."));
-		g_signal_connect (G_OBJECT (widget), "selection-changed", G_CALLBACK (changed_cb), user_data);
+		g_signal_connect (G_OBJECT (key), "selection-changed", G_CALLBACK (changed_cb), user_data);
 
 		if (s_vpn) {
 			value = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
 			if (value && strlen (value))
-				gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (widget), value);
+				gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (key), value);
 		}
+
+		/* Link choosers to the PKCS#12 changer callback */
+		g_signal_connect (ca, "selection-changed", G_CALLBACK (tls_cert_changed_cb), cert);
+		g_signal_connect (cert, "selection-changed", G_CALLBACK (tls_cert_changed_cb), key);
+		g_signal_connect (key, "selection-changed", G_CALLBACK (tls_cert_changed_cb), ca);
 	}
 
 	if (!strcmp (contype, NM_OPENVPN_CONTYPE_PASSWORD) || !strcmp (contype, NM_OPENVPN_CONTYPE_PASSWORD_TLS)) {
@@ -317,19 +369,23 @@ validate_file_chooser (GladeXML *xml, const char *name)
 {
 	GtkWidget *widget;
 	char *str;
+	gboolean valid = FALSE;
 
 	widget = glade_xml_get_widget (xml, name);
 	str = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
-	if (!str || !strlen (str))
-		return FALSE;
-	return TRUE;
+	if (str && strlen (str))
+		valid = TRUE;
+	g_free (str);
+	return valid;
 }
 
 static gboolean
 validate_tls (GladeXML *xml, const char *prefix, GError **error)
 {
 	char *tmp;
-	gboolean valid;
+	gboolean valid, encrypted = FALSE;
+	GtkWidget *widget;
+	char *str;
 
 	tmp = g_strdup_printf ("%s_ca_cert_chooser", prefix);
 	valid = validate_file_chooser (xml, tmp);
@@ -354,6 +410,7 @@ validate_tls (GladeXML *xml, const char *prefix, GError **error)
 	}
 
 	tmp = g_strdup_printf ("%s_private_key_chooser", prefix);
+	widget = glade_xml_get_widget (xml, tmp);
 	valid = validate_file_chooser (xml, tmp);
 	g_free (tmp);
 	if (!valid) {
@@ -364,6 +421,24 @@ validate_tls (GladeXML *xml, const char *prefix, GError **error)
 		return FALSE;
 	}
 
+	/* Encrypted certificates require a password */
+	str = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
+	encrypted = is_pkcs12 (str) || is_encrypted_pem (str);
+	g_free (str);
+	if (encrypted) {
+		tmp = g_strdup_printf ("%s_private_key_password_entry", prefix);
+		widget = glade_xml_get_widget (xml, tmp);
+		g_free (tmp);
+
+		if (!gtk_entry_get_text_length (GTK_ENTRY (widget))) {
+			g_set_error (error,
+			             OPENVPN_PLUGIN_UI_ERROR,
+			             OPENVPN_PLUGIN_UI_ERROR_INVALID_PROPERTY,
+			             NM_OPENVPN_KEY_CERTPASS);
+			return FALSE;
+		}
+	}
+
 	return TRUE;
 }
 
@@ -461,12 +536,8 @@ update_from_filechooser (GladeXML *xml,
 	g_free (tmp);
 
 	filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
-	if (!filename)
-		return;
-
-	if (strlen (filename))
+	if (filename && strlen (filename))
 		nm_setting_vpn_add_data_item (s_vpn, key, filename);
-	
 	g_free (filename);
 }
 
@@ -630,6 +701,7 @@ tls_default_filter (const GtkFileFilterInfo *filter_info, gpointer data)
 	char *contents = NULL, *p, *ext;
 	gsize bytes_read = 0;
 	gboolean show = FALSE;
+	gboolean pkcs_allowed = GPOINTER_TO_UINT (data);
 	struct stat statbuf;
 
 	if (!filter_info->filename)
@@ -642,6 +714,12 @@ tls_default_filter (const GtkFileFilterInfo *filter_info, gpointer data)
 	ext = g_ascii_strdown (p, -1);
 	if (!ext)
 		return FALSE;
+
+	if (pkcs_allowed && !strcmp (ext, ".p12") && is_pkcs12 (filter_info->filename)) {
+		g_free (ext);
+		return TRUE;
+	}
+
 	if (strcmp (ext, ".pem") && strcmp (ext, ".crt") && strcmp (ext, ".key") && strcmp (ext, ".cer")) {
 		g_free (ext);
 		return FALSE;
@@ -682,13 +760,14 @@ out:
 }
 
 GtkFileFilter *
-tls_file_chooser_filter_new (void)
+tls_file_chooser_filter_new (gboolean pkcs_allowed)
 {
 	GtkFileFilter *filter;
 
 	filter = gtk_file_filter_new ();
-	gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_FILENAME, tls_default_filter, NULL, NULL);
-	gtk_file_filter_set_name (filter, _("PEM certificates (*.pem, *.crt, *.key, *.cer)"));
+	gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_FILENAME, tls_default_filter, GUINT_TO_POINTER (pkcs_allowed), NULL);
+	gtk_file_filter_set_name (filter, pkcs_allowed ? _("PEM or PKCS#12 certificates (*.pem, *.crt, *.key, *.cer, *.p12)")
+	                                               : _("PEM certificates (*.pem, *.crt, *.key, *.cer)"));
 	return filter;
 }
 
diff --git a/properties/auth-helpers.h b/properties/auth-helpers.h
index b5ea395..d3af84b 100644
--- a/properties/auth-helpers.h
+++ b/properties/auth-helpers.h
@@ -63,7 +63,7 @@ gboolean auth_widget_save_secrets (GladeXML *xml,
 								   const char *uuid,
 								   const char *name);
 
-GtkFileFilter *tls_file_chooser_filter_new (void);
+GtkFileFilter *tls_file_chooser_filter_new (gboolean pkcs_allowed);
 
 GtkFileFilter *sk_file_chooser_filter_new (void);
 
diff --git a/properties/import-export.c b/properties/import-export.c
index 4f54a57..b35404e 100644
--- a/properties/import-export.c
+++ b/properties/import-export.c
@@ -65,6 +65,7 @@
 #define MSSFIX_TAG "mssfix"
 #define TUNMTU_TAG "tun-mtu"
 #define FRAGMENT_TAG "fragment"
+#define PKCS12_TAG "pkcs12"
 
 
 static char *
@@ -388,6 +389,11 @@ do_import (const char *path, char **lines, GError **error)
 			}
 		}
 
+		if ( handle_path_item (*line, PKCS12_TAG, NM_OPENVPN_KEY_CA, s_vpn, default_path, NULL) &&
+		     handle_path_item (*line, PKCS12_TAG, NM_OPENVPN_KEY_CERT, s_vpn, default_path, NULL) &&
+		     handle_path_item (*line, PKCS12_TAG, NM_OPENVPN_KEY_KEY, s_vpn, default_path, NULL))
+			continue;
+
 		if (handle_path_item (*line, CA_TAG, NM_OPENVPN_KEY_CA, s_vpn, default_path, NULL))
 			continue;
 
@@ -652,12 +658,18 @@ do_export (const char *path, NMConnection *connection, GError **error)
 	         port ? " " : "",
 	         port ? port : "");
 
-	if (cacert)
-		fprintf (f, "ca %s\n", cacert);
-	if (user_cert)
-		fprintf (f, "cert %s\n", user_cert);
-	if (private_key)
-		fprintf(f, "key %s\n", private_key);
+	/* Handle PKCS#12 (all certs are the same file) */
+	if (   cacert && user_cert && private_key
+	    && !strcmp (cacert, user_cert) && !strcmp (cacert, private_key))
+		fprintf (f, "pkcs12 %s\n", cacert);
+	else {
+		if (cacert)
+			fprintf (f, "ca %s\n", cacert);
+		if (user_cert)
+			fprintf (f, "cert %s\n", user_cert);
+		if (private_key)
+			fprintf(f, "key %s\n", private_key);
+	}
 
 	if (   !strcmp(connection_type, NM_OPENVPN_CONTYPE_PASSWORD)
 	    || !strcmp(connection_type, NM_OPENVPN_CONTYPE_PASSWORD_TLS))
diff --git a/properties/tests/conf/Makefile.am b/properties/tests/conf/Makefile.am
index f999b76..6823fa9 100644
--- a/properties/tests/conf/Makefile.am
+++ b/properties/tests/conf/Makefile.am
@@ -6,6 +6,7 @@ EXTRA_DIST = \
 	static.ovpn \
 	port.ovpn \
 	rport.ovpn \
-	tun-opts.conf
+	tun-opts.conf \
+	pkcs12.ovpn
 
 
diff --git a/properties/tests/conf/pkcs12.ovpn b/properties/tests/conf/pkcs12.ovpn
new file mode 100644
index 0000000..12281b0
--- /dev/null
+++ b/properties/tests/conf/pkcs12.ovpn
@@ -0,0 +1,17 @@
+remote 173.8.149.245 1194
+resolv-retry infinite
+
+dev tun
+persist-key
+persist-tun
+link-mtu 1400
+proto udp
+nobind
+pull
+tls-client
+
+pkcs12 keys/mine.p12
+
+comp-lzo
+verb 3
+
diff --git a/properties/tests/test-import-export.c b/properties/tests/test-import-export.c
index 6cb1df4..8c6e137 100644
--- a/properties/tests/test-import-export.c
+++ b/properties/tests/test-import-export.c
@@ -351,6 +351,115 @@ test_tls_export (NMVpnPluginUiInterface *plugin, const char *dir)
 }
 
 static void
+test_pkcs12_import (NMVpnPluginUiInterface *plugin, const char *dir)
+{
+	NMConnection *connection;
+	NMSettingConnection *s_con;
+	NMSettingIP4Config *s_ip4;
+	NMSettingVPN *s_vpn;
+	const char *expected_id = "pkcs12";
+	char *expected_path;
+
+	connection = get_basic_connection ("pkcs12-import", plugin, dir, "pkcs12.ovpn");
+	ASSERT (connection != NULL, "pkcs12-import", "failed to import connection");
+
+	/* Connection setting */
+	s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+	ASSERT (s_con != NULL,
+	        "pkcs12-import", "missing 'connection' setting");
+
+	ASSERT (strcmp (nm_setting_connection_get_id (s_con), expected_id) == 0,
+	        "pkcs12-import", "unexpected connection ID");
+
+	ASSERT (nm_setting_connection_get_uuid (s_con) == NULL,
+	        "pkcs12-import", "unexpected valid UUID");
+
+	/* IP4 setting */
+	s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	ASSERT (s_ip4 == NULL,
+	        "pkcs12-import", "unexpected 'ip4-config' setting");
+
+	/* VPN setting */
+	s_vpn = (NMSettingVPN *) nm_connection_get_setting (connection, NM_TYPE_SETTING_VPN);
+	ASSERT (s_vpn != NULL,
+	        "pkcs12-import", "missing 'vpn' setting");
+
+	/* Data items */
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_CONNECTION_TYPE, NM_OPENVPN_CONTYPE_TLS);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_TAP_DEV, NULL);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_PROTO_TCP, NULL);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_COMP_LZO, "yes");
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_RENEG_SECONDS, NULL);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_REMOTE, "173.8.149.245");
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_PORT, "1194");
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_STATIC_KEY, NULL);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_STATIC_KEY_DIRECTION, NULL);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_CIPHER, NULL);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_LOCAL_IP, NULL);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_REMOTE_IP, NULL);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_AUTH, NULL);
+
+	expected_path = g_strdup_printf ("%s/keys/mine.p12", dir);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_CA, expected_path);
+	g_free (expected_path);
+
+	expected_path = g_strdup_printf ("%s/keys/mine.p12", dir);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_CERT, expected_path);
+	g_free (expected_path);
+
+	expected_path = g_strdup_printf ("%s/keys/mine.p12", dir);
+	test_item ("pkcs12-import-data", s_vpn, NM_OPENVPN_KEY_KEY, expected_path);
+	g_free (expected_path);
+
+	/* Secrets */
+	test_secret ("pkcs12-import-secrets", s_vpn, NM_OPENVPN_KEY_PASSWORD, NULL);
+	test_secret ("pkcs12-import-secrets", s_vpn, NM_OPENVPN_KEY_CERTPASS, NULL);
+
+	g_object_unref (connection);
+}
+
+#define PKCS12_EXPORTED_NAME "pkcs12.ovpntest"
+static void
+test_pkcs12_export (NMVpnPluginUiInterface *plugin, const char *dir)
+{
+	NMConnection *connection;
+	NMConnection *reimported;
+	char *path;
+	gboolean success;
+	GError *error = NULL;
+	int ret;
+
+	connection = get_basic_connection ("pkcs12-export", plugin, dir, "pkcs12.ovpn");
+	ASSERT (connection != NULL, "pkcs12-export", "failed to import connection");
+
+	path = g_build_path ("/", dir, PKCS12_EXPORTED_NAME, NULL);
+	success = nm_vpn_plugin_ui_interface_export (plugin, path, connection, &error);
+	if (!success) {
+		if (!error)
+			FAIL ("pkcs12-export", "export failed with missing error");
+		else
+			FAIL ("pkcs12-export", "export failed: %s", error->message);
+	}
+
+	/* Now re-import it and compare the connections to ensure they are the same */
+	reimported = get_basic_connection ("pkcs12-export", plugin, dir, PKCS12_EXPORTED_NAME);
+	ret = unlink (path);
+	ASSERT (connection != NULL, "pkcs12-export", "failed to re-import connection");
+
+	/* Clear secrets first, since they don't get exported, and thus would
+	 * make the connection comparison below fail.
+	 */
+	remove_secrets (connection);
+
+	ASSERT (nm_connection_compare (connection, reimported, NM_SETTING_COMPARE_FLAG_EXACT) == TRUE,
+	        "pkcs12-export", "original and reimported connection differ");
+
+	g_object_unref (reimported);
+	g_object_unref (connection);
+	g_free (path);
+}
+
+static void
 test_non_utf8_import (NMVpnPluginUiInterface *plugin, const char *dir)
 {
 	NMConnection *connection;
@@ -385,7 +494,7 @@ test_non_utf8_import (NMVpnPluginUiInterface *plugin, const char *dir)
 	        "non-utf8-import", "missing 'vpn' setting");
 
 	expected_path = g_strdup_printf ("%s/%s", dir, expected_cacert);
-	test_item ("tls-import-data", s_vpn, NM_OPENVPN_KEY_CA, expected_path);
+	test_item ("non-utf8-import-data", s_vpn, NM_OPENVPN_KEY_CA, expected_path);
 	g_free (expected_path);
 
 	g_object_unref (connection);
@@ -664,6 +773,9 @@ int main (int argc, char **argv)
 	test_tls_import (plugin, argv[1]);
 	test_tls_export (plugin, argv[1]);
 
+	test_pkcs12_import (plugin, argv[1]);
+	test_pkcs12_export (plugin, argv[1]);
+
 	test_non_utf8_import (plugin, argv[1]);
 
 	test_static_key_import (plugin, argv[1]);
diff --git a/src/Makefile.am b/src/Makefile.am
index ea40630..9557f6b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,31 +1,32 @@
-AM_CPPFLAGS =							\
-	$(DBUS_CFLAGS)						\
-	$(NETWORK_MANAGER_CFLAGS)				\
-	-DG_DISABLE_DEPRECATED					\
-	-DBINDIR=\"$(bindir)\"					\
-	-DPREFIX=\""$(prefix)"\"				\
-	-DSYSCONFDIR=\""$(sysconfdir)"\"			\
-	-DVERSION="\"$(VERSION)\""				\
-	-DLIBDIR=\""$(libdir)"\"				\
-	-DLIBEXECDIR=\""$(libexecdir)"\"			\
-	-DLOCALSTATEDIR=\""$(localstatedir)"\"		 	\
-	-DDATADIR=\"$(datadir)\"
+AM_CPPFLAGS = \
+	$(DBUS_CFLAGS) \
+	$(NETWORK_MANAGER_CFLAGS) \
+	-DG_DISABLE_DEPRECATED \
+	-DBINDIR=\"$(bindir)\" \
+	-DPREFIX=\""$(prefix)"\" \
+	-DSYSCONFDIR=\""$(sysconfdir)"\" \
+	-DVERSION="\"$(VERSION)\"" \
+	-DLIBDIR=\""$(libdir)"\" \
+	-DLIBEXECDIR=\""$(libexecdir)"\" \
+	-DLOCALSTATEDIR=\""$(localstatedir)"\" \
+	-DDATADIR=\"$(datadir)\" \
+	-I$(top_srcdir)
 
 libexec_PROGRAMS = nm-openvpn-service nm-openvpn-service-openvpn-helper
 
-nm_openvpn_service_SOURCES =				\
-				nm-openvpn-service.c	\
-				nm-openvpn-service.h
+nm_openvpn_service_SOURCES = \
+	nm-openvpn-service.c \
+	nm-openvpn-service.h
 
+nm_openvpn_service_LDADD = \
+	$(NETWORK_MANAGER_LIBS) \
+        $(top_builddir)/common/libnm-openvpn-common.la
 
-nm_openvpn_service_LDADD = $(NETWORK_MANAGER_LIBS)
+nm_openvpn_service_openvpn_helper_SOURCES = \
+	nm-openvpn-service-openvpn-helper.c
 
-
-nm_openvpn_service_openvpn_helper_SOURCES = 					\
-				nm-openvpn-service-openvpn-helper.c
-
-nm_openvpn_service_openvpn_helper_LDADD = 				\
-				$(DBUS_LIBS)				\
-				$(NETWORK_MANAGER_LIBS)
+nm_openvpn_service_openvpn_helper_LDADD = \
+	$(DBUS_LIBS) \
+	$(NETWORK_MANAGER_LIBS)
 
 CLEANFILES = *~
diff --git a/src/nm-openvpn-service.c b/src/nm-openvpn-service.c
index 920abcb..3a71dab 100644
--- a/src/nm-openvpn-service.c
+++ b/src/nm-openvpn-service.c
@@ -52,6 +52,7 @@
 
 #include "nm-openvpn-service.h"
 #include "nm-utils.h"
+#include "common/utils.h"
 
 #define NM_OPENVPN_HELPER_PATH		LIBEXECDIR"/nm-openvpn-service-openvpn-helper"
 
@@ -630,6 +631,43 @@ add_openvpn_arg_int (GPtrArray *args, const char *arg)
 	return TRUE;
 }
 
+static void
+add_cert_args (GPtrArray *args, NMSettingVPN *s_vpn)
+{
+	const char *ca, *cert, *key;
+
+	g_return_if_fail (args != NULL);
+	g_return_if_fail (s_vpn != NULL);
+
+	ca = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CA);
+	cert = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CERT);
+	key = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
+
+	if (   ca && strlen (ca)
+	    && cert && strlen (cert)
+	    && key && strlen (key)
+	    && !strcmp (ca, cert)
+	    && !strcmp (ca, key)) {
+		add_openvpn_arg (args, "--pkcs12");
+		add_openvpn_arg (args, ca);
+	} else {
+		if (ca && strlen (ca)) {
+			add_openvpn_arg (args, "--ca");
+			add_openvpn_arg (args, ca);
+		}
+
+		if (cert && strlen (cert)) {
+			add_openvpn_arg (args, "--cert");
+			add_openvpn_arg (args, cert);
+		}
+
+		if (key && strlen (key)) {
+			add_openvpn_arg (args, "--key");
+			add_openvpn_arg (args, key);
+		}
+	}
+}
+
 static gboolean
 nm_openvpn_start_openvpn_binary (NMOpenvpnPlugin *plugin,
                                  NMSettingVPN *s_vpn,
@@ -847,24 +885,7 @@ nm_openvpn_start_openvpn_binary (NMOpenvpnPlugin *plugin,
 	/* Now append configuration options which are dependent on the configuration type */
 	if (!strcmp (connection_type, NM_OPENVPN_CONTYPE_TLS)) {
 		add_openvpn_arg (args, "--client");
-
-		tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CA);
-		if (tmp && strlen (tmp)) {
-			add_openvpn_arg (args, "--ca");
-			add_openvpn_arg (args, tmp);
-		}
-
-		tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CERT);
-		if (tmp && strlen (tmp)) {
-			add_openvpn_arg (args, "--cert");
-			add_openvpn_arg (args, tmp);
-		}
-
-		tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
-		if (tmp && strlen (tmp)) {
-			add_openvpn_arg (args, "--key");
-			add_openvpn_arg (args, tmp);
-		}
+		add_cert_args (args, s_vpn);
 	} else if (!strcmp (connection_type, NM_OPENVPN_CONTYPE_STATIC_KEY)) {
 		tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_STATIC_KEY);
 		if (tmp && strlen (tmp)) {
@@ -916,25 +937,7 @@ nm_openvpn_start_openvpn_binary (NMOpenvpnPlugin *plugin,
 		}
 	} else if (!strcmp (connection_type, NM_OPENVPN_CONTYPE_PASSWORD_TLS)) {
 		add_openvpn_arg (args, "--client");
-
-		tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CA);
-		if (tmp && strlen (tmp)) {
-			add_openvpn_arg (args, "--ca");
-			add_openvpn_arg (args, tmp);
-		}
-
-		tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_CERT);
-		if (tmp && strlen (tmp)) {
-			add_openvpn_arg (args, "--cert");
-			add_openvpn_arg (args, tmp);
-		}
-
-		tmp = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
-		if (tmp && strlen (tmp)) {
-			add_openvpn_arg (args, "--key");
-			add_openvpn_arg (args, tmp);
-		}
-
+		add_cert_args (args, s_vpn);
 		/* Use user/path authentication */
 		add_openvpn_arg (args, "--auth-user-pass");
 	} else {
@@ -1097,8 +1100,13 @@ real_need_secrets (NMVPNPlugin *plugin,
 	}
 
 	if (!strcmp (connection_type, NM_OPENVPN_CONTYPE_PASSWORD_TLS)) {
+		const char *key;
+
 		/* Will require a password and maybe private key password */
-		if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_CERTPASS))
+
+		key = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
+		if (   (is_pkcs12 (key) || is_encrypted_pem (key))
+		    && !nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_CERTPASS))
 			need_secrets = TRUE;
 
 		if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_PASSWORD))
@@ -1108,8 +1116,12 @@ real_need_secrets (NMVPNPlugin *plugin,
 		if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_PASSWORD))
 			need_secrets = TRUE;
 	} else if (!strcmp (connection_type, NM_OPENVPN_CONTYPE_TLS)) {
+		const char *key;
+
 		/* May require private key password */
-		if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_CERTPASS))
+		key = nm_setting_vpn_get_data_item (s_vpn, NM_OPENVPN_KEY_KEY);
+		if (   (is_pkcs12 (key) || is_encrypted_pem (key))
+		    && !nm_setting_vpn_get_secret (s_vpn, NM_OPENVPN_KEY_CERTPASS))
 			need_secrets = TRUE;
 	}
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]