// SPDX-License-Identifier: GPL-2.0+
/* NetworkManager Applet -- allow user control over networking
*
* Dan Williams <dcbw@redhat.com>
* Lubomir Rintel <lkundrak@v3.sk>
*
* Copyright 2007 - 2017 Red Hat, Inc.
*/
#include "nm-default.h"
#include <ctype.h>
#include <string.h>
#include "eap-method.h"
#include "wireless-security.h"
#include "helpers.h"
#include "nma-ui-utils.h"
#include "nma-cert-chooser.h"
#include "utils.h"
struct _EAPMethodTLS {
EAPMethod parent;
const char *ca_cert_password_flags_name;
const char *client_cert_password_flags_name;
const char *client_key_password_flags_name;
gboolean editing_connection;
GtkWidget *ca_cert_chooser;
GtkWidget *client_cert_chooser;
};
static gboolean
validate (EAPMethod *parent, GError **error)
{
EAPMethodTLS *method = (EAPMethodTLS *) parent;
GtkWidget *widget;
const char *identity;
widget = GTK_WIDGET (gtk_builder_get_object (parent->builder, "eap_tls_identity_entry"));
g_assert (widget);
identity = gtk_entry_get_text (GTK_ENTRY (widget));
if (!identity || !strlen (identity)) {
widget_set_error (widget);
g_set_error_literal (error, NMA_ERROR, NMA_ERROR_GENERIC, _("missing EAP-TLS identity"));
return FALSE;
} else {
widget_unset_error (widget);
}
if ( gtk_widget_get_sensitive (method->ca_cert_chooser)
&& !nma_cert_chooser_validate (NMA_CERT_CHOOSER (method->ca_cert_chooser), error))
return FALSE;
if (!nma_cert_chooser_validate (NMA_CERT_CHOOSER (method->client_cert_chooser), error))
return FALSE;
return TRUE;
}
static void
ca_cert_not_required_toggled (GtkWidget *button, gpointer user_data)
{
EAPMethodTLS *method = (EAPMethodTLS *) user_data;
gtk_widget_set_sensitive (method->ca_cert_chooser,
!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
}
static void
add_to_size_group (EAPMethod *parent, GtkSizeGroup *group)
{
EAPMethodTLS *method = (EAPMethodTLS *) parent;
GtkWidget *widget;
widget = GTK_WIDGET (gtk_builder_get_object (parent->builder, "eap_tls_identity_label"));
g_assert (widget);
gtk_size_group_add_widget (group, widget);
widget = GTK_WIDGET (gtk_builder_get_object (parent->builder, "eap_tls_domain_label"));
g_assert (widget);
gtk_size_group_add_widget (group, widget);
nma_cert_chooser_add_to_size_group (NMA_CERT_CHOOSER (method->client_cert_chooser), group);
nma_cert_chooser_add_to_size_group (NMA_CERT_CHOOSER (method->ca_cert_chooser), group);
}
static void
fill_connection (EAPMethod *parent, NMConnection *connection)
{
EAPMethodTLS *method = (EAPMethodTLS *) parent;
NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
NMSetting8021x *s_8021x;
NMSettingSecretFlags secret_flags;
GtkWidget *widget;
char *value = NULL;
const char *password = NULL;
GError *error = NULL;
gboolean ca_cert_error = FALSE;
NMSetting8021xCKScheme scheme;
s_8021x = nm_connection_get_setting_802_1x (connection);
g_assert (s_8021x);
if (parent->phase2)
g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, "tls", NULL);
else
nm_setting_802_1x_add_eap_method (s_8021x, "tls");
widget = GTK_WIDGET (gtk_builder_get_object (parent->builder, "eap_tls_identity_entry"));
g_assert (widget);
g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, gtk_entry_get_text (GTK_ENTRY (widget)), NULL);
widget = GTK_WIDGET (gtk_builder_get_object (parent->builder, "eap_tls_domain_entry"));
g_assert (widget);
g_object_set (s_8021x,
parent->phase2 ? NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_MATCH : NM_SETTING_802_1X_DOMAIN_SUFFIX_MATCH,
gtk_entry_get_text (GTK_ENTRY (widget)), NULL);
/* TLS private key */
password = nma_cert_chooser_get_key_password (NMA_CERT_CHOOSER (method->client_cert_chooser));
value = nma_cert_chooser_get_key (NMA_CERT_CHOOSER (method->client_cert_chooser), &scheme);
if (parent->phase2) {
if (!nm_setting_802_1x_set_phase2_private_key (s_8021x, value, password, scheme, &format, &error)) {
g_warning ("Couldn't read phase2 private key '%s': %s", value, error ? error->message : "(unknown)");
g_clear_error (&error);
}
} else {
if (!nm_setting_802_1x_set_private_key (s_8021x, value, password, scheme, &format, &error)) {
g_warning ("Couldn't read private key '%s': %s", value, error ? error->message : "(unknown)");
g_clear_error (&error);
}
}
g_free (value);
/* Save CA certificate PIN and its flags to the connection */
secret_flags = nma_cert_chooser_get_cert_password_flags (NMA_CERT_CHOOSER (method->ca_cert_chooser));
nm_setting_set_secret_flags (NM_SETTING (s_8021x), method->ca_cert_password_flags_name,
secret_flags, NULL);
if (method->editing_connection) {
/* Update secret flags and popup when editing the connection */
nma_cert_chooser_update_cert_password_storage (NMA_CERT_CHOOSER (method->ca_cert_chooser),
secret_flags, NM_SETTING (s_8021x),
method->ca_cert_password_flags_name);
g_object_set (s_8021x, method->ca_cert_password_flags_name,
nma_cert_chooser_get_cert_password (NMA_CERT_CHOOSER (method->ca_cert_chooser)),
NULL);
}
/* Save user certificate PIN and its flags flags to the connection */
secret_flags = nma_cert_chooser_get_cert_password_flags (NMA_CERT_CHOOSER (method->client_cert_chooser));
nm_setting_set_secret_flags (NM_SETTING (s_8021x), method->client_cert_password_flags_name,
secret_flags, NULL);
if (method->editing_connection) {
nma_cert_chooser_update_cert_password_storage (NMA_CERT_CHOOSER (method->client_cert_chooser),
secret_flags, NM_SETTING (s_8021x),
method->client_cert_password_flags_name);
g_object_set (s_8021x, method->client_cert_password_flags_name,
nma_cert_chooser_get_cert_password (NMA_CERT_CHOOSER (method->client_cert_chooser)),
NULL);
}
/* Save user private key password flags to the connection */
secret_flags = nma_cert_chooser_get_key_password_flags (NMA_CERT_CHOOSER (method->client_cert_chooser));
nm_setting_set_secret_flags (NM_SETTING (s_8021x), method->client_key_password_flags_name,
secret_flags, NULL);
if (method->editing_connection) {
nma_cert_chooser_update_key_password_storage (NMA_CERT_CHOOSER (method->client_cert_chooser),
secret_flags, NM_SETTING (s_8021x),
method->client_key_password_flags_name);
}
/* TLS client certificate */
if (format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
/* If the key is pkcs#12 nm_setting_802_1x_set_private_key() already
* set the client certificate for us.
*/
value = nma_cert_chooser_get_cert (NMA_CERT_CHOOSER (method->client_cert_chooser), &scheme);
format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
if (parent->phase2) {
if (!nm_setting_802_1x_set_phase2_client_cert (s_8021x, value, scheme, &format, &error)) {
g_warning ("Couldn't read phase2 client certificate '%s': %s", value, error ? error->message : "(unknown)");
g_clear_error (&error);
}
} else {
if (!nm_setting_802_1x_set_client_cert (s_8021x, value, scheme, &format, &error)) {
g_warning ("Couldn't read client certificate '%s': %s", value, error ? error->message : "(unknown)");
g_clear_error (&error);
}
}
g_free (value);
}
/* TLS CA certificate */
if (gtk_widget_get_sensitive (method->ca_cert_chooser))
value = nma_cert_chooser_get_cert (NMA_CERT_CHOOSER (method->ca_cert_chooser), &scheme);
else
value = NULL;
format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
if (parent->phase2) {
if (!nm_setting_802_1x_set_phase2_ca_cert (s_8021x, value, scheme, &format, &error)) {
g_warning ("Couldn't read phase2 CA certificate '%s': %s", value, error ? error->message : "(unknown)");
g_clear_error (&error);
ca_cert_error = TRUE;
}
} else {
if (!nm_setting_802_1x_set_ca_cert (s_8021x, value, scheme, &format, &error)) {
g_warning ("Couldn't read CA certificate '%s': %s", value, error ? error->message : "(unknown)");
g_clear_error (&error);
ca_cert_error = TRUE;
}
}
eap_method_ca_cert_ignore_set (parent, connection, value, ca_cert_error);
g_free (value);
}
static GError *
client_cert_validate_cb (NMACertChooser *cert_chooser, gpointer user_data)
{
NMSetting8021xCKScheme scheme;
NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
gs_unref_object NMSetting8021x *setting = NULL;
gs_free char *value = NULL;
GError *local = NULL;
setting = (NMSetting8021x *) nm_setting_802_1x_new ();
value = nma_cert_chooser_get_cert (cert_chooser, &scheme);
if (!value) {
return g_error_new_literal (NMA_ERROR, NMA_ERROR_GENERIC,
_("no user certificate selected"));
}
if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
if (!g_file_test (value, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
return g_error_new_literal (NMA_ERROR, NMA_ERROR_GENERIC,
_("selected user certificate file does not exist"));
}
}
if (!nm_setting_802_1x_set_client_cert (setting, value, scheme, &format, &local))
return local;
return NULL;
}
static GError *
client_key_validate_cb (NMACertChooser *cert_chooser, gpointer user_data)
{
NMSetting8021xCKScheme scheme;
gs_free char *value = NULL;
value = nma_cert_chooser_get_key (cert_chooser, &scheme);
if (!value) {
return g_error_new_literal (NMA_ERROR, NMA_ERROR_GENERIC,
_("no key selected"));
}
if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
if (!g_file_test (value, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
return g_error_new_literal (NMA_ERROR, NMA_ERROR_GENERIC,
_("selected key file does not exist"));
}
}
return NULL;
}
static GError *
client_key_password_validate_cb (NMACertChooser *cert_chooser, gpointer user_data)
{
NMSetting8021xCKScheme scheme;
NMSettingSecretFlags secret_flags;
gs_unref_object NMSetting8021x *setting = NULL;
gs_free char *value = NULL;
const char *password = NULL;
GError *local = NULL;
secret_flags = nma_cert_chooser_get_key_password_flags (cert_chooser);
if ( secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED
|| secret_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
return NULL;
setting = (NMSetting8021x *) nm_setting_802_1x_new ();
value = nma_cert_chooser_get_key (cert_chooser, &scheme);
password = nma_cert_chooser_get_key_password (cert_chooser);
if (!nm_setting_802_1x_set_private_key (setting, value, password, scheme, NULL, &local))
return local;
return NULL;
}
static void
client_cert_fixup_pkcs12 (NMACertChooser *cert_chooser, gpointer user_data)
{
NMSetting8021xCKScheme cert_scheme, key_scheme;
NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
gs_free char *cert_value = NULL;
gs_free char *key_value = NULL;
gs_unref_object NMSetting8021x *setting = NULL;
setting = (NMSetting8021x *) nm_setting_802_1x_new ();
cert_value = nma_cert_chooser_get_cert (cert_chooser, &cert_scheme);
key_value = nma_cert_chooser_get_key (cert_chooser, &key_scheme);
if ( !cert_value || key_value
|| !nm_setting_802_1x_set_client_cert (setting, cert_value, cert_scheme, &format, NULL))
return;
if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
nma_cert_chooser_set_key (cert_chooser, cert_value, cert_scheme);
}
static void
update_secrets (EAPMethod *parent, NMConnection *connection)
{
EAPMethodTLS *method = (EAPMethodTLS *) parent;
eap_method_setup_cert_chooser (NMA_CERT_CHOOSER (method->client_cert_chooser),
nm_connection_get_setting_802_1x (connection),
NULL,
NULL,
NULL,
parent->phase2 ? nm_setting_802_1x_get_phase2_client_cert_password : nm_setting_802_1x_get_client_cert_password,
parent->phase2 ? nm_setting_802_1x_get_phase2_private_key_scheme : nm_setting_802_1x_get_private_key_scheme,
parent->phase2 ? nm_setting_802_1x_get_phase2_private_key_path : nm_setting_802_1x_get_private_key_path,
parent->phase2 ? nm_setting_802_1x_get_phase2_private_key_uri : nm_setting_802_1x_get_private_key_uri,
parent->phase2 ? nm_setting_802_1x_get_phase2_private_key_password : nm_setting_802_1x_get_private_key_password);
}
EAPMethodTLS *
eap_method_tls_new (WirelessSecurity *ws_parent,
NMConnection *connection,
gboolean phase2,
gboolean secrets_only)
{
EAPMethodTLS *method;
EAPMethod *parent;
GtkWidget *widget;
NMSetting8021x *s_8021x = NULL;
gboolean ca_not_required = FALSE;
parent = eap_method_init (sizeof (EAPMethodTLS),
validate,
add_to_size_group,
fill_connection,
update_secrets,
NULL,
"/org/freedesktop/network-manager-applet/eap-method-tls.ui",
"eap_tls_notebook",
"eap_tls_identity_entry",
phase2);
if (!parent)
return NULL;
method = (EAPMethodTLS *) parent;
method->ca_cert_password_flags_name = phase2
? NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD
: NM_SETTING_802_1X_CA_CERT_PASSWORD;
method->client_cert_password_flags_name = phase2
? NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD
: NM_SETTING_802_1X_CLIENT_CERT_PASSWORD;
method->client_key_password_flags_name = phase2
? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD
: NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD;
method->editing_connection = secrets_only ? FALSE : TRUE;
if (connection)
s_8021x = nm_connection_get_setting_802_1x (connection);
widget = GTK_WIDGET (gtk_builder_get_object (parent->builder, "eap_tls_ca_cert_not_required_checkbox"));
g_assert (widget);
g_signal_connect (G_OBJECT (widget), "toggled",
(GCallback) ca_cert_not_required_toggled,
parent);
g_signal_connect (G_OBJECT (widget), "toggled",
(GCallback) wireless_security_changed_cb,
ws_parent);
widget = GTK_WIDGET (gtk_builder_get_object (parent->builder, "eap_tls_identity_entry"));
g_assert (widget);
g_signal_connect (G_OBJECT (widget), "changed",
(GCallback) wireless_security_changed_cb,
ws_parent);
if (s_8021x && nm_setting_802_1x_get_identity (s_8021x))
gtk_entry_set_text (GTK_ENTRY (widget), nm_setting_802_1x_get_identity (s_8021x));
widget = GTK_WIDGET (gtk_builder_get_object (parent->builder, "eap_tls_domain_entry"));
g_assert (widget);
g_signal_connect (G_OBJECT (widget), "changed",
(GCallback) wireless_security_changed_cb,
ws_parent);
if (phase2) {
if (s_8021x && nm_setting_802_1x_get_phase2_domain_suffix_match (s_8021x))
gtk_entry_set_text (GTK_ENTRY (widget), nm_setting_802_1x_get_phase2_domain_suffix_match (s_8021x));
} else {
if (s_8021x && nm_setting_802_1x_get_domain_suffix_match (s_8021x))
gtk_entry_set_text (GTK_ENTRY (widget), nm_setting_802_1x_get_domain_suffix_match (s_8021x));
}
widget = GTK_WIDGET (gtk_builder_get_object (parent->builder, "eap_tls_grid"));
g_assert (widget);
method->ca_cert_chooser = nma_cert_chooser_new ("CA",
NMA_CERT_CHOOSER_FLAG_CERT
| (secrets_only ? NMA_CERT_CHOOSER_FLAG_PASSWORDS : 0));
gtk_grid_attach (GTK_GRID (widget), method->ca_cert_chooser, 0, 2, 2, 1);
gtk_widget_show (method->ca_cert_chooser);
g_signal_connect (method->ca_cert_chooser,
"cert-validate",
G_CALLBACK (eap_method_ca_cert_validate_cb),
NULL);
g_signal_connect (method->ca_cert_chooser,
"changed",
G_CALLBACK (wireless_security_changed_cb),
ws_parent);
eap_method_setup_cert_chooser (NMA_CERT_CHOOSER (method->ca_cert_chooser), s_8021x,
phase2 ? nm_setting_802_1x_get_phase2_ca_cert_scheme : nm_setting_802_1x_get_ca_cert_scheme,
phase2 ? nm_setting_802_1x_get_phase2_ca_cert_path : nm_setting_802_1x_get_ca_cert_path,
phase2 ? nm_setting_802_1x_get_phase2_ca_cert_uri : nm_setting_802_1x_get_ca_cert_uri,
phase2 ? nm_setting_802_1x_get_phase2_ca_cert_password : nm_setting_802_1x_get_ca_cert_password,
NULL,
NULL,
NULL,
NULL);
if (connection && eap_method_ca_cert_ignore_get (parent, connection)) {
gchar *ca_cert;
NMSetting8021xCKScheme scheme;
ca_cert = nma_cert_chooser_get_cert (NMA_CERT_CHOOSER (method->ca_cert_chooser), &scheme);
if (ca_cert)
g_free (ca_cert);
else
ca_not_required = TRUE;
}
if (secrets_only)
ca_not_required = TRUE;
method->client_cert_chooser = nma_cert_chooser_new ("User",
secrets_only ? NMA_CERT_CHOOSER_FLAG_PASSWORDS : 0);
gtk_grid_attach (GTK_GRID (widget), method->client_cert_chooser, 0, 4, 2, 1);
gtk_widget_show (method->client_cert_chooser);
g_signal_connect (method->client_cert_chooser, "cert-validate",
G_CALLBACK (client_cert_validate_cb),
NULL);
g_signal_connect (method->client_cert_chooser,
"key-validate",
G_CALLBACK (client_key_validate_cb),
NULL);
g_signal_connect (method->client_cert_chooser,
"key-password-validate",
G_CALLBACK (client_key_password_validate_cb),
NULL);
g_signal_connect (method->client_cert_chooser,
"changed",
G_CALLBACK (client_cert_fixup_pkcs12),
ws_parent);
g_signal_connect (method->client_cert_chooser,
"changed",
G_CALLBACK (wireless_security_changed_cb),
ws_parent);
eap_method_setup_cert_chooser (NMA_CERT_CHOOSER (method->client_cert_chooser), s_8021x,
phase2 ? nm_setting_802_1x_get_phase2_client_cert_scheme : nm_setting_802_1x_get_client_cert_scheme,
phase2 ? nm_setting_802_1x_get_phase2_client_cert_path : nm_setting_802_1x_get_client_cert_path,
phase2 ? nm_setting_802_1x_get_phase2_client_cert_uri : nm_setting_802_1x_get_client_cert_uri,
phase2 ? nm_setting_802_1x_get_phase2_client_cert_password : nm_setting_802_1x_get_client_cert_password,
phase2 ? nm_setting_802_1x_get_phase2_private_key_scheme : nm_setting_802_1x_get_private_key_scheme,
phase2 ? nm_setting_802_1x_get_phase2_private_key_path : nm_setting_802_1x_get_private_key_path,
phase2 ? nm_setting_802_1x_get_phase2_private_key_uri : nm_setting_802_1x_get_private_key_uri,
phase2 ? nm_setting_802_1x_get_phase2_private_key_password : nm_setting_802_1x_get_private_key_password);
widget = GTK_WIDGET (gtk_builder_get_object (parent->builder, "eap_tls_ca_cert_not_required_checkbox"));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), ca_not_required);
/* Create password-storage popup menus for password entries under their secondary icon */
nma_cert_chooser_setup_cert_password_storage (NMA_CERT_CHOOSER (method->ca_cert_chooser),
0, (NMSetting *) s_8021x, method->ca_cert_password_flags_name,
FALSE, secrets_only);
nma_cert_chooser_setup_cert_password_storage (NMA_CERT_CHOOSER (method->client_cert_chooser),
0, (NMSetting *) s_8021x, method->client_cert_password_flags_name,
FALSE, secrets_only);
nma_cert_chooser_setup_key_password_storage (NMA_CERT_CHOOSER (method->client_cert_chooser),
0, (NMSetting *) s_8021x, method->client_key_password_flags_name,
FALSE, secrets_only);
return method;
}