wireguard: merge branch 'th/wireguard-import'

https://github.com/NetworkManager/NetworkManager/pull/304

(cherry picked from commit bb25a1c805)
This commit is contained in:
Thomas Haller 2019-03-07 17:54:38 +01:00
commit f9d7712e62
29 changed files with 968 additions and 126 deletions

View file

@ -4004,6 +4004,13 @@ clients_common_tests_test_general_LDADD = \
$(clients_common_tests_test_general_OBJECTS): $(libnm_core_lib_h_pub_mkenums)
EXTRA_DIST += \
clients/common/tests/wg-test0.conf \
clients/common/tests/wg-test1.conf \
clients/common/tests/wg-test2.conf \
clients/common/tests/wg-test3.conf \
$(NULL)
###############################################################################
# clients/cli
###############################################################################

View file

@ -1349,23 +1349,39 @@ nmc_do_cmd (NmCli *nmc, const NMCCommand cmds[], const char *cmd, int argc, char
/**
* nmc_complete_strings:
* @prefix: a string to match
* @...: a %NULL-terminated list of candidate strings
* @nargs: the number of elements in @args. Or -1 if @args is a NULL terminated
* strv array.
* @args: the argument list. If @nargs is not -1, then some elements may
* be %NULL to indicate to silently skip the values.
*
* Prints all the matching candidates for completion. Useful when there's
* no better way to suggest completion other than a hardcoded string list.
*/
void
nmc_complete_strings (const char *prefix, ...)
nmc_complete_strv (const char *prefix, gssize nargs, const char *const*args)
{
va_list args;
const char *candidate;
gsize i, n;
va_start (args, prefix);
while ((candidate = va_arg (args, const char *))) {
if (!*prefix || matches (prefix, candidate))
g_print ("%s\n", candidate);
if (prefix && !prefix[0])
prefix = NULL;
if (nargs < 0) {
nm_assert (nargs == -1);
n = NM_PTRARRAY_LEN (args);
} else
n = (gsize) nargs;
for (i = 0; i < n; i++) {
const char *candidate = args[i];
if (!candidate)
continue;
if ( prefix
&& !matches (prefix, candidate))
continue;
g_print ("%s\n", candidate);
}
va_end (args);
}
/**

View file

@ -88,7 +88,9 @@ typedef struct {
void nmc_do_cmd (NmCli *nmc, const NMCCommand cmds[], const char *cmd, int argc, char **argv);
void nmc_complete_strings (const char *prefix, ...) G_GNUC_NULL_TERMINATED;
void nmc_complete_strv (const char *prefix, gssize nargs, const char *const*args);
#define nmc_complete_strings(prefix, ...) nmc_complete_strv ((prefix), NM_NARG (__VA_ARGS__), (const char *const[]) { __VA_ARGS__ })
void nmc_complete_bool (const char *prefix);

View file

@ -8888,8 +8888,11 @@ do_connection_import (NmCli *nmc, int argc, char **argv)
}
while (argc > 0) {
if (argc == 1 && nmc->complete)
nmc_complete_strings (*argv, "type", "file", NULL);
if (argc == 1 && nmc->complete) {
nmc_complete_strings (*argv,
type ? NULL : "type",
filename ? NULL : "file");
}
if (strcmp (*argv, "type") == 0) {
argc--;
@ -8899,8 +8902,13 @@ do_connection_import (NmCli *nmc, int argc, char **argv)
NMC_RETURN (nmc, NMC_RESULT_ERROR_USER_INPUT);
}
if (argc == 1 && nmc->complete)
complete_option ((const NMMetaAbstractInfo *) nm_meta_property_info_vpn_service_type, *argv, NULL);
if ( argc == 1
&& nmc->complete) {
nmc_complete_strings (*argv, "wireguard");
complete_option ((const NMMetaAbstractInfo *) nm_meta_property_info_vpn_service_type,
*argv,
NULL);
}
if (!type)
type = *argv;
@ -8940,21 +8948,26 @@ do_connection_import (NmCli *nmc, int argc, char **argv)
NMC_RETURN (nmc, NMC_RESULT_ERROR_USER_INPUT);
}
service_type = nm_vpn_plugin_info_list_find_service_type (nm_vpn_get_plugin_infos (), type);
if (!service_type) {
g_string_printf (nmc->return_text, _("Error: failed to find VPN plugin for %s."), type);
NMC_RETURN (nmc, NMC_RESULT_ERROR_UNKNOWN);
if (nm_streq (type, "wireguard"))
connection = nm_vpn_wireguard_import (filename, &error);
else {
service_type = nm_vpn_plugin_info_list_find_service_type (nm_vpn_get_plugin_infos (), type);
if (!service_type) {
g_string_printf (nmc->return_text, _("Error: failed to find VPN plugin for %s."), type);
NMC_RETURN (nmc, NMC_RESULT_ERROR_UNKNOWN);
}
/* Import VPN configuration */
plugin = nm_vpn_get_editor_plugin (service_type, &error);
if (!plugin) {
g_string_printf (nmc->return_text, _("Error: failed to load VPN plugin: %s."),
error->message);
NMC_RETURN (nmc, NMC_RESULT_ERROR_UNKNOWN);
}
connection = nm_vpn_editor_plugin_import (plugin, filename, &error);
}
/* Import VPN configuration */
plugin = nm_vpn_get_editor_plugin (service_type, &error);
if (!plugin) {
g_string_printf (nmc->return_text, _("Error: failed to load VPN plugin: %s."),
error->message);
NMC_RETURN (nmc, NMC_RESULT_ERROR_UNKNOWN);
}
connection = nm_vpn_editor_plugin_import (plugin, filename, &error);
if (!connection) {
g_string_printf (nmc->return_text, _("Error: failed to import '%s': %s."),
filename, error->message);

View file

@ -24,6 +24,13 @@
#include "nm-active-connection.h"
#include "nm-device.h"
#define nm_auto_unref_ip_address nm_auto (_nm_ip_address_unref)
NM_AUTO_DEFINE_FCN0 (NMIPAddress *, _nm_ip_address_unref, nm_ip_address_unref)
#define nm_auto_unref_wgpeer nm_auto (_nm_auto_unref_wgpeer)
NM_AUTO_DEFINE_FCN0 (NMWireGuardPeer *, _nm_auto_unref_wgpeer, nm_wireguard_peer_unref)
const NMObject **nmc_objects_sort_by_path (const NMObject *const*objs, gssize len);
const char *nmc_string_is_valid (const char *input, const char **allowed, GError **error);

View file

@ -25,7 +25,13 @@
#include "nm-vpn-helpers.h"
#include <arpa/inet.h>
#include <net/if.h>
#include "nm-client-utils.h"
#include "nm-utils.h"
#include "nm-utils/nm-io-utils.h"
#include "nm-utils/nm-secret-utils.h"
/*****************************************************************************/
@ -247,3 +253,556 @@ nm_vpn_openconnect_authenticate_helper (const char *host,
return TRUE;
}
static gboolean
_wg_complete_peer (GPtrArray **p_peers,
NMWireGuardPeer *peer_take,
gsize peer_start_line_nr,
const char *filename,
GError **error)
{
nm_auto_unref_wgpeer NMWireGuardPeer *peer = peer_take;
gs_free_error GError *local = NULL;
if (!peer)
return TRUE;
if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &local)) {
nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
_("Invalid peer starting at %s:%zu: %s"),
filename,
peer_start_line_nr,
local->message);
return FALSE;
}
if (!*p_peers)
*p_peers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_wireguard_peer_unref);
g_ptr_array_add (*p_peers, g_steal_pointer (&peer));
return TRUE;
}
static gboolean
_line_match (char *line, const char *key, gsize key_len, const char **out_key, char **out_value)
{
nm_assert (line);
nm_assert (key);
nm_assert (strlen (key) == key_len);
nm_assert (!strchr (key, '='));
nm_assert (out_key && !*out_key);
nm_assert (out_value && !*out_value);
/* Note that `wg-quick` (linux.bash) does case-insensitive comparison (shopt -s nocasematch).
* `wg setconf` does case-insensitive comparison too (with strncasecmp, which is locale dependent).
*
* We do a case-insensitive comparison of the key, however in a locale-independent manner. */
if (g_ascii_strncasecmp (line, key, key_len) != 0)
return FALSE;
if (line[key_len] != '=')
return FALSE;
*out_key = key;
*out_value = &line[key_len + 1];
return TRUE;
}
#define line_match(line, key, out_key, out_value) \
_line_match ((line), ""key"", NM_STRLEN (key), (out_key), (out_value))
static gboolean
value_split_word (char **line_remainder, char **out_word)
{
char *str;
if ((*line_remainder)[0] == '\0')
return FALSE;
*out_word = *line_remainder;
str = strchrnul (*line_remainder, ',');
if (str[0] == ',') {
str[0] = '\0';
*line_remainder = &str[1];
} else
*line_remainder = str;
return TRUE;
}
NMConnection *
nm_vpn_wireguard_import (const char *filename,
GError **error)
{
nm_auto_clear_secret_ptr NMSecretPtr file_content = NM_SECRET_PTR_INIT ();
char ifname[IFNAMSIZ];
gs_free char *uuid = NULL;
gboolean ifname_valid = FALSE;
const char *cstr;
char *line_remainder;
gs_unref_object NMConnection *connection = NULL;
NMSettingConnection *s_con;
NMSettingIPConfig *s_ip4;
NMSettingIPConfig *s_ip6;
NMSettingWireGuard *s_wg;
gs_free_error GError *local = NULL;
enum {
LINE_CONTEXT_INIT,
LINE_CONTEXT_INTERFACE,
LINE_CONTEXT_PEER,
} line_context;
gsize line_nr;
gsize current_peer_start_line_nr = 0;
nm_auto_unref_wgpeer NMWireGuardPeer *current_peer = NULL;
gs_unref_ptrarray GPtrArray *data_dns_v4 = NULL;
gs_unref_ptrarray GPtrArray *data_dns_v6 = NULL;
gs_unref_ptrarray GPtrArray *data_addr_v4 = NULL;
gs_unref_ptrarray GPtrArray *data_addr_v6 = NULL;
gs_unref_ptrarray GPtrArray *data_peers = NULL;
const char *data_private_key = NULL;
gint64 data_table;
guint data_listen_port = 0;
guint data_fwmark = 0;
guint data_mtu = 0;
int is_v4;
guint i;
g_return_val_if_fail (filename, NULL);
g_return_val_if_fail (!error || !*error, NULL);
/* contrary to "wg-quick", we never interpret the filename as "/etc/wireguard/$INTERFACE.conf".
* If the filename has no '/', it is interpreted as relative to the current working directory.
* However, we do require a suitable filename suffix and that the name corresponds to the interface
* name. */
cstr = strrchr (filename, '/');
cstr = cstr ? &cstr[1] : filename;
if (NM_STR_HAS_SUFFIX (cstr, ".conf")) {
gsize len = strlen (cstr) - NM_STRLEN (".conf");
if (len > 0 && len < sizeof (ifname)) {
memcpy (ifname, cstr, len);
ifname[len] = '\0';
if (nm_utils_is_valid_iface_name (ifname, NULL))
ifname_valid = TRUE;
}
}
if (!ifname_valid) {
nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN,
_("The WireGuard config file must be a valid interface name followed by \".conf\""));
return FALSE;
}
if (nm_utils_file_get_contents (-1,
filename,
10*1024*1024,
NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET,
&file_content.str,
&file_content.len,
error) < 0)
return NULL;
/* We interpret the file like `wg-quick up` and `wg setconf` do.
*
* Of course the WireGuard scripts do something fundamentlly different. They
* perform actions to configure the WireGuard link in kernel, add routes and
* addresses, and call resolvconf. It all happens at the time when the script
* run.
*
* This code here instead generates a NetworkManager connection profile so that
* NetworkManager will apply a similar configuration when later activating the profile. */
#define _TABLE_AUTO ((gint64) -1)
#define _TABLE_OFF ((gint64) -2)
data_table = _TABLE_AUTO;
line_remainder = file_content.str;
line_context = LINE_CONTEXT_INIT;
line_nr = 0;
while (line_remainder[0] != '\0') {
const char *matched_key = NULL;
char *value = NULL;
char *line;
char ch;
gint64 i64;
line_nr++;
line = line_remainder;
line_remainder = strchrnul (line, '\n');
if (line_remainder[0] != '\0')
(line_remainder++)[0] = '\0';
/* Drop all spaces and truncate at first '#'.
* See wg's config_read_line().
*
* Note that wg-quick doesn't do that.
*
* Neither `wg setconf` nor `wg-quick` does a strict parsing.
* We don't either. Just try to interpret the file (mostly) the same as
* they would.
*/
{
gsize l, n;
n = 0;
for (l = 0; (ch = line[l]); l++) {
if (g_ascii_isspace (ch)) {
/* wg-setconf strips all whitespace before parsing the content. That means,
* *[I nterface]" will be accepted. We do that too. */
continue;
}
if (ch == '#')
break;
line[n++] = line[l];
}
if (n == 0)
continue;
line[n] = '\0';
}
if (g_ascii_strcasecmp (line, "[Interface]") == 0) {
if (!_wg_complete_peer (&data_peers,
g_steal_pointer (&current_peer),
current_peer_start_line_nr,
filename,
error))
return FALSE;
line_context = LINE_CONTEXT_INTERFACE;
continue;
}
if (g_ascii_strcasecmp (line, "[Peer]") == 0) {
if (!_wg_complete_peer (&data_peers,
g_steal_pointer (&current_peer),
current_peer_start_line_nr,
filename,
error))
return FALSE;
current_peer_start_line_nr = line_nr;
current_peer = nm_wireguard_peer_new ();
line_context = LINE_CONTEXT_PEER;
continue;
}
if (line_context == LINE_CONTEXT_INTERFACE) {
if (line_match (line, "Address", &matched_key, &value)) {
char *value_word;
while (value_split_word (&value, &value_word)) {
GPtrArray **p_data_addr;
NMIPAddr addr_bin;
int addr_family;
int prefix_len;
if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC,
value_word,
&addr_family,
&addr_bin,
&prefix_len))
goto fail_invalid_value;
p_data_addr = (addr_family == AF_INET)
? &data_addr_v4
: &data_addr_v6;
if (!*p_data_addr)
*p_data_addr = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_address_unref);
g_ptr_array_add (*p_data_addr,
nm_ip_address_new_binary (addr_family,
&addr_bin,
prefix_len == -1
? ((addr_family == AF_INET) ? 32 : 128)
: prefix_len,
NULL));
}
continue;
}
if (line_match (line, "MTU", &matched_key, &value)) {
i64 = _nm_utils_ascii_str_to_int64 (value, 0, 0, G_MAXUINT32, -1);
if (i64 == -1)
goto fail_invalid_value;
/* wg-quick accepts the "MTU" value, but it also fetches routes to
* autodetect it. NetworkManager won't do that, we can only configure
* an explict MTU or no autodetection will be performed. */
data_mtu = i64;
continue;
}
if (line_match (line, "DNS", &matched_key, &value)) {
char *value_word;
while (value_split_word (&value, &value_word)) {
char addr_s[NM_CONST_MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
GPtrArray **p_data_dns;
NMIPAddr addr_bin;
int addr_family;
if (!nm_utils_parse_inaddr_bin (AF_UNSPEC,
value_word,
&addr_family,
&addr_bin))
goto fail_invalid_value;
p_data_dns = (addr_family == AF_INET)
? &data_dns_v4
: &data_dns_v6;
if (!*p_data_dns)
*p_data_dns = g_ptr_array_new_with_free_func (g_free);
inet_ntop (addr_family, &addr_bin, addr_s, sizeof (addr_s));
g_ptr_array_add (*p_data_dns, g_strdup (addr_s));
}
continue;
}
if (line_match (line, "Table", &matched_key, &value)) {
if (nm_streq (value, "auto"))
data_table = _TABLE_AUTO;
else if (nm_streq (value, "off"))
data_table = _TABLE_OFF;
else {
/* we don't support table names from /etc/iproute2/rt_tables
* But we accept hex like `ip route add` would. */
i64 = _nm_utils_ascii_str_to_int64 (value, 0, 0, G_MAXINT32, -1);
if (i64 == -1)
goto fail_invalid_value;
data_table = i64;
}
continue;
}
if ( line_match (line, "PreUp", &matched_key, &value)
|| line_match (line, "PreDown", &matched_key, &value)
|| line_match (line, "PostUp", &matched_key, &value)
|| line_match (line, "PostDown", &matched_key, &value)) {
/* we don't run any scripts. Silently ignore these paramters. */
continue;
}
if (line_match (line, "SaveConfig", &matched_key, &value)) {
/* we ignore the setting, but enforce that it's either true or false (like
* wg-quick. */
if (!NM_IN_STRSET (value, "true", "false"))
goto fail_invalid_value;
continue;
}
if (line_match (line, "ListenPort", &matched_key, &value)) {
/* we don't use getaddrinfo(), unlike `wg setconf`. Just interpret
* the port as plain decimal number. */
i64 = _nm_utils_ascii_str_to_int64 (value, 10, 0, 0xFFFF, -1);
if (i64 == -1)
goto fail_invalid_value;
data_listen_port = i64;
continue;
}
if (line_match (line, "FwMark", &matched_key, &value)) {
if (nm_streq (value, "off"))
data_fwmark = 0;
else {
i64 = _nm_utils_ascii_str_to_int64 (value, 0, 0, G_MAXINT32, -1);
if (i64 == -1)
goto fail_invalid_value;
data_fwmark = i64;
}
continue;
}
if (line_match (line, "PrivateKey", &matched_key, &value)) {
if (!nm_utils_base64secret_decode (value, NM_WIREGUARD_PUBLIC_KEY_LEN, NULL))
goto fail_invalid_secret;
data_private_key = value;
continue;
}
goto fail_invalid_line;
}
if (line_context == LINE_CONTEXT_PEER) {
if (line_match (line, "Endpoint", &matched_key, &value)) {
if (!nm_wireguard_peer_set_endpoint (current_peer, value, FALSE))
goto fail_invalid_value;
continue;
}
if (line_match (line, "PublicKey", &matched_key, &value)) {
if (!nm_wireguard_peer_set_public_key (current_peer, value, FALSE))
goto fail_invalid_value;
continue;
}
if (line_match (line, "AllowedIPs", &matched_key, &value)) {
char *value_word;
while (value_split_word (&value, &value_word)) {
if (!nm_wireguard_peer_append_allowed_ip (current_peer,
value_word,
FALSE))
goto fail_invalid_value;
}
continue;
}
if (line_match (line, "PersistentKeepalive", &matched_key, &value)) {
if (nm_streq (value, "off"))
i64 = 0;
else {
i64 = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT16, -1);
if (i64 == -1)
goto fail_invalid_value;
}
nm_wireguard_peer_set_persistent_keepalive (current_peer, i64);
continue;
}
if (line_match (line, "PresharedKey", &matched_key, &value)) {
if (!nm_wireguard_peer_set_preshared_key (current_peer, value, FALSE))
goto fail_invalid_secret;
nm_wireguard_peer_set_preshared_key_flags (current_peer, NM_SETTING_SECRET_FLAG_NONE);
continue;
}
goto fail_invalid_line;
}
fail_invalid_line:
nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT,
_("unrecognized line at %s:%zu"),
filename, line_nr);
return FALSE;
fail_invalid_value:
nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT,
_("invalid value for '%s' at %s:%zu"),
matched_key, filename, line_nr);
return FALSE;
fail_invalid_secret:
nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT,
_("invalid secret '%s' at %s:%zu"),
matched_key, filename, line_nr);
return FALSE;
}
if (!_wg_complete_peer (&data_peers,
g_steal_pointer (&current_peer),
current_peer_start_line_nr,
filename,
error))
return FALSE;
connection = nm_simple_connection_new ();
s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
nm_connection_add_setting (connection, NM_SETTING (s_con));
s_ip4 = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
nm_connection_add_setting (connection, NM_SETTING (s_ip4));
s_ip6 = NM_SETTING_IP_CONFIG (nm_setting_ip6_config_new ());
nm_connection_add_setting (connection, NM_SETTING (s_ip6));
s_wg = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
nm_connection_add_setting (connection, NM_SETTING (s_wg));
uuid = nm_utils_uuid_generate ();
g_object_set (s_con,
NM_SETTING_CONNECTION_ID,
ifname,
NM_SETTING_CONNECTION_UUID,
uuid,
NM_SETTING_CONNECTION_TYPE,
NM_SETTING_WIREGUARD_SETTING_NAME,
NM_SETTING_CONNECTION_INTERFACE_NAME,
ifname,
NULL);
g_object_set (s_wg,
NM_SETTING_WIREGUARD_PRIVATE_KEY,
data_private_key,
NM_SETTING_WIREGUARD_LISTEN_PORT,
data_listen_port,
NM_SETTING_WIREGUARD_FWMARK,
data_fwmark,
NM_SETTING_WIREGUARD_MTU,
data_mtu,
NULL);
if (data_peers) {
for (i = 0; i < data_peers->len; i++)
nm_setting_wireguard_append_peer (s_wg, data_peers->pdata[i]);
}
for (is_v4 = 0; is_v4 < 2; is_v4++) {
const char *method_disabled = is_v4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
const char *method_manual = is_v4 ? NM_SETTING_IP4_CONFIG_METHOD_MANUAL : NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
NMSettingIPConfig *s_ip = is_v4 ? s_ip4 : s_ip6;
GPtrArray *data_dns = is_v4 ? data_dns_v4 : data_dns_v6;
GPtrArray *data_addr = is_v4 ? data_addr_v4 : data_addr_v6;
if (data_dns && !data_addr) {
/* When specifying "DNS", we also require an "Address" for the same address
* family. That is because a NMSettingIPConfig cannot have @method_disabled
* and DNS settings at the same time.
*
* We don't have addresses. Silently ignore the DNS setting. */
data_dns = NULL;
}
g_object_set (s_ip,
NM_SETTING_IP_CONFIG_METHOD,
data_addr ? method_manual : method_disabled,
NULL);
if (data_addr) {
for (i = 0; i < data_addr->len; i++)
nm_setting_ip_config_add_address (s_ip, data_addr->pdata[i]);
}
if (data_dns) {
for (i = 0; i < data_dns->len; i++)
nm_setting_ip_config_add_dns (s_ip, data_dns->pdata[i]);
}
if (data_table == _TABLE_AUTO) {
/* in the "auto" setting, wg-quick adds peer-routes automatically to the main
* table. NetworkManager will do that too, but there are differences:
*
* - NetworkManager (contrary to wg-quick) does not check whether the peer-route is necessary.
* It will always add a route for each allowed-ips range, even if there is already another
* route that would ensure packets to the endpoint are routed via the WireGuard interface.
* If you don't want that, disable "wireguard.peer-routes", and add the necessary routes
* yourself to "ipv4.routes" and "ipv6.routes".
*
* - With "auto", wg-quick also configures policy routing to handle default-routes (/0) to
* avoid routing loops. That is not yet solved by NetworkManager, you need to configure
* that explicitly (for example, by adding a direct route to the gateway on the interface
* that has the default-route, or by using a script (possibly dispatcher script).
*/
} else if (data_table == _TABLE_OFF) {
if (is_v4) {
g_object_set (s_wg,
NM_SETTING_WIREGUARD_PEER_ROUTES,
FALSE,
NULL);
}
} else {
g_object_set (s_ip,
NM_SETTING_IP_CONFIG_ROUTE_TABLE,
(guint) data_table,
NULL);
}
}
if (!nm_connection_normalize (connection, NULL, NULL, &local)) {
nm_utils_error_set (error, NM_UTILS_ERROR_INVALID_ARGUMENT,
_("Failed to create WireGuard connection: %s"),
local->message);
return FALSE;
}
return g_steal_pointer (&connection);
}

View file

@ -39,4 +39,7 @@ gboolean nm_vpn_openconnect_authenticate_helper (const char *host,
int *status,
GError **error);
NMConnection *nm_vpn_wireguard_import (const char *filename,
GError **error);
#endif /* __NM_VPN_HELPERS_H__ */

View file

@ -20,6 +20,7 @@
#include "nm-default.h"
#include "nm-meta-setting-access.h"
#include "nm-vpn-helpers.h"
#include "nm-utils/nm-test-utils.h"
@ -145,6 +146,96 @@ test_client_meta_check (void)
/*****************************************************************************/
static void
test_client_import_wireguard_test0 (void)
{
gs_unref_object NMConnection *connection;
NMSettingWireGuard *s_wg;
NMWireGuardPeer *peer;
gs_free_error GError *error = NULL;
connection = nm_vpn_wireguard_import (NM_BUILD_SRCDIR"/clients/common/tests/wg-test0.conf",
&error);
g_assert_no_error (error);
g_assert_cmpstr (nm_connection_get_id (connection), ==, "wg-test0");
g_assert_cmpstr (nm_connection_get_interface_name (connection), ==, "wg-test0");
g_assert_cmpstr (nm_connection_get_connection_type (connection), ==, NM_SETTING_WIREGUARD_SETTING_NAME);
s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
g_assert_cmpint (nm_setting_wireguard_get_listen_port (s_wg), ==, 51820);
g_assert_cmpstr (nm_setting_wireguard_get_private_key (s_wg), ==, "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=");
g_assert_cmpint (nm_setting_wireguard_get_peers_len (s_wg), ==, 3);
peer = nm_setting_wireguard_get_peer (s_wg, 0);
g_assert_cmpstr (nm_wireguard_peer_get_public_key (peer), ==, "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=");
g_assert_cmpstr (nm_wireguard_peer_get_endpoint (peer), ==, "192.95.5.67:1234");
g_assert_cmpint (nm_wireguard_peer_get_allowed_ips_len (peer), ==, 2);
g_assert_cmpstr (nm_wireguard_peer_get_allowed_ip (peer, 0, NULL), ==, "10.192.122.3/32");
g_assert_cmpstr (nm_wireguard_peer_get_allowed_ip (peer, 1, NULL), ==, "10.192.124.1/24");
peer = nm_setting_wireguard_get_peer (s_wg, 1);
g_assert_cmpstr (nm_wireguard_peer_get_public_key (peer), ==, "TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=");
g_assert_cmpstr (nm_wireguard_peer_get_endpoint (peer), ==, "[2607:5300:60:6b0::c05f:543]:2468");
g_assert_cmpint (nm_wireguard_peer_get_allowed_ips_len (peer), ==, 2);
g_assert_cmpstr (nm_wireguard_peer_get_allowed_ip (peer, 0, NULL), ==, "10.192.122.4/32");
g_assert_cmpstr (nm_wireguard_peer_get_allowed_ip (peer, 1, NULL), ==, "192.168.0.0/16");
peer = nm_setting_wireguard_get_peer (s_wg, 2);
g_assert_cmpstr (nm_wireguard_peer_get_public_key (peer), ==, "gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=");
g_assert_cmpstr (nm_wireguard_peer_get_endpoint (peer), ==, "test.wireguard.com:18981");
g_assert_cmpint (nm_wireguard_peer_get_allowed_ips_len (peer), ==, 1);
g_assert_cmpstr (nm_wireguard_peer_get_allowed_ip (peer, 0, NULL), ==, "10.10.10.230/32");
}
static void
test_client_import_wireguard_test1 (void)
{
gs_free_error GError *error = NULL;
nm_vpn_wireguard_import (NM_BUILD_SRCDIR"/clients/common/tests/wg-test1.conf", &error);
g_assert_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT);
g_assert (g_str_has_prefix (error->message, "invalid secret 'PrivateKey'"));
g_assert (g_str_has_suffix (error->message, "wg-test1.conf:2"));
}
static void
test_client_import_wireguard_test2 (void)
{
gs_free_error GError *error = NULL;
nm_vpn_wireguard_import (NM_BUILD_SRCDIR"/clients/common/tests/wg-test2.conf", &error);
g_assert_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT);
g_assert (g_str_has_prefix (error->message, "unrecognized line at"));
g_assert (g_str_has_suffix (error->message, "wg-test2.conf:5"));
}
static void
test_client_import_wireguard_test3 (void)
{
gs_free_error GError *error = NULL;
nm_vpn_wireguard_import (NM_BUILD_SRCDIR"/clients/common/tests/wg-test3.conf", &error);
g_assert_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT);
g_assert (g_str_has_prefix (error->message, "invalid value for 'ListenPort'"));
g_assert (g_str_has_suffix (error->message, "wg-test3.conf:3"));
}
static void
test_client_import_wireguard_missing (void)
{
gs_free_error GError *error = NULL;
nm_vpn_wireguard_import (NM_BUILD_SRCDIR"/clients/common/tests/wg-missing.conf", &error);
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT);
}
/*****************************************************************************/
NMTST_DEFINE ();
int
@ -153,6 +244,11 @@ main (int argc, char **argv)
nmtst_init (&argc, &argv, TRUE);
g_test_add_func ("/client/meta/check", test_client_meta_check);
g_test_add_func ("/client/import/wireguard/test0", test_client_import_wireguard_test0);
g_test_add_func ("/client/import/wireguard/test1", test_client_import_wireguard_test1);
g_test_add_func ("/client/import/wireguard/test2", test_client_import_wireguard_test2);
g_test_add_func ("/client/import/wireguard/test3", test_client_import_wireguard_test3);
g_test_add_func ("/client/import/wireguard/missing", test_client_import_wireguard_missing);
return g_test_run ();
}

View file

@ -0,0 +1,18 @@
[Interface]
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
ListenPort = 51820
[Peer]
PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
Endpoint = 192.95.5.67:1234
AllowedIPs = 10.192.122.3/32, 10.192.124.1/24
[Peer]
PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
Endpoint = [2607:5300:60:6b0::c05f:543]:2468
AllowedIPs = 10.192.122.4/32, 192.168.0.0/16
[Peer]
PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
Endpoint = test.wireguard.com:18981
AllowedIPs = 10.10.10.230/32

View file

@ -0,0 +1,3 @@
[Interface]
PrivateKey = bad
ListenPort = 51820

View file

@ -0,0 +1,8 @@
[Interface]
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
ListenPort = 51820
[Beer]
PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
Endpoint = 192.95.5.67:1234
AllowedIPs = 10.192.122.3/32, 10.192.124.1/24

View file

@ -0,0 +1,3 @@
[Interface]
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
ListenPort = 666666666

View file

@ -343,7 +343,7 @@ def do_set(nm_client, conn, argv):
else:
peer_idx = None
peer = NM.WireGuardPeer()
peer.set_public_key(public_key)
peer.set_public_key(public_key, True)
wg_peer_is_valid(peer, 'public key "%s" is invalid' % (public_key))
peer_remove = False
idx += 2
@ -355,11 +355,11 @@ def do_set(nm_client, conn, argv):
if peer and argv[idx] == 'preshared-key':
psk = argv_get_one(argv, idx + 1, None, idx)
if psk == '':
peer.set_preshared_key(None)
peer.set_preshared_key(None, True)
if peer_secret_flags is not None:
peer_secret_flags = NM.SettingSecretFlags.NOT_REQUIRED
else:
peer.set_preshared_key(wg_read_private_key(psk))
peer.set_preshared_key(wg_read_private_key(psk), True)
if peer_secret_flags is not None:
peer_secret_flags = NM.SettingSecretFlags.NONE
idx += 2
@ -369,7 +369,7 @@ def do_set(nm_client, conn, argv):
idx += 2
continue
if peer and argv[idx] == 'endpoint':
peer.set_endpoint(argv_get_one(argv, idx + 1, None, idx))
peer.set_endpoint(argv_get_one(argv, idx + 1, None, idx), True)
idx += 2
continue
if peer and argv[idx] == 'persistent-keepalive':

View file

@ -765,13 +765,9 @@ gboolean _nm_connection_find_secret (NMConnection *self,
#define nm_auto_unref_wgpeer nm_auto(_nm_auto_unref_wgpeer)
NM_AUTO_DEFINE_FCN_VOID0 (NMWireGuardPeer *, _nm_auto_unref_wgpeer, nm_wireguard_peer_unref)
gboolean _nm_utils_wireguard_decode_key (const char *base64_key,
gsize required_key_len,
guint8 *out_key);
gboolean _nm_utils_wireguard_normalize_key (const char *base64_key,
gsize required_key_len,
char **out_base64_key_norm);
gboolean nm_utils_base64secret_normalize (const char *base64_key,
gsize required_key_len,
char **out_base64_key_norm);
/*****************************************************************************/

View file

@ -2920,7 +2920,7 @@ _read_setting_wireguard_peer (KeyfileReaderInfo *info)
nm_assert (g_str_has_prefix (info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER));
cstr = &info->group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)];
if ( !_nm_utils_wireguard_normalize_key (cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str)
if ( !nm_utils_base64secret_normalize (cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str)
|| !nm_streq0 (str, cstr)) {
/* the group name must be identical to the normalized(!) key, so that it
* is uniquely identified. */
@ -2929,19 +2929,18 @@ _read_setting_wireguard_peer (KeyfileReaderInfo *info)
info->group);
return;
}
nm_wireguard_peer_set_public_key (peer, cstr);
nm_wireguard_peer_set_public_key (peer, cstr, TRUE);
nm_clear_g_free (&str);
key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY;
str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL);
if (str) {
if (!_nm_utils_wireguard_decode_key (str, NM_WIREGUARD_SYMMETRIC_KEY_LEN, NULL)) {
if (!nm_wireguard_peer_set_preshared_key (peer, str, FALSE)) {
if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
_("key '%s.%s' is not not a valid 256 bit key in base64 encoding"),
info->group, key))
return;
} else
nm_wireguard_peer_set_preshared_key (peer, str);
}
nm_clear_g_free (&str);
}
@ -2973,16 +2972,12 @@ _read_setting_wireguard_peer (KeyfileReaderInfo *info)
key = NM_WIREGUARD_PEER_ATTR_ENDPOINT;
str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL);
if (str && str[0]) {
nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL;
ep = nm_sock_addr_endpoint_new (str);
if (!nm_sock_addr_endpoint_get_host (ep)) {
if (!nm_wireguard_peer_set_endpoint (peer, str, FALSE)) {
if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
_("key '%s.%s' is not not a valid endpoint"),
info->group, key))
return;
} else
_nm_wireguard_peer_set_endpoint (peer, ep);
}
}
nm_clear_g_free (&str);

View file

@ -280,34 +280,48 @@ nm_wireguard_peer_get_public_key (const NMWireGuardPeer *self)
* @self: the unsealed #NMWireGuardPeer instance
* @public_key: (allow-none) (transfer none): the new public
* key or %NULL to clear the public key.
* @accept_invalid: if %TRUE and @public_key is not %NULL and
* invalid, then do not modify the instance.
*
* Reset the public key. Note that if the public key is valid, it
* will be normalized (which may or may not modify the set value).
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Returns: %TRUE if the key was valid or %NULL. Returns
* %FALSE for invalid keys. Depending on @accept_invalid
* will an invalid key be set or not.
*
* Since: 1.16
*/
void
gboolean
nm_wireguard_peer_set_public_key (NMWireGuardPeer *self,
const char *public_key)
const char *public_key,
gboolean accept_invalid)
{
char *public_key_normalized = NULL;
gboolean is_valid;
g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
if (!public_key) {
nm_clear_g_free (&self->public_key);
return;
return TRUE;
}
self->public_key_valid = _nm_utils_wireguard_normalize_key (public_key,
NM_WIREGUARD_PUBLIC_KEY_LEN,
&public_key_normalized);
nm_assert (self->public_key_valid == (public_key_normalized != NULL));
is_valid = nm_utils_base64secret_normalize (public_key,
NM_WIREGUARD_PUBLIC_KEY_LEN,
&public_key_normalized);
nm_assert (is_valid == (public_key_normalized != NULL));
if ( !is_valid
&& !accept_invalid)
return FALSE;
self->public_key_valid = is_valid;
g_free (self->public_key);
self->public_key = public_key_normalized ?: g_strdup (public_key);
return is_valid;
}
void
@ -346,6 +360,9 @@ nm_wireguard_peer_get_preshared_key (const NMWireGuardPeer *self)
* @self: the unsealed #NMWireGuardPeer instance
* @preshared_key: (allow-none) (transfer none): the new preshared
* key or %NULL to clear the preshared key.
* @accept_invalid: whether to allow setting the key to an invalid
* value. If %FALSE, @self is unchanged if the key is invalid
* and if %FALSE is returned.
*
* Reset the preshared key. Note that if the preshared key is valid, it
* will be normalized (which may or may not modify the set value).
@ -358,28 +375,41 @@ nm_wireguard_peer_get_preshared_key (const NMWireGuardPeer *self)
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Returns: %TRUE if the preshared-key is valid, otherwise %FALSE.
* %NULL is considered a valid value.
* If the key is invalid, it depends on @accept_invalid whether the
* previous value was reset.
*
* Since: 1.16
*/
void
gboolean
nm_wireguard_peer_set_preshared_key (NMWireGuardPeer *self,
const char *preshared_key)
const char *preshared_key,
gboolean accept_invalid)
{
char *preshared_key_normalized = NULL;
gboolean is_valid;
g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
if (!preshared_key) {
nm_clear_pointer (&self->preshared_key, nm_free_secret);
return;
return TRUE;
}
self->preshared_key_valid = _nm_utils_wireguard_normalize_key (preshared_key,
NM_WIREGUARD_SYMMETRIC_KEY_LEN,
&preshared_key_normalized);
nm_assert (self->preshared_key_valid == (preshared_key_normalized != NULL));
is_valid = nm_utils_base64secret_normalize (preshared_key,
NM_WIREGUARD_SYMMETRIC_KEY_LEN,
&preshared_key_normalized);
nm_assert (is_valid == (preshared_key_normalized != NULL));
if ( !is_valid
&& !accept_invalid)
return FALSE;
self->preshared_key_valid = is_valid;
nm_free_secret (self->preshared_key);
self->preshared_key = preshared_key_normalized ?: g_strdup (preshared_key);
return is_valid;
}
/**
@ -494,26 +524,50 @@ _nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
* nm_wireguard_peer_set_endpoint:
* @self: the unsealed #NMWireGuardPeer instance
* @endpoint: the socket address endpoint to set or %NULL.
* @allow_invalid: if %TRUE, also invalid values are set.
* If %FALSE, the function does nothing for invalid @endpoint
* arguments.
*
* Sets or clears the endpoint of @self.
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Returns: %TRUE if the endpoint is %NULL or valid. For an
* invalid @endpoint argument, %FALSE is returned. Depending
* on @allow_invalid, the instance will be modified.
*
* Since: 1.16
*/
void
gboolean
nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
const char *endpoint)
const char *endpoint,
gboolean allow_invalid)
{
NMSockAddrEndpoint *old;
NMSockAddrEndpoint *new;
gboolean is_valid;
g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
if (!endpoint) {
nm_clear_pointer (&self->endpoint, nm_sock_addr_endpoint_unref);
return TRUE;
}
new = nm_sock_addr_endpoint_new (endpoint);
is_valid = (nm_sock_addr_endpoint_get_host (new) != NULL);
if ( !allow_invalid
&& !is_valid) {
nm_sock_addr_endpoint_unref (new);
return FALSE;
}
old = self->endpoint;
self->endpoint = endpoint
? nm_sock_addr_endpoint_new (endpoint)
: NULL;
self->endpoint = new;
nm_sock_addr_endpoint_unref (old);
return is_valid;
}
/**
@ -593,6 +647,7 @@ _peer_append_allowed_ip (NMWireGuardPeer *self,
int prefix;
NMIPAddr addrbin;
char *str;
gboolean is_valid = TRUE;
nm_assert (NM_IS_WIREGUARD_PEER (self, FALSE));
nm_assert (allowed_ip);
@ -608,6 +663,7 @@ _peer_append_allowed_ip (NMWireGuardPeer *self,
return FALSE;
/* mark the entry as invalid by having a "X" prefix. */
str = g_strconcat (ALLOWED_IP_INVALID_X_STR, allowed_ip, NULL);
is_valid = FALSE;
} else {
char addrstr[NM_UTILS_INET_ADDRSTRLEN];
@ -625,7 +681,7 @@ _peer_append_allowed_ip (NMWireGuardPeer *self,
self->allowed_ips = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (self->allowed_ips, str);
return TRUE;
return is_valid;
}
/**
@ -1072,9 +1128,9 @@ again:
return pd;
}
if ( try_with_normalized_key
&& _nm_utils_wireguard_normalize_key (public_key,
NM_WIREGUARD_PUBLIC_KEY_LEN,
&public_key_normalized)) {
&& nm_utils_base64secret_normalize (public_key,
NM_WIREGUARD_PUBLIC_KEY_LEN,
&public_key_normalized)) {
public_key = public_key_normalized;
try_with_normalized_key = FALSE;
goto again;
@ -1516,8 +1572,7 @@ _peers_dbus_only_set (NMSetting *setting,
}
peer = nm_wireguard_peer_new ();
nm_wireguard_peer_set_public_key (peer, cstr);
if (!peer->public_key_valid) {
if (!nm_wireguard_peer_set_public_key (peer, cstr, TRUE)) {
if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
_("peer #%u has invalid public-key"),
@ -1543,7 +1598,7 @@ _peers_dbus_only_set (NMSetting *setting,
}
if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr))
nm_wireguard_peer_set_preshared_key (peer, cstr);
nm_wireguard_peer_set_preshared_key (peer, cstr, TRUE);
if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, "u", &u32))
nm_wireguard_peer_set_preshared_key_flags (peer, u32);
@ -1873,7 +1928,7 @@ update_one_secret (NMSetting *setting,
continue;
peer = nm_wireguard_peer_new_clone (pd->peer, FALSE);
nm_wireguard_peer_set_preshared_key (peer, cstr);
nm_wireguard_peer_set_preshared_key (peer, cstr, TRUE);
if (!_peers_set (priv, peer, pd->idx, FALSE))
nm_assert_not_reached ();
@ -2244,9 +2299,9 @@ set_property (GObject *object, guint prop_id,
nm_clear_pointer (&priv->private_key, nm_free_secret);
str = g_value_get_string (value);
if (str) {
if (_nm_utils_wireguard_normalize_key (str,
NM_WIREGUARD_PUBLIC_KEY_LEN,
&priv->private_key))
if (nm_utils_base64secret_normalize (str,
NM_WIREGUARD_PUBLIC_KEY_LEN,
&priv->private_key))
priv->private_key_valid = TRUE;
else {
priv->private_key = g_strdup (str);

View file

@ -61,14 +61,16 @@ gboolean nm_wireguard_peer_is_sealed (const NMWireGuardPeer *self);
NM_AVAILABLE_IN_1_16
const char *nm_wireguard_peer_get_public_key (const NMWireGuardPeer *self);
NM_AVAILABLE_IN_1_16
void nm_wireguard_peer_set_public_key (NMWireGuardPeer *self,
const char *public_key);
gboolean nm_wireguard_peer_set_public_key (NMWireGuardPeer *self,
const char *public_key,
gboolean accept_invalid);
NM_AVAILABLE_IN_1_16
const char *nm_wireguard_peer_get_preshared_key (const NMWireGuardPeer *self);
NM_AVAILABLE_IN_1_16
void nm_wireguard_peer_set_preshared_key (NMWireGuardPeer *self,
const char *preshared_key);
gboolean nm_wireguard_peer_set_preshared_key (NMWireGuardPeer *self,
const char *preshared_key,
gboolean accept_invalid);
NM_AVAILABLE_IN_1_16
NMSettingSecretFlags nm_wireguard_peer_get_preshared_key_flags (const NMWireGuardPeer *self);
@ -85,8 +87,9 @@ void nm_wireguard_peer_set_persistent_keepalive (NMWireGuardPeer *self,
NM_AVAILABLE_IN_1_16
const char *nm_wireguard_peer_get_endpoint (const NMWireGuardPeer *self);
NM_AVAILABLE_IN_1_16
void nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
const char *endpoint);
gboolean nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
const char *endpoint,
gboolean allow_invalid);
NM_AVAILABLE_IN_1_16
guint nm_wireguard_peer_get_allowed_ips_len (const NMWireGuardPeer *self);

View file

@ -6673,21 +6673,23 @@ nm_utils_version (void)
/*****************************************************************************/
/**
* _nm_utils_wireguard_decode_key:
* nm_utils_base64secret_decode:
* @base64_key: the (possibly invalid) base64 encode key.
* @required_key_len: the expected (binary) length of the key after
* decoding. If the length does not match, the validation fails.
* @out_key: (allow-none): an optional output buffer for the binary
* @out_key: (allow-none): (out): an optional output buffer for the binary
* key. If given, it will be filled with exactly @required_key_len
* bytes.
*
* Returns: %TRUE if the input key is a valid base64 encoded key
* with @required_key_len bytes.
*
* Since: 1.16
*/
gboolean
_nm_utils_wireguard_decode_key (const char *base64_key,
gsize required_key_len,
guint8 *out_key)
nm_utils_base64secret_decode (const char *base64_key,
gsize required_key_len,
guint8 *out_key)
{
gs_free guint8 *bin_arr = NULL;
gsize base64_key_len;
@ -6707,11 +6709,6 @@ _nm_utils_wireguard_decode_key (const char *base64_key,
return FALSE;
}
if (nm_utils_memeqzero (bin_arr, required_key_len)) {
/* an all zero key is not valid either. That is used to represet an unset key */
return FALSE;
}
if (out_key)
memcpy (out_key, bin_arr, required_key_len);
@ -6720,9 +6717,9 @@ _nm_utils_wireguard_decode_key (const char *base64_key,
}
gboolean
_nm_utils_wireguard_normalize_key (const char *base64_key,
gsize required_key_len,
char **out_base64_key_norm)
nm_utils_base64secret_normalize (const char *base64_key,
gsize required_key_len,
char **out_base64_key_norm)
{
gs_free guint8 *buf_free = NULL;
guint8 buf_static[200];
@ -6734,7 +6731,7 @@ _nm_utils_wireguard_normalize_key (const char *base64_key,
} else
buf = buf_static;
if (!_nm_utils_wireguard_decode_key (base64_key, required_key_len, buf)) {
if (!nm_utils_base64secret_decode (base64_key, required_key_len, buf)) {
NM_SET_OUT (out_base64_key_norm, NULL);
return FALSE;
}

View file

@ -263,6 +263,10 @@ NMSriovVF *nm_utils_sriov_vf_from_str (const char *str, GError **error);
NM_AVAILABLE_IN_1_12
gint64 nm_utils_get_timestamp_msec (void);
NM_AVAILABLE_IN_1_16
gboolean nm_utils_base64secret_decode (const char *base64_key,
gsize required_key_len,
guint8 *out_key);
G_END_DECLS

View file

@ -2065,9 +2065,11 @@ _rndt_wg_peers_create (void)
s_endpoint = _create_random_ipaddr (AF_UNSPEC, TRUE);
peer = nm_wireguard_peer_new ();
nm_wireguard_peer_set_public_key (peer, public_key);
if (!nm_wireguard_peer_set_public_key (peer, public_key, TRUE))
g_assert_not_reached ();
nm_wireguard_peer_set_preshared_key (peer, nmtst_rand_select (NULL, preshared_key));
if (!nm_wireguard_peer_set_preshared_key (peer, nmtst_rand_select (NULL, preshared_key), TRUE))
g_assert_not_reached ();
nm_wireguard_peer_set_preshared_key_flags (peer, nmtst_rand_select (NM_SETTING_SECRET_FLAG_NONE,
NM_SETTING_SECRET_FLAG_NOT_SAVED,
@ -2076,7 +2078,8 @@ _rndt_wg_peers_create (void)
nm_wireguard_peer_set_persistent_keepalive (peer,
nmtst_rand_select ((guint32) 0, nmtst_get_rand_int ()));
nm_wireguard_peer_set_endpoint (peer, nmtst_rand_select (s_endpoint, NULL));
if (!nm_wireguard_peer_set_endpoint (peer, nmtst_rand_select (s_endpoint, NULL), TRUE))
g_assert_not_reached ();
n_aip = nmtst_rand_select (0, nmtst_get_rand_int () % 10);
for (i_aip = 0; i_aip < n_aip; i_aip++) {
@ -2245,7 +2248,8 @@ _rndt_wg_peers_fix_secrets (NMSettingWireGuard *s_wg,
g_assert (NM_IN_SET (nm_wireguard_peer_get_preshared_key_flags (a), NM_SETTING_SECRET_FLAG_AGENT_OWNED,
NM_SETTING_SECRET_FLAG_NOT_SAVED));
b_clone = nm_wireguard_peer_new_clone (b, TRUE);
nm_wireguard_peer_set_preshared_key (b_clone, nm_wireguard_peer_get_preshared_key (a));
if (!nm_wireguard_peer_set_preshared_key (b_clone, nm_wireguard_peer_get_preshared_key (a), TRUE))
g_assert_not_reached ();
nm_setting_wireguard_set_peer (s_wg, b_clone, i);
b = nm_setting_wireguard_get_peer (s_wg, i);
g_assert (b == b_clone);

View file

@ -1479,6 +1479,7 @@ global:
nm_setting_wireguard_set_peer;
nm_team_link_watcher_get_vlanid;
nm_team_link_watcher_new_arp_ping2;
nm_utils_base64secret_decode;
nm_wifi_p2p_peer_connection_valid;
nm_wifi_p2p_peer_filter_connections;
nm_wifi_p2p_peer_get_flags;

View file

@ -268,8 +268,8 @@ nm_utils_fd_get_contents (int fd,
* @flags: %NMUtilsFileGetContentsFlags for reading the file.
* @contents: the output buffer with the file read. It is always
* NUL terminated. The buffer is at most @max_length long, including
* the NUL byte. That is, it reads only files up to a length of
* @max_length - 1 bytes.
* the NUL byte. That is, it reads only files up to a length of
* @max_length - 1 bytes.
* @length: optional output argument of the read file size.
*
* A reimplementation of g_file_get_contents() with a few differences:

View file

@ -17,6 +17,7 @@
* Boston, MA 02110-1301 USA.
*
* (C) Copyright 2018 Red Hat, Inc.
* (C) Copyright 2015 - 2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
#include "nm-default.h"
@ -132,3 +133,29 @@ nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len)
_secret_buf_free,
secret);
}
/*****************************************************************************/
/**
* nm_utils_memeqzero_secret:
* @data: the data pointer to check (may be %NULL if @length is zero).
* @length: the number of bytes to check.
*
* Checks that all bytes are zero. This always takes the same amount
* of time to prevent timing attacks.
*
* Returns: whether all bytes are zero.
*/
gboolean
nm_utils_memeqzero_secret (gconstpointer data, gsize length)
{
const guint8 *const key = data;
volatile guint8 acc = 0;
gsize i;
for (i = 0; i < length; i++) {
acc |= key[i];
asm volatile("" : "=r"(acc) : "0"(acc));
}
return 1 & ((acc - 1) >> 8);
}

View file

@ -173,4 +173,6 @@ GBytes *nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len);
/*****************************************************************************/
gboolean nm_utils_memeqzero_secret (gconstpointer data, gsize length);
#endif /* __NM_SECRET_UTILS_H__ */

View file

@ -672,6 +672,8 @@ nm_utils_parse_inaddr_prefix_bin (int addr_family,
return FALSE;
if (slash) {
/* For IPv4, `ip addr add` supports the prefix-length as a netmask. We don't
* do that. */
prefix = _nm_utils_ascii_str_to_int64 (slash + 1, 10,
0,
addr_family == AF_INET ? 32 : 128,

View file

@ -37,9 +37,6 @@ _LOG_DECLARE_SELF(NMDeviceWireGuard);
/*****************************************************************************/
/* TODO: ensure externally-managed works. Both after start of NM and
* when adding a wg link with NM running. */
/* TODO: activate profile with peer preshared-key-flags=2. On first activation, the secret is
* requested (good). Enter it and connect. Reactivate the profile, now there is no password
* prompt, as the secret is cached (good??). */
@ -47,7 +44,15 @@ _LOG_DECLARE_SELF(NMDeviceWireGuard);
/* TODO: unlike for other VPNs, we don't inject a direct route to the peers. That means,
* you might get a routing sceneraio where the peer (VPN server) is reachable via the VPN.
* How we handle adding routes to external gateway for other peers, has severe issues
* as well. I think the only solution is https://www.wireguard.com/netns/#improving-the-classic-solutions */
* as well. We may use policy-routing like wg-quick does. See also disussions at
* https://www.wireguard.com/netns/#improving-the-classic-solutions */
/* TODO: honor the TTL of DNS to determine when to retry resolving endpoints. */
/* TODO: when we get multiple IP addresses when resolving a peer endpoint. We currently
* just take the first from GAI. We should only accept AAAA/IPv6 if we also have a suitable
* IPv6 address. The problem is, that we have to recheck that when IP addressing on other
* interfaces changes. This makes it almost too cumbersome to implement. */
/*****************************************************************************/
@ -729,9 +734,9 @@ _peers_get_platform_list (NMDeviceWireGuardPrivate *priv,
NMPWireGuardPeer *plp = &plpeers[i_good];
NMSettingSecretFlags psk_secret_flags;
if (!_nm_utils_wireguard_decode_key (nm_wireguard_peer_get_public_key (peer_data->peer),
sizeof (plp->public_key),
plp->public_key))
if (!nm_utils_base64secret_decode (nm_wireguard_peer_get_public_key (peer_data->peer),
sizeof (plp->public_key),
plp->public_key))
continue;
*plf = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE;
@ -754,9 +759,9 @@ _peers_get_platform_list (NMDeviceWireGuardPrivate *priv,
LINK_CONFIG_MODE_REAPPLY)) {
psk_secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer_data->peer);
if (!NM_FLAGS_HAS (psk_secret_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
if ( !_nm_utils_wireguard_decode_key (nm_wireguard_peer_get_preshared_key (peer_data->peer),
sizeof (plp->preshared_key),
plp->preshared_key)
if ( !nm_utils_base64secret_decode (nm_wireguard_peer_get_preshared_key (peer_data->peer),
sizeof (plp->preshared_key),
plp->preshared_key)
&& config_mode == LINK_CONFIG_MODE_FULL)
goto skip;
}
@ -1128,9 +1133,9 @@ link_config (NMDeviceWireGuard *self,
wg_lnk.fwmark = nm_setting_wireguard_get_fwmark (s_wg),
wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK;
if (_nm_utils_wireguard_decode_key (nm_setting_wireguard_get_private_key (s_wg),
sizeof (wg_lnk.private_key),
wg_lnk.private_key)) {
if (nm_utils_base64secret_decode (nm_setting_wireguard_get_private_key (s_wg),
sizeof (wg_lnk.private_key),
wg_lnk.private_key)) {
wg_lnk_clear_private_key = NM_SECRET_PTR_ARRAY (wg_lnk.private_key);
wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY;
} else {

View file

@ -2019,8 +2019,10 @@ _wireguard_update_from_peers_nla (CList *peers,
if (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL])
peer_c->data.persistent_keepalive_interval = nla_get_u16 (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
if (tb[WGPEER_A_LAST_HANDSHAKE_TIME])
nla_memcpy (&peer_c->data.last_handshake_time, tb[WGPEER_A_LAST_HANDSHAKE_TIME], sizeof (peer_c->data.last_handshake_time));
if (tb[WGPEER_A_LAST_HANDSHAKE_TIME]) {
if (nla_len (tb[WGPEER_A_LAST_HANDSHAKE_TIME]) >= sizeof (peer_c->data.last_handshake_time))
nla_memcpy (&peer_c->data.last_handshake_time, tb[WGPEER_A_LAST_HANDSHAKE_TIME], sizeof (peer_c->data.last_handshake_time));
}
if (tb[WGPEER_A_RX_BYTES])
peer_c->data.rx_bytes = nla_get_u64 (tb[WGPEER_A_RX_BYTES]);
if (tb[WGPEER_A_TX_BYTES])

View file

@ -39,6 +39,7 @@
#include "nm-core-internal.h"
#include "nm-utils/nm-dedup-multi.h"
#include "nm-utils/nm-udev-utils.h"
#include "nm-utils/nm-secret-utils.h"
#include "nm-core-utils.h"
#include "nm-platform-utils.h"
@ -5658,7 +5659,7 @@ nm_platform_wireguard_peer_to_string (const NMPWireGuardPeer *peer, char *buf, g
"%s" /* persistent-keepalive */
"%s", /* allowed-ips */
public_key_b64,
nm_utils_memeqzero (peer->preshared_key, sizeof (peer->preshared_key))
nm_utils_memeqzero_secret (peer->preshared_key, sizeof (peer->preshared_key))
? ""
: " preshared-key (hidden)",
s_endpoint,
@ -5704,7 +5705,7 @@ nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *bu
? " public-key "
: "",
public_b64 ?: "",
nm_utils_memeqzero (lnk->private_key, sizeof (lnk->private_key))
nm_utils_memeqzero_secret (lnk->private_key, sizeof (lnk->private_key))
? ""
: " private-key (hidden)",
lnk->listen_port,

View file

@ -31,6 +31,18 @@ struct udev_device;
/*****************************************************************************/
/* "struct __kernel_timespec" uses "long long", but we use gint64. In practice,
* these are the same types. */
G_STATIC_ASSERT (sizeof (long long) == sizeof (gint64));
typedef struct {
/* like "struct __kernel_timespec". */
gint64 tv_sec;
gint64 tv_nsec;
} NMPTimespec64;
/*****************************************************************************/
typedef union {
struct sockaddr sa;
struct sockaddr_in in;
@ -72,7 +84,8 @@ typedef struct {
typedef struct _NMPWireGuardPeer {
NMSockAddrUnion endpoint;
struct timespec last_handshake_time;
NMPTimespec64 last_handshake_time;
guint64 rx_bytes;
guint64 tx_bytes;