diff options
author | Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> | 2019-04-09 10:52:52 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-09 10:52:52 +0200 |
commit | 52efbd8f0e1b13ccbe4c623d17171a94d51186d2 (patch) | |
tree | bd14f6b04a0650f2ec67965fdff035e54e9c9720 | |
parent | l10n: Updated Lithuanian translation (diff) | |
parent | NEWS: mention PresharedKeyFile= (diff) | |
download | systemd-52efbd8f0e1b13ccbe4c623d17171a94d51186d2.tar.xz systemd-52efbd8f0e1b13ccbe4c623d17171a94d51186d2.zip |
Merge pull request #12223 from yuwata/network-wireguard-preshared-key-file
network: add PresharedKeyFile= setting and make reading key file failure fatal
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | man/systemd.netdev.xml | 16 | ||||
-rw-r--r-- | src/basic/fileio.c | 104 | ||||
-rw-r--r-- | src/basic/fileio.h | 20 | ||||
-rw-r--r-- | src/basic/hexdecoct.c | 75 | ||||
-rw-r--r-- | src/basic/hexdecoct.h | 5 | ||||
-rw-r--r-- | src/network/netdev/netdev-gperf.gperf | 1 | ||||
-rw-r--r-- | src/network/netdev/wireguard.c | 299 | ||||
-rw-r--r-- | src/network/netdev/wireguard.h | 2 | ||||
-rw-r--r-- | test/fuzz/fuzz-netdev-parser/directives.netdev | 1 | ||||
-rw-r--r-- | test/test-network/conf/25-wireguard-preshared-key.txt | 3 | ||||
-rw-r--r-- | test/test-network/conf/25-wireguard.netdev | 1 | ||||
-rw-r--r-- | test/test-network/conf/25-wireguard.netdev.d/peer.conf | 1 | ||||
-rwxr-xr-x | test/test-network/systemd-networkd-tests.py | 6 |
14 files changed, 357 insertions, 181 deletions
@@ -116,8 +116,8 @@ CHANGES WITH 242: * The new TripleSampling= option in .network files may be used to configure CAN triple sampling. - * A new .netdev setting PrivateKeyFile= may be used to point to private - key for a WireGuard interface. + * A new .netdev settings PrivateKeyFile= and PresharedKeyFile= may be + used to point to private or preshared key for a WireGuard interface. * /etc/crypttab now supports the same-cpu-crypt and submit-from-crypt-cpus options to tweak encryption work scheduling diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index a44018cad6..1836b5fe00 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -1241,10 +1241,8 @@ <varlistentry> <term><varname>PrivateKeyFile=</varname></term> <listitem> - <para>Takes a absolute path to a file which contains the Base64 encoded private key for the interface. - If both <varname>PrivateKey=</varname> and <varname>PrivateKeyFile=</varname> are specified, and if - the file specified in <varname>PrivateKeyFile=</varname> contains valid wireguard key, then - the key provided by <varname>PrivateKey=</varname> is ignored. + <para>Takes an absolute path to a file which contains the Base64 encoded private key for the interface. + When this option is specified, then <varname>PrivateKey=</varname> is ignored. Note that the file must be readable by the user <literal>systemd-network</literal>, so it should be, e.g., owned by <literal>root:systemd-network</literal> with a <literal>0640</literal> file mode.</para> @@ -1299,6 +1297,16 @@ </listitem> </varlistentry> <varlistentry> + <term><varname>PresharedKeyFile=</varname></term> + <listitem> + <para>Takes an absolute path to a file which contains the Base64 encoded preshared key for the + peer. When this option is specified, then <varname>PresharedKey=</varname> is ignored. + Note that the file must be readable by the user <literal>systemd-network</literal>, so it + should be, e.g., owned by <literal>root:systemd-network</literal> with a + <literal>0640</literal> file mode.</para> + </listitem> + </varlistentry> + <varlistentry> <term><varname>AllowedIPs=</varname></term> <listitem> <para>Sets a comma-separated list of IP (v4 or v6) addresses with CIDR masks diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 91e0c9ec8b..9ab2f501c7 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -17,6 +17,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "hexdecoct.h" #include "log.h" #include "macro.h" #include "missing.h" @@ -264,26 +265,29 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { return 1; } -int read_full_stream( +int read_full_stream_full( FILE *f, + const char *filename, + ReadFullFileFlags flags, char **ret_contents, size_t *ret_size) { _cleanup_free_ char *buf = NULL; struct stat st; - size_t n, l; - int fd; + size_t n, n_next, l; + int fd, r; assert(f); assert(ret_contents); + assert(!(flags & READ_FULL_FILE_UNBASE64) || ret_size); - n = LINE_MAX; /* Start size */ + n_next = LINE_MAX; /* Start size */ fd = fileno(f); if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen(), let's * optimize our buffering) */ - if (fstat(fileno(f), &st) < 0) + if (fstat(fd, &st) < 0) return -errno; if (S_ISREG(st.st_mode)) { @@ -296,27 +300,44 @@ int read_full_stream( * size of 0. Note that we increase the size to read here by one, so that the first read attempt * already makes us notice the EOF. */ if (st.st_size > 0) - n = st.st_size + 1; + n_next = st.st_size + 1; + + if (flags & READ_FULL_FILE_SECURE) + (void) warn_file_is_world_accessible(filename, &st, NULL, 0); } } - l = 0; + n = l = 0; for (;;) { char *t; size_t k; - t = realloc(buf, n + 1); - if (!t) - return -ENOMEM; + if (flags & READ_FULL_FILE_SECURE) { + t = malloc(n_next + 1); + if (!t) { + r = -ENOMEM; + goto finalize; + } + memcpy_safe(t, buf, n); + explicit_bzero_safe(buf, n); + } else { + t = realloc(buf, n_next + 1); + if (!t) + return -ENOMEM; + } buf = t; + n = n_next; + errno = 0; k = fread(buf + l, 1, n - l, f); if (k > 0) l += k; - if (ferror(f)) - return errno > 0 ? -errno : -EIO; + if (ferror(f)) { + r = errno > 0 ? -errno : -EIO; + goto finalize; + } if (feof(f)) break; @@ -327,10 +348,18 @@ int read_full_stream( assert(l == n); /* Safety check */ - if (n >= READ_FULL_BYTES_MAX) - return -E2BIG; + if (n >= READ_FULL_BYTES_MAX) { + r = -E2BIG; + goto finalize; + } + + n_next = MIN(n * 2, READ_FULL_BYTES_MAX); + } - n = MIN(n * 2, READ_FULL_BYTES_MAX); + if (flags & READ_FULL_FILE_UNBASE64) { + buf[l++] = 0; + r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size); + goto finalize; } if (!ret_size) { @@ -338,8 +367,10 @@ int read_full_stream( * trailing NUL byte. But if there's an embedded NUL byte, then we should refuse operation as otherwise * there'd be ambiguity about what we just read. */ - if (memchr(buf, 0, l)) - return -EBADMSG; + if (memchr(buf, 0, l)) { + r = -EBADMSG; + goto finalize; + } } buf[l] = 0; @@ -349,21 +380,27 @@ int read_full_stream( *ret_size = l; return 0; + +finalize: + if (flags & READ_FULL_FILE_SECURE) + explicit_bzero_safe(buf, n); + + return r; } -int read_full_file(const char *fn, char **contents, size_t *size) { +int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) { _cleanup_fclose_ FILE *f = NULL; - assert(fn); + assert(filename); assert(contents); - f = fopen(fn, "re"); + f = fopen(filename, "re"); if (!f) return -errno; (void) __fsetlocking(f, FSETLOCKING_BYCALLER); - return read_full_stream(f, contents, size); + return read_full_stream_full(f, filename, flags, contents, size); } int executable_is_script(const char *path, char **interpreter) { @@ -818,3 +855,28 @@ int safe_fgetc(FILE *f, char *ret) { return 1; } + +int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line) { + struct stat _st; + + if (!filename) + return 0; + + if (!st) { + if (stat(filename, &_st) < 0) + return -errno; + st = &_st; + } + + if ((st->st_mode & S_IRWXO) == 0) + return 0; + + if (unit) + log_syntax(unit, LOG_WARNING, filename, line, 0, + "%s has %04o mode that is too permissive, please adjust the access mode.", + filename, st->st_mode & 07777); + else + log_warning("%s has %04o mode that is too permissive, please adjust the access mode.", + filename, st->st_mode & 07777); + return 0; +} diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 53e3f4ef5f..760e738688 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -5,6 +5,7 @@ #include <stdbool.h> #include <stddef.h> #include <stdio.h> +#include <sys/stat.h> #include <sys/types.h> #include "macro.h" @@ -27,6 +28,11 @@ typedef enum { } WriteStringFileFlags; +typedef enum { + READ_FULL_FILE_SECURE = 1 << 0, + READ_FULL_FILE_UNBASE64 = 1 << 1, +} ReadFullFileFlags; + int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags, struct timespec *ts); static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) { return write_string_stream_ts(f, line, flags, NULL); @@ -38,9 +44,15 @@ static inline int write_string_file(const char *fn, const char *line, WriteStrin int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4); -int read_one_line_file(const char *fn, char **line); -int read_full_file(const char *fn, char **contents, size_t *size); -int read_full_stream(FILE *f, char **contents, size_t *size); +int read_one_line_file(const char *filename, char **line); +int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size); +static inline int read_full_file(const char *filename, char **contents, size_t *size) { + return read_full_file_full(filename, 0, contents, size); +} +int read_full_stream_full(FILE *f, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size); +static inline int read_full_stream(FILE *f, char **contents, size_t *size) { + return read_full_stream_full(f, NULL, 0, contents, size); +} int verify_file(const char *fn, const char *blob, bool accept_extra_nl); @@ -76,3 +88,5 @@ static inline int read_nul_string(FILE *f, size_t limit, char **ret) { } int safe_fgetc(FILE *f, char *ret); + +int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line); diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index a5660a0ff3..132439fd1c 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -685,11 +685,12 @@ static int unbase64_next(const char **p, size_t *l) { return ret; } -int unbase64mem(const char *p, size_t l, void **ret, size_t *ret_size) { +int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_size) { _cleanup_free_ uint8_t *buf = NULL; const char *x; uint8_t *z; size_t len; + int r; assert(p || l == 0); assert(ret); @@ -712,36 +713,54 @@ int unbase64mem(const char *p, size_t l, void **ret, size_t *ret_size) { a = unbase64_next(&x, &l); if (a == -EPIPE) /* End of string */ break; - if (a < 0) - return a; - if (a == INT_MAX) /* Padding is not allowed at the beginning of a 4ch block */ - return -EINVAL; + if (a < 0) { + r = a; + goto on_failure; + } + if (a == INT_MAX) { /* Padding is not allowed at the beginning of a 4ch block */ + r = -EINVAL; + goto on_failure; + } b = unbase64_next(&x, &l); - if (b < 0) - return b; - if (b == INT_MAX) /* Padding is not allowed at the second character of a 4ch block either */ - return -EINVAL; + if (b < 0) { + r = b; + goto on_failure; + } + if (b == INT_MAX) { /* Padding is not allowed at the second character of a 4ch block either */ + r = -EINVAL; + goto on_failure; + } c = unbase64_next(&x, &l); - if (c < 0) - return c; + if (c < 0) { + r = c; + goto on_failure; + } d = unbase64_next(&x, &l); - if (d < 0) - return d; + if (d < 0) { + r = d; + goto on_failure; + } if (c == INT_MAX) { /* Padding at the third character */ - if (d != INT_MAX) /* If the third character is padding, the fourth must be too */ - return -EINVAL; + if (d != INT_MAX) { /* If the third character is padding, the fourth must be too */ + r = -EINVAL; + goto on_failure; + } /* b == 00YY0000 */ - if (b & 15) - return -EINVAL; + if (b & 15) { + r = -EINVAL; + goto on_failure; + } - if (l > 0) /* Trailing rubbish? */ - return -ENAMETOOLONG; + if (l > 0) { /* Trailing rubbish? */ + r = -ENAMETOOLONG; + goto on_failure; + } *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ break; @@ -749,11 +768,15 @@ int unbase64mem(const char *p, size_t l, void **ret, size_t *ret_size) { if (d == INT_MAX) { /* c == 00ZZZZ00 */ - if (c & 3) - return -EINVAL; + if (c & 3) { + r = -EINVAL; + goto on_failure; + } - if (l > 0) /* Trailing rubbish? */ - return -ENAMETOOLONG; + if (l > 0) { /* Trailing rubbish? */ + r = -ENAMETOOLONG; + goto on_failure; + } *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ @@ -771,6 +794,12 @@ int unbase64mem(const char *p, size_t l, void **ret, size_t *ret_size) { *ret = TAKE_PTR(buf); return 0; + +on_failure: + if (secure) + explicit_bzero_safe(buf, len); + + return r; } void hexdump(FILE *f, const void *p, size_t s) { diff --git a/src/basic/hexdecoct.h b/src/basic/hexdecoct.h index 9477d16e37..fa6013ee75 100644 --- a/src/basic/hexdecoct.h +++ b/src/basic/hexdecoct.h @@ -33,6 +33,9 @@ ssize_t base64mem(const void *p, size_t l, char **out); int base64_append(char **prefix, int plen, const void *p, size_t l, int margin, int width); -int unbase64mem(const char *p, size_t l, void **mem, size_t *len); +int unbase64mem_full(const char *p, size_t l, bool secure, void **mem, size_t *len); +static inline int unbase64mem(const char *p, size_t l, void **mem, size_t *len) { + return unbase64mem_full(p, l, false, mem, len); +} void hexdump(FILE *f, const void *p, size_t s); diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf index fcd2ec2097..1a3d6caeb9 100644 --- a/src/network/netdev/netdev-gperf.gperf +++ b/src/network/netdev/netdev-gperf.gperf @@ -187,4 +187,5 @@ WireGuardPeer.AllowedIPs, config_parse_wireguard_allowed_ips, 0, WireGuardPeer.Endpoint, config_parse_wireguard_endpoint, 0, 0 WireGuardPeer.PublicKey, config_parse_wireguard_public_key, 0, 0 WireGuardPeer.PresharedKey, config_parse_wireguard_preshared_key, 0, 0 +WireGuardPeer.PresharedKeyFile, config_parse_wireguard_preshared_key_file, 0, 0 WireGuardPeer.PersistentKeepalive, config_parse_wireguard_keepalive, 0, 0 diff --git a/src/network/netdev/wireguard.c b/src/network/netdev/wireguard.c index 5d9df16349..5aa10fc4de 100644 --- a/src/network/netdev/wireguard.c +++ b/src/network/netdev/wireguard.c @@ -53,6 +53,8 @@ static void wireguard_peer_free(WireguardPeer *peer) { free(peer->endpoint_host); free(peer->endpoint_port); + free(peer->preshared_key_file); + explicit_bzero_safe(peer->preshared_key, WG_KEY_LEN); free(peer); } @@ -438,16 +440,18 @@ static int netdev_wireguard_post_create(NetDev *netdev, Link *link, sd_netlink_m return 0; } -int config_parse_wireguard_listen_port(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_wireguard_listen_port( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + uint16_t *s = data; uint16_t port = 0; int r; @@ -465,17 +469,17 @@ int config_parse_wireguard_listen_port(const char *unit, } *s = port; - return 0; } static int wireguard_decode_key_and_warn( const char *rvalue, - uint8_t *ret, + uint8_t ret[static WG_KEY_LEN], const char *unit, const char *filename, unsigned line, const char *lvalue) { + _cleanup_free_ void *key = NULL; size_t len; int r; @@ -490,43 +494,50 @@ static int wireguard_decode_key_and_warn( return 0; } - r = unbase64mem(rvalue, strlen(rvalue), &key, &len); + if (!streq(lvalue, "PublicKey")) + (void) warn_file_is_world_accessible(filename, NULL, unit, line); + + r = unbase64mem_full(rvalue, strlen(rvalue), true, &key, &len); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, "Failed to decode wireguard key provided by %s=, ignoring assignment: %m", lvalue); - return 0; + goto finalize; } if (len != WG_KEY_LEN) { log_syntax(unit, LOG_ERR, filename, line, 0, "Wireguard key provided by %s= has invalid length (%zu bytes), ignoring assignment.", lvalue, len); - return 0; + goto finalize; } memcpy(ret, key, WG_KEY_LEN); - return true; + r = 0; + +finalize: + explicit_bzero_safe(key, len); + return r; } -int config_parse_wireguard_private_key(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_wireguard_private_key( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Wireguard *w; assert(data); - w = WIREGUARD(data); - assert(w); - return wireguard_decode_key_and_warn(rvalue, w->private_key, unit, filename, line, lvalue); - + (void) wireguard_decode_key_and_warn(rvalue, w->private_key, unit, filename, line, lvalue); + return 0; } int config_parse_wireguard_private_key_file( @@ -563,25 +574,24 @@ int config_parse_wireguard_private_key_file( return free_and_replace(w->private_key_file, path); } -int config_parse_wireguard_preshared_key(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_wireguard_preshared_key( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; Wireguard *w; int r; assert(data); - w = WIREGUARD(data); - assert(w); r = wireguard_peer_new_static(w, filename, section_line, &peer); @@ -596,25 +606,67 @@ int config_parse_wireguard_preshared_key(const char *unit, return 0; } -int config_parse_wireguard_public_key(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_wireguard_preshared_key_file( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; + _cleanup_free_ char *path = NULL; Wireguard *w; int r; assert(data); - w = WIREGUARD(data); + assert(w); + + r = wireguard_peer_new_static(w, filename, section_line, &peer); + if (r < 0) + return r; + + if (isempty(rvalue)) { + peer->preshared_key_file = mfree(peer->preshared_key_file); + TAKE_PTR(peer); + return 0; + } + + path = strdup(rvalue); + if (!path) + return log_oom(); + + if (path_simplify_and_warn(path, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue) < 0) + return 0; + + free_and_replace(peer->preshared_key_file, path); + TAKE_PTR(peer); + return 0; +} +int config_parse_wireguard_public_key( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; + Wireguard *w; + int r; + + assert(data); + w = WIREGUARD(data); assert(w); r = wireguard_peer_new_static(w, filename, section_line, &peer); @@ -629,16 +681,17 @@ int config_parse_wireguard_public_key(const char *unit, return 0; } -int config_parse_wireguard_allowed_ips(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_wireguard_allowed_ips( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; union in_addr_union addr; @@ -651,7 +704,6 @@ int config_parse_wireguard_allowed_ips(const char *unit, assert(data); w = WIREGUARD(data); - assert(w); r = wireguard_peer_new_static(w, filename, section_line, &peer); @@ -696,16 +748,17 @@ int config_parse_wireguard_allowed_ips(const char *unit, return 0; } -int config_parse_wireguard_endpoint(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_wireguard_endpoint( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; const char *begin, *end; @@ -717,7 +770,6 @@ int config_parse_wireguard_endpoint(const char *unit, assert(rvalue); w = WIREGUARD(data); - assert(w); r = wireguard_peer_new_static(w, filename, section_line, &peer); @@ -775,16 +827,17 @@ int config_parse_wireguard_endpoint(const char *unit, return 0; } -int config_parse_wireguard_keepalive(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_wireguard_keepalive( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; uint16_t keepalive = 0; @@ -795,7 +848,6 @@ int config_parse_wireguard_keepalive(const char *unit, assert(data); w = WIREGUARD(data); - assert(w); r = wireguard_peer_new_static(w, filename, section_line, &peer); @@ -824,9 +876,7 @@ static void wireguard_init(NetDev *netdev) { Wireguard *w; assert(netdev); - w = WIREGUARD(netdev); - assert(w); w->flags = WGDEVICE_F_REPLACE_PEERS; @@ -841,6 +891,7 @@ static void wireguard_done(NetDev *netdev) { sd_event_source_unref(w->resolve_retry_event_source); + explicit_bzero_safe(w->private_key, WG_KEY_LEN); free(w->private_key_file); hashmap_free_with_destructor(w->peers_by_section, wireguard_peer_free); @@ -848,45 +899,34 @@ static void wireguard_done(NetDev *netdev) { set_free(w->peers_with_failed_endpoint); } -static int wireguard_read_private_key_file(Wireguard *w, bool fatal) { - _cleanup_free_ char *contents = NULL; - _cleanup_free_ void *key = NULL; - size_t size, key_len; - NetDev *netdev; - int level, r; - - assert(w); - - netdev = NETDEV(w); +static int wireguard_read_key_file(const char *filename, uint8_t dest[static WG_KEY_LEN]) { + _cleanup_free_ char *key = NULL; + size_t key_len; + int r; - if (!w->private_key_file) + if (!filename) return 0; - level = fatal ? LOG_ERR : LOG_INFO; - - r = read_full_file(w->private_key_file, &contents, &size); + r = read_full_file_full(filename, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNBASE64, &key, &key_len); if (r < 0) - return log_netdev_full(netdev, level, r, - "Failed to read private key from '%s'%s: %m", - w->private_key_file, fatal ? "" : ", ignoring"); + return r; - r = unbase64mem(contents, size, &key, &key_len); - if (r < 0) - return log_netdev_full(netdev, level, r, - "Failed to decode private key%s: %m", - fatal ? "" : ", ignoring"); + if (key_len != WG_KEY_LEN) { + r = -EINVAL; + goto finalize; + } - if (key_len != WG_KEY_LEN) - return log_netdev_full(netdev, level, SYNTHETIC_ERRNO(EINVAL), - "Wireguard private key has invalid length (%zu bytes)%s: %m", - key_len, fatal ? "" : ", ignoring"); + memcpy(dest, key, WG_KEY_LEN); + r = 0; - memcpy(w->private_key, key, WG_KEY_LEN); - return 0; +finalize: + explicit_bzero_safe(key, key_len); + return r; } static int wireguard_peer_verify(WireguardPeer *peer) { NetDev *netdev = NETDEV(peer->wireguard); + int r; if (section_is_invalid(peer->section)) return -EINVAL; @@ -897,28 +937,37 @@ static int wireguard_peer_verify(WireguardPeer *peer) { "Ignoring [WireGuardPeer] section from line %u.", peer->section->filename, peer->section->line); + r = wireguard_read_key_file(peer->preshared_key_file, peer->preshared_key); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "%s: Failed to read preshared key from '%s'. " + "Ignoring [WireGuardPeer] section from line %u.", + peer->section->filename, peer->preshared_key_file, + peer->section->line); + return 0; } static int wireguard_verify(NetDev *netdev, const char *filename) { WireguardPeer *peer, *peer_next; Wireguard *w; - bool empty; int r; assert(netdev); w = WIREGUARD(netdev); assert(w); - empty = eqzero(w->private_key); - if (empty && !w->private_key_file) - return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "%s: Missing PrivateKey= or PrivateKeyFile=, ignoring.", - filename); + r = wireguard_read_key_file(w->private_key_file, w->private_key); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "Failed to read private key from %s. Dropping network device %s.", + w->private_key_file, netdev->ifname); - r = wireguard_read_private_key_file(w, empty); - if (r < 0 && empty) - return r; + if (eqzero(w->private_key)) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: Missing PrivateKey= or PrivateKeyFile=, " + "Dropping network device %s.", + filename, netdev->ifname); LIST_FOREACH_SAFE(peers, peer, peer_next, w->peers) if (wireguard_peer_verify(peer) < 0) diff --git a/src/network/netdev/wireguard.h b/src/network/netdev/wireguard.h index 6cf6eec14d..4ae520c52b 100644 --- a/src/network/netdev/wireguard.h +++ b/src/network/netdev/wireguard.h @@ -21,6 +21,7 @@ typedef struct WireguardPeer { uint8_t public_key[WG_KEY_LEN]; uint8_t preshared_key[WG_KEY_LEN]; + char *preshared_key_file; uint32_t flags; uint16_t persistent_keepalive_interval; @@ -63,4 +64,5 @@ CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_public_key); CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_private_key); CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_private_key_file); CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_preshared_key); +CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_preshared_key_file); CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_keepalive); diff --git a/test/fuzz/fuzz-netdev-parser/directives.netdev b/test/fuzz/fuzz-netdev-parser/directives.netdev index e0756dc755..7da3955af6 100644 --- a/test/fuzz/fuzz-netdev-parser/directives.netdev +++ b/test/fuzz/fuzz-netdev-parser/directives.netdev @@ -52,6 +52,7 @@ Name= [WireGuardPeer] Endpoint= PresharedKey= +PresharedKeyFile= PersistentKeepalive= PublicKey= AllowedIPs= diff --git a/test/test-network/conf/25-wireguard-preshared-key.txt b/test/test-network/conf/25-wireguard-preshared-key.txt new file mode 100644 index 0000000000..021c443aba --- /dev/null +++ b/test/test-network/conf/25-wireguard-preshared-key.txt @@ -0,0 +1,3 @@ +cPLOy1YUrEI0EM + MIycPJmOo0aTu3RZnw8bL5 + meVD6m0= diff --git a/test/test-network/conf/25-wireguard.netdev b/test/test-network/conf/25-wireguard.netdev index 61afd1f5e7..4866c31cca 100644 --- a/test/test-network/conf/25-wireguard.netdev +++ b/test/test-network/conf/25-wireguard.netdev @@ -4,7 +4,6 @@ Kind=wireguard [WireGuard] PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong= -PrivateKeyFile=/run/systemd/network/not-exist ListenPort=51820 FwMark=1234 diff --git a/test/test-network/conf/25-wireguard.netdev.d/peer.conf b/test/test-network/conf/25-wireguard.netdev.d/peer.conf index 7ba692378e..f559ea68f6 100644 --- a/test/test-network/conf/25-wireguard.netdev.d/peer.conf +++ b/test/test-network/conf/25-wireguard.netdev.d/peer.conf @@ -2,3 +2,4 @@ PublicKey=lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc= AllowedIPs=fdbc:bae2:7871:0500:e1fe:0793:8636:dad1/128 AllowedIPs=fdbc:bae2:7871:e1fe:0793:8636::/96 +PresharedKeyFile=/run/systemd/network/25-wireguard-preshared-key.txt diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index ffc63d0838..b44cfe8d45 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -307,6 +307,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities): '25-vxlan.netdev', '25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network', + '25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt', '25-wireguard.netdev', '25-wireguard.network', @@ -528,7 +529,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities): def test_wireguard(self): self.copy_unit_to_networkd_unit_path('25-wireguard.netdev', '25-wireguard.network', '25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network', - '25-wireguard-private-key.txt') + '25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt') self.start_networkd(0) self.wait_online(['wg99:carrier', 'wg98:routable']) @@ -551,6 +552,9 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities): self.assertTrue(output, 'RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\t192.168.27.3:51820') output = subprocess.check_output(['wg', 'show', 'wg99', 'private-key']).rstrip().decode('utf-8') self.assertTrue(output, 'EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=') + output = subprocess.check_output(['wg', 'show', 'wg99', 'preshared-keys']).rstrip().decode('utf-8') + self.assertTrue(output, 'RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA= IIWIV17wutHv7t4cR6pOT91z6NSz/T8Arh0yaywhw3M=') + self.assertTrue(output, 'lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc= cPLOy1YUrEI0EMMIycPJmOo0aTu3RZnw8bL5meVD6m0=') output = subprocess.check_output(['wg', 'show', 'wg98', 'private-key']).rstrip().decode('utf-8') self.assertTrue(output, 'CJQUtcS9emY2fLYqDlpSZiE/QJyHkPWr+WHtZLZ90FU=') |