Merge pull request #12223 from yuwata/network-wireguard-preshared-key-file

network: add PresharedKeyFile= setting and make reading key file failure fatal
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2019-04-09 10:52:52 +02:00 committed by GitHub
commit 52efbd8f0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 358 additions and 182 deletions

4
NEWS
View file

@ -116,8 +116,8 @@ CHANGES WITH 242:
* The new TripleSampling= option in .network files may be used to * The new TripleSampling= option in .network files may be used to
configure CAN triple sampling. configure CAN triple sampling.
* A new .netdev setting PrivateKeyFile= may be used to point to private * A new .netdev settings PrivateKeyFile= and PresharedKeyFile= may be
key for a WireGuard interface. used to point to private or preshared key for a WireGuard interface.
* /etc/crypttab now supports the same-cpu-crypt and * /etc/crypttab now supports the same-cpu-crypt and
submit-from-crypt-cpus options to tweak encryption work scheduling submit-from-crypt-cpus options to tweak encryption work scheduling

View file

@ -1241,10 +1241,8 @@
<varlistentry> <varlistentry>
<term><varname>PrivateKeyFile=</varname></term> <term><varname>PrivateKeyFile=</varname></term>
<listitem> <listitem>
<para>Takes a absolute path to a file which contains the Base64 encoded private key for the interface. <para>Takes an 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 When this option is specified, then <varname>PrivateKey=</varname> is ignored.
the file specified in <varname>PrivateKeyFile=</varname> contains valid wireguard key, then
the key provided by <varname>PrivateKey=</varname> is ignored.
Note that the file must be readable by the user <literal>systemd-network</literal>, so it 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 should be, e.g., owned by <literal>root:systemd-network</literal> with a
<literal>0640</literal> file mode.</para> <literal>0640</literal> file mode.</para>
@ -1298,6 +1296,16 @@
with a <literal>0640</literal> file mode.</para> with a <literal>0640</literal> file mode.</para>
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><varname>AllowedIPs=</varname></term> <term><varname>AllowedIPs=</varname></term>
<listitem> <listitem>

View file

@ -17,6 +17,7 @@
#include "fd-util.h" #include "fd-util.h"
#include "fileio.h" #include "fileio.h"
#include "fs-util.h" #include "fs-util.h"
#include "hexdecoct.h"
#include "log.h" #include "log.h"
#include "macro.h" #include "macro.h"
#include "missing.h" #include "missing.h"
@ -264,26 +265,29 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
return 1; return 1;
} }
int read_full_stream( int read_full_stream_full(
FILE *f, FILE *f,
const char *filename,
ReadFullFileFlags flags,
char **ret_contents, char **ret_contents,
size_t *ret_size) { size_t *ret_size) {
_cleanup_free_ char *buf = NULL; _cleanup_free_ char *buf = NULL;
struct stat st; struct stat st;
size_t n, l; size_t n, n_next, l;
int fd; int fd, r;
assert(f); assert(f);
assert(ret_contents); 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); 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 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) */ * optimize our buffering) */
if (fstat(fileno(f), &st) < 0) if (fstat(fd, &st) < 0)
return -errno; return -errno;
if (S_ISREG(st.st_mode)) { 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 * 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. */ * already makes us notice the EOF. */
if (st.st_size > 0) 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 (;;) { for (;;) {
char *t; char *t;
size_t k; size_t k;
t = realloc(buf, n + 1); if (flags & READ_FULL_FILE_SECURE) {
if (!t) t = malloc(n_next + 1);
return -ENOMEM; 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; buf = t;
n = n_next;
errno = 0; errno = 0;
k = fread(buf + l, 1, n - l, f); k = fread(buf + l, 1, n - l, f);
if (k > 0) if (k > 0)
l += k; l += k;
if (ferror(f)) if (ferror(f)) {
return errno > 0 ? -errno : -EIO; r = errno > 0 ? -errno : -EIO;
goto finalize;
}
if (feof(f)) if (feof(f))
break; break;
@ -327,10 +348,18 @@ int read_full_stream(
assert(l == n); assert(l == n);
/* Safety check */ /* Safety check */
if (n >= READ_FULL_BYTES_MAX) if (n >= READ_FULL_BYTES_MAX) {
return -E2BIG; r = -E2BIG;
goto finalize;
}
n = MIN(n * 2, READ_FULL_BYTES_MAX); n_next = 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) { 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 * 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. */ * there'd be ambiguity about what we just read. */
if (memchr(buf, 0, l)) if (memchr(buf, 0, l)) {
return -EBADMSG; r = -EBADMSG;
goto finalize;
}
} }
buf[l] = 0; buf[l] = 0;
@ -349,21 +380,27 @@ int read_full_stream(
*ret_size = l; *ret_size = l;
return 0; 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; _cleanup_fclose_ FILE *f = NULL;
assert(fn); assert(filename);
assert(contents); assert(contents);
f = fopen(fn, "re"); f = fopen(filename, "re");
if (!f) if (!f)
return -errno; return -errno;
(void) __fsetlocking(f, FSETLOCKING_BYCALLER); (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) { int executable_is_script(const char *path, char **interpreter) {
@ -818,3 +855,28 @@ int safe_fgetc(FILE *f, char *ret) {
return 1; 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;
}

View file

@ -5,6 +5,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include "macro.h" #include "macro.h"
@ -27,6 +28,11 @@ typedef enum {
} WriteStringFileFlags; } 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); 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) { static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) {
return write_string_stream_ts(f, line, flags, NULL); 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 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_one_line_file(const char *filename, char **line);
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);
int read_full_stream(FILE *f, 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); 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 safe_fgetc(FILE *f, char *ret);
int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line);

View file

@ -685,11 +685,12 @@ static int unbase64_next(const char **p, size_t *l) {
return ret; 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; _cleanup_free_ uint8_t *buf = NULL;
const char *x; const char *x;
uint8_t *z; uint8_t *z;
size_t len; size_t len;
int r;
assert(p || l == 0); assert(p || l == 0);
assert(ret); 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); a = unbase64_next(&x, &l);
if (a == -EPIPE) /* End of string */ if (a == -EPIPE) /* End of string */
break; break;
if (a < 0) if (a < 0) {
return a; r = a;
if (a == INT_MAX) /* Padding is not allowed at the beginning of a 4ch block */ goto on_failure;
return -EINVAL; }
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); b = unbase64_next(&x, &l);
if (b < 0) if (b < 0) {
return b; r = b;
if (b == INT_MAX) /* Padding is not allowed at the second character of a 4ch block either */ goto on_failure;
return -EINVAL; }
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); c = unbase64_next(&x, &l);
if (c < 0) if (c < 0) {
return c; r = c;
goto on_failure;
}
d = unbase64_next(&x, &l); d = unbase64_next(&x, &l);
if (d < 0) if (d < 0) {
return d; r = d;
goto on_failure;
}
if (c == INT_MAX) { /* Padding at the third character */ if (c == INT_MAX) { /* Padding at the third character */
if (d != INT_MAX) /* If the third character is padding, the fourth must be too */ if (d != INT_MAX) { /* If the third character is padding, the fourth must be too */
return -EINVAL; r = -EINVAL;
goto on_failure;
}
/* b == 00YY0000 */ /* b == 00YY0000 */
if (b & 15) if (b & 15) {
return -EINVAL; r = -EINVAL;
goto on_failure;
}
if (l > 0) /* Trailing rubbish? */ if (l > 0) { /* Trailing rubbish? */
return -ENAMETOOLONG; r = -ENAMETOOLONG;
goto on_failure;
}
*(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
break; break;
@ -749,11 +768,15 @@ int unbase64mem(const char *p, size_t l, void **ret, size_t *ret_size) {
if (d == INT_MAX) { if (d == INT_MAX) {
/* c == 00ZZZZ00 */ /* c == 00ZZZZ00 */
if (c & 3) if (c & 3) {
return -EINVAL; r = -EINVAL;
goto on_failure;
}
if (l > 0) /* Trailing rubbish? */ if (l > 0) { /* Trailing rubbish? */
return -ENAMETOOLONG; r = -ENAMETOOLONG;
goto on_failure;
}
*(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
*(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ *(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); *ret = TAKE_PTR(buf);
return 0; return 0;
on_failure:
if (secure)
explicit_bzero_safe(buf, len);
return r;
} }
void hexdump(FILE *f, const void *p, size_t s) { void hexdump(FILE *f, const void *p, size_t s) {

View file

@ -33,6 +33,9 @@ ssize_t base64mem(const void *p, size_t l, char **out);
int base64_append(char **prefix, int plen, int base64_append(char **prefix, int plen,
const void *p, size_t l, const void *p, size_t l,
int margin, int width); 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); void hexdump(FILE *f, const void *p, size_t s);

View file

@ -187,4 +187,5 @@ WireGuardPeer.AllowedIPs, config_parse_wireguard_allowed_ips, 0,
WireGuardPeer.Endpoint, config_parse_wireguard_endpoint, 0, 0 WireGuardPeer.Endpoint, config_parse_wireguard_endpoint, 0, 0
WireGuardPeer.PublicKey, config_parse_wireguard_public_key, 0, 0 WireGuardPeer.PublicKey, config_parse_wireguard_public_key, 0, 0
WireGuardPeer.PresharedKey, config_parse_wireguard_preshared_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 WireGuardPeer.PersistentKeepalive, config_parse_wireguard_keepalive, 0, 0

View file

@ -53,6 +53,8 @@ static void wireguard_peer_free(WireguardPeer *peer) {
free(peer->endpoint_host); free(peer->endpoint_host);
free(peer->endpoint_port); free(peer->endpoint_port);
free(peer->preshared_key_file);
explicit_bzero_safe(peer->preshared_key, WG_KEY_LEN);
free(peer); free(peer);
} }
@ -438,16 +440,18 @@ static int netdev_wireguard_post_create(NetDev *netdev, Link *link, sd_netlink_m
return 0; return 0;
} }
int config_parse_wireguard_listen_port(const char *unit, int config_parse_wireguard_listen_port(
const char *filename, const char *unit,
unsigned line, const char *filename,
const char *section, unsigned line,
unsigned section_line, const char *section,
const char *lvalue, unsigned section_line,
int ltype, const char *lvalue,
const char *rvalue, int ltype,
void *data, const char *rvalue,
void *userdata) { void *data,
void *userdata) {
uint16_t *s = data; uint16_t *s = data;
uint16_t port = 0; uint16_t port = 0;
int r; int r;
@ -465,17 +469,17 @@ int config_parse_wireguard_listen_port(const char *unit,
} }
*s = port; *s = port;
return 0; return 0;
} }
static int wireguard_decode_key_and_warn( static int wireguard_decode_key_and_warn(
const char *rvalue, const char *rvalue,
uint8_t *ret, uint8_t ret[static WG_KEY_LEN],
const char *unit, const char *unit,
const char *filename, const char *filename,
unsigned line, unsigned line,
const char *lvalue) { const char *lvalue) {
_cleanup_free_ void *key = NULL; _cleanup_free_ void *key = NULL;
size_t len; size_t len;
int r; int r;
@ -490,43 +494,50 @@ static int wireguard_decode_key_and_warn(
return 0; 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) { if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, log_syntax(unit, LOG_ERR, filename, line, r,
"Failed to decode wireguard key provided by %s=, ignoring assignment: %m", lvalue); "Failed to decode wireguard key provided by %s=, ignoring assignment: %m", lvalue);
return 0; goto finalize;
} }
if (len != WG_KEY_LEN) { if (len != WG_KEY_LEN) {
log_syntax(unit, LOG_ERR, filename, line, 0, log_syntax(unit, LOG_ERR, filename, line, 0,
"Wireguard key provided by %s= has invalid length (%zu bytes), ignoring assignment.", "Wireguard key provided by %s= has invalid length (%zu bytes), ignoring assignment.",
lvalue, len); lvalue, len);
return 0; goto finalize;
} }
memcpy(ret, key, WG_KEY_LEN); 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, int config_parse_wireguard_private_key(
const char *filename, const char *unit,
unsigned line, const char *filename,
const char *section, unsigned line,
unsigned section_line, const char *section,
const char *lvalue, unsigned section_line,
int ltype, const char *lvalue,
const char *rvalue, int ltype,
void *data, const char *rvalue,
void *userdata) { void *data,
void *userdata) {
Wireguard *w; Wireguard *w;
assert(data); assert(data);
w = WIREGUARD(data); w = WIREGUARD(data);
assert(w); 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( 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); return free_and_replace(w->private_key_file, path);
} }
int config_parse_wireguard_preshared_key(const char *unit, int config_parse_wireguard_preshared_key(
const char *filename, const char *unit,
unsigned line, const char *filename,
const char *section, unsigned line,
unsigned section_line, const char *section,
const char *lvalue, unsigned section_line,
int ltype, const char *lvalue,
const char *rvalue, int ltype,
void *data, const char *rvalue,
void *userdata) { void *data,
void *userdata) {
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
Wireguard *w; Wireguard *w;
int r; int r;
assert(data); assert(data);
w = WIREGUARD(data); w = WIREGUARD(data);
assert(w); assert(w);
r = wireguard_peer_new_static(w, filename, section_line, &peer); 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; return 0;
} }
int config_parse_wireguard_public_key(const char *unit, int config_parse_wireguard_preshared_key_file(
const char *filename, const char *unit,
unsigned line, const char *filename,
const char *section, unsigned line,
unsigned section_line, const char *section,
const char *lvalue, unsigned section_line,
int ltype, const char *lvalue,
const char *rvalue, int ltype,
void *data, const char *rvalue,
void *userdata) { 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; _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
Wireguard *w; Wireguard *w;
int r; int r;
assert(data); assert(data);
w = WIREGUARD(data); w = WIREGUARD(data);
assert(w); assert(w);
r = wireguard_peer_new_static(w, filename, section_line, &peer); 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; return 0;
} }
int config_parse_wireguard_allowed_ips(const char *unit, int config_parse_wireguard_allowed_ips(
const char *filename, const char *unit,
unsigned line, const char *filename,
const char *section, unsigned line,
unsigned section_line, const char *section,
const char *lvalue, unsigned section_line,
int ltype, const char *lvalue,
const char *rvalue, int ltype,
void *data, const char *rvalue,
void *userdata) { void *data,
void *userdata) {
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
union in_addr_union addr; union in_addr_union addr;
@ -651,7 +704,6 @@ int config_parse_wireguard_allowed_ips(const char *unit,
assert(data); assert(data);
w = WIREGUARD(data); w = WIREGUARD(data);
assert(w); assert(w);
r = wireguard_peer_new_static(w, filename, section_line, &peer); 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; return 0;
} }
int config_parse_wireguard_endpoint(const char *unit, int config_parse_wireguard_endpoint(
const char *filename, const char *unit,
unsigned line, const char *filename,
const char *section, unsigned line,
unsigned section_line, const char *section,
const char *lvalue, unsigned section_line,
int ltype, const char *lvalue,
const char *rvalue, int ltype,
void *data, const char *rvalue,
void *userdata) { void *data,
void *userdata) {
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
const char *begin, *end; const char *begin, *end;
@ -717,7 +770,6 @@ int config_parse_wireguard_endpoint(const char *unit,
assert(rvalue); assert(rvalue);
w = WIREGUARD(data); w = WIREGUARD(data);
assert(w); assert(w);
r = wireguard_peer_new_static(w, filename, section_line, &peer); r = wireguard_peer_new_static(w, filename, section_line, &peer);
@ -775,16 +827,17 @@ int config_parse_wireguard_endpoint(const char *unit,
return 0; return 0;
} }
int config_parse_wireguard_keepalive(const char *unit, int config_parse_wireguard_keepalive(
const char *filename, const char *unit,
unsigned line, const char *filename,
const char *section, unsigned line,
unsigned section_line, const char *section,
const char *lvalue, unsigned section_line,
int ltype, const char *lvalue,
const char *rvalue, int ltype,
void *data, const char *rvalue,
void *userdata) { void *data,
void *userdata) {
_cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
uint16_t keepalive = 0; uint16_t keepalive = 0;
@ -795,7 +848,6 @@ int config_parse_wireguard_keepalive(const char *unit,
assert(data); assert(data);
w = WIREGUARD(data); w = WIREGUARD(data);
assert(w); assert(w);
r = wireguard_peer_new_static(w, filename, section_line, &peer); r = wireguard_peer_new_static(w, filename, section_line, &peer);
@ -824,9 +876,7 @@ static void wireguard_init(NetDev *netdev) {
Wireguard *w; Wireguard *w;
assert(netdev); assert(netdev);
w = WIREGUARD(netdev); w = WIREGUARD(netdev);
assert(w); assert(w);
w->flags = WGDEVICE_F_REPLACE_PEERS; 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); sd_event_source_unref(w->resolve_retry_event_source);
explicit_bzero_safe(w->private_key, WG_KEY_LEN);
free(w->private_key_file); free(w->private_key_file);
hashmap_free_with_destructor(w->peers_by_section, wireguard_peer_free); 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); set_free(w->peers_with_failed_endpoint);
} }
static int wireguard_read_private_key_file(Wireguard *w, bool fatal) { static int wireguard_read_key_file(const char *filename, uint8_t dest[static WG_KEY_LEN]) {
_cleanup_free_ char *contents = NULL; _cleanup_free_ char *key = NULL;
_cleanup_free_ void *key = NULL; size_t key_len;
size_t size, key_len; int r;
NetDev *netdev;
int level, r;
assert(w); if (!filename)
netdev = NETDEV(w);
if (!w->private_key_file)
return 0; return 0;
level = fatal ? LOG_ERR : LOG_INFO; r = read_full_file_full(filename, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNBASE64, &key, &key_len);
r = read_full_file(w->private_key_file, &contents, &size);
if (r < 0) if (r < 0)
return log_netdev_full(netdev, level, r, return r;
"Failed to read private key from '%s'%s: %m",
w->private_key_file, fatal ? "" : ", ignoring");
r = unbase64mem(contents, size, &key, &key_len); if (key_len != WG_KEY_LEN) {
if (r < 0) r = -EINVAL;
return log_netdev_full(netdev, level, r, goto finalize;
"Failed to decode private key%s: %m", }
fatal ? "" : ", ignoring");
if (key_len != WG_KEY_LEN) memcpy(dest, key, WG_KEY_LEN);
return log_netdev_full(netdev, level, SYNTHETIC_ERRNO(EINVAL), r = 0;
"Wireguard private key has invalid length (%zu bytes)%s: %m",
key_len, fatal ? "" : ", ignoring");
memcpy(w->private_key, key, WG_KEY_LEN); finalize:
return 0; explicit_bzero_safe(key, key_len);
return r;
} }
static int wireguard_peer_verify(WireguardPeer *peer) { static int wireguard_peer_verify(WireguardPeer *peer) {
NetDev *netdev = NETDEV(peer->wireguard); NetDev *netdev = NETDEV(peer->wireguard);
int r;
if (section_is_invalid(peer->section)) if (section_is_invalid(peer->section))
return -EINVAL; return -EINVAL;
@ -897,28 +937,37 @@ static int wireguard_peer_verify(WireguardPeer *peer) {
"Ignoring [WireGuardPeer] section from line %u.", "Ignoring [WireGuardPeer] section from line %u.",
peer->section->filename, peer->section->line); 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; return 0;
} }
static int wireguard_verify(NetDev *netdev, const char *filename) { static int wireguard_verify(NetDev *netdev, const char *filename) {
WireguardPeer *peer, *peer_next; WireguardPeer *peer, *peer_next;
Wireguard *w; Wireguard *w;
bool empty;
int r; int r;
assert(netdev); assert(netdev);
w = WIREGUARD(netdev); w = WIREGUARD(netdev);
assert(w); assert(w);
empty = eqzero(w->private_key); r = wireguard_read_key_file(w->private_key_file, w->private_key);
if (empty && !w->private_key_file) if (r < 0)
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), return log_netdev_error_errno(netdev, r,
"%s: Missing PrivateKey= or PrivateKeyFile=, ignoring.", "Failed to read private key from %s. Dropping network device %s.",
filename); w->private_key_file, netdev->ifname);
r = wireguard_read_private_key_file(w, empty); if (eqzero(w->private_key))
if (r < 0 && empty) return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
return r; "%s: Missing PrivateKey= or PrivateKeyFile=, "
"Dropping network device %s.",
filename, netdev->ifname);
LIST_FOREACH_SAFE(peers, peer, peer_next, w->peers) LIST_FOREACH_SAFE(peers, peer, peer_next, w->peers)
if (wireguard_peer_verify(peer) < 0) if (wireguard_peer_verify(peer) < 0)

View file

@ -21,6 +21,7 @@ typedef struct WireguardPeer {
uint8_t public_key[WG_KEY_LEN]; uint8_t public_key[WG_KEY_LEN];
uint8_t preshared_key[WG_KEY_LEN]; uint8_t preshared_key[WG_KEY_LEN];
char *preshared_key_file;
uint32_t flags; uint32_t flags;
uint16_t persistent_keepalive_interval; 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);
CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_private_key_file); 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);
CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_preshared_key_file);
CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_keepalive); CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_keepalive);

View file

@ -52,6 +52,7 @@ Name=
[WireGuardPeer] [WireGuardPeer]
Endpoint= Endpoint=
PresharedKey= PresharedKey=
PresharedKeyFile=
PersistentKeepalive= PersistentKeepalive=
PublicKey= PublicKey=
AllowedIPs= AllowedIPs=

View file

@ -0,0 +1,3 @@
cPLOy1YUrEI0EM
MIycPJmOo0aTu3RZnw8bL5
meVD6m0=

View file

@ -4,7 +4,6 @@ Kind=wireguard
[WireGuard] [WireGuard]
PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong= PrivateKey=EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=
PrivateKeyFile=/run/systemd/network/not-exist
ListenPort=51820 ListenPort=51820
FwMark=1234 FwMark=1234

View file

@ -2,3 +2,4 @@
PublicKey=lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc= PublicKey=lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=
AllowedIPs=fdbc:bae2:7871:0500:e1fe:0793:8636:dad1/128 AllowedIPs=fdbc:bae2:7871:0500:e1fe:0793:8636:dad1/128
AllowedIPs=fdbc:bae2:7871:e1fe:0793:8636::/96 AllowedIPs=fdbc:bae2:7871:e1fe:0793:8636::/96
PresharedKeyFile=/run/systemd/network/25-wireguard-preshared-key.txt

View file

@ -307,6 +307,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
'25-vxlan.netdev', '25-vxlan.netdev',
'25-wireguard-23-peers.netdev', '25-wireguard-23-peers.netdev',
'25-wireguard-23-peers.network', '25-wireguard-23-peers.network',
'25-wireguard-preshared-key.txt',
'25-wireguard-private-key.txt', '25-wireguard-private-key.txt',
'25-wireguard.netdev', '25-wireguard.netdev',
'25-wireguard.network', '25-wireguard.network',
@ -528,7 +529,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
def test_wireguard(self): def test_wireguard(self):
self.copy_unit_to_networkd_unit_path('25-wireguard.netdev', '25-wireguard.network', 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-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.start_networkd(0)
self.wait_online(['wg99:carrier', 'wg98:routable']) 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') 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') output = subprocess.check_output(['wg', 'show', 'wg99', 'private-key']).rstrip().decode('utf-8')
self.assertTrue(output, 'EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=') 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') output = subprocess.check_output(['wg', 'show', 'wg98', 'private-key']).rstrip().decode('utf-8')
self.assertTrue(output, 'CJQUtcS9emY2fLYqDlpSZiE/QJyHkPWr+WHtZLZ90FU=') self.assertTrue(output, 'CJQUtcS9emY2fLYqDlpSZiE/QJyHkPWr+WHtZLZ90FU=')