base: remove if_wg(4) and associated utilities, manpage

After length decisions, we've decided that the if_wg(4) driver and
related work is not yet ready to live in the tree.  This driver has
larger security implications than many, and thus will be held to
more scrutiny than other drivers.

Please also see the related message sent to the freebsd-hackers@
and freebsd-arch@ lists by Kyle Evans <kevans@FreeBSD.org> on
2021/03/16, with the subject line "Removing WireGuard Support From Base"
for additional context.
This commit is contained in:
Kyle Evans 2021-03-15 21:38:23 -05:00
parent 5bffdafd6c
commit f187d6dfbf
26 changed files with 8 additions and 8320 deletions

View File

@ -64,8 +64,6 @@
..
iicbus
..
if_wg
..
io
..
mfi

View File

@ -43,7 +43,7 @@ LDIRS= geom net net80211 netgraph netinet netinet6 \
LSUBDIRS= dev/acpica dev/agp dev/an dev/ciss dev/filemon dev/firewire \
dev/hwpmc dev/hyperv \
dev/ic dev/iicbus dev/if_wg dev/io dev/mfi dev/mmc dev/nvme \
dev/ic dev/iicbus dev/io dev/mfi dev/mmc dev/nvme \
dev/ofw dev/pbio dev/pci ${_dev_powermac_nvram} dev/ppbus dev/pwm \
dev/smbus dev/speaker dev/tcp_log dev/veriexec dev/vkbd \
fs/devfs fs/fdescfs fs/msdosfs fs/nfs fs/nullfs \
@ -220,10 +220,6 @@ NVPAIRDIR= ${INCLUDEDIR}/sys
MLX5= mlx5io.h
MLX5DIR= ${INCLUDEDIR}/dev/mlx5
.PATH: ${SRCTOP}/sys/dev/if_wg
WG= if_wg.h
WGDIR= ${INCLUDEDIR}/dev/if_wg
INCSGROUPS= INCS \
ACPICA \
AGP \
@ -241,8 +237,7 @@ INCSGROUPS= INCS \
PCI \
RPC \
TEKEN \
VERIEXEC \
WG
VERIEXEC
.if ${MK_AUDIT} != "no"
INCSGROUPS+= BSM

View File

@ -35,7 +35,6 @@ SRCS+= ifvxlan.c # VXLAN support
SRCS+= ifgre.c # GRE keys etc
SRCS+= ifgif.c # GIF reversed header workaround
SRCS+= ifipsec.c # IPsec VTI
SRCS+= ifwg.c # Wireguard
SRCS+= sfp.c # SFP/SFP+ information
LIBADD+= ifconfig m util

View File

@ -1,731 +0,0 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2020 Rubicon Communications, LLC (Netgate)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#ifndef RESCUE
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/nv.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/if_media.h>
#include <net/route.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <dev/if_wg/if_wg.h>
#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <stddef.h> /* NB: for offsetof */
#include <locale.h>
#include <langinfo.h>
#include <resolv.h>
#include "ifconfig.h"
static void wgfinish(int s, void *arg);
static bool wgfinish_registered;
static int allowed_ips_count;
static int allowed_ips_max;
static nvlist_t **allowed_ips, *nvl_peer;
#define ALLOWEDIPS_START 16
#define WG_KEY_SIZE_BASE64 ((((WG_KEY_SIZE) + 2) / 3) * 4 + 1)
#define WG_KEY_SIZE_HEX (WG_KEY_SIZE * 2 + 1)
#define WG_MAX_STRLEN 64
struct allowedip {
union {
struct in_addr ip4;
struct in6_addr ip6;
};
};
static void
register_wgfinish(void)
{
if (wgfinish_registered)
return;
callback_register(wgfinish, NULL);
wgfinish_registered = true;
}
static nvlist_t *
nvl_device(void)
{
static nvlist_t *_nvl_device;
if (_nvl_device == NULL)
_nvl_device = nvlist_create(0);
register_wgfinish();
return (_nvl_device);
}
static bool
key_from_base64(uint8_t key[static WG_KEY_SIZE], const char *base64)
{
if (strlen(base64) != WG_KEY_SIZE_BASE64 - 1) {
warnx("bad key len - need %d got %zu\n", WG_KEY_SIZE_BASE64 - 1, strlen(base64));
return false;
}
if (base64[WG_KEY_SIZE_BASE64 - 2] != '=') {
warnx("bad key terminator, expected '=' got '%c'", base64[WG_KEY_SIZE_BASE64 - 2]);
return false;
}
return (b64_pton(base64, key, WG_KEY_SIZE));
}
static void
parse_endpoint(const char *endpoint_)
{
int err;
char *base, *endpoint, *port, *colon, *tmp;
struct addrinfo hints, *res;
endpoint = base = strdup(endpoint_);
colon = rindex(endpoint, ':');
if (colon == NULL)
errx(1, "bad endpoint format %s - no port delimiter found", endpoint);
*colon = '\0';
port = colon + 1;
/* [::]:<> */
if (endpoint[0] == '[') {
endpoint++;
tmp = index(endpoint, ']');
if (tmp == NULL)
errx(1, "bad endpoint format %s - '[' found with no matching ']'", endpoint);
*tmp = '\0';
}
bzero(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
err = getaddrinfo(endpoint, port, &hints, &res);
if (err)
errx(1, "%s", gai_strerror(err));
nvlist_add_binary(nvl_peer, "endpoint", res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
free(base);
}
static void
in_len2mask(struct in_addr *mask, u_int len)
{
u_int i;
u_char *p;
p = (u_char *)mask;
memset(mask, 0, sizeof(*mask));
for (i = 0; i < len / NBBY; i++)
p[i] = 0xff;
if (len % NBBY)
p[i] = (0xff00 >> (len % NBBY)) & 0xff;
}
static u_int
in_mask2len(struct in_addr *mask)
{
u_int x, y;
u_char *p;
p = (u_char *)mask;
for (x = 0; x < sizeof(*mask); x++) {
if (p[x] != 0xff)
break;
}
y = 0;
if (x < sizeof(*mask)) {
for (y = 0; y < NBBY; y++) {
if ((p[x] & (0x80 >> y)) == 0)
break;
}
}
return x * NBBY + y;
}
static void
in6_prefixlen2mask(struct in6_addr *maskp, int len)
{
static const u_char maskarray[NBBY] = {0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff};
int bytelen, bitlen, i;
/* sanity check */
if (len < 0 || len > 128) {
errx(1, "in6_prefixlen2mask: invalid prefix length(%d)\n",
len);
return;
}
memset(maskp, 0, sizeof(*maskp));
bytelen = len / NBBY;
bitlen = len % NBBY;
for (i = 0; i < bytelen; i++)
maskp->s6_addr[i] = 0xff;
if (bitlen)
maskp->s6_addr[bytelen] = maskarray[bitlen - 1];
}
static int
in6_mask2len(struct in6_addr *mask, u_char *lim0)
{
int x = 0, y;
u_char *lim = lim0, *p;
/* ignore the scope_id part */
if (lim0 == NULL || lim0 - (u_char *)mask > sizeof(*mask))
lim = (u_char *)mask + sizeof(*mask);
for (p = (u_char *)mask; p < lim; x++, p++) {
if (*p != 0xff)
break;
}
y = 0;
if (p < lim) {
for (y = 0; y < NBBY; y++) {
if ((*p & (0x80 >> y)) == 0)
break;
}
}
/*
* when the limit pointer is given, do a stricter check on the
* remaining bits.
*/
if (p < lim) {
if (y != 0 && (*p & (0x00ff >> y)) != 0)
return -1;
for (p = p + 1; p < lim; p++)
if (*p != 0)
return -1;
}
return x * NBBY + y;
}
static bool
parse_ip(struct allowedip *aip, uint16_t *family, const char *value)
{
struct addrinfo hints, *res;
int err;
bool ret;
ret = true;
bzero(aip, sizeof(*aip));
bzero(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
err = getaddrinfo(value, NULL, &hints, &res);
if (err)
errx(1, "%s", gai_strerror(err));
*family = res->ai_family;
if (res->ai_family == AF_INET) {
struct sockaddr_in *sin = (struct sockaddr_in *)res->ai_addr;
aip->ip4 = sin->sin_addr;
} else if (res->ai_family == AF_INET6) {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)res->ai_addr;
aip->ip6 = sin6->sin6_addr;
} else {
ret = false;
}
freeaddrinfo(res);
return (ret);
}
static void
sa_ntop(const struct sockaddr *sa, char *buf, int *port)
{
const struct sockaddr_in *sin;
const struct sockaddr_in6 *sin6;
int err;
err = getnameinfo(sa, sa->sa_len, buf, INET6_ADDRSTRLEN, NULL,
0, NI_NUMERICHOST);
if (sa->sa_family == AF_INET) {
sin = (const struct sockaddr_in *)sa;
if (port)
*port = sin->sin_port;
} else if (sa->sa_family == AF_INET6) {
sin6 = (const struct sockaddr_in6 *)sa;
if (port)
*port = sin6->sin6_port;
}
if (err)
errx(1, "%s", gai_strerror(err));
}
static void
dump_peer(const nvlist_t *nvl_peer_cfg)
{
const void *key;
const struct sockaddr *endpoint;
char outbuf[WG_MAX_STRLEN];
char addr_buf[INET6_ADDRSTRLEN];
size_t aip_count, size;
int port;
uint16_t persistent_keepalive;
const nvlist_t * const *nvl_aips;
printf("[Peer]\n");
if (nvlist_exists_binary(nvl_peer_cfg, "public-key")) {
key = nvlist_get_binary(nvl_peer_cfg, "public-key", &size);
b64_ntop((const uint8_t *)key, size, outbuf, WG_MAX_STRLEN);
printf("PublicKey = %s\n", outbuf);
}
if (nvlist_exists_binary(nvl_peer_cfg, "preshared-key")) {
key = nvlist_get_binary(nvl_peer_cfg, "preshared-key", &size);
b64_ntop((const uint8_t *)key, size, outbuf, WG_MAX_STRLEN);
printf("PresharedKey = %s\n", outbuf);
}
if (nvlist_exists_binary(nvl_peer_cfg, "endpoint")) {
endpoint = nvlist_get_binary(nvl_peer_cfg, "endpoint", &size);
sa_ntop(endpoint, addr_buf, &port);
printf("Endpoint = %s:%d\n", addr_buf, ntohs(port));
}
if (nvlist_exists_number(nvl_peer_cfg,
"persistent-keepalive-interval")) {
persistent_keepalive = nvlist_get_number(nvl_peer_cfg,
"persistent-keepalive-interval");
printf("PersistentKeepalive = %d\n", persistent_keepalive);
}
if (!nvlist_exists_nvlist_array(nvl_peer_cfg, "allowed-ips"))
return;
nvl_aips = nvlist_get_nvlist_array(nvl_peer_cfg, "allowed-ips", &aip_count);
if (nvl_aips == NULL || aip_count == 0)
return;
printf("AllowedIPs = ");
for (size_t i = 0; i < aip_count; i++) {
uint8_t cidr;
struct sockaddr_storage ss;
sa_family_t family;
if (!nvlist_exists_number(nvl_aips[i], "cidr"))
continue;
cidr = nvlist_get_number(nvl_aips[i], "cidr");
if (nvlist_exists_binary(nvl_aips[i], "ipv4")) {
struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
const struct in_addr *ip4;
ip4 = nvlist_get_binary(nvl_aips[i], "ipv4", &size);
if (ip4 == NULL || cidr > 32)
continue;
sin->sin_len = sizeof(*sin);
sin->sin_family = AF_INET;
sin->sin_addr = *ip4;
} else if (nvlist_exists_binary(nvl_aips[i], "ipv6")) {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss;
const struct in6_addr *ip6;
ip6 = nvlist_get_binary(nvl_aips[i], "ipv6", &size);
if (ip6 == NULL || cidr > 128)
continue;
sin6->sin6_len = sizeof(*sin6);
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = *ip6;
} else {
continue;
}
family = ss.ss_family;
getnameinfo((struct sockaddr *)&ss, ss.ss_len, addr_buf,
INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST);
printf("%s/%d", addr_buf, cidr);
if (i < aip_count - 1)
printf(", ");
}
printf("\n");
}
static int
get_nvl_out_size(int sock, u_long op, size_t *size)
{
struct wg_data_io wgd;
int err;
memset(&wgd, 0, sizeof(wgd));
strlcpy(wgd.wgd_name, name, sizeof(wgd.wgd_name));
wgd.wgd_size = 0;
wgd.wgd_data = NULL;
err = ioctl(sock, op, &wgd);
if (err)
return (err);
*size = wgd.wgd_size;
return (0);
}
static int
do_cmd(int sock, u_long op, void *arg, size_t argsize, int set)
{
struct wg_data_io wgd;
memset(&wgd, 0, sizeof(wgd));
strlcpy(wgd.wgd_name, name, sizeof(wgd.wgd_name));
wgd.wgd_size = argsize;
wgd.wgd_data = arg;
return (ioctl(sock, op, &wgd));
}
static
DECL_CMD_FUNC(peerlist, val, d)
{
size_t size, peercount;
void *packed;
const nvlist_t *nvl;
const nvlist_t *const *nvl_peerlist;
if (get_nvl_out_size(s, SIOCGWG, &size))
errx(1, "can't get peer list size");
if ((packed = malloc(size)) == NULL)
errx(1, "malloc failed for peer list");
if (do_cmd(s, SIOCGWG, packed, size, 0))
errx(1, "failed to obtain peer list");
nvl = nvlist_unpack(packed, size, 0);
if (!nvlist_exists_nvlist_array(nvl, "peers"))
return;
nvl_peerlist = nvlist_get_nvlist_array(nvl, "peers", &peercount);
for (int i = 0; i < peercount; i++, nvl_peerlist++) {
dump_peer(*nvl_peerlist);
}
}
static void
wgfinish(int s, void *arg)
{
void *packed;
size_t size;
static nvlist_t *nvl_dev;
nvl_dev = nvl_device();
if (nvl_peer != NULL) {
if (!nvlist_exists_binary(nvl_peer, "public-key"))
errx(1, "must specify a public-key for adding peer");
if (allowed_ips_count != 0) {
nvlist_add_nvlist_array(nvl_peer, "allowed-ips",
(const nvlist_t * const *)allowed_ips,
allowed_ips_count);
for (size_t i = 0; i < allowed_ips_count; i++) {
nvlist_destroy(allowed_ips[i]);
}
free(allowed_ips);
}
nvlist_add_nvlist_array(nvl_dev, "peers",
(const nvlist_t * const *)&nvl_peer, 1);
}
packed = nvlist_pack(nvl_dev, &size);
if (do_cmd(s, SIOCSWG, packed, size, true))
errx(1, "failed to configure");
}
static
DECL_CMD_FUNC(peerstart, val, d)
{
if (nvl_peer != NULL)
errx(1, "cannot both add and remove a peer");
register_wgfinish();
nvl_peer = nvlist_create(0);
allowed_ips = calloc(ALLOWEDIPS_START, sizeof(*allowed_ips));
allowed_ips_max = ALLOWEDIPS_START;
if (allowed_ips == NULL)
errx(1, "failed to allocate array for allowedips");
}
static
DECL_CMD_FUNC(peerdel, val, d)
{
if (nvl_peer != NULL)
errx(1, "cannot both add and remove a peer");
register_wgfinish();
nvl_peer = nvlist_create(0);
nvlist_add_bool(nvl_peer, "remove", true);
}
static
DECL_CMD_FUNC(setwglistenport, val, d)
{
struct addrinfo hints, *res;
const struct sockaddr_in *sin;
const struct sockaddr_in6 *sin6;
u_long ul;
int err;
bzero(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
err = getaddrinfo(NULL, val, &hints, &res);
if (err)
errx(1, "%s", gai_strerror(err));
if (res->ai_family == AF_INET) {
sin = (struct sockaddr_in *)res->ai_addr;
ul = sin->sin_port;
} else if (res->ai_family == AF_INET6) {
sin6 = (struct sockaddr_in6 *)res->ai_addr;
ul = sin6->sin6_port;
} else {
errx(1, "unknown family");
}
ul = ntohs((u_short)ul);
nvlist_add_number(nvl_device(), "listen-port", ul);
}
static
DECL_CMD_FUNC(setwgprivkey, val, d)
{
uint8_t key[WG_KEY_SIZE];
if (!key_from_base64(key, val))
errx(1, "invalid key %s", val);
nvlist_add_binary(nvl_device(), "private-key", key, WG_KEY_SIZE);
}
static
DECL_CMD_FUNC(setwgpubkey, val, d)
{
uint8_t key[WG_KEY_SIZE];
if (nvl_peer == NULL)
errx(1, "setting public key only valid when adding peer");
if (!key_from_base64(key, val))
errx(1, "invalid key %s", val);
nvlist_add_binary(nvl_peer, "public-key", key, WG_KEY_SIZE);
}
static
DECL_CMD_FUNC(setwgpresharedkey, val, d)
{
uint8_t key[WG_KEY_SIZE];
if (nvl_peer == NULL)
errx(1, "setting preshared-key only valid when adding peer");
if (!key_from_base64(key, val))
errx(1, "invalid key %s", val);
nvlist_add_binary(nvl_peer, "preshared-key", key, WG_KEY_SIZE);
}
static
DECL_CMD_FUNC(setwgpersistentkeepalive, val, d)
{
unsigned long persistent_keepalive;
char *endp;
if (nvl_peer == NULL)
errx(1, "setting persistent keepalive only valid when adding peer");
errno = 0;
persistent_keepalive = strtoul(val, &endp, 0);
if (errno != 0 || *endp != '\0')
errx(1, "persistent-keepalive must be numeric (seconds)");
if (persistent_keepalive > USHRT_MAX)
errx(1, "persistent-keepalive '%lu' too large",
persistent_keepalive);
nvlist_add_number(nvl_peer, "persistent-keepalive-interval",
persistent_keepalive);
}
static
DECL_CMD_FUNC(setallowedips, val, d)
{
char *base, *allowedip, *mask;
u_long ul;
char *endp;
struct allowedip aip;
nvlist_t *nvl_aip;
uint16_t family;
if (nvl_peer == NULL)
errx(1, "setting allowed ip only valid when adding peer");
if (allowed_ips_count == allowed_ips_max) {
allowed_ips_max *= 2;
allowed_ips = reallocarray(allowed_ips, allowed_ips_max,
sizeof(*allowed_ips));
if (allowed_ips == NULL)
errx(1, "failed to grow allowed ip array");
}
allowed_ips[allowed_ips_count] = nvl_aip = nvlist_create(0);
if (nvl_aip == NULL)
errx(1, "failed to create new allowedip nvlist");
base = allowedip = strdup(val);
mask = index(allowedip, '/');
if (mask == NULL)
errx(1, "mask separator not found in allowedip %s", val);
*mask = '\0';
mask++;
parse_ip(&aip, &family, allowedip);
ul = strtoul(mask, &endp, 0);
if (*endp != '\0')
errx(1, "invalid value for allowedip mask");
nvlist_add_number(nvl_aip, "cidr", ul);
if (family == AF_INET) {
nvlist_add_binary(nvl_aip, "ipv4", &aip.ip4, sizeof(aip.ip4));
} else if (family == AF_INET6) {
nvlist_add_binary(nvl_aip, "ipv6", &aip.ip6, sizeof(aip.ip6));
} else {
/* Shouldn't happen */
nvlist_destroy(nvl_aip);
goto out;
}
allowed_ips_count++;
out:
free(base);
}
static
DECL_CMD_FUNC(setendpoint, val, d)
{
if (nvl_peer == NULL)
errx(1, "setting endpoint only valid when adding peer");
parse_endpoint(val);
}
static void
wireguard_status(int s)
{
size_t size;
void *packed;
nvlist_t *nvl;
char buf[WG_KEY_SIZE_BASE64];
const void *key;
uint16_t listen_port;
if (get_nvl_out_size(s, SIOCGWG, &size))
return;
if ((packed = malloc(size)) == NULL)
return;
if (do_cmd(s, SIOCGWG, packed, size, 0))
return;
nvl = nvlist_unpack(packed, size, 0);
if (nvlist_exists_number(nvl, "listen-port")) {
listen_port = nvlist_get_number(nvl, "listen-port");
printf("\tlisten-port: %d\n", listen_port);
}
if (nvlist_exists_binary(nvl, "private-key")) {
key = nvlist_get_binary(nvl, "private-key", &size);
b64_ntop((const uint8_t *)key, size, buf, WG_MAX_STRLEN);
printf("\tprivate-key: %s\n", buf);
}
if (nvlist_exists_binary(nvl, "public-key")) {
key = nvlist_get_binary(nvl, "public-key", &size);
b64_ntop((const uint8_t *)key, size, buf, WG_MAX_STRLEN);
printf("\tpublic-key: %s\n", buf);
}
}
static struct cmd wireguard_cmds[] = {
DEF_CMD_ARG("listen-port", setwglistenport),
DEF_CMD_ARG("private-key", setwgprivkey),
/* XXX peer-list is deprecated. */
DEF_CMD("peer-list", 0, peerlist),
DEF_CMD("peers", 0, peerlist),
DEF_CMD("peer", 0, peerstart),
DEF_CMD("-peer", 0, peerdel),
DEF_CMD_ARG("preshared-key", setwgpresharedkey),
DEF_CMD_ARG("public-key", setwgpubkey),
DEF_CMD_ARG("persistent-keepalive", setwgpersistentkeepalive),
DEF_CMD_ARG("allowed-ips", setallowedips),
DEF_CMD_ARG("endpoint", setendpoint),
};
static struct afswtch af_wireguard = {
.af_name = "af_wireguard",
.af_af = AF_UNSPEC,
.af_other_status = wireguard_status,
};
static void
wg_create(int s, struct ifreq *ifr)
{
setproctitle("ifconfig %s create ...\n", name);
ifr->ifr_data = NULL;
if (ioctl(s, SIOCIFCREATE, ifr) < 0)
err(1, "SIOCIFCREATE");
}
static __constructor void
wireguard_ctor(void)
{
int i;
for (i = 0; i < nitems(wireguard_cmds); i++)
cmd_register(&wireguard_cmds[i]);
af_register(&af_wireguard);
clone_setdefcallback_prefix("wg", wg_create);
}
#endif

View File

@ -585,7 +585,6 @@ MAN= aac.4 \
vtnet.4 \
watchdog.4 \
${_wbwd.4} \
wg.4 \
witness.4 \
wlan.4 \
wlan_acl.4 \
@ -768,7 +767,6 @@ MLINKS+=vr.4 if_vr.4
MLINKS+=vte.4 if_vte.4
MLINKS+=vtnet.4 if_vtnet.4
MLINKS+=watchdog.4 SW_WATCHDOG.4
MLINKS+=wg.4 if_wg.4
MLINKS+=${_wpi.4} ${_if_wpi.4}
MLINKS+=xl.4 if_xl.4

View File

@ -1,259 +0,0 @@
.\" Copyright (c) 2020 Gordon Bergling <gbe@FreeBSD.org>
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd March 12, 2021
.Dt WG 4
.Os
.Sh NAME
.Nm wg
.Nd "WireGuard - pseudo-device"
.Sh SYNOPSIS
To load the driver as a module at boot time, place the following line in
.Xr loader.conf 5 :
.Bd -literal -offset indent
if_wg_load="YES"
.Ed
.Sh DESCRIPTION
The
.Nm
driver provides Virtual Private Network (VPN) interfaces for the secure
exchange of layer 3 traffic with other WireGuard peers using the WireGuard
protocol.
.Pp
A
.Nm
interface recognises one or more peers, establishes a secure tunnel with
each on demand, and tracks each peer's UDP endpoint for exchanging encrypted
traffic with.
.Pp
The interfaces can be created at runtime using the
.Ic ifconfig Cm wg Ns Ar N Cm create
command.
The interface itself can be configured with
.Xr ifconfig 8 .
.Pp
The following parameters are available:
.Bl -tag -width indent
.It Cm listen-port
The listing port of the
.Nm
interface.
.It Cm public-key
The public key of the
.Nm
interface.
.It Cm private-key
The private key of the
.Nm
interface.
.It Cm preshared-key
Defines a pre-shared key for the
.Nm
interface.
.It Cm allowed-ips
A list of allowed IP addresses.
.It Cm endpoint
The IP address of the WiredGuard to connect to.
.It Cm peers
A list of peering IP addresses to connect to.
.It Cm persistent-keepalive-interval
Interval, in seconds, at which to send persistent keepalive packets.
.El
.Pp
The
.Nm
interfaces support the following
.Xr ioctl 2 Ns s :
.Bl -tag -width Ds -offset indent
.It Dv SIOCSWG Fa "struct wg_device_io *"
Set the device configuration.
.It Dv SIOCGWG Fa "struct wg_device_io *"
Get the device configuration.
.El
.Pp
The following glossary provides a brief overview of WireGuard
terminology:
.Bl -tag -width indent -offset 3n
.It Peer
Peers exchange IPv4 or IPv6 traffic over secure tunnels.
Each
.Nm
interface may be configured to recognise one or more peers.
.It Key
Each peer uses its private key and corresponding public key to
identify itself to others.
A peer configures a
.Nm
interface with its own private key and with the public keys of its peers.
.It Pre-shared key
In addition to the public keys, each peer pair may be configured with a
unique pre-shared symmetric key.
This is used in their handshake to guard against future compromise of the
peers' encrypted tunnel if a quantum-computational attack on their
Diffie-Hellman exchange becomes feasible.
It is optional, but recommended.
.It Allowed IPs
A single
.Nm
interface may maintain concurrent tunnels connecting diverse networks.
The interface therefore implements rudimentary routing and reverse-path
filtering functions for its tunneled traffic.
These functions reference a set of allowed IP ranges configured against
each peer.
.Pp
The interface will route outbound tunneled traffic to the peer configured
with the most specific matching allowed IP address range, or drop it
if no such match exists.
.Pp
The interface will accept tunneled traffic only from the peer
configured with the most specific matching allowed IP address range
for the incoming traffic, or drop it if no such match exists.
That is, tunneled traffic routed to a given peer cannot return through
another peer of the same
.Nm
interface.
This ensures that peers cannot spoof another's traffic.
.It Handshake
Two peers handshake to mutually authenticate each other and to
establish a shared series of secret ephemeral encryption keys.
Any peer may initiate a handshake.
Handshakes occur only when there is traffic to send, and recur every
two minutes during transfers.
.It Connectionless
Due to the handshake behavior, there is no connected or disconnected
state.
.El
.Ss Keys
Private keys for WireGuard can be generated from any sufficiently
secure random source.
The Curve25519 keys and the pre-shared keys are both 32 bytes
long and are commonly encoded in base64 for ease of use.
.Pp
Keys can be generated with
.Xr openssl 1
as follows:
.Pp
.Dl $ openssl rand -base64 32
.Pp
Although a valid Curve25519 key must have 5 bits set to
specific values, this is done by the interface and so it
will accept any random 32-byte base64 string.
.Pp
When an interface has a private key set with
.Nm public-key ,
the corresponding
public key is shown in the status output of the interface:
.Bd -literal -offset indent
# ifconfig wg0 | grep public-key
public-key: 7lWtsDdqaGB3EY9WNxRN3hVaHMtu1zXw71+bOjNOVUw=
.Ed
.Sh EXAMPLES
Create a
.Nm
interface and set random private key.
.Bd -literal -offset indent
# ifconfig wg0 create listen-port 54321 private-key `openssl rand -base64 32`
.Ed
.Pp
Retrieve the associated public key from a
.Nm
interface.
.Bd -literal -offset indent
$ ifconfig wg0 | awk '/public-key/ { print $2 }'`
.Ed
.Pp
Connect to a specific endpoint using its public-key and set the allowed IP address
.Bd -literal -offset indent
# ifconfig wg0 peer public-key '7lWtsDdqaGB3EY9WNxRN3hVaHMtu1zXw71+bOjNOVUw=' endpoint 10.0.1.100:54321 allowed-ips 192.168.2.100/32
.Ed
.Pp
Remove a peer
.Bd -literal -offset indent
# ifconfig wg0 -peer public-key '7lWtsDdqaGB3EY9WNxRN3hVaHMtu1zXw71+bOjNOVUw='
.Ed
.Sh DIAGNOSTICS
The
.Nm
interface supports runtime debugging, which can be enabled with:
.Pp
.D1 Ic ifconfig Cm wg Ns Ar N Cm debug
.Pp
Some common error messages include:
.Bl -diag
.It "Handshake for peer X did not complete after 5 seconds, retrying"
Peer X did not reply to our initiation packet, for example because:
.Bl -bullet
.It
The peer does not have the local interface configured as a peer.
Peers must be able to mutually authenticate each other.
.It
The peer endpoint IP address is incorrectly configured.
.It
There are firewall rules preventing communication between hosts.
.El
.It "Invalid handshake initiation"
The incoming handshake packet could not be processed.
This is likely due to the local interface not containing
the correct public key for the peer.
.It "Invalid initiation MAC"
The incoming handshake initiation packet had an invalid MAC.
This is likely because the initiation sender has the wrong public key
for the handshake receiver.
.It "Packet has unallowed src IP from peer X"
After decryption, an incoming data packet has a source IP address that
is not assigned to the allowed IPs of Peer X.
.El
.Sh SEE ALSO
.Xr inet 4 ,
.Xr ip 4 ,
.Xr netintro 4 ,
.Xr ipf 5 ,
.Xr pf.conf 5 ,
.Xr ifconfig 8 ,
.Xr ipfw 8
.Rs
.%T WireGuard whitepaper
.%U https://www.wireguard.com/papers/wireguard.pdf
.Re
.Sh HISTORY
The
.Nm
device driver first appeared in
.Fx 13.0 .
.Sh AUTHORS
The
.Nm
device driver written by
.An Jason A. Donenfeld Aq Mt Jason@zx2c4.com ,
.An Matt Dunwoodie Aq Mt ncon@nconroy.net ,
and
.An Kyle Evans Aq Mt kevans@FreeBSD.org .
.Pp
This manual page was written by
.An Gordon Bergling Aq Mt gbe@FreeBSD.org
and is based on the
.Ox
manual page written by
.An David Gwynne Aq Mt dlg@openbsd.org .

File diff suppressed because it is too large Load Diff

View File

@ -1,114 +0,0 @@
/*
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _WG_CRYPTO
#define _WG_CRYPTO
#include <sys/types.h>
enum chacha20poly1305_lengths {
XCHACHA20POLY1305_NONCE_SIZE = 24,
CHACHA20POLY1305_KEY_SIZE = 32,
CHACHA20POLY1305_AUTHTAG_SIZE = 16
};
void
chacha20poly1305_encrypt(uint8_t *dst, const uint8_t *src, const size_t src_len,
const uint8_t *ad, const size_t ad_len,
const uint64_t nonce,
const uint8_t key[CHACHA20POLY1305_KEY_SIZE]);
bool
chacha20poly1305_decrypt(uint8_t *dst, const uint8_t *src, const size_t src_len,
const uint8_t *ad, const size_t ad_len,
const uint64_t nonce,
const uint8_t key[CHACHA20POLY1305_KEY_SIZE]);
void
xchacha20poly1305_encrypt(uint8_t *dst, const uint8_t *src,
const size_t src_len, const uint8_t *ad,
const size_t ad_len,
const uint8_t nonce[XCHACHA20POLY1305_NONCE_SIZE],
const uint8_t key[CHACHA20POLY1305_KEY_SIZE]);
bool
xchacha20poly1305_decrypt(uint8_t *dst, const uint8_t *src,
const size_t src_len, const uint8_t *ad,
const size_t ad_len,
const uint8_t nonce[XCHACHA20POLY1305_NONCE_SIZE],
const uint8_t key[CHACHA20POLY1305_KEY_SIZE]);
enum blake2s_lengths {
BLAKE2S_BLOCK_SIZE = 64,
BLAKE2S_HASH_SIZE = 32,
BLAKE2S_KEY_SIZE = 32
};
struct blake2s_state {
uint32_t h[8];
uint32_t t[2];
uint32_t f[2];
uint8_t buf[BLAKE2S_BLOCK_SIZE];
unsigned int buflen;
unsigned int outlen;
};
void blake2s_init(struct blake2s_state *state, const size_t outlen);
void blake2s_init_key(struct blake2s_state *state, const size_t outlen,
const uint8_t *key, const size_t keylen);
void blake2s_update(struct blake2s_state *state, const uint8_t *in, size_t inlen);
void blake2s_final(struct blake2s_state *state, uint8_t *out);
void blake2s(uint8_t *out, const uint8_t *in, const uint8_t *key,
const size_t outlen, const size_t inlen, const size_t keylen);
void blake2s_hmac(uint8_t *out, const uint8_t *in, const uint8_t *key,
const size_t outlen, const size_t inlen, const size_t keylen);
enum curve25519_lengths {
CURVE25519_KEY_SIZE = 32
};
bool curve25519(uint8_t mypublic[static CURVE25519_KEY_SIZE],
const uint8_t secret[static CURVE25519_KEY_SIZE],
const uint8_t basepoint[static CURVE25519_KEY_SIZE]);
static inline bool
curve25519_generate_public(uint8_t pub[static CURVE25519_KEY_SIZE],
const uint8_t secret[static CURVE25519_KEY_SIZE])
{
static const uint8_t basepoint[CURVE25519_KEY_SIZE] = { 9 };
return curve25519(pub, secret, basepoint);
}
static inline void curve25519_clamp_secret(uint8_t secret[static CURVE25519_KEY_SIZE])
{
secret[0] &= 248;
secret[31] = (secret[31] & 127) | 64;
}
static inline void curve25519_generate_secret(uint8_t secret[CURVE25519_KEY_SIZE])
{
arc4random_buf(secret, CURVE25519_KEY_SIZE);
curve25519_clamp_secret(secret);
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +0,0 @@
/*
* Copyright (c) 2019 Matt Dunwoodie <ncon@noconroy.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $FreeBSD$
*/
#ifndef __IF_WG_H__
#define __IF_WG_H__
#include <net/if.h>
#include <netinet/in.h>
struct wg_data_io {
char wgd_name[IFNAMSIZ];
void *wgd_data;
size_t wgd_size;
};
#define WG_KEY_SIZE 32
#define SIOCSWG _IOWR('i', 210, struct wg_data_io)
#define SIOCGWG _IOWR('i', 211, struct wg_data_io)
#endif /* __IF_WG_H__ */

View File

@ -1,56 +0,0 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (C) 2021 Matt Dunwoodie <ncon@noconroy.net>
*/
#ifndef _WG_SUPPORT
#define _WG_SUPPORT
#include <sys/types.h>
#include <sys/limits.h>
#include <sys/endian.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/lock.h>
#include <vm/uma.h>
/* TODO the following is openbsd compat defines to allow us to copy the wg_*
* files from openbsd (almost) verbatim. this will greatly increase maintenance
* across the platforms. it should be moved to it's own file. the only thing
* we're missing from this is struct pool (freebsd: uma_zone_t), which isn't a
* show stopper, but is something worth considering in the future.
* - md */
#define rw_assert_wrlock(x) rw_assert(x, RA_WLOCKED)
#define rw_enter_write rw_wlock
#define rw_exit_write rw_wunlock
#define rw_enter_read rw_rlock
#define rw_exit_read rw_runlock
#define rw_exit rw_unlock
#define RW_DOWNGRADE 1
#define rw_enter(x, y) do { \
CTASSERT(y == RW_DOWNGRADE); \
rw_downgrade(x); \
} while (0)
MALLOC_DECLARE(M_WG);
#include <crypto/siphash/siphash.h>
typedef struct {
uint64_t k0;
uint64_t k1;
} SIPHASH_KEY;
static inline uint64_t
siphash24(const SIPHASH_KEY *key, const void *src, size_t len)
{
SIPHASH_CTX ctx;
return (SipHashX(&ctx, 2, 4, (const uint8_t *)key, src, len));
}
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif

View File

@ -1,438 +0,0 @@
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (C) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/rwlock.h>
#include <sys/malloc.h> /* Because systm doesn't include M_NOWAIT, M_DEVBUF */
#include <sys/socket.h>
#include "support.h"
#include "wg_cookie.h"
static void cookie_precompute_key(uint8_t *,
const uint8_t[COOKIE_INPUT_SIZE], const char *);
static void cookie_macs_mac1(struct cookie_macs *, const void *, size_t,
const uint8_t[COOKIE_KEY_SIZE]);
static void cookie_macs_mac2(struct cookie_macs *, const void *, size_t,
const uint8_t[COOKIE_COOKIE_SIZE]);
static int cookie_timer_expired(struct timespec *, time_t, long);
static void cookie_checker_make_cookie(struct cookie_checker *,
uint8_t[COOKIE_COOKIE_SIZE], struct sockaddr *);
static int ratelimit_init(struct ratelimit *, uma_zone_t);
static void ratelimit_deinit(struct ratelimit *);
static void ratelimit_gc(struct ratelimit *, int);
static int ratelimit_allow(struct ratelimit *, struct sockaddr *);
/* Public Functions */
void
cookie_maker_init(struct cookie_maker *cp, const uint8_t key[COOKIE_INPUT_SIZE])
{
bzero(cp, sizeof(*cp));
cookie_precompute_key(cp->cp_mac1_key, key, COOKIE_MAC1_KEY_LABEL);
cookie_precompute_key(cp->cp_cookie_key, key, COOKIE_COOKIE_KEY_LABEL);
rw_init(&cp->cp_lock, "cookie_maker");
}
int
cookie_checker_init(struct cookie_checker *cc, uma_zone_t zone)
{
int res;
bzero(cc, sizeof(*cc));
rw_init(&cc->cc_key_lock, "cookie_checker_key");
rw_init(&cc->cc_secret_lock, "cookie_checker_secret");
if ((res = ratelimit_init(&cc->cc_ratelimit_v4, zone)) != 0)
return res;
#ifdef INET6
if ((res = ratelimit_init(&cc->cc_ratelimit_v6, zone)) != 0) {
ratelimit_deinit(&cc->cc_ratelimit_v4);
return res;
}
#endif
return 0;
}
void
cookie_checker_update(struct cookie_checker *cc,
const uint8_t key[COOKIE_INPUT_SIZE])
{
rw_enter_write(&cc->cc_key_lock);
if (key) {
cookie_precompute_key(cc->cc_mac1_key, key, COOKIE_MAC1_KEY_LABEL);
cookie_precompute_key(cc->cc_cookie_key, key, COOKIE_COOKIE_KEY_LABEL);
} else {
bzero(cc->cc_mac1_key, sizeof(cc->cc_mac1_key));
bzero(cc->cc_cookie_key, sizeof(cc->cc_cookie_key));
}
rw_exit_write(&cc->cc_key_lock);
}
void
cookie_checker_deinit(struct cookie_checker *cc)
{
ratelimit_deinit(&cc->cc_ratelimit_v4);
#ifdef INET6
ratelimit_deinit(&cc->cc_ratelimit_v6);
#endif
}
void
cookie_checker_create_payload(struct cookie_checker *cc,
struct cookie_macs *cm, uint8_t nonce[COOKIE_NONCE_SIZE],
uint8_t ecookie[COOKIE_ENCRYPTED_SIZE], struct sockaddr *sa)
{
uint8_t cookie[COOKIE_COOKIE_SIZE];
cookie_checker_make_cookie(cc, cookie, sa);
arc4random_buf(nonce, COOKIE_NONCE_SIZE);
rw_enter_read(&cc->cc_key_lock);
xchacha20poly1305_encrypt(ecookie, cookie, COOKIE_COOKIE_SIZE,
cm->mac1, COOKIE_MAC_SIZE, nonce, cc->cc_cookie_key);
rw_exit_read(&cc->cc_key_lock);
explicit_bzero(cookie, sizeof(cookie));
}
int
cookie_maker_consume_payload(struct cookie_maker *cp,
uint8_t nonce[COOKIE_NONCE_SIZE], uint8_t ecookie[COOKIE_ENCRYPTED_SIZE])
{
int ret = 0;
uint8_t cookie[COOKIE_COOKIE_SIZE];
rw_enter_write(&cp->cp_lock);
if (cp->cp_mac1_valid == 0) {
ret = ETIMEDOUT;
goto error;
}
if (xchacha20poly1305_decrypt(cookie, ecookie, COOKIE_ENCRYPTED_SIZE,
cp->cp_mac1_last, COOKIE_MAC_SIZE, nonce, cp->cp_cookie_key) == 0) {
ret = EINVAL;
goto error;
}
memcpy(cp->cp_cookie, cookie, COOKIE_COOKIE_SIZE);
getnanouptime(&cp->cp_birthdate);
cp->cp_mac1_valid = 0;
error:
rw_exit_write(&cp->cp_lock);
return ret;
}
void
cookie_maker_mac(struct cookie_maker *cp, struct cookie_macs *cm, void *buf,
size_t len)
{
rw_enter_read(&cp->cp_lock);
cookie_macs_mac1(cm, buf, len, cp->cp_mac1_key);
memcpy(cp->cp_mac1_last, cm->mac1, COOKIE_MAC_SIZE);
cp->cp_mac1_valid = 1;
if (!cookie_timer_expired(&cp->cp_birthdate,
COOKIE_SECRET_MAX_AGE - COOKIE_SECRET_LATENCY, 0))
cookie_macs_mac2(cm, buf, len, cp->cp_cookie);
else
bzero(cm->mac2, COOKIE_MAC_SIZE);
rw_exit_read(&cp->cp_lock);
}
int
cookie_checker_validate_macs(struct cookie_checker *cc, struct cookie_macs *cm,
void *buf, size_t len, int busy, struct sockaddr *sa)
{
struct cookie_macs our_cm;
uint8_t cookie[COOKIE_COOKIE_SIZE];
/* Validate incoming MACs */
rw_enter_read(&cc->cc_key_lock);
cookie_macs_mac1(&our_cm, buf, len, cc->cc_mac1_key);
rw_exit_read(&cc->cc_key_lock);
/* If mac1 is invald, we want to drop the packet */
if (timingsafe_bcmp(our_cm.mac1, cm->mac1, COOKIE_MAC_SIZE) != 0)
return EINVAL;
if (busy != 0) {
cookie_checker_make_cookie(cc, cookie, sa);
cookie_macs_mac2(&our_cm, buf, len, cookie);
/* If the mac2 is invalid, we want to send a cookie response */
if (timingsafe_bcmp(our_cm.mac2, cm->mac2, COOKIE_MAC_SIZE) != 0)
return EAGAIN;
/* If the mac2 is valid, we may want rate limit the peer.
* ratelimit_allow will return either 0 or ECONNREFUSED,
* implying there is no ratelimiting, or we should ratelimit
* (refuse) respectively. */
if (sa->sa_family == AF_INET)
return ratelimit_allow(&cc->cc_ratelimit_v4, sa);
#ifdef INET6
else if (sa->sa_family == AF_INET6)
return ratelimit_allow(&cc->cc_ratelimit_v6, sa);
#endif
else
return EAFNOSUPPORT;
}
return 0;
}
/* Private functions */
static void
cookie_precompute_key(uint8_t *key, const uint8_t input[COOKIE_INPUT_SIZE],
const char *label)
{
struct blake2s_state blake;
blake2s_init(&blake, COOKIE_KEY_SIZE);
blake2s_update(&blake, label, strlen(label));
blake2s_update(&blake, input, COOKIE_INPUT_SIZE);
/* TODO we shouldn't need to provide outlen to _final. we can align
* this with openbsd after fixing the blake library. */
blake2s_final(&blake, key);
}
static void
cookie_macs_mac1(struct cookie_macs *cm, const void *buf, size_t len,
const uint8_t key[COOKIE_KEY_SIZE])
{
struct blake2s_state state;
blake2s_init_key(&state, COOKIE_MAC_SIZE, key, COOKIE_KEY_SIZE);
blake2s_update(&state, buf, len);
blake2s_final(&state, cm->mac1);
}
static void
cookie_macs_mac2(struct cookie_macs *cm, const void *buf, size_t len,
const uint8_t key[COOKIE_COOKIE_SIZE])
{
struct blake2s_state state;
blake2s_init_key(&state, COOKIE_MAC_SIZE, key, COOKIE_COOKIE_SIZE);
blake2s_update(&state, buf, len);
blake2s_update(&state, cm->mac1, COOKIE_MAC_SIZE);
blake2s_final(&state, cm->mac2);
}
static int
cookie_timer_expired(struct timespec *birthdate, time_t sec, long nsec)
{
struct timespec uptime;
struct timespec expire = { .tv_sec = sec, .tv_nsec = nsec };
if (birthdate->tv_sec == 0 && birthdate->tv_nsec == 0)
return ETIMEDOUT;
getnanouptime(&uptime);
timespecadd(birthdate, &expire, &expire);
return timespeccmp(&uptime, &expire, >) ? ETIMEDOUT : 0;
}
static void
cookie_checker_make_cookie(struct cookie_checker *cc,
uint8_t cookie[COOKIE_COOKIE_SIZE], struct sockaddr *sa)
{
struct blake2s_state state;
rw_enter_write(&cc->cc_secret_lock);
if (cookie_timer_expired(&cc->cc_secret_birthdate,
COOKIE_SECRET_MAX_AGE, 0)) {
arc4random_buf(cc->cc_secret, COOKIE_SECRET_SIZE);
getnanouptime(&cc->cc_secret_birthdate);
}
blake2s_init_key(&state, COOKIE_COOKIE_SIZE, cc->cc_secret,
COOKIE_SECRET_SIZE);
rw_exit_write(&cc->cc_secret_lock);
if (sa->sa_family == AF_INET) {
blake2s_update(&state, (uint8_t *)&satosin(sa)->sin_addr,
sizeof(struct in_addr));
blake2s_update(&state, (uint8_t *)&satosin(sa)->sin_port,
sizeof(in_port_t));
blake2s_final(&state, cookie);
#ifdef INET6
} else if (sa->sa_family == AF_INET6) {
blake2s_update(&state, (uint8_t *)&satosin6(sa)->sin6_addr,
sizeof(struct in6_addr));
blake2s_update(&state, (uint8_t *)&satosin6(sa)->sin6_port,
sizeof(in_port_t));
blake2s_final(&state, cookie);
#endif
} else {
arc4random_buf(cookie, COOKIE_COOKIE_SIZE);
}
}
static int
ratelimit_init(struct ratelimit *rl, uma_zone_t zone)
{
rw_init(&rl->rl_lock, "ratelimit_lock");
arc4random_buf(&rl->rl_secret, sizeof(rl->rl_secret));
rl->rl_table = hashinit_flags(RATELIMIT_SIZE, M_DEVBUF,
&rl->rl_table_mask, M_NOWAIT);
rl->rl_zone = zone;
rl->rl_table_num = 0;
return rl->rl_table == NULL ? ENOBUFS : 0;
}
static void
ratelimit_deinit(struct ratelimit *rl)
{
rw_enter_write(&rl->rl_lock);
ratelimit_gc(rl, 1);
hashdestroy(rl->rl_table, M_DEVBUF, rl->rl_table_mask);
rw_exit_write(&rl->rl_lock);
}
static void
ratelimit_gc(struct ratelimit *rl, int force)
{
size_t i;
struct ratelimit_entry *r, *tr;
struct timespec expiry;
rw_assert_wrlock(&rl->rl_lock);
if (force) {
for (i = 0; i < RATELIMIT_SIZE; i++) {
LIST_FOREACH_SAFE(r, &rl->rl_table[i], r_entry, tr) {
rl->rl_table_num--;
LIST_REMOVE(r, r_entry);
uma_zfree(rl->rl_zone, r);
}
}
return;
}
if ((cookie_timer_expired(&rl->rl_last_gc, ELEMENT_TIMEOUT, 0) &&
rl->rl_table_num > 0)) {
getnanouptime(&rl->rl_last_gc);
getnanouptime(&expiry);
expiry.tv_sec -= ELEMENT_TIMEOUT;
for (i = 0; i < RATELIMIT_SIZE; i++) {
LIST_FOREACH_SAFE(r, &rl->rl_table[i], r_entry, tr) {
if (timespeccmp(&r->r_last_time, &expiry, <)) {
rl->rl_table_num--;
LIST_REMOVE(r, r_entry);
uma_zfree(rl->rl_zone, r);
}
}
}
}
}
static int
ratelimit_allow(struct ratelimit *rl, struct sockaddr *sa)
{
uint64_t key, tokens;
struct timespec diff;
struct ratelimit_entry *r;
int ret = ECONNREFUSED;
if (sa->sa_family == AF_INET)
/* TODO siphash24 is the FreeBSD siphash, OK? */
key = siphash24(&rl->rl_secret, &satosin(sa)->sin_addr,
IPV4_MASK_SIZE);
#ifdef INET6
else if (sa->sa_family == AF_INET6)
key = siphash24(&rl->rl_secret, &satosin6(sa)->sin6_addr,
IPV6_MASK_SIZE);
#endif
else
return ret;
rw_enter_write(&rl->rl_lock);
LIST_FOREACH(r, &rl->rl_table[key & rl->rl_table_mask], r_entry) {
if (r->r_af != sa->sa_family)
continue;
if (r->r_af == AF_INET && bcmp(&r->r_in,
&satosin(sa)->sin_addr, IPV4_MASK_SIZE) != 0)
continue;
#ifdef INET6
if (r->r_af == AF_INET6 && bcmp(&r->r_in6,
&satosin6(sa)->sin6_addr, IPV6_MASK_SIZE) != 0)
continue;
#endif
/* If we get to here, we've found an entry for the endpoint.
* We apply standard token bucket, by calculating the time
* lapsed since our last_time, adding that, ensuring that we
* cap the tokens at TOKEN_MAX. If the endpoint has no tokens
* left (that is tokens <= INITIATION_COST) then we block the
* request, otherwise we subtract the INITITIATION_COST and
* return OK. */
diff = r->r_last_time;
getnanouptime(&r->r_last_time);
timespecsub(&r->r_last_time, &diff, &diff);
tokens = r->r_tokens + diff.tv_sec * NSEC_PER_SEC + diff.tv_nsec;
if (tokens > TOKEN_MAX)
tokens = TOKEN_MAX;
if (tokens >= INITIATION_COST) {
r->r_tokens = tokens - INITIATION_COST;
goto ok;
} else {
r->r_tokens = tokens;
goto error;
}
}
/* If we get to here, we didn't have an entry for the endpoint. */
ratelimit_gc(rl, 0);
/* Hard limit on number of entries */
if (rl->rl_table_num >= RATELIMIT_SIZE_MAX)
goto error;
/* Goto error if out of memory */
if ((r = uma_zalloc(rl->rl_zone, M_NOWAIT)) == NULL)
goto error;
rl->rl_table_num++;
/* Insert entry into the hashtable and ensure it's initialised */
LIST_INSERT_HEAD(&rl->rl_table[key & rl->rl_table_mask], r, r_entry);
r->r_af = sa->sa_family;
if (r->r_af == AF_INET)
memcpy(&r->r_in, &satosin(sa)->sin_addr, IPV4_MASK_SIZE);
#ifdef INET6
else if (r->r_af == AF_INET6)
memcpy(&r->r_in6, &satosin6(sa)->sin6_addr, IPV6_MASK_SIZE);
#endif
getnanouptime(&r->r_last_time);
r->r_tokens = TOKEN_MAX - INITIATION_COST;
ok:
ret = 0;
error:
rw_exit_write(&rl->rl_lock);
return ret;
}

View File

@ -1,125 +0,0 @@
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (C) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef __COOKIE_H__
#define __COOKIE_H__
#include <sys/types.h>
#include <sys/time.h>
#include <sys/rwlock.h>
#include <sys/queue.h>
#include <netinet/in.h>
#include "crypto.h"
#define COOKIE_MAC_SIZE 16
#define COOKIE_KEY_SIZE 32
#define COOKIE_NONCE_SIZE XCHACHA20POLY1305_NONCE_SIZE
#define COOKIE_COOKIE_SIZE 16
#define COOKIE_SECRET_SIZE 32
#define COOKIE_INPUT_SIZE 32
#define COOKIE_ENCRYPTED_SIZE (COOKIE_COOKIE_SIZE + COOKIE_MAC_SIZE)
#define COOKIE_MAC1_KEY_LABEL "mac1----"
#define COOKIE_COOKIE_KEY_LABEL "cookie--"
#define COOKIE_SECRET_MAX_AGE 120
#define COOKIE_SECRET_LATENCY 5
/* Constants for initiation rate limiting */
#define RATELIMIT_SIZE (1 << 13)
#define RATELIMIT_SIZE_MAX (RATELIMIT_SIZE * 8)
#define NSEC_PER_SEC 1000000000LL
#define INITIATIONS_PER_SECOND 20
#define INITIATIONS_BURSTABLE 5
#define INITIATION_COST (NSEC_PER_SEC / INITIATIONS_PER_SECOND)
#define TOKEN_MAX (INITIATION_COST * INITIATIONS_BURSTABLE)
#define ELEMENT_TIMEOUT 1
#define IPV4_MASK_SIZE 4 /* Use all 4 bytes of IPv4 address */
#define IPV6_MASK_SIZE 8 /* Use top 8 bytes (/64) of IPv6 address */
struct cookie_macs {
uint8_t mac1[COOKIE_MAC_SIZE];
uint8_t mac2[COOKIE_MAC_SIZE];
};
struct ratelimit_entry {
LIST_ENTRY(ratelimit_entry) r_entry;
sa_family_t r_af;
union {
struct in_addr r_in;
#ifdef INET6
struct in6_addr r_in6;
#endif
};
struct timespec r_last_time; /* nanouptime */
uint64_t r_tokens;
};
struct ratelimit {
SIPHASH_KEY rl_secret;
uma_zone_t rl_zone;
struct rwlock rl_lock;
LIST_HEAD(, ratelimit_entry) *rl_table;
u_long rl_table_mask;
size_t rl_table_num;
struct timespec rl_last_gc; /* nanouptime */
};
struct cookie_maker {
uint8_t cp_mac1_key[COOKIE_KEY_SIZE];
uint8_t cp_cookie_key[COOKIE_KEY_SIZE];
struct rwlock cp_lock;
uint8_t cp_cookie[COOKIE_COOKIE_SIZE];
struct timespec cp_birthdate; /* nanouptime */
int cp_mac1_valid;
uint8_t cp_mac1_last[COOKIE_MAC_SIZE];
};
struct cookie_checker {
struct ratelimit cc_ratelimit_v4;
#ifdef INET6
struct ratelimit cc_ratelimit_v6;
#endif
struct rwlock cc_key_lock;
uint8_t cc_mac1_key[COOKIE_KEY_SIZE];
uint8_t cc_cookie_key[COOKIE_KEY_SIZE];
struct rwlock cc_secret_lock;
struct timespec cc_secret_birthdate; /* nanouptime */
uint8_t cc_secret[COOKIE_SECRET_SIZE];
};
void cookie_maker_init(struct cookie_maker *, const uint8_t[COOKIE_INPUT_SIZE]);
int cookie_checker_init(struct cookie_checker *, uma_zone_t);
void cookie_checker_update(struct cookie_checker *,
const uint8_t[COOKIE_INPUT_SIZE]);
void cookie_checker_deinit(struct cookie_checker *);
void cookie_checker_create_payload(struct cookie_checker *,
struct cookie_macs *cm, uint8_t[COOKIE_NONCE_SIZE],
uint8_t [COOKIE_ENCRYPTED_SIZE], struct sockaddr *);
int cookie_maker_consume_payload(struct cookie_maker *,
uint8_t[COOKIE_NONCE_SIZE], uint8_t[COOKIE_ENCRYPTED_SIZE]);
void cookie_maker_mac(struct cookie_maker *, struct cookie_macs *,
void *, size_t);
int cookie_checker_validate_macs(struct cookie_checker *,
struct cookie_macs *, void *, size_t, int, struct sockaddr *);
#endif /* __COOKIE_H__ */

View File

@ -1,963 +0,0 @@
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (C) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/rwlock.h>
#include "support.h"
#include "wg_noise.h"
/* Private functions */
static struct noise_keypair *
noise_remote_keypair_allocate(struct noise_remote *);
static void
noise_remote_keypair_free(struct noise_remote *,
struct noise_keypair *);
static uint32_t noise_remote_handshake_index_get(struct noise_remote *);
static void noise_remote_handshake_index_drop(struct noise_remote *);
static uint64_t noise_counter_send(struct noise_counter *);
static int noise_counter_recv(struct noise_counter *, uint64_t);
static void noise_kdf(uint8_t *, uint8_t *, uint8_t *, const uint8_t *,
size_t, size_t, size_t, size_t,
const uint8_t [NOISE_HASH_LEN]);
static int noise_mix_dh(
uint8_t [NOISE_HASH_LEN],
uint8_t [NOISE_SYMMETRIC_KEY_LEN],
const uint8_t [NOISE_PUBLIC_KEY_LEN],
const uint8_t [NOISE_PUBLIC_KEY_LEN]);
static int noise_mix_ss(
uint8_t ck[NOISE_HASH_LEN],
uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
const uint8_t ss[NOISE_PUBLIC_KEY_LEN]);
static void noise_mix_hash(
uint8_t [NOISE_HASH_LEN],
const uint8_t *,
size_t);
static void noise_mix_psk(
uint8_t [NOISE_HASH_LEN],
uint8_t [NOISE_HASH_LEN],
uint8_t [NOISE_SYMMETRIC_KEY_LEN],
const uint8_t [NOISE_SYMMETRIC_KEY_LEN]);
static void noise_param_init(
uint8_t [NOISE_HASH_LEN],
uint8_t [NOISE_HASH_LEN],
const uint8_t [NOISE_PUBLIC_KEY_LEN]);
static void noise_msg_encrypt(uint8_t *, const uint8_t *, size_t,
uint8_t [NOISE_SYMMETRIC_KEY_LEN],
uint8_t [NOISE_HASH_LEN]);
static int noise_msg_decrypt(uint8_t *, const uint8_t *, size_t,
uint8_t [NOISE_SYMMETRIC_KEY_LEN],
uint8_t [NOISE_HASH_LEN]);
static void noise_msg_ephemeral(
uint8_t [NOISE_HASH_LEN],
uint8_t [NOISE_HASH_LEN],
const uint8_t src[NOISE_PUBLIC_KEY_LEN]);
static void noise_tai64n_now(uint8_t [NOISE_TIMESTAMP_LEN]);
static int noise_timer_expired(struct timespec *, time_t, long);
/* Set/Get noise parameters */
void
noise_local_init(struct noise_local *l, struct noise_upcall *upcall)
{
bzero(l, sizeof(*l));
rw_init(&l->l_identity_lock, "noise_local_identity");
l->l_upcall = *upcall;
}
void
noise_local_lock_identity(struct noise_local *l)
{
rw_enter_write(&l->l_identity_lock);
}
void
noise_local_unlock_identity(struct noise_local *l)
{
rw_exit_write(&l->l_identity_lock);
}
int
noise_local_set_private(struct noise_local *l,
const uint8_t private[NOISE_PUBLIC_KEY_LEN])
{
rw_assert_wrlock(&l->l_identity_lock);
memcpy(l->l_private, private, NOISE_PUBLIC_KEY_LEN);
curve25519_clamp_secret(l->l_private);
l->l_has_identity = curve25519_generate_public(l->l_public, private);
return l->l_has_identity ? 0 : ENXIO;
}
int
noise_local_keys(struct noise_local *l, uint8_t public[NOISE_PUBLIC_KEY_LEN],
uint8_t private[NOISE_PUBLIC_KEY_LEN])
{
int ret = 0;
rw_enter_read(&l->l_identity_lock);
if (l->l_has_identity) {
if (public != NULL)
memcpy(public, l->l_public, NOISE_PUBLIC_KEY_LEN);
if (private != NULL)
memcpy(private, l->l_private, NOISE_PUBLIC_KEY_LEN);
} else {
ret = ENXIO;
}
rw_exit_read(&l->l_identity_lock);
return ret;
}
void
noise_remote_init(struct noise_remote *r,
const uint8_t public[NOISE_PUBLIC_KEY_LEN], struct noise_local *l)
{
bzero(r, sizeof(*r));
memcpy(r->r_public, public, NOISE_PUBLIC_KEY_LEN);
rw_init(&r->r_handshake_lock, "noise_handshake");
rw_init(&r->r_keypair_lock, "noise_keypair");
SLIST_INSERT_HEAD(&r->r_unused_keypairs, &r->r_keypair[0], kp_entry);
SLIST_INSERT_HEAD(&r->r_unused_keypairs, &r->r_keypair[1], kp_entry);
SLIST_INSERT_HEAD(&r->r_unused_keypairs, &r->r_keypair[2], kp_entry);
KASSERT(l != NULL, ("must provide local"));
r->r_local = l;
rw_enter_write(&l->l_identity_lock);
noise_remote_precompute(r);
rw_exit_write(&l->l_identity_lock);
}
int
noise_remote_set_psk(struct noise_remote *r,
const uint8_t psk[NOISE_SYMMETRIC_KEY_LEN])
{
int same;
rw_enter_write(&r->r_handshake_lock);
same = !timingsafe_bcmp(r->r_psk, psk, NOISE_SYMMETRIC_KEY_LEN);
if (!same) {
memcpy(r->r_psk, psk, NOISE_SYMMETRIC_KEY_LEN);
}
rw_exit_write(&r->r_handshake_lock);
return same ? EEXIST : 0;
}
int
noise_remote_keys(struct noise_remote *r, uint8_t public[NOISE_PUBLIC_KEY_LEN],
uint8_t psk[NOISE_SYMMETRIC_KEY_LEN])
{
static uint8_t null_psk[NOISE_SYMMETRIC_KEY_LEN];
int ret;
if (public != NULL)
memcpy(public, r->r_public, NOISE_PUBLIC_KEY_LEN);
rw_enter_read(&r->r_handshake_lock);
if (psk != NULL)
memcpy(psk, r->r_psk, NOISE_SYMMETRIC_KEY_LEN);
ret = timingsafe_bcmp(r->r_psk, null_psk, NOISE_SYMMETRIC_KEY_LEN);
rw_exit_read(&r->r_handshake_lock);
/* If r_psk != null_psk return 0, else ENOENT (no psk) */
return ret ? 0 : ENOENT;
}
void
noise_remote_precompute(struct noise_remote *r)
{
struct noise_local *l = r->r_local;
rw_assert_wrlock(&l->l_identity_lock);
if (!l->l_has_identity)
bzero(r->r_ss, NOISE_PUBLIC_KEY_LEN);
else if (!curve25519(r->r_ss, l->l_private, r->r_public))
bzero(r->r_ss, NOISE_PUBLIC_KEY_LEN);
rw_enter_write(&r->r_handshake_lock);
noise_remote_handshake_index_drop(r);
explicit_bzero(&r->r_handshake, sizeof(r->r_handshake));
rw_exit_write(&r->r_handshake_lock);
}
/* Handshake functions */
int
noise_create_initiation(struct noise_remote *r, uint32_t *s_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN],
uint8_t es[NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN],
uint8_t ets[NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN])
{
struct noise_handshake *hs = &r->r_handshake;
struct noise_local *l = r->r_local;
uint8_t key[NOISE_SYMMETRIC_KEY_LEN];
int ret = EINVAL;
rw_enter_read(&l->l_identity_lock);
rw_enter_write(&r->r_handshake_lock);
if (!l->l_has_identity)
goto error;
noise_param_init(hs->hs_ck, hs->hs_hash, r->r_public);
/* e */
curve25519_generate_secret(hs->hs_e);
if (curve25519_generate_public(ue, hs->hs_e) == 0)
goto error;
noise_msg_ephemeral(hs->hs_ck, hs->hs_hash, ue);
/* es */
if (noise_mix_dh(hs->hs_ck, key, hs->hs_e, r->r_public) != 0)
goto error;
/* s */
noise_msg_encrypt(es, l->l_public,
NOISE_PUBLIC_KEY_LEN, key, hs->hs_hash);
/* ss */
if (noise_mix_ss(hs->hs_ck, key, r->r_ss) != 0)
goto error;
/* {t} */
noise_tai64n_now(ets);
noise_msg_encrypt(ets, ets,
NOISE_TIMESTAMP_LEN, key, hs->hs_hash);
noise_remote_handshake_index_drop(r);
hs->hs_state = CREATED_INITIATION;
hs->hs_local_index = noise_remote_handshake_index_get(r);
*s_idx = hs->hs_local_index;
ret = 0;
error:
rw_exit_write(&r->r_handshake_lock);
rw_exit_read(&l->l_identity_lock);
explicit_bzero(key, NOISE_SYMMETRIC_KEY_LEN);
return ret;
}
int
noise_consume_initiation(struct noise_local *l, struct noise_remote **rp,
uint32_t s_idx, uint8_t ue[NOISE_PUBLIC_KEY_LEN],
uint8_t es[NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN],
uint8_t ets[NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN])
{
struct noise_remote *r;
struct noise_handshake hs;
uint8_t key[NOISE_SYMMETRIC_KEY_LEN];
uint8_t r_public[NOISE_PUBLIC_KEY_LEN];
uint8_t timestamp[NOISE_TIMESTAMP_LEN];
int ret = EINVAL;
rw_enter_read(&l->l_identity_lock);
if (!l->l_has_identity)
goto error;
noise_param_init(hs.hs_ck, hs.hs_hash, l->l_public);
/* e */
noise_msg_ephemeral(hs.hs_ck, hs.hs_hash, ue);
/* es */
if (noise_mix_dh(hs.hs_ck, key, l->l_private, ue) != 0)
goto error;
/* s */
if (noise_msg_decrypt(r_public, es,
NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN, key, hs.hs_hash) != 0)
goto error;
/* Lookup the remote we received from */
if ((r = l->l_upcall.u_remote_get(l->l_upcall.u_arg, r_public)) == NULL)
goto error;
/* ss */
if (noise_mix_ss(hs.hs_ck, key, r->r_ss) != 0)
goto error;
/* {t} */
if (noise_msg_decrypt(timestamp, ets,
NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN, key, hs.hs_hash) != 0)
goto error;
hs.hs_state = CONSUMED_INITIATION;
hs.hs_local_index = 0;
hs.hs_remote_index = s_idx;
memcpy(hs.hs_e, ue, NOISE_PUBLIC_KEY_LEN);
/* We have successfully computed the same results, now we ensure that
* this is not an initiation replay, or a flood attack */
rw_enter_write(&r->r_handshake_lock);
/* Replay */
if (memcmp(timestamp, r->r_timestamp, NOISE_TIMESTAMP_LEN) > 0)
memcpy(r->r_timestamp, timestamp, NOISE_TIMESTAMP_LEN);
else
goto error_set;
/* Flood attack */
if (noise_timer_expired(&r->r_last_init, 0, REJECT_INTERVAL))
getnanouptime(&r->r_last_init);
else
goto error_set;
/* Ok, we're happy to accept this initiation now */
noise_remote_handshake_index_drop(r);
r->r_handshake = hs;
*rp = r;
ret = 0;
error_set:
rw_exit_write(&r->r_handshake_lock);
error:
rw_exit_read(&l->l_identity_lock);
explicit_bzero(key, NOISE_SYMMETRIC_KEY_LEN);
explicit_bzero(&hs, sizeof(hs));
return ret;
}
int
noise_create_response(struct noise_remote *r, uint32_t *s_idx, uint32_t *r_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN], uint8_t en[0 + NOISE_AUTHTAG_LEN])
{
struct noise_handshake *hs = &r->r_handshake;
uint8_t key[NOISE_SYMMETRIC_KEY_LEN];
uint8_t e[NOISE_PUBLIC_KEY_LEN];
int ret = EINVAL;
rw_enter_read(&r->r_local->l_identity_lock);
rw_enter_write(&r->r_handshake_lock);
if (hs->hs_state != CONSUMED_INITIATION)
goto error;
/* e */
curve25519_generate_secret(e);
if (curve25519_generate_public(ue, e) == 0)
goto error;
noise_msg_ephemeral(hs->hs_ck, hs->hs_hash, ue);
/* ee */
if (noise_mix_dh(hs->hs_ck, NULL, e, hs->hs_e) != 0)
goto error;
/* se */
if (noise_mix_dh(hs->hs_ck, NULL, e, r->r_public) != 0)
goto error;
/* psk */
noise_mix_psk(hs->hs_ck, hs->hs_hash, key, r->r_psk);
/* {} */
noise_msg_encrypt(en, NULL, 0, key, hs->hs_hash);
hs->hs_state = CREATED_RESPONSE;
hs->hs_local_index = noise_remote_handshake_index_get(r);
*r_idx = hs->hs_remote_index;
*s_idx = hs->hs_local_index;
ret = 0;
error:
rw_exit_write(&r->r_handshake_lock);
rw_exit_read(&r->r_local->l_identity_lock);
explicit_bzero(key, NOISE_SYMMETRIC_KEY_LEN);
explicit_bzero(e, NOISE_PUBLIC_KEY_LEN);
return ret;
}
int
noise_consume_response(struct noise_remote *r, uint32_t s_idx, uint32_t r_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN], uint8_t en[0 + NOISE_AUTHTAG_LEN])
{
struct noise_local *l = r->r_local;
struct noise_handshake hs;
uint8_t key[NOISE_SYMMETRIC_KEY_LEN];
uint8_t preshared_key[NOISE_PUBLIC_KEY_LEN];
int ret = EINVAL;
rw_enter_read(&l->l_identity_lock);
if (!l->l_has_identity)
goto error;
rw_enter_read(&r->r_handshake_lock);
hs = r->r_handshake;
memcpy(preshared_key, r->r_psk, NOISE_SYMMETRIC_KEY_LEN);
rw_exit_read(&r->r_handshake_lock);
if (hs.hs_state != CREATED_INITIATION ||
hs.hs_local_index != r_idx)
goto error;
/* e */
noise_msg_ephemeral(hs.hs_ck, hs.hs_hash, ue);
/* ee */
if (noise_mix_dh(hs.hs_ck, NULL, hs.hs_e, ue) != 0)
goto error;
/* se */
if (noise_mix_dh(hs.hs_ck, NULL, l->l_private, ue) != 0)
goto error;
/* psk */
noise_mix_psk(hs.hs_ck, hs.hs_hash, key, preshared_key);
/* {} */
if (noise_msg_decrypt(NULL, en,
0 + NOISE_AUTHTAG_LEN, key, hs.hs_hash) != 0)
goto error;
hs.hs_remote_index = s_idx;
rw_enter_write(&r->r_handshake_lock);
if (r->r_handshake.hs_state == hs.hs_state &&
r->r_handshake.hs_local_index == hs.hs_local_index) {
r->r_handshake = hs;
r->r_handshake.hs_state = CONSUMED_RESPONSE;
ret = 0;
}
rw_exit_write(&r->r_handshake_lock);
error:
rw_exit_read(&l->l_identity_lock);
explicit_bzero(&hs, sizeof(hs));
explicit_bzero(key, NOISE_SYMMETRIC_KEY_LEN);
return ret;
}
int
noise_remote_begin_session(struct noise_remote *r)
{
struct noise_handshake *hs = &r->r_handshake;
struct noise_keypair kp, *next, *current, *previous;
rw_enter_write(&r->r_handshake_lock);
/* We now derive the keypair from the handshake */
if (hs->hs_state == CONSUMED_RESPONSE) {
kp.kp_is_initiator = 1;
noise_kdf(kp.kp_send, kp.kp_recv, NULL, NULL,
NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0,
hs->hs_ck);
} else if (hs->hs_state == CREATED_RESPONSE) {
kp.kp_is_initiator = 0;
noise_kdf(kp.kp_recv, kp.kp_send, NULL, NULL,
NOISE_SYMMETRIC_KEY_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, 0,
hs->hs_ck);
} else {
rw_exit_write(&r->r_handshake_lock);
return EINVAL;
}
kp.kp_valid = 1;
kp.kp_local_index = hs->hs_local_index;
kp.kp_remote_index = hs->hs_remote_index;
getnanouptime(&kp.kp_birthdate);
bzero(&kp.kp_ctr, sizeof(kp.kp_ctr));
rw_init(&kp.kp_ctr.c_lock, "noise_counter");
/* Now we need to add_new_keypair */
rw_enter_write(&r->r_keypair_lock);
next = r->r_next;
current = r->r_current;
previous = r->r_previous;
if (kp.kp_is_initiator) {
if (next != NULL) {
r->r_next = NULL;
r->r_previous = next;
noise_remote_keypair_free(r, current);
} else {
r->r_previous = current;
}
noise_remote_keypair_free(r, previous);
r->r_current = noise_remote_keypair_allocate(r);
*r->r_current = kp;
} else {
noise_remote_keypair_free(r, next);
r->r_previous = NULL;
noise_remote_keypair_free(r, previous);
r->r_next = noise_remote_keypair_allocate(r);
*r->r_next = kp;
}
rw_exit_write(&r->r_keypair_lock);
explicit_bzero(&r->r_handshake, sizeof(r->r_handshake));
rw_exit_write(&r->r_handshake_lock);
explicit_bzero(&kp, sizeof(kp));
return 0;
}
void
noise_remote_clear(struct noise_remote *r)
{
rw_enter_write(&r->r_handshake_lock);
noise_remote_handshake_index_drop(r);
explicit_bzero(&r->r_handshake, sizeof(r->r_handshake));
rw_exit_write(&r->r_handshake_lock);
rw_enter_write(&r->r_keypair_lock);
noise_remote_keypair_free(r, r->r_next);
noise_remote_keypair_free(r, r->r_current);
noise_remote_keypair_free(r, r->r_previous);
r->r_next = NULL;
r->r_current = NULL;
r->r_previous = NULL;
rw_exit_write(&r->r_keypair_lock);
}
void
noise_remote_expire_current(struct noise_remote *r)
{
rw_enter_write(&r->r_keypair_lock);
if (r->r_next != NULL)
r->r_next->kp_valid = 0;
if (r->r_current != NULL)
r->r_current->kp_valid = 0;
rw_exit_write(&r->r_keypair_lock);
}
int
noise_remote_ready(struct noise_remote *r)
{
struct noise_keypair *kp;
int ret;
rw_enter_read(&r->r_keypair_lock);
/* kp_ctr isn't locked here, we're happy to accept a racy read. */
if ((kp = r->r_current) == NULL ||
!kp->kp_valid ||
noise_timer_expired(&kp->kp_birthdate, REJECT_AFTER_TIME, 0) ||
kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES ||
kp->kp_ctr.c_send >= REJECT_AFTER_MESSAGES)
ret = EINVAL;
else
ret = 0;
rw_exit_read(&r->r_keypair_lock);
return ret;
}
int
noise_remote_encrypt(struct noise_remote *r, uint32_t *r_idx, uint64_t *nonce,
uint8_t *buf, size_t buflen)
{
struct noise_keypair *kp;
int ret = EINVAL;
rw_enter_read(&r->r_keypair_lock);
if ((kp = r->r_current) == NULL)
goto error;
/* We confirm that our values are within our tolerances. We want:
* - a valid keypair
* - our keypair to be less than REJECT_AFTER_TIME seconds old
* - our receive counter to be less than REJECT_AFTER_MESSAGES
* - our send counter to be less than REJECT_AFTER_MESSAGES
*
* kp_ctr isn't locked here, we're happy to accept a racy read. */
if (!kp->kp_valid ||
noise_timer_expired(&kp->kp_birthdate, REJECT_AFTER_TIME, 0) ||
kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES ||
((*nonce = noise_counter_send(&kp->kp_ctr)) > REJECT_AFTER_MESSAGES))
goto error;
/* We encrypt into the same buffer, so the caller must ensure that buf
* has NOISE_AUTHTAG_LEN bytes to store the MAC. The nonce and index
* are passed back out to the caller through the provided data pointer. */
*r_idx = kp->kp_remote_index;
chacha20poly1305_encrypt(buf, buf, buflen,
NULL, 0, *nonce, kp->kp_send);
/* If our values are still within tolerances, but we are approaching
* the tolerances, we notify the caller with ESTALE that they should
* establish a new keypair. The current keypair can continue to be used
* until the tolerances are hit. We notify if:
* - our send counter is valid and not less than REKEY_AFTER_MESSAGES
* - we're the initiator and our keypair is older than
* REKEY_AFTER_TIME seconds */
ret = ESTALE;
if ((kp->kp_valid && *nonce >= REKEY_AFTER_MESSAGES) ||
(kp->kp_is_initiator &&
noise_timer_expired(&kp->kp_birthdate, REKEY_AFTER_TIME, 0)))
goto error;
ret = 0;
error:
rw_exit_read(&r->r_keypair_lock);
return ret;
}
int
noise_remote_decrypt(struct noise_remote *r, uint32_t r_idx, uint64_t nonce,
uint8_t *buf, size_t buflen)
{
struct noise_keypair *kp;
int ret = EINVAL;
/* We retrieve the keypair corresponding to the provided index. We
* attempt the current keypair first as that is most likely. We also
* want to make sure that the keypair is valid as it would be
* catastrophic to decrypt against a zero'ed keypair. */
rw_enter_read(&r->r_keypair_lock);
if (r->r_current != NULL && r->r_current->kp_local_index == r_idx) {
kp = r->r_current;
} else if (r->r_previous != NULL && r->r_previous->kp_local_index == r_idx) {
kp = r->r_previous;
} else if (r->r_next != NULL && r->r_next->kp_local_index == r_idx) {
kp = r->r_next;
} else {
goto error;
}
/* We confirm that our values are within our tolerances. These values
* are the same as the encrypt routine.
*
* kp_ctr isn't locked here, we're happy to accept a racy read. */
if (noise_timer_expired(&kp->kp_birthdate, REJECT_AFTER_TIME, 0) ||
kp->kp_ctr.c_recv >= REJECT_AFTER_MESSAGES)
goto error;
/* Decrypt, then validate the counter. We don't want to validate the
* counter before decrypting as we do not know the message is authentic
* prior to decryption. */
if (chacha20poly1305_decrypt(buf, buf, buflen,
NULL, 0, nonce, kp->kp_recv) == 0)
goto error;
if (noise_counter_recv(&kp->kp_ctr, nonce) != 0)
goto error;
/* If we've received the handshake confirming data packet then move the
* next keypair into current. If we do slide the next keypair in, then
* we skip the REKEY_AFTER_TIME_RECV check. This is safe to do as a
* data packet can't confirm a session that we are an INITIATOR of. */
if (kp == r->r_next) {
rw_exit_read(&r->r_keypair_lock);
rw_enter_write(&r->r_keypair_lock);
if (kp == r->r_next && kp->kp_local_index == r_idx) {
noise_remote_keypair_free(r, r->r_previous);
r->r_previous = r->r_current;
r->r_current = r->r_next;
r->r_next = NULL;
ret = ECONNRESET;
goto error;
}
rw_enter(&r->r_keypair_lock, RW_DOWNGRADE);
}
/* Similar to when we encrypt, we want to notify the caller when we
* are approaching our tolerances. We notify if:
* - we're the initiator and the current keypair is older than
* REKEY_AFTER_TIME_RECV seconds. */
ret = ESTALE;
kp = r->r_current;
if (kp != NULL &&
kp->kp_valid &&
kp->kp_is_initiator &&
noise_timer_expired(&kp->kp_birthdate, REKEY_AFTER_TIME_RECV, 0))
goto error;
ret = 0;
error:
rw_exit(&r->r_keypair_lock);
return ret;
}
/* Private functions - these should not be called outside this file under any
* circumstances. */
static struct noise_keypair *
noise_remote_keypair_allocate(struct noise_remote *r)
{
struct noise_keypair *kp;
kp = SLIST_FIRST(&r->r_unused_keypairs);
SLIST_REMOVE_HEAD(&r->r_unused_keypairs, kp_entry);
return kp;
}
static void
noise_remote_keypair_free(struct noise_remote *r, struct noise_keypair *kp)
{
struct noise_upcall *u = &r->r_local->l_upcall;
if (kp != NULL) {
SLIST_INSERT_HEAD(&r->r_unused_keypairs, kp, kp_entry);
u->u_index_drop(u->u_arg, kp->kp_local_index);
bzero(kp->kp_send, sizeof(kp->kp_send));
bzero(kp->kp_recv, sizeof(kp->kp_recv));
}
}
static uint32_t
noise_remote_handshake_index_get(struct noise_remote *r)
{
struct noise_upcall *u = &r->r_local->l_upcall;
return u->u_index_set(u->u_arg, r);
}
static void
noise_remote_handshake_index_drop(struct noise_remote *r)
{
struct noise_handshake *hs = &r->r_handshake;
struct noise_upcall *u = &r->r_local->l_upcall;
rw_assert_wrlock(&r->r_handshake_lock);
if (hs->hs_state != HS_ZEROED)
u->u_index_drop(u->u_arg, hs->hs_local_index);
}
static uint64_t
noise_counter_send(struct noise_counter *ctr)
{
uint64_t ret;
rw_enter_write(&ctr->c_lock);
ret = ctr->c_send++;
rw_exit_write(&ctr->c_lock);
return ret;
}
static int
noise_counter_recv(struct noise_counter *ctr, uint64_t recv)
{
uint64_t i, top, index_recv, index_ctr;
unsigned long bit;
int ret = EEXIST;
rw_enter_write(&ctr->c_lock);
/* Check that the recv counter is valid */
if (ctr->c_recv >= REJECT_AFTER_MESSAGES ||
recv >= REJECT_AFTER_MESSAGES)
goto error;
/* If the packet is out of the window, invalid */
if (recv + COUNTER_WINDOW_SIZE < ctr->c_recv)
goto error;
/* If the new counter is ahead of the current counter, we'll need to
* zero out the bitmap that has previously been used */
index_recv = recv / COUNTER_BITS;
index_ctr = ctr->c_recv / COUNTER_BITS;
if (recv > ctr->c_recv) {
top = MIN(index_recv - index_ctr, COUNTER_NUM);
for (i = 1; i <= top; i++)
ctr->c_backtrack[
(i + index_ctr) & (COUNTER_NUM - 1)] = 0;
ctr->c_recv = recv;
}
index_recv %= COUNTER_NUM;
bit = 1ul << (recv % COUNTER_BITS);
if (ctr->c_backtrack[index_recv] & bit)
goto error;
ctr->c_backtrack[index_recv] |= bit;
ret = 0;
error:
rw_exit_write(&ctr->c_lock);
return ret;
}
static void
noise_kdf(uint8_t *a, uint8_t *b, uint8_t *c, const uint8_t *x,
size_t a_len, size_t b_len, size_t c_len, size_t x_len,
const uint8_t ck[NOISE_HASH_LEN])
{
uint8_t out[BLAKE2S_HASH_SIZE + 1];
uint8_t sec[BLAKE2S_HASH_SIZE];
#ifdef DIAGNOSTIC
MPASS(a_len <= BLAKE2S_HASH_SIZE && b_len <= BLAKE2S_HASH_SIZE &&
c_len <= BLAKE2S_HASH_SIZE);
MPASS(!(b || b_len || c || c_len) || (a && a_len));
MPASS(!(c || c_len) || (b && b_len));
#endif
/* Extract entropy from "x" into sec */
blake2s_hmac(sec, x, ck, BLAKE2S_HASH_SIZE, x_len, NOISE_HASH_LEN);
if (a == NULL || a_len == 0)
goto out;
/* Expand first key: key = sec, data = 0x1 */
out[0] = 1;
blake2s_hmac(out, out, sec, BLAKE2S_HASH_SIZE, 1, BLAKE2S_HASH_SIZE);
memcpy(a, out, a_len);
if (b == NULL || b_len == 0)
goto out;
/* Expand second key: key = sec, data = "a" || 0x2 */
out[BLAKE2S_HASH_SIZE] = 2;
blake2s_hmac(out, out, sec, BLAKE2S_HASH_SIZE, BLAKE2S_HASH_SIZE + 1,
BLAKE2S_HASH_SIZE);
memcpy(b, out, b_len);
if (c == NULL || c_len == 0)
goto out;
/* Expand third key: key = sec, data = "b" || 0x3 */
out[BLAKE2S_HASH_SIZE] = 3;
blake2s_hmac(out, out, sec, BLAKE2S_HASH_SIZE, BLAKE2S_HASH_SIZE + 1,
BLAKE2S_HASH_SIZE);
memcpy(c, out, c_len);
out:
/* Clear sensitive data from stack */
explicit_bzero(sec, BLAKE2S_HASH_SIZE);
explicit_bzero(out, BLAKE2S_HASH_SIZE + 1);
}
static int
noise_mix_dh(uint8_t ck[NOISE_HASH_LEN], uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
const uint8_t private[NOISE_PUBLIC_KEY_LEN],
const uint8_t public[NOISE_PUBLIC_KEY_LEN])
{
uint8_t dh[NOISE_PUBLIC_KEY_LEN];
if (!curve25519(dh, private, public))
return EINVAL;
noise_kdf(ck, key, NULL, dh,
NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, ck);
explicit_bzero(dh, NOISE_PUBLIC_KEY_LEN);
return 0;
}
static int
noise_mix_ss(uint8_t ck[NOISE_HASH_LEN], uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
const uint8_t ss[NOISE_PUBLIC_KEY_LEN])
{
static uint8_t null_point[NOISE_PUBLIC_KEY_LEN];
if (timingsafe_bcmp(ss, null_point, NOISE_PUBLIC_KEY_LEN) == 0)
return ENOENT;
noise_kdf(ck, key, NULL, ss,
NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN, 0, NOISE_PUBLIC_KEY_LEN, ck);
return 0;
}
static void
noise_mix_hash(uint8_t hash[NOISE_HASH_LEN], const uint8_t *src,
size_t src_len)
{
struct blake2s_state blake;
blake2s_init(&blake, NOISE_HASH_LEN);
blake2s_update(&blake, hash, NOISE_HASH_LEN);
blake2s_update(&blake, src, src_len);
blake2s_final(&blake, hash);
}
static void
noise_mix_psk(uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN],
uint8_t key[NOISE_SYMMETRIC_KEY_LEN],
const uint8_t psk[NOISE_SYMMETRIC_KEY_LEN])
{
uint8_t tmp[NOISE_HASH_LEN];
noise_kdf(ck, tmp, key, psk,
NOISE_HASH_LEN, NOISE_HASH_LEN, NOISE_SYMMETRIC_KEY_LEN,
NOISE_SYMMETRIC_KEY_LEN, ck);
noise_mix_hash(hash, tmp, NOISE_HASH_LEN);
explicit_bzero(tmp, NOISE_HASH_LEN);
}
static void
noise_param_init(uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN],
const uint8_t s[NOISE_PUBLIC_KEY_LEN])
{
struct blake2s_state blake;
blake2s(ck, (uint8_t *)NOISE_HANDSHAKE_NAME, NULL,
NOISE_HASH_LEN, strlen(NOISE_HANDSHAKE_NAME), 0);
blake2s_init(&blake, NOISE_HASH_LEN);
blake2s_update(&blake, ck, NOISE_HASH_LEN);
blake2s_update(&blake, (uint8_t *)NOISE_IDENTIFIER_NAME,
strlen(NOISE_IDENTIFIER_NAME));
blake2s_final(&blake, hash);
noise_mix_hash(hash, s, NOISE_PUBLIC_KEY_LEN);
}
static void
noise_msg_encrypt(uint8_t *dst, const uint8_t *src, size_t src_len,
uint8_t key[NOISE_SYMMETRIC_KEY_LEN], uint8_t hash[NOISE_HASH_LEN])
{
/* Nonce always zero for Noise_IK */
chacha20poly1305_encrypt(dst, src, src_len,
hash, NOISE_HASH_LEN, 0, key);
noise_mix_hash(hash, dst, src_len + NOISE_AUTHTAG_LEN);
}
static int
noise_msg_decrypt(uint8_t *dst, const uint8_t *src, size_t src_len,
uint8_t key[NOISE_SYMMETRIC_KEY_LEN], uint8_t hash[NOISE_HASH_LEN])
{
/* Nonce always zero for Noise_IK */
if (!chacha20poly1305_decrypt(dst, src, src_len,
hash, NOISE_HASH_LEN, 0, key))
return EINVAL;
noise_mix_hash(hash, src, src_len);
return 0;
}
static void
noise_msg_ephemeral(uint8_t ck[NOISE_HASH_LEN], uint8_t hash[NOISE_HASH_LEN],
const uint8_t src[NOISE_PUBLIC_KEY_LEN])
{
noise_mix_hash(hash, src, NOISE_PUBLIC_KEY_LEN);
noise_kdf(ck, NULL, NULL, src, NOISE_HASH_LEN, 0, 0,
NOISE_PUBLIC_KEY_LEN, ck);
}
static void
noise_tai64n_now(uint8_t output[NOISE_TIMESTAMP_LEN])
{
struct timespec time;
uint64_t sec;
uint32_t nsec;
getnanotime(&time);
/* Round down the nsec counter to limit precise timing leak. */
time.tv_nsec &= REJECT_INTERVAL_MASK;
/* https://cr.yp.to/libtai/tai64.html */
sec = htobe64(0x400000000000000aULL + time.tv_sec);
nsec = htobe32(time.tv_nsec);
/* memcpy to output buffer, assuming output could be unaligned. */
memcpy(output, &sec, sizeof(sec));
memcpy(output + sizeof(sec), &nsec, sizeof(nsec));
}
static int
noise_timer_expired(struct timespec *birthdate, time_t sec, long nsec)
{
struct timespec uptime;
struct timespec expire = { .tv_sec = sec, .tv_nsec = nsec };
/* We don't really worry about a zeroed birthdate, to avoid the extra
* check on every encrypt/decrypt. This does mean that r_last_init
* check may fail if getnanouptime is < REJECT_INTERVAL from 0. */
getnanouptime(&uptime);
timespecadd(birthdate, &expire, &expire);
return timespeccmp(&uptime, &expire, >) ? ETIMEDOUT : 0;
}

View File

@ -1,191 +0,0 @@
/*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
* Copyright (C) 2019-2020 Matt Dunwoodie <ncon@noconroy.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef __NOISE_H__
#define __NOISE_H__
#include <sys/types.h>
#include <sys/time.h>
#include <sys/rwlock.h>
#include "crypto.h"
#define NOISE_PUBLIC_KEY_LEN CURVE25519_KEY_SIZE
#define NOISE_SYMMETRIC_KEY_LEN CHACHA20POLY1305_KEY_SIZE
#define NOISE_TIMESTAMP_LEN (sizeof(uint64_t) + sizeof(uint32_t))
#define NOISE_AUTHTAG_LEN CHACHA20POLY1305_AUTHTAG_SIZE
#define NOISE_HASH_LEN BLAKE2S_HASH_SIZE
/* Protocol string constants */
#define NOISE_HANDSHAKE_NAME "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"
#define NOISE_IDENTIFIER_NAME "WireGuard v1 zx2c4 Jason@zx2c4.com"
/* Constants for the counter */
#define COUNTER_BITS_TOTAL 8192
#define COUNTER_BITS (sizeof(unsigned long) * 8)
#define COUNTER_NUM (COUNTER_BITS_TOTAL / COUNTER_BITS)
#define COUNTER_WINDOW_SIZE (COUNTER_BITS_TOTAL - COUNTER_BITS)
/* Constants for the keypair */
#define REKEY_AFTER_MESSAGES (1ull << 60)
#define REJECT_AFTER_MESSAGES (UINT64_MAX - COUNTER_WINDOW_SIZE - 1)
#define REKEY_AFTER_TIME 120
#define REKEY_AFTER_TIME_RECV 165
#define REJECT_AFTER_TIME 180
#define REJECT_INTERVAL (1000000000 / 50) /* fifty times per sec */
/* 24 = floor(log2(REJECT_INTERVAL)) */
#define REJECT_INTERVAL_MASK (~((1ull<<24)-1))
enum noise_state_hs {
HS_ZEROED = 0,
CREATED_INITIATION,
CONSUMED_INITIATION,
CREATED_RESPONSE,
CONSUMED_RESPONSE,
};
struct noise_handshake {
enum noise_state_hs hs_state;
uint32_t hs_local_index;
uint32_t hs_remote_index;
uint8_t hs_e[NOISE_PUBLIC_KEY_LEN];
uint8_t hs_hash[NOISE_HASH_LEN];
uint8_t hs_ck[NOISE_HASH_LEN];
};
struct noise_counter {
struct rwlock c_lock;
uint64_t c_send;
uint64_t c_recv;
unsigned long c_backtrack[COUNTER_NUM];
};
struct noise_keypair {
SLIST_ENTRY(noise_keypair) kp_entry;
int kp_valid;
int kp_is_initiator;
uint32_t kp_local_index;
uint32_t kp_remote_index;
uint8_t kp_send[NOISE_SYMMETRIC_KEY_LEN];
uint8_t kp_recv[NOISE_SYMMETRIC_KEY_LEN];
struct timespec kp_birthdate; /* nanouptime */
struct noise_counter kp_ctr;
};
struct noise_remote {
uint8_t r_public[NOISE_PUBLIC_KEY_LEN];
struct noise_local *r_local;
uint8_t r_ss[NOISE_PUBLIC_KEY_LEN];
struct rwlock r_handshake_lock;
struct noise_handshake r_handshake;
uint8_t r_psk[NOISE_SYMMETRIC_KEY_LEN];
uint8_t r_timestamp[NOISE_TIMESTAMP_LEN];
struct timespec r_last_init; /* nanouptime */
struct rwlock r_keypair_lock;
SLIST_HEAD(,noise_keypair) r_unused_keypairs;
struct noise_keypair *r_next, *r_current, *r_previous;
struct noise_keypair r_keypair[3]; /* 3: next, current, previous. */
};
struct noise_local {
struct rwlock l_identity_lock;
int l_has_identity;
uint8_t l_public[NOISE_PUBLIC_KEY_LEN];
uint8_t l_private[NOISE_PUBLIC_KEY_LEN];
struct noise_upcall {
void *u_arg;
struct noise_remote *
(*u_remote_get)(void *, uint8_t[NOISE_PUBLIC_KEY_LEN]);
uint32_t
(*u_index_set)(void *, struct noise_remote *);
void (*u_index_drop)(void *, uint32_t);
} l_upcall;
};
/* Set/Get noise parameters */
void noise_local_init(struct noise_local *, struct noise_upcall *);
void noise_local_lock_identity(struct noise_local *);
void noise_local_unlock_identity(struct noise_local *);
int noise_local_set_private(struct noise_local *,
const uint8_t[NOISE_PUBLIC_KEY_LEN]);
int noise_local_keys(struct noise_local *, uint8_t[NOISE_PUBLIC_KEY_LEN],
uint8_t[NOISE_PUBLIC_KEY_LEN]);
void noise_remote_init(struct noise_remote *,
const uint8_t[NOISE_PUBLIC_KEY_LEN], struct noise_local *);
int noise_remote_set_psk(struct noise_remote *,
const uint8_t[NOISE_SYMMETRIC_KEY_LEN]);
int noise_remote_keys(struct noise_remote *, uint8_t[NOISE_PUBLIC_KEY_LEN],
uint8_t[NOISE_SYMMETRIC_KEY_LEN]);
/* Should be called anytime noise_local_set_private is called */
void noise_remote_precompute(struct noise_remote *);
/* Cryptographic functions */
int noise_create_initiation(
struct noise_remote *,
uint32_t *s_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN],
uint8_t es[NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN],
uint8_t ets[NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN]);
int noise_consume_initiation(
struct noise_local *,
struct noise_remote **,
uint32_t s_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN],
uint8_t es[NOISE_PUBLIC_KEY_LEN + NOISE_AUTHTAG_LEN],
uint8_t ets[NOISE_TIMESTAMP_LEN + NOISE_AUTHTAG_LEN]);
int noise_create_response(
struct noise_remote *,
uint32_t *s_idx,
uint32_t *r_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN],
uint8_t en[0 + NOISE_AUTHTAG_LEN]);
int noise_consume_response(
struct noise_remote *,
uint32_t s_idx,
uint32_t r_idx,
uint8_t ue[NOISE_PUBLIC_KEY_LEN],
uint8_t en[0 + NOISE_AUTHTAG_LEN]);
int noise_remote_begin_session(struct noise_remote *);
void noise_remote_clear(struct noise_remote *);
void noise_remote_expire_current(struct noise_remote *);
int noise_remote_ready(struct noise_remote *);
int noise_remote_encrypt(
struct noise_remote *,
uint32_t *r_idx,
uint64_t *nonce,
uint8_t *buf,
size_t buflen);
int noise_remote_decrypt(
struct noise_remote *,
uint32_t r_idx,
uint64_t nonce,
uint8_t *buf,
size_t buflen);
#endif /* __NOISE_H__ */

View File

@ -3433,7 +3433,6 @@ prison_priv_check(struct ucred *cred, int priv)
case PRIV_NET_GIF:
case PRIV_NET_SETIFVNET:
case PRIV_NET_SETIFFIB:
case PRIV_NET_WG:
/*
* 802.11-related privileges.

View File

@ -850,17 +850,6 @@ sopeeloff(struct socket *head)
}
#endif /* SCTP */
int
sogetsockaddr(struct socket *so, struct sockaddr **nam)
{
int error;
CURVNET_SET(so->so_vnet);
error = (*so->so_proto->pr_usrreqs->pru_sockaddr)(so, nam);
CURVNET_RESTORE();
return (error);
}
int
sobind(struct socket *so, struct sockaddr *nam, struct thread *td)
{

View File

@ -1386,7 +1386,9 @@ kern_getsockname(struct thread *td, int fd, struct sockaddr **sa,
return (error);
so = fp->f_data;
*sa = NULL;
error = sogetsockaddr(so, sa);
CURVNET_SET(so->so_vnet);
error = (*so->so_proto->pr_usrreqs->pru_sockaddr)(so, sa);
CURVNET_RESTORE();
if (error != 0)
goto bad;
if (*sa == NULL)

View File

@ -163,7 +163,6 @@ SUBDIR= \
if_tuntap \
if_vlan \
if_vxlan \
${_if_wg} \
iflib \
${_iir} \
imgact_binmisc \

View File

@ -1,12 +0,0 @@
# $FreeBSD$
KMOD= if_wg
.PATH: ${SRCTOP}/sys/dev/if_wg
SRCS= opt_inet.h opt_inet6.h device_if.h bus_if.h ifdi_if.h
SRCS+= if_wg.c wg_noise.c wg_cookie.c crypto.c
.include <bsd.kmod.mk>

View File

@ -256,7 +256,6 @@ typedef enum {
IFT_ENC = 0xf4, /* Encapsulating interface */
IFT_PFLOG = 0xf6, /* PF packet filter logging */
IFT_PFSYNC = 0xf7, /* PF packet filter synchronization */
IFT_WIREGUARD = 0xf8, /* WireGuard tunnel */
} ifType;
/*

View File

@ -284,8 +284,8 @@ nd6_ifattach(struct ifnet *ifp)
* default regardless of the V_ip6_auto_linklocal configuration to
* give a reasonable default behavior.
*/
if ((V_ip6_auto_linklocal && ifp->if_type != IFT_BRIDGE &&
ifp->if_type != IFT_WIREGUARD) || (ifp->if_flags & IFF_LOOPBACK))
if ((V_ip6_auto_linklocal && ifp->if_type != IFT_BRIDGE) ||
(ifp->if_flags & IFF_LOOPBACK))
nd->flags |= ND6_IFF_AUTO_LINKLOCAL;
/*
* A loopback interface does not need to accept RTADV.

View File

@ -347,7 +347,6 @@
#define PRIV_NET_VXLAN 420 /* Administer vxlan. */
#define PRIV_NET_SETLANPCP 421 /* Set LAN priority. */
#define PRIV_NET_SETVLANPCP PRIV_NET_SETLANPCP /* Alias Set VLAN priority */
#define PRIV_NET_WG 422 /* Administrate if_wg. */
/*
* 802.11-related privileges.

View File

@ -414,7 +414,6 @@ void soaio_enqueue(struct task *task);
void soaio_rcv(void *context, int pending);
void soaio_snd(void *context, int pending);
int socheckuid(struct socket *so, uid_t uid);
int sogetsockaddr(struct socket *so, struct sockaddr **nam);
int sobind(struct socket *so, struct sockaddr *nam, struct thread *td);
int sobindat(int fd, struct socket *so, struct sockaddr *nam,
struct thread *td);

View File

@ -10,15 +10,7 @@ ATF_TESTS_C= ip_reass_test \
socket_afinet \
tcp_connect_port_test
ATF_TESTS_SH= carp fibs \
fibs_test \
redirect \
divert \
forward \
output \
lpm \
arp \
if_wg_test
ATF_TESTS_SH= carp fibs fibs_test redirect divert forward output lpm arp
TEST_METADATA.output+= required_programs="python"
PROGS= udp_dontroute tcp_user_cookie

View File

@ -1,188 +0,0 @@
# $FreeBSD$
#
# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
#
# Copyright (c) 2021 The FreeBSD Foundation
#
# This software was developed by Mark Johnston under sponsorship
# from the FreeBSD Foundation.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
. $(atf_get_srcdir)/../common/vnet.subr
atf_test_case "wg_basic" "cleanup"
wg_basic_head()
{
atf_set descr 'Create a wg(4) tunnel over an epair and pass traffic between jails'
atf_set require.user root
}
wg_basic_body()
{
local epair pri1 pri2 pub1 pub2 wg1 wg2
local endpoint1 endpoint2 tunnel1 tunnel2
kldload -n if_wg
pri1=$(openssl rand -base64 32)
pri2=$(openssl rand -base64 32)
endpoint1=192.168.2.1
endpoint2=192.168.2.2
tunnel1=169.254.0.1
tunnel2=169.254.0.2
epair=$(vnet_mkepair)
vnet_init
vnet_mkjail wgtest1 ${epair}a
vnet_mkjail wgtest2 ${epair}b
# Workaround for PR 254212.
jexec wgtest1 ifconfig lo0 up
jexec wgtest2 ifconfig lo0 up
jexec wgtest1 ifconfig ${epair}a $endpoint1 up
jexec wgtest2 ifconfig ${epair}b $endpoint2 up
wg1=$(jexec wgtest1 ifconfig wg create listen-port 12345 private-key "$pri1")
pub1=$(jexec wgtest1 ifconfig $wg1 | awk '/public-key:/ {print $2}')
wg2=$(jexec wgtest2 ifconfig wg create listen-port 12345 private-key "$pri2")
pub2=$(jexec wgtest2 ifconfig $wg2 | awk '/public-key:/ {print $2}')
atf_check -s exit:0 -o ignore \
jexec wgtest1 ifconfig $wg1 peer public-key "$pub2" \
endpoint ${endpoint2}:12345 allowed-ips ${tunnel2}/32
atf_check -s exit:0 \
jexec wgtest1 ifconfig $wg1 inet $tunnel1 up
atf_check -s exit:0 -o ignore \
jexec wgtest2 ifconfig $wg2 peer public-key "$pub1" \
endpoint ${endpoint1}:12345 allowed-ips ${tunnel1}/32
atf_check -s exit:0 \
jexec wgtest2 ifconfig $wg2 inet $tunnel2 up
# Generous timeout since the handshake takes some time.
atf_check -s exit:0 -o ignore jexec wgtest1 ping -o -t 5 -i 0.25 $tunnel2
atf_check -s exit:0 -o ignore jexec wgtest2 ping -o -t 5 -i 0.25 $tunnel1
}
wg_basic_cleanup()
{
vnet_cleanup
}
# The kernel is expecteld to silently ignore any attempt to add a peer with a
# public key identical to the host's.
atf_test_case "wg_key_peerdev_shared" "cleanup"
wg_key_peerdev_shared_head()
{
atf_set descr 'Create a wg(4) interface with a shared pubkey between device and a peer'
atf_set require.user root
}
wg_key_peerdev_shared_body()
{
local epair pri1 pub1 wg1
local endpoint1 tunnel1
kldload -n if_wg
pri1=$(openssl rand -base64 32)
endpoint1=192.168.2.1
tunnel1=169.254.0.1
vnet_mkjail wgtest1
wg1=$(jexec wgtest1 ifconfig wg create listen-port 12345 private-key "$pri1")
pub1=$(jexec wgtest1 ifconfig $wg1 | awk '/public-key:/ {print $2}')
atf_check -s exit:0 \
jexec wgtest1 ifconfig ${wg1} peer public-key "${pub1}" \
allowed-ips "${tunnel1}/32"
atf_check -o empty jexec wgtest1 ifconfig ${wg1} peers
}
wg_key_peerdev_shared_cleanup()
{
vnet_cleanup
}
# When a wg(8) interface has a private key reassigned that corresponds to the
# public key already on a peer, the kernel is expected to deconfigure the peer
# to resolve the conflict.
atf_test_case "wg_key_peerdev_makeshared" "cleanup"
wg_key_peerdev_makeshared_head()
{
atf_set descr 'Create a wg(4) interface and assign peer key to device'
atf_set require.progs wg
}
wg_key_peerdev_makeshared_body()
{
local epair pri1 pub1 pri2 wg1 wg2
local endpoint1 tunnel1
kldload -n if_wg
pri1=$(openssl rand -base64 32)
pri2=$(openssl rand -base64 32)
endpoint1=192.168.2.1
tunnel1=169.254.0.1
vnet_mkjail wgtest1
wg1=$(jexec wgtest1 ifconfig wg create listen-port 12345 private-key "$pri1")
pub1=$(jexec wgtest1 ifconfig $wg1 | awk '/public-key:/ {print $2}')
wg2=$(jexec wgtest1 ifconfig wg create listen-port 12345 private-key "$pri2")
atf_check -s exit:0 -o ignore \
jexec wgtest1 ifconfig ${wg2} peer public-key "${pub1}" \
allowed-ips "${tunnel1}/32"
atf_check -o not-empty jexec wgtest1 ifconfig ${wg2} peers
jexec wgtest1 sh -c "echo '${pri1}' > pri1"
atf_check -s exit:0 \
jexec wgtest1 wg set ${wg2} private-key pri1
atf_check -o empty jexec wgtest1 ifconfig ${wg2} peers
}
wg_key_peerdev_makeshared_cleanup()
{
vnet_cleanup
}
atf_init_test_cases()
{
atf_add_test_case "wg_basic"
atf_add_test_case "wg_key_peerdev_shared"
atf_add_test_case "wg_key_peerdev_makeshared"
}