summaryrefslogtreecommitdiff
path: root/panels/network/wireless-security/eap-method.c
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2012-12-09 19:38:32 -0500
committerDan Winship <danw@gnome.org>2013-01-30 13:05:33 -0500
commite5cc7d801142e5ef537c9a1c092cbc04110eadcd (patch)
tree79af2139005e27b56d89ac875f1ebef14336c816 /panels/network/wireless-security/eap-method.c
parent66b1fe7b453d3e41435d3997b119349ff1d226da (diff)
downloadgnome-control-center-e5cc7d801142e5ef537c9a1c092cbc04110eadcd.tar.gz
network: Break out wifi details code and add editing support
This code is fairly independent of the rest, and we don't want net-device-wifi.c to become too massive and unmaintainable. The code in connection-editor/ is fairly similar to nm-connection-editor, with some simplification because we currently only edit wireless connections. The code in wireless-security/ is almost a straight copy of the same code in nm-connection-editor, with some changes to the .ui files to make them fit better in the new design.
Diffstat (limited to 'panels/network/wireless-security/eap-method.c')
-rw-r--r--panels/network/wireless-security/eap-method.c660
1 files changed, 660 insertions, 0 deletions
diff --git a/panels/network/wireless-security/eap-method.c b/panels/network/wireless-security/eap-method.c
new file mode 100644
index 000000000..8ff9778f8
--- /dev/null
+++ b/panels/network/wireless-security/eap-method.c
@@ -0,0 +1,660 @@
+/* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */
+
+/* NetworkManager Applet -- allow user control over networking
+ *
+ * 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 2007 - 2012 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <nm-setting-connection.h>
+#include <nm-setting-8021x.h>
+#include "eap-method.h"
+#include "nm-utils.h"
+
+GType
+eap_method_get_g_type (void)
+{
+ static GType type_id = 0;
+
+ if (!type_id) {
+ type_id = g_boxed_type_register_static ("EAPMethod",
+ (GBoxedCopyFunc) eap_method_ref,
+ (GBoxedFreeFunc) eap_method_unref);
+ }
+
+ return type_id;
+}
+
+GtkWidget *
+eap_method_get_widget (EAPMethod *method)
+{
+ g_return_val_if_fail (method != NULL, NULL);
+
+ return method->ui_widget;
+}
+
+gboolean
+eap_method_validate (EAPMethod *method)
+{
+ g_return_val_if_fail (method != NULL, FALSE);
+
+ g_assert (method->validate);
+ return (*(method->validate)) (method);
+}
+
+void
+eap_method_add_to_size_group (EAPMethod *method, GtkSizeGroup *group)
+{
+ g_return_if_fail (method != NULL);
+ g_return_if_fail (group != NULL);
+
+ g_assert (method->add_to_size_group);
+ return (*(method->add_to_size_group)) (method, group);
+}
+
+void
+eap_method_fill_connection (EAPMethod *method, NMConnection *connection)
+{
+ g_return_if_fail (method != NULL);
+ g_return_if_fail (connection != NULL);
+
+ g_assert (method->fill_connection);
+ return (*(method->fill_connection)) (method, connection);
+}
+
+void
+eap_method_update_secrets (EAPMethod *method, NMConnection *connection)
+{
+ g_return_if_fail (method != NULL);
+ g_return_if_fail (connection != NULL);
+
+ if (method->update_secrets)
+ method->update_secrets (method, connection);
+}
+
+typedef struct {
+ EAPMethod *method;
+ NMConnection *connection;
+} NagDialogResponseInfo;
+
+static void
+nag_dialog_destroyed (gpointer data, GObject *dialog_ptr)
+{
+ NagDialogResponseInfo *info = (NagDialogResponseInfo *) data;
+
+ memset (info, '\0', sizeof (NagDialogResponseInfo));
+ g_free (info);
+}
+
+static GSettings *
+_get_ca_ignore_settings (const char *uuid)
+{
+ GSettings *settings;
+ char *path = NULL;
+
+ path = g_strdup_printf ("/org/gnome/nm-applet/eap/%s", uuid);
+ settings = g_settings_new_with_path ("org.gnome.nm-applet.eap", path);
+ g_free (path);
+
+ return settings;
+}
+
+static void
+_set_ignore_ca_cert (const char *uuid, gboolean phase2, gboolean ignore)
+{
+ GSettings *settings;
+ const char *key;
+
+ g_return_if_fail (uuid != NULL);
+
+ settings = _get_ca_ignore_settings (uuid);
+ key = phase2 ? "ignore-phase2-ca-cert" : "ignore-ca-cert";
+ g_settings_set_boolean (settings, key, ignore);
+ g_object_unref (settings);
+}
+
+static void
+nag_dialog_response_cb (GtkDialog *nag_dialog,
+ gint response,
+ gpointer user_data)
+{
+ NagDialogResponseInfo *info = (NagDialogResponseInfo *) user_data;
+ EAPMethod *method = (EAPMethod *) info->method;
+ NMConnection *connection = (NMConnection *) info->connection;
+ GtkWidget *widget;
+
+ if (response == GTK_RESPONSE_NO) {
+ /* Grab the value of the "don't bother me" checkbox */
+ widget = GTK_WIDGET (gtk_builder_get_object (method->nag_builder, "ignore_checkbox"));
+ g_assert (widget);
+
+ method->ignore_ca_cert = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+
+ /* And save it */
+ _set_ignore_ca_cert (nm_connection_get_uuid (connection),
+ method->phase2,
+ method->ignore_ca_cert);
+ }
+
+ gtk_widget_hide (GTK_WIDGET (nag_dialog));
+}
+
+static gboolean
+nag_dialog_delete_event_cb (GtkDialog *nag_dialog, GdkEvent *e, gpointer user_data)
+{
+ // FIXME?: By emitting response signal, dismissing nag dialog with upper right "x" icon,
+ // Alt-F4, or Esc would have the same behaviour as clicking "Ignore" button.
+ //g_signal_emit_by_name (nag_dialog, "response", GTK_RESPONSE_NO, user_data);
+ return TRUE; /* do not destroy */
+}
+
+GtkWidget *
+eap_method_nag_user (EAPMethod *method)
+{
+ GtkWidget *widget;
+ char *filename = NULL;
+
+ g_return_val_if_fail (method != NULL, NULL);
+
+ if (!method->nag_dialog || method->ignore_ca_cert)
+ return NULL;
+
+ /* Checkbox should be unchecked each time dialog comes up */
+ widget = GTK_WIDGET (gtk_builder_get_object (method->nag_builder, "ignore_checkbox"));
+ g_assert (widget);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE);
+
+ /* Nag the user if the CA Cert is blank, since it's a security risk. */
+ widget = GTK_WIDGET (gtk_builder_get_object (method->builder, method->ca_cert_chooser));
+ g_assert (widget);
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
+ if (filename != NULL) {
+ g_free (filename);
+ return NULL;
+ }
+
+ gtk_window_present (GTK_WINDOW (method->nag_dialog));
+ return method->nag_dialog;
+}
+
+#define NAG_DIALOG_UI "/org/gnome/control-center/network/nag-user-dialog.ui"
+
+static gboolean
+_get_ignore_ca_cert (const char *uuid, gboolean phase2)
+{
+ GSettings *settings;
+ const char *key;
+ gboolean ignore = FALSE;
+
+ g_return_val_if_fail (uuid != NULL, FALSE);
+
+ settings = _get_ca_ignore_settings (uuid);
+
+ key = phase2 ? "ignore-phase2-ca-cert" : "ignore-ca-cert";
+ ignore = g_settings_get_boolean (settings, key);
+
+ g_object_unref (settings);
+ return ignore;
+}
+
+gboolean
+eap_method_nag_init (EAPMethod *method,
+ const char *ca_cert_chooser,
+ NMConnection *connection)
+{
+ GtkWidget *dialog, *widget;
+ NagDialogResponseInfo *info;
+ GError *error = NULL;
+ char *text;
+
+ g_return_val_if_fail (method != NULL, FALSE);
+ g_return_val_if_fail (ca_cert_chooser != NULL, FALSE);
+
+ method->nag_builder = gtk_builder_new ();
+ if (!gtk_builder_add_from_resource (method->nag_builder, NAG_DIALOG_UI, &error)) {
+ g_warning ("Couldn't load UI builder file " NAG_DIALOG_UI ": %s",
+ error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ method->ca_cert_chooser = g_strdup (ca_cert_chooser);
+ if (connection) {
+ NMSettingConnection *s_con;
+ const char *uuid;
+
+ s_con = nm_connection_get_setting_connection (connection);
+ g_assert (s_con);
+ uuid = nm_setting_connection_get_uuid (s_con);
+ g_assert (uuid);
+
+ /* Figure out if the user wants to ignore missing CA cert */
+ method->ignore_ca_cert = _get_ignore_ca_cert (uuid, method->phase2);
+ }
+
+ info = g_malloc0 (sizeof (NagDialogResponseInfo));
+ info->method = method;
+ info->connection = connection;
+
+ dialog = GTK_WIDGET (gtk_builder_get_object (method->nag_builder, "nag_user_dialog"));
+ g_assert (dialog);
+ g_signal_connect (dialog, "response", G_CALLBACK (nag_dialog_response_cb), info);
+ g_signal_connect (dialog, "delete-event", G_CALLBACK (nag_dialog_delete_event_cb), info);
+ g_object_weak_ref (G_OBJECT (dialog), nag_dialog_destroyed, info);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (method->nag_builder, "content_label"));
+ g_assert (widget);
+
+ text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s",
+ _("No Certificate Authority certificate chosen"),
+ _("Not using a Certificate Authority (CA) certificate can result in connections to insecure, rogue Wi-Fi networks. Would you like to choose a Certificate Authority certificate?"));
+ gtk_label_set_markup (GTK_LABEL (widget), text);
+ g_free (text);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (method->nag_builder, "ignore_button"));
+ gtk_button_set_label (GTK_BUTTON (widget), _("Ignore"));
+ g_assert (widget);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (method->nag_builder, "change_button"));
+ gtk_button_set_label (GTK_BUTTON (widget), _("Choose CA Certificate"));
+ g_assert (widget);
+
+ method->nag_dialog = dialog;
+ return TRUE;
+}
+
+void
+eap_method_phase2_update_secrets_helper (EAPMethod *method,
+ NMConnection *connection,
+ const char *combo_name,
+ guint32 column)
+{
+ GtkWidget *combo;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ g_return_if_fail (method != NULL);
+ g_return_if_fail (connection != NULL);
+ g_return_if_fail (combo_name != NULL);
+
+ combo = GTK_WIDGET (gtk_builder_get_object (method->builder, combo_name));
+ g_assert (combo);
+
+ /* Let each EAP phase2 method try to update its secrets */
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ do {
+ EAPMethod *eap = NULL;
+
+ gtk_tree_model_get (model, &iter, column, &eap, -1);
+ if (eap) {
+ eap_method_update_secrets (eap, connection);
+ eap_method_unref (eap);
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+ }
+}
+
+EAPMethod *
+eap_method_init (gsize obj_size,
+ EMValidateFunc validate,
+ EMAddToSizeGroupFunc add_to_size_group,
+ EMFillConnectionFunc fill_connection,
+ EMUpdateSecretsFunc update_secrets,
+ EMDestroyFunc destroy,
+ const char *ui_resource,
+ const char *ui_widget_name,
+ const char *default_field,
+ gboolean phase2)
+{
+ EAPMethod *method;
+ GError *error = NULL;
+
+ g_return_val_if_fail (obj_size > 0, NULL);
+ g_return_val_if_fail (ui_resource != NULL, NULL);
+ g_return_val_if_fail (ui_widget_name != NULL, NULL);
+
+ method = g_slice_alloc0 (obj_size);
+ g_assert (method);
+
+ method->refcount = 1;
+ method->obj_size = obj_size;
+ method->validate = validate;
+ method->add_to_size_group = add_to_size_group;
+ method->fill_connection = fill_connection;
+ method->update_secrets = update_secrets;
+ method->destroy = destroy;
+ method->default_field = default_field;
+ method->phase2 = phase2;
+
+ method->builder = gtk_builder_new ();
+ if (!gtk_builder_add_from_resource (method->builder, ui_resource, &error)) {
+ g_warning ("Couldn't load UI builder file %s: %s",
+ ui_resource, error->message);
+ eap_method_unref (method);
+ return NULL;
+ }
+
+ method->ui_widget = GTK_WIDGET (gtk_builder_get_object (method->builder, ui_widget_name));
+ if (!method->ui_widget) {
+ g_warning ("Couldn't load UI widget '%s' from UI file %s",
+ ui_widget_name, ui_resource);
+ eap_method_unref (method);
+ return NULL;
+ }
+ g_object_ref_sink (method->ui_widget);
+
+ return method;
+}
+
+
+EAPMethod *
+eap_method_ref (EAPMethod *method)
+{
+ g_return_val_if_fail (method != NULL, NULL);
+ g_return_val_if_fail (method->refcount > 0, NULL);
+
+ method->refcount++;
+ return method;
+}
+
+void
+eap_method_unref (EAPMethod *method)
+{
+ g_return_if_fail (method != NULL);
+ g_return_if_fail (method->refcount > 0);
+
+ method->refcount--;
+ if (method->refcount == 0) {
+ if (method->destroy)
+ method->destroy (method);
+
+ if (method->nag_dialog)
+ gtk_widget_destroy (method->nag_dialog);
+ if (method->nag_builder)
+ g_object_unref (method->nag_builder);
+ g_free (method->ca_cert_chooser);
+ if (method->builder)
+ g_object_unref (method->builder);
+ if (method->ui_widget)
+ g_object_unref (method->ui_widget);
+
+ g_slice_free1 (method->obj_size, method);
+ }
+}
+
+gboolean
+eap_method_validate_filepicker (GtkBuilder *builder,
+ const char *name,
+ guint32 item_type,
+ const char *password,
+ NMSetting8021xCKFormat *out_format)
+{
+ GtkWidget *widget;
+ char *filename;
+ NMSetting8021x *setting;
+ gboolean success = FALSE;
+ GError *error = NULL;
+
+ if (item_type == TYPE_PRIVATE_KEY) {
+ g_return_val_if_fail (password != NULL, FALSE);
+ g_return_val_if_fail (strlen (password), FALSE);
+ }
+
+ widget = GTK_WIDGET (gtk_builder_get_object (builder, name));
+ g_assert (widget);
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
+ if (!filename)
+ return (item_type == TYPE_CA_CERT) ? TRUE : FALSE;
+
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
+ goto out;
+
+ setting = (NMSetting8021x *) nm_setting_802_1x_new ();
+
+ if (item_type == TYPE_PRIVATE_KEY) {
+ if (!nm_setting_802_1x_set_private_key (setting, filename, password, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, &error)) {
+ g_warning ("Error: couldn't verify private key: %d %s",
+ error ? error->code : -1, error ? error->message : "(none)");
+ g_clear_error (&error);
+ } else
+ success = TRUE;
+ } else if (item_type == TYPE_CLIENT_CERT) {
+ if (!nm_setting_802_1x_set_client_cert (setting, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, &error)) {
+ g_warning ("Error: couldn't verify client certificate: %d %s",
+ error ? error->code : -1, error ? error->message : "(none)");
+ g_clear_error (&error);
+ } else
+ success = TRUE;
+ } else if (item_type == TYPE_CA_CERT) {
+ if (!nm_setting_802_1x_set_ca_cert (setting, filename, NM_SETTING_802_1X_CK_SCHEME_PATH, out_format, &error)) {
+ g_warning ("Error: couldn't verify CA certificate: %d %s",
+ error ? error->code : -1, error ? error->message : "(none)");
+ g_clear_error (&error);
+ } else
+ success = TRUE;
+ } else
+ g_warning ("%s: invalid item type %d.", __func__, item_type);
+
+ g_object_unref (setting);
+
+out:
+ g_free (filename);
+ return success;
+}
+
+static const char *
+find_tag (const char *tag, const char *buf, gsize len)
+{
+ gsize i, taglen;
+
+ taglen = strlen (tag);
+ if (len < taglen)
+ return NULL;
+
+ for (i = 0; i < len - taglen + 1; i++) {
+ if (memcmp (buf + i, tag, taglen) == 0)
+ return buf + i;
+ }
+ return NULL;
+}
+
+static const char *pem_rsa_key_begin = "-----BEGIN RSA PRIVATE KEY-----";
+static const char *pem_dsa_key_begin = "-----BEGIN DSA PRIVATE KEY-----";
+static const char *pem_pkcs8_enc_key_begin = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
+static const char *pem_pkcs8_dec_key_begin = "-----BEGIN PRIVATE KEY-----";
+static const char *pem_cert_begin = "-----BEGIN CERTIFICATE-----";
+static const char *proc_type_tag = "Proc-Type: 4,ENCRYPTED";
+static const char *dek_info_tag = "DEK-Info:";
+
+static gboolean
+file_has_extension (const char *filename, const char *extensions[])
+{
+ char *p, *ext;
+ int i = 0;
+ gboolean found = FALSE;
+
+ p = strrchr (filename, '.');
+ if (!p)
+ return FALSE;
+
+ ext = g_ascii_strdown (p, -1);
+ if (ext) {
+ while (extensions[i]) {
+ if (!strcmp (ext, extensions[i++])) {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+ g_free (ext);
+
+ return found;
+}
+
+static gboolean
+pem_file_is_encrypted (const char *buffer, gsize bytes_read)
+{
+ /* Check if the private key is encrypted or not by looking for the
+ * old OpenSSL-style proc-type and dec-info tags.
+ */
+ if (find_tag (proc_type_tag, (const char *) buffer, bytes_read)) {
+ if (find_tag (dek_info_tag, (const char *) buffer, bytes_read))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+file_is_der_or_pem (const char *filename,
+ gboolean privkey,
+ gboolean *out_privkey_encrypted)
+{
+ int fd;
+ unsigned char buffer[8192];
+ ssize_t bytes_read;
+ gboolean success = FALSE;
+
+ fd = open (filename, O_RDONLY);
+ if (fd < 0)
+ return FALSE;
+
+ bytes_read = read (fd, buffer, sizeof (buffer) - 1);
+ if (bytes_read < 400) /* needs to be lower? */
+ goto out;
+ buffer[bytes_read] = '\0';
+
+ /* Check for DER signature */
+ if (bytes_read > 2 && buffer[0] == 0x30 && buffer[1] == 0x82) {
+ success = TRUE;
+ goto out;
+ }
+
+ /* Check for PEM signatures */
+ if (privkey) {
+ if (find_tag (pem_rsa_key_begin, (const char *) buffer, bytes_read)) {
+ success = TRUE;
+ if (out_privkey_encrypted)
+ *out_privkey_encrypted = pem_file_is_encrypted ((const char *) buffer, bytes_read);
+ goto out;
+ }
+
+ if (find_tag (pem_dsa_key_begin, (const char *) buffer, bytes_read)) {
+ success = TRUE;
+ if (out_privkey_encrypted)
+ *out_privkey_encrypted = pem_file_is_encrypted ((const char *) buffer, bytes_read);
+ goto out;
+ }
+
+ if (find_tag (pem_pkcs8_enc_key_begin, (const char *) buffer, bytes_read)) {
+ success = TRUE;
+ if (out_privkey_encrypted)
+ *out_privkey_encrypted = TRUE;
+ goto out;
+ }
+
+ if (find_tag (pem_pkcs8_dec_key_begin, (const char *) buffer, bytes_read)) {
+ success = TRUE;
+ if (out_privkey_encrypted)
+ *out_privkey_encrypted = FALSE;
+ goto out;
+ }
+ } else {
+ if (find_tag (pem_cert_begin, (const char *) buffer, bytes_read)) {
+ success = TRUE;
+ goto out;
+ }
+ }
+
+out:
+ close (fd);
+ return success;
+}
+
+static gboolean
+default_filter_privkey (const GtkFileFilterInfo *filter_info, gpointer user_data)
+{
+ const char *extensions[] = { ".der", ".pem", ".p12", NULL };
+ gboolean require_encrypted = !!user_data;
+ gboolean is_encrypted = TRUE;
+
+ if (!filter_info->filename)
+ return FALSE;
+
+ if (!file_has_extension (filter_info->filename, extensions))
+ return FALSE;
+
+ if ( !file_is_der_or_pem (filter_info->filename, TRUE, &is_encrypted)
+ && !nm_utils_file_is_pkcs12 (filter_info->filename))
+ return FALSE;
+
+ return require_encrypted ? is_encrypted : TRUE;
+}
+
+static gboolean
+default_filter_cert (const GtkFileFilterInfo *filter_info, gpointer user_data)
+{
+ const char *extensions[] = { ".der", ".pem", ".crt", ".cer", NULL };
+
+ if (!filter_info->filename)
+ return FALSE;
+
+ if (!file_has_extension (filter_info->filename, extensions))
+ return FALSE;
+
+ if (!file_is_der_or_pem (filter_info->filename, FALSE, NULL))
+ return FALSE;
+
+ return TRUE;
+}
+
+GtkFileFilter *
+eap_method_default_file_chooser_filter_new (gboolean privkey)
+{
+ GtkFileFilter *filter;
+
+ filter = gtk_file_filter_new ();
+ if (privkey) {
+ gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_FILENAME, default_filter_privkey, NULL, NULL);
+ gtk_file_filter_set_name (filter, _("DER, PEM, or PKCS#12 private keys (*.der, *.pem, *.p12)"));
+ } else {
+ gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_FILENAME, default_filter_cert, NULL, NULL);
+ gtk_file_filter_set_name (filter, _("DER or PEM certificates (*.der, *.pem, *.crt, *.cer)"));
+ }
+ return filter;
+}
+
+gboolean
+eap_method_is_encrypted_private_key (const char *path)
+{
+ GtkFileFilterInfo info = { .filename = path };
+
+ return default_filter_privkey (&info, (gpointer) TRUE);
+}
+