Initial import
There's still more to do with wiring this up properly. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
commit
362884e650
|
@ -0,0 +1,17 @@
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,9 @@
|
|||
# WireGuard for FreeBSD
|
||||
|
||||
This is a kernel module for FreeBSD to support [WireGuard](https://www.wireguard.com/). It is being developed here before its eventual submission to FreeBSD 13.1 or 14.
|
||||
|
||||
### Installation instructions
|
||||
|
||||
```
|
||||
TODO
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
# $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>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,103 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#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
|
@ -0,0 +1,37 @@
|
|||
/* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* 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__ */
|
|
@ -0,0 +1,56 @@
|
|||
/* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* 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
|
|
@ -0,0 +1,427 @@
|
|||
/* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* Copyright (C) 2019-2021 Matt Dunwoodie <ncon@noconroy.net>
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* Copyright (C) 2019-2021 Matt Dunwoodie <ncon@noconroy.net>
|
||||
*/
|
||||
|
||||
#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__ */
|
|
@ -0,0 +1,952 @@
|
|||
/* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* Copyright (C) 2019-2021 Matt Dunwoodie <ncon@noconroy.net>
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
/* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (C) 2015-2021 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
* Copyright (C) 2019-2021 Matt Dunwoodie <ncon@noconroy.net>
|
||||
*/
|
||||
|
||||
#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__ */
|
|
@ -0,0 +1,164 @@
|
|||
# $FreeBSD$
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
#
|
||||
# Copyright (c) 2021 The FreeBSD Foundation
|
||||
|
||||
. $(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"
|
||||
}
|
|
@ -0,0 +1,643 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
#
|
||||
# This script tests the below topology:
|
||||
#
|
||||
# ┌─────────────────────┐ ┌──────────────────────────────────┐ ┌─────────────────────┐
|
||||
# │ $ns1 namespace │ │ $ns0 namespace │ │ $ns2 namespace │
|
||||
# │ │ │ │ │ │
|
||||
# │┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐│
|
||||
# ││ wg0 │───────────┼───┼────────────│ lo │────────────┼───┼───────────│ wg0 ││
|
||||
# │├────────┴──────────┐│ │ ┌───────┴────────┴────────┐ │ │┌──────────┴────────┤│
|
||||
# ││192.168.241.1/24 ││ │ │(ns1) (ns2) │ │ ││192.168.241.2/24 ││
|
||||
# ││fd00::1/24 ││ │ │127.0.0.1:1 127.0.0.1:2│ │ ││fd00::2/24 ││
|
||||
# │└───────────────────┘│ │ │[::]:1 [::]:2 │ │ │└───────────────────┘│
|
||||
# └─────────────────────┘ │ └─────────────────────────┘ │ └─────────────────────┘
|
||||
# └──────────────────────────────────┘
|
||||
#
|
||||
# After the topology is prepared we run a series of TCP/UDP iperf3 tests between the
|
||||
# wireguard peers in $ns1 and $ns2. Note that $ns0 is the endpoint for the wg0
|
||||
# interfaces in $ns1 and $ns2. See https://www.wireguard.com/netns/ for further
|
||||
# details on how this is accomplished.
|
||||
set -e
|
||||
|
||||
# Needs iperf3
|
||||
|
||||
exec 3>&1
|
||||
export LANG=C
|
||||
export WG_HIDE_KEYS=never
|
||||
jail0="wg-test-$$-0"
|
||||
jail1="wg-test-$$-1"
|
||||
jail2="wg-test-$$-2"
|
||||
pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
|
||||
pp() { pretty "" "$*"; "$@"; }
|
||||
maybe_exec() { if [[ $BASHPID -eq $$ ]]; then "$@"; else exec "$@"; fi; }
|
||||
j0() { pretty 0 "$*"; maybe_exec jexec $jail0 "$@"; }
|
||||
j1() { pretty 1 "$*"; maybe_exec jexec $jail1 "$@"; }
|
||||
j2() { pretty 2 "$*"; maybe_exec jexec $jail2 "$@"; }
|
||||
ifconfig0() { j0 ifconfig "$@"; }
|
||||
ifconfig1() { j1 ifconfig "$@"; }
|
||||
ifconfig2() { j2 ifconfig "$@"; }
|
||||
sleep() { read -t "$1" -N 1 || true; }
|
||||
#waitiperf() { pretty "${1//*-}" "wait for iperf:${3:-5201} pid $2"; while [[ $(ss -N "$1" -tlpH "sport = ${3:-5201}") != *\"iperf3\",pid=$2,fd=* ]]; do sleep 0.1; done; }
|
||||
waitiperf() { pretty "${1//*-}" "wait for iperf:${3:-5201} pid $2"; while ! sockstat -qj "$1" -ql -P tcp -p "${3:-5201}" | grep -Eq "iperf3[[:space:]]+$2[[:space:]]"; do sleep 0.1; done; }
|
||||
waitncatudp() { pretty "${1//*-}" "wait for udp:1111 pid $2"; while [[ $(ss -N "$1" -ulpH 'sport = 1111') != *\"ncat\",pid=$2,fd=* ]]; do sleep 0.1; done; }
|
||||
waitiface() { pretty "${1//*-}" "wait for $2 to come up"; jexec "$1" bash -c "while ! ifconfig wg0 | grep -qE 'flags.+UP'; do read -t .1 -N 0 || true; done;"; }
|
||||
|
||||
cj() { pretty "" "Creating $1"; jail -c path=/ vnet=new name="$1" persist; }
|
||||
dj() { pretty "" "Deleting $1"; jail -r "$1" >/dev/null; }
|
||||
|
||||
cleanup() {
|
||||
set +e
|
||||
exec 2>/dev/null
|
||||
# printf "$orig_message_cost" > /proc/sys/net/core/message_cost
|
||||
dj $jail0
|
||||
dj $jail1
|
||||
dj $jail2
|
||||
|
||||
for iface in wg1 wg2; do
|
||||
pretty "" "Awaiting return of ${iface}"
|
||||
# Give interfaces a second to return
|
||||
while ! ifconfig ${iface} &> /dev/null; do
|
||||
sleep 0.1
|
||||
done
|
||||
ifconfig ${iface} destroy
|
||||
done
|
||||
exit
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
dj $jail0 || true
|
||||
dj $jail1 || true
|
||||
dj $jail2 || true
|
||||
cj $jail0
|
||||
cj $jail1
|
||||
cj $jail2
|
||||
|
||||
ifconfig wg1 create
|
||||
ifconfig wg1 vnet ${jail1}
|
||||
ifconfig wg2 create
|
||||
ifconfig wg2 vnet ${jail2}
|
||||
|
||||
key1="$(pp wg genkey)"
|
||||
key2="$(pp wg genkey)"
|
||||
key3="$(pp wg genkey)"
|
||||
key4="$(pp wg genkey)"
|
||||
pub1="$(pp wg pubkey <<<"$key1")"
|
||||
pub2="$(pp wg pubkey <<<"$key2")"
|
||||
pub3="$(pp wg pubkey <<<"$key3")"
|
||||
pub4="$(pp wg pubkey <<<"$key4")"
|
||||
psk="$(pp wg genpsk)"
|
||||
[[ -n $key1 && -n $key2 && -n $psk ]]
|
||||
|
||||
configure_peers() {
|
||||
ifconfig1 wg1 inet 192.168.241.1/24
|
||||
ifconfig1 wg1 inet6 fd00::1/112 up
|
||||
|
||||
ifconfig2 wg2 inet 192.168.241.2/24
|
||||
ifconfig2 wg2 inet6 fd00::2/112 up
|
||||
|
||||
j1 wg set wg1 \
|
||||
private-key <(echo "$key1") \
|
||||
listen-port 1 \
|
||||
peer "$pub2" \
|
||||
preshared-key <(echo "$psk") \
|
||||
allowed-ips 192.168.241.2/32,fd00::2/128
|
||||
j2 wg set wg2 \
|
||||
private-key <(echo "$key2") \
|
||||
listen-port 2 \
|
||||
peer "$pub1" \
|
||||
preshared-key <(echo "$psk") \
|
||||
allowed-ips 192.168.241.1/32,fd00::1/128
|
||||
}
|
||||
configure_peers
|
||||
|
||||
tests() {
|
||||
# Ping over IPv4
|
||||
j2 ping -c 10 -f -W 1 192.168.241.1
|
||||
j1 ping -c 10 -f -W 1 192.168.241.2
|
||||
|
||||
# Ping over IPv6
|
||||
j2 ping6 -c 10 -f -W 1 fd00::1
|
||||
j1 ping6 -c 10 -f -W 1 fd00::2
|
||||
|
||||
# TCP over IPv4
|
||||
j2 iperf3 -s -1 -B 192.168.241.2 &
|
||||
waitiperf $jail2 $!
|
||||
j1 iperf3 -Z -t 3 -c 192.168.241.2
|
||||
|
||||
# TCP over IPv6
|
||||
j1 iperf3 -s -1 -B fd00::1 &
|
||||
waitiperf $jail1 $!
|
||||
j2 iperf3 -Z -t 3 -c fd00::1
|
||||
|
||||
# UDP over IPv4
|
||||
j1 iperf3 -s -1 -B 192.168.241.1 &
|
||||
waitiperf $jail1 $!
|
||||
j2 iperf3 -Z -t 3 -b 0 -u -c 192.168.241.1
|
||||
|
||||
# UDP over IPv6
|
||||
j2 iperf3 -s -1 -B fd00::2 &
|
||||
waitiperf $jail2 $!
|
||||
j1 iperf3 -Z -t 3 -b 0 -u -c fd00::2
|
||||
|
||||
# TCP over IPv4, in parallel
|
||||
for max in 4 5 50; do
|
||||
local pids=( )
|
||||
for ((i=0; i < max; ++i)) do
|
||||
j2 iperf3 -p $(( 5200 + i )) -s -1 -B 192.168.241.2 &
|
||||
pids+=( $! ); waitiperf $jail2 $! $(( 5200 + i ))
|
||||
done
|
||||
for ((i=0; i < max; ++i)) do
|
||||
j1 iperf3 -Z -t 3 -p $(( 5200 + i )) -c 192.168.241.2 &
|
||||
done
|
||||
wait "${pids[@]}"
|
||||
done
|
||||
}
|
||||
|
||||
[[ $(ifconfig1 wg1) =~ mtu\ ([0-9]+) ]] && orig_mtu="${BASH_REMATCH[1]}"
|
||||
#big_mtu=$(( 34816 - 1500 + $orig_mtu ))
|
||||
# XXX
|
||||
big_mtu=16304
|
||||
|
||||
# Test using IPv4 as outer transport
|
||||
j1 wg set wg1 peer "$pub2" endpoint 127.0.0.1:2
|
||||
j2 wg set wg2 peer "$pub1" endpoint 127.0.0.1:1
|
||||
|
||||
# Before calling tests, we first make sure that the stats counters and timestamper are working
|
||||
#j2 ping -c 10 -f -W 1 192.168.241.1
|
||||
#{ read _; read _; read _; read rx_bytes _; read _; read tx_bytes _; } < <(ip2 -stats link show dev wg0)
|
||||
#(( rx_bytes == 1372 && (tx_bytes == 1428 || tx_bytes == 1460) ))
|
||||
#{ read _; read _; read _; read rx_bytes _; read _; read tx_bytes _; } < <(ip1 -stats link show dev wg0)
|
||||
#(( tx_bytes == 1372 && (rx_bytes == 1428 || rx_bytes == 1460) ))
|
||||
#read _ rx_bytes tx_bytes < <(n2 wg show wg0 transfer)
|
||||
#(( rx_bytes == 1372 && (tx_bytes == 1428 || tx_bytes == 1460) ))
|
||||
#read _ rx_bytes tx_bytes < <(n1 wg show wg0 transfer)
|
||||
#(( tx_bytes == 1372 && (rx_bytes == 1428 || rx_bytes == 1460) ))
|
||||
#read _ timestamp < <(n1 wg show wg0 latest-handshakes)
|
||||
#(( timestamp != 0 ))
|
||||
|
||||
tests
|
||||
ifconfig1 wg1 mtu $big_mtu
|
||||
ifconfig2 wg2 mtu $big_mtu
|
||||
tests
|
||||
|
||||
exit 1
|
||||
|
||||
ip1 link set wg0 mtu $orig_mtu
|
||||
ip2 link set wg0 mtu $orig_mtu
|
||||
|
||||
# Test using IPv6 as outer transport
|
||||
n1 wg set wg0 peer "$pub2" endpoint [::1]:2
|
||||
n2 wg set wg0 peer "$pub1" endpoint [::1]:1
|
||||
tests
|
||||
ip1 link set wg0 mtu $big_mtu
|
||||
ip2 link set wg0 mtu $big_mtu
|
||||
tests
|
||||
|
||||
# Test that route MTUs work with the padding
|
||||
ip1 link set wg0 mtu 1300
|
||||
ip2 link set wg0 mtu 1300
|
||||
n1 wg set wg0 peer "$pub2" endpoint 127.0.0.1:2
|
||||
n2 wg set wg0 peer "$pub1" endpoint 127.0.0.1:1
|
||||
n0 iptables -A INPUT -m length --length 1360 -j DROP
|
||||
n1 ip route add 192.168.241.2/32 dev wg0 mtu 1299
|
||||
n2 ip route add 192.168.241.1/32 dev wg0 mtu 1299
|
||||
n2 ping -c 1 -W 1 -s 1269 192.168.241.1
|
||||
n2 ip route delete 192.168.241.1/32 dev wg0 mtu 1299
|
||||
n1 ip route delete 192.168.241.2/32 dev wg0 mtu 1299
|
||||
n0 iptables -F INPUT
|
||||
|
||||
ip1 link set wg0 mtu $orig_mtu
|
||||
ip2 link set wg0 mtu $orig_mtu
|
||||
|
||||
# Test using IPv4 that roaming works
|
||||
ip0 -4 addr del 127.0.0.1/8 dev lo
|
||||
ip0 -4 addr add 127.212.121.99/8 dev lo
|
||||
n1 wg set wg0 listen-port 9999
|
||||
n1 wg set wg0 peer "$pub2" endpoint 127.0.0.1:2
|
||||
n1 ping6 -W 1 -c 1 fd00::2
|
||||
[[ $(n2 wg show wg0 endpoints) == "$pub1 127.212.121.99:9999" ]]
|
||||
|
||||
# Test using IPv6 that roaming works
|
||||
n1 wg set wg0 listen-port 9998
|
||||
n1 wg set wg0 peer "$pub2" endpoint [::1]:2
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
[[ $(n2 wg show wg0 endpoints) == "$pub1 [::1]:9998" ]]
|
||||
|
||||
# Test that crypto-RP filter works
|
||||
n1 wg set wg0 peer "$pub2" allowed-ips 192.168.241.0/24
|
||||
exec 4< <(n1 ncat -l -u -p 1111)
|
||||
ncat_pid=$!
|
||||
waitncatudp $jail1 $ncat_pid
|
||||
n2 ncat -u 192.168.241.1 1111 <<<"X"
|
||||
read -r -N 1 -t 1 out <&4 && [[ $out == "X" ]]
|
||||
kill $ncat_pid
|
||||
more_specific_key="$(pp wg genkey | pp wg pubkey)"
|
||||
n1 wg set wg0 peer "$more_specific_key" allowed-ips 192.168.241.2/32
|
||||
n2 wg set wg0 listen-port 9997
|
||||
exec 4< <(n1 ncat -l -u -p 1111)
|
||||
ncat_pid=$!
|
||||
waitncatudp $jail1 $ncat_pid
|
||||
n2 ncat -u 192.168.241.1 1111 <<<"X"
|
||||
! read -r -N 1 -t 1 out <&4 || false
|
||||
kill $ncat_pid
|
||||
n1 wg set wg0 peer "$more_specific_key" remove
|
||||
[[ $(n1 wg show wg0 endpoints) == "$pub2 [::1]:9997" ]]
|
||||
|
||||
# Test that we can change private keys keys and immediately handshake
|
||||
n1 wg set wg0 private-key <(echo "$key1") peer "$pub2" preshared-key <(echo "$psk") allowed-ips 192.168.241.2/32 endpoint 127.0.0.1:2
|
||||
n2 wg set wg0 private-key <(echo "$key2") listen-port 2 peer "$pub1" preshared-key <(echo "$psk") allowed-ips 192.168.241.1/32
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
n1 wg set wg0 private-key <(echo "$key3")
|
||||
n2 wg set wg0 peer "$pub3" preshared-key <(echo "$psk") allowed-ips 192.168.241.1/32 peer "$pub1" remove
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
n2 wg set wg0 peer "$pub3" remove
|
||||
|
||||
# Test that we can route wg through wg
|
||||
ip1 addr flush dev wg0
|
||||
ip2 addr flush dev wg0
|
||||
ip1 addr add fd00::5:1/112 dev wg0
|
||||
ip2 addr add fd00::5:2/112 dev wg0
|
||||
n1 wg set wg0 private-key <(echo "$key1") peer "$pub2" preshared-key <(echo "$psk") allowed-ips fd00::5:2/128 endpoint 127.0.0.1:2
|
||||
n2 wg set wg0 private-key <(echo "$key2") listen-port 2 peer "$pub1" preshared-key <(echo "$psk") allowed-ips fd00::5:1/128 endpoint 127.212.121.99:9998
|
||||
ip1 link add wg1 type wireguard
|
||||
ip2 link add wg1 type wireguard
|
||||
ip1 addr add 192.168.241.1/24 dev wg1
|
||||
ip1 addr add fd00::1/112 dev wg1
|
||||
ip2 addr add 192.168.241.2/24 dev wg1
|
||||
ip2 addr add fd00::2/112 dev wg1
|
||||
ip1 link set mtu 1340 up dev wg1
|
||||
ip2 link set mtu 1340 up dev wg1
|
||||
n1 wg set wg1 listen-port 5 private-key <(echo "$key3") peer "$pub4" allowed-ips 192.168.241.2/32,fd00::2/128 endpoint [fd00::5:2]:5
|
||||
n2 wg set wg1 listen-port 5 private-key <(echo "$key4") peer "$pub3" allowed-ips 192.168.241.1/32,fd00::1/128 endpoint [fd00::5:1]:5
|
||||
tests
|
||||
# Try to set up a routing loop between the two namespaces
|
||||
ip1 link set netns $jail0 dev wg1
|
||||
ip0 addr add 192.168.241.1/24 dev wg1
|
||||
ip0 link set up dev wg1
|
||||
n0 ping -W 1 -c 1 192.168.241.2
|
||||
n1 wg set wg0 peer "$pub2" endpoint 192.168.241.2:7
|
||||
ip2 link del wg0
|
||||
ip2 link del wg1
|
||||
! n0 ping -W 1 -c 10 -f 192.168.241.2 || false # Should not crash kernel
|
||||
|
||||
ip0 link del wg1
|
||||
ip1 link del wg0
|
||||
|
||||
# Test using NAT. We now change the topology to this:
|
||||
# ┌────────────────────────────────────────┐ ┌────────────────────────────────────────────────┐ ┌────────────────────────────────────────┐
|
||||
# │ $ns1 namespace │ │ $ns0 namespace │ │ $ns2 namespace │
|
||||
# │ │ │ │ │ │
|
||||
# │ ┌─────┐ ┌─────┐ │ │ ┌──────┐ ┌──────┐ │ │ ┌─────┐ ┌─────┐ │
|
||||
# │ │ wg0 │─────────────│vethc│───────────┼────┼────│vethrc│ │vethrs│──────────────┼─────┼──│veths│────────────│ wg0 │ │
|
||||
# │ ├─────┴──────────┐ ├─────┴──────────┐│ │ ├──────┴─────────┐ ├──────┴────────────┐ │ │ ├─────┴──────────┐ ├─────┴──────────┐ │
|
||||
# │ │192.168.241.1/24│ │192.168.1.100/24││ │ │192.168.1.1/24 │ │10.0.0.1/24 │ │ │ │10.0.0.100/24 │ │192.168.241.2/24│ │
|
||||
# │ │fd00::1/24 │ │ ││ │ │ │ │SNAT:192.168.1.0/24│ │ │ │ │ │fd00::2/24 │ │
|
||||
# │ └────────────────┘ └────────────────┘│ │ └────────────────┘ └───────────────────┘ │ │ └────────────────┘ └────────────────┘ │
|
||||
# └────────────────────────────────────────┘ └────────────────────────────────────────────────┘ └────────────────────────────────────────┘
|
||||
|
||||
ip1 link add dev wg0 type wireguard
|
||||
ip2 link add dev wg0 type wireguard
|
||||
configure_peers
|
||||
|
||||
ip0 link add vethrc type veth peer name vethc
|
||||
ip0 link add vethrs type veth peer name veths
|
||||
ip0 link set vethc netns $jail1
|
||||
ip0 link set veths netns $jail2
|
||||
ip0 link set vethrc up
|
||||
ip0 link set vethrs up
|
||||
ip0 addr add 192.168.1.1/24 dev vethrc
|
||||
ip0 addr add 10.0.0.1/24 dev vethrs
|
||||
ip1 addr add 192.168.1.100/24 dev vethc
|
||||
ip1 link set vethc up
|
||||
ip1 route add default via 192.168.1.1
|
||||
ip2 addr add 10.0.0.100/24 dev veths
|
||||
ip2 link set veths up
|
||||
waitiface $jail0 vethrc
|
||||
waitiface $jail0 vethrs
|
||||
waitiface $jail1 vethc
|
||||
waitiface $jail2 veths
|
||||
|
||||
n0 bash -c 'printf 1 > /proc/sys/net/ipv4/ip_forward'
|
||||
n0 bash -c 'printf 2 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout'
|
||||
n0 bash -c 'printf 2 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream'
|
||||
n0 iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 10.0.0.0/24 -j SNAT --to 10.0.0.1
|
||||
|
||||
n1 wg set wg0 peer "$pub2" endpoint 10.0.0.100:2 persistent-keepalive 1
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.1:1" ]]
|
||||
# Demonstrate n2 can still send packets to n1, since persistent-keepalive will prevent connection tracking entry from expiring (to see entries: `n0 conntrack -L`).
|
||||
pp sleep 3
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
n1 wg set wg0 peer "$pub2" persistent-keepalive 0
|
||||
|
||||
# Test that sk_bound_dev_if works
|
||||
n1 ping -I wg0 -c 1 -W 1 192.168.241.2
|
||||
# What about when the mark changes and the packet must be rerouted?
|
||||
n1 iptables -t mangle -I OUTPUT -j MARK --set-xmark 1
|
||||
n1 ping -c 1 -W 1 192.168.241.2 # First the boring case
|
||||
n1 ping -I wg0 -c 1 -W 1 192.168.241.2 # Then the sk_bound_dev_if case
|
||||
n1 iptables -t mangle -D OUTPUT -j MARK --set-xmark 1
|
||||
|
||||
# Test that onion routing works, even when it loops
|
||||
n1 wg set wg0 peer "$pub3" allowed-ips 192.168.242.2/32 endpoint 192.168.241.2:5
|
||||
ip1 addr add 192.168.242.1/24 dev wg0
|
||||
ip2 link add wg1 type wireguard
|
||||
ip2 addr add 192.168.242.2/24 dev wg1
|
||||
n2 wg set wg1 private-key <(echo "$key3") listen-port 5 peer "$pub1" allowed-ips 192.168.242.1/32
|
||||
ip2 link set wg1 up
|
||||
n1 ping -W 1 -c 1 192.168.242.2
|
||||
ip2 link del wg1
|
||||
n1 wg set wg0 peer "$pub3" endpoint 192.168.242.2:5
|
||||
! n1 ping -W 1 -c 1 192.168.242.2 || false # Should not crash kernel
|
||||
n1 wg set wg0 peer "$pub3" remove
|
||||
ip1 addr del 192.168.242.1/24 dev wg0
|
||||
|
||||
# Do a wg-quick(8)-style policy routing for the default route, making sure vethc has a v6 address to tease out bugs.
|
||||
ip1 -6 addr add fc00::9/96 dev vethc
|
||||
ip1 -6 route add default via fc00::1
|
||||
ip2 -4 addr add 192.168.99.7/32 dev wg0
|
||||
ip2 -6 addr add abab::1111/128 dev wg0
|
||||
n1 wg set wg0 fwmark 51820 peer "$pub2" allowed-ips 192.168.99.7,abab::1111
|
||||
ip1 -6 route add default dev wg0 table 51820
|
||||
ip1 -6 rule add not fwmark 51820 table 51820
|
||||
ip1 -6 rule add table main suppress_prefixlength 0
|
||||
ip1 -4 route add default dev wg0 table 51820
|
||||
ip1 -4 rule add not fwmark 51820 table 51820
|
||||
ip1 -4 rule add table main suppress_prefixlength 0
|
||||
# Flood the pings instead of sending just one, to trigger routing table reference counting bugs.
|
||||
n1 ping -W 1 -c 100 -f 192.168.99.7
|
||||
n1 ping -W 1 -c 100 -f abab::1111
|
||||
|
||||
# Have ns2 NAT into wg0 packets from ns0, but return an icmp error along the right route.
|
||||
n2 iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -d 192.168.241.0/24 -j SNAT --to 192.168.241.2
|
||||
n0 iptables -t filter -A INPUT \! -s 10.0.0.0/24 -i vethrs -j DROP # Manual rpfilter just to be explicit.
|
||||
n2 bash -c 'printf 1 > /proc/sys/net/ipv4/ip_forward'
|
||||
ip0 -4 route add 192.168.241.1 via 10.0.0.100
|
||||
n2 wg set wg0 peer "$pub1" remove
|
||||
[[ $(! n0 ping -W 1 -c 1 192.168.241.1 || false) == *"From 10.0.0.100 icmp_seq=1 Destination Host Unreachable"* ]]
|
||||
|
||||
n0 iptables -t nat -F
|
||||
n0 iptables -t filter -F
|
||||
n2 iptables -t nat -F
|
||||
ip0 link del vethrc
|
||||
ip0 link del vethrs
|
||||
ip1 link del wg0
|
||||
ip2 link del wg0
|
||||
|
||||
# Test that saddr routing is sticky but not too sticky, changing to this topology:
|
||||
# ┌────────────────────────────────────────┐ ┌────────────────────────────────────────┐
|
||||
# │ $ns1 namespace │ │ $ns2 namespace │
|
||||
# │ │ │ │
|
||||
# │ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ │
|
||||
# │ │ wg0 │─────────────│veth1│───────────┼────┼──│veth2│────────────│ wg0 │ │
|
||||
# │ ├─────┴──────────┐ ├─────┴──────────┐│ │ ├─────┴──────────┐ ├─────┴──────────┐ │
|
||||
# │ │192.168.241.1/24│ │10.0.0.1/24 ││ │ │10.0.0.2/24 │ │192.168.241.2/24│ │
|
||||
# │ │fd00::1/24 │ │fd00:aa::1/96 ││ │ │fd00:aa::2/96 │ │fd00::2/24 │ │
|
||||
# │ └────────────────┘ └────────────────┘│ │ └────────────────┘ └────────────────┘ │
|
||||
# └────────────────────────────────────────┘ └────────────────────────────────────────┘
|
||||
|
||||
ip1 link add dev wg0 type wireguard
|
||||
ip2 link add dev wg0 type wireguard
|
||||
configure_peers
|
||||
ip1 link add veth1 type veth peer name veth2
|
||||
ip1 link set veth2 netns $jail2
|
||||
n1 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/all/accept_dad'
|
||||
n2 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/all/accept_dad'
|
||||
n1 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/veth1/accept_dad'
|
||||
n2 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/veth2/accept_dad'
|
||||
n1 bash -c 'printf 1 > /proc/sys/net/ipv4/conf/veth1/promote_secondaries'
|
||||
|
||||
# First we check that we aren't overly sticky and can fall over to new IPs when old ones are removed
|
||||
ip1 addr add 10.0.0.1/24 dev veth1
|
||||
ip1 addr add fd00:aa::1/96 dev veth1
|
||||
ip2 addr add 10.0.0.2/24 dev veth2
|
||||
ip2 addr add fd00:aa::2/96 dev veth2
|
||||
ip1 link set veth1 up
|
||||
ip2 link set veth2 up
|
||||
waitiface $jail1 veth1
|
||||
waitiface $jail2 veth2
|
||||
n1 wg set wg0 peer "$pub2" endpoint 10.0.0.2:2
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
ip1 addr add 10.0.0.10/24 dev veth1
|
||||
ip1 addr del 10.0.0.1/24 dev veth1
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
n1 wg set wg0 peer "$pub2" endpoint [fd00:aa::2]:2
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
ip1 addr add fd00:aa::10/96 dev veth1
|
||||
ip1 addr del fd00:aa::1/96 dev veth1
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
|
||||
# Now we show that we can successfully do reply to sender routing
|
||||
ip1 link set veth1 down
|
||||
ip2 link set veth2 down
|
||||
ip1 addr flush dev veth1
|
||||
ip2 addr flush dev veth2
|
||||
ip1 addr add 10.0.0.1/24 dev veth1
|
||||
ip1 addr add 10.0.0.2/24 dev veth1
|
||||
ip1 addr add fd00:aa::1/96 dev veth1
|
||||
ip1 addr add fd00:aa::2/96 dev veth1
|
||||
ip2 addr add 10.0.0.3/24 dev veth2
|
||||
ip2 addr add fd00:aa::3/96 dev veth2
|
||||
ip1 link set veth1 up
|
||||
ip2 link set veth2 up
|
||||
waitiface $jail1 veth1
|
||||
waitiface $jail2 veth2
|
||||
n2 wg set wg0 peer "$pub1" endpoint 10.0.0.1:1
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.1:1" ]]
|
||||
n2 wg set wg0 peer "$pub1" endpoint [fd00:aa::1]:1
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n2 wg show wg0 endpoints) == "$pub1 [fd00:aa::1]:1" ]]
|
||||
n2 wg set wg0 peer "$pub1" endpoint 10.0.0.2:1
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.2:1" ]]
|
||||
n2 wg set wg0 peer "$pub1" endpoint [fd00:aa::2]:1
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n2 wg show wg0 endpoints) == "$pub1 [fd00:aa::2]:1" ]]
|
||||
|
||||
# What happens if the inbound destination address belongs to a different interface as the default route?
|
||||
ip1 link add dummy0 type dummy
|
||||
ip1 addr add 10.50.0.1/24 dev dummy0
|
||||
ip1 link set dummy0 up
|
||||
ip2 route add 10.50.0.0/24 dev veth2
|
||||
n2 wg set wg0 peer "$pub1" endpoint 10.50.0.1:1
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.50.0.1:1" ]]
|
||||
|
||||
ip1 link del dummy0
|
||||
ip1 addr flush dev veth1
|
||||
ip2 addr flush dev veth2
|
||||
ip1 route flush dev veth1
|
||||
ip2 route flush dev veth2
|
||||
|
||||
# Now we see what happens if another interface route takes precedence over an ongoing one
|
||||
ip1 link add veth3 type veth peer name veth4
|
||||
ip1 link set veth4 netns $jail2
|
||||
ip1 addr add 10.0.0.1/24 dev veth1
|
||||
ip2 addr add 10.0.0.2/24 dev veth2
|
||||
ip1 addr add 10.0.0.3/24 dev veth3
|
||||
ip1 link set veth1 up
|
||||
ip2 link set veth2 up
|
||||
ip1 link set veth3 up
|
||||
ip2 link set veth4 up
|
||||
waitiface $jail1 veth1
|
||||
waitiface $jail2 veth2
|
||||
waitiface $jail1 veth3
|
||||
waitiface $jail2 veth4
|
||||
ip1 route flush dev veth1
|
||||
ip1 route flush dev veth3
|
||||
ip1 route add 10.0.0.0/24 dev veth1 src 10.0.0.1 metric 2
|
||||
n1 wg set wg0 peer "$pub2" endpoint 10.0.0.2:2
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.1:1" ]]
|
||||
ip1 route add 10.0.0.0/24 dev veth3 src 10.0.0.3 metric 1
|
||||
n1 bash -c 'printf 0 > /proc/sys/net/ipv4/conf/veth1/rp_filter'
|
||||
n2 bash -c 'printf 0 > /proc/sys/net/ipv4/conf/veth4/rp_filter'
|
||||
n1 bash -c 'printf 0 > /proc/sys/net/ipv4/conf/all/rp_filter'
|
||||
n2 bash -c 'printf 0 > /proc/sys/net/ipv4/conf/all/rp_filter'
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
[[ $(n2 wg show wg0 endpoints) == "$pub1 10.0.0.3:1" ]]
|
||||
|
||||
ip1 link del veth1
|
||||
ip1 link del veth3
|
||||
ip1 link del wg0
|
||||
ip2 link del wg0
|
||||
|
||||
# We test that Netlink/IPC is working properly by doing things that usually cause split responses
|
||||
ip0 link add dev wg0 type wireguard
|
||||
config=( "[Interface]" "PrivateKey=$(wg genkey)" "[Peer]" "PublicKey=$(wg genkey)" )
|
||||
for a in {1..255}; do
|
||||
for b in {0..255}; do
|
||||
config+=( "AllowedIPs=$a.$b.0.0/16,$a::$b/128" )
|
||||
done
|
||||
done
|
||||
n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
|
||||
i=0
|
||||
for ip in $(n0 wg show wg0 allowed-ips); do
|
||||
((++i))
|
||||
done
|
||||
((i == 255*256*2+1))
|
||||
ip0 link del wg0
|
||||
ip0 link add dev wg0 type wireguard
|
||||
config=( "[Interface]" "PrivateKey=$(wg genkey)" )
|
||||
for a in {1..40}; do
|
||||
config+=( "[Peer]" "PublicKey=$(wg genkey)" )
|
||||
for b in {1..52}; do
|
||||
config+=( "AllowedIPs=$a.$b.0.0/16" )
|
||||
done
|
||||
done
|
||||
n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
|
||||
i=0
|
||||
while read -r line; do
|
||||
j=0
|
||||
for ip in $line; do
|
||||
((++j))
|
||||
done
|
||||
((j == 53))
|
||||
((++i))
|
||||
done < <(n0 wg show wg0 allowed-ips)
|
||||
((i == 40))
|
||||
ip0 link del wg0
|
||||
ip0 link add wg0 type wireguard
|
||||
config=( )
|
||||
for i in {1..29}; do
|
||||
config+=( "[Peer]" "PublicKey=$(wg genkey)" )
|
||||
done
|
||||
config+=( "[Peer]" "PublicKey=$(wg genkey)" "AllowedIPs=255.2.3.4/32,abcd::255/128" )
|
||||
n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
|
||||
n0 wg showconf wg0 > /dev/null
|
||||
ip0 link del wg0
|
||||
|
||||
allowedips=( )
|
||||
for i in {1..197}; do
|
||||
allowedips+=( abcd::$i )
|
||||
done
|
||||
saved_ifs="$IFS"
|
||||
IFS=,
|
||||
allowedips="${allowedips[*]}"
|
||||
IFS="$saved_ifs"
|
||||
ip0 link add wg0 type wireguard
|
||||
n0 wg set wg0 peer "$pub1"
|
||||
n0 wg set wg0 peer "$pub2" allowed-ips "$allowedips"
|
||||
{
|
||||
read -r pub allowedips
|
||||
[[ $pub == "$pub1" && $allowedips == "(none)" ]]
|
||||
read -r pub allowedips
|
||||
[[ $pub == "$pub2" ]]
|
||||
i=0
|
||||
for _ in $allowedips; do
|
||||
((++i))
|
||||
done
|
||||
((i == 197))
|
||||
} < <(n0 wg show wg0 allowed-ips)
|
||||
ip0 link del wg0
|
||||
|
||||
! n0 wg show doesnotexist || false
|
||||
|
||||
ip0 link add wg0 type wireguard
|
||||
n0 wg set wg0 private-key <(echo "$key1") peer "$pub2" preshared-key <(echo "$psk")
|
||||
[[ $(n0 wg show wg0 private-key) == "$key1" ]]
|
||||
[[ $(n0 wg show wg0 preshared-keys) == "$pub2 $psk" ]]
|
||||
n0 wg set wg0 private-key /dev/null peer "$pub2" preshared-key /dev/null
|
||||
[[ $(n0 wg show wg0 private-key) == "(none)" ]]
|
||||
[[ $(n0 wg show wg0 preshared-keys) == "$pub2 (none)" ]]
|
||||
n0 wg set wg0 peer "$pub2"
|
||||
n0 wg set wg0 private-key <(echo "$key2")
|
||||
[[ $(n0 wg show wg0 public-key) == "$pub2" ]]
|
||||
[[ -z $(n0 wg show wg0 peers) ]]
|
||||
n0 wg set wg0 peer "$pub2"
|
||||
[[ -z $(n0 wg show wg0 peers) ]]
|
||||
n0 wg set wg0 private-key <(echo "$key1")
|
||||
n0 wg set wg0 peer "$pub2"
|
||||
[[ $(n0 wg show wg0 peers) == "$pub2" ]]
|
||||
n0 wg set wg0 private-key <(echo "/${key1:1}")
|
||||
[[ $(n0 wg show wg0 private-key) == "+${key1:1}" ]]
|
||||
n0 wg set wg0 peer "$pub2" allowed-ips 0.0.0.0/0,10.0.0.0/8,100.0.0.0/10,172.16.0.0/12,192.168.0.0/16
|
||||
n0 wg set wg0 peer "$pub2" allowed-ips 0.0.0.0/0
|
||||
n0 wg set wg0 peer "$pub2" allowed-ips ::/0,1700::/111,5000::/4,e000::/37,9000::/75
|
||||
n0 wg set wg0 peer "$pub2" allowed-ips ::/0
|
||||
n0 wg set wg0 peer "$pub2" remove
|
||||
for low_order_point in AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 4Ot6fDtBuK4WVuP68Z/EatoJjeucMrH9hmIFFl9JuAA= X5yVvKNQjCSx0LFVnIPvWwREXMRYHI6G2CJO3dCfEVc= 7P///////////////////////////////////////38= 7f///////////////////////////////////////38= 7v///////////////////////////////////////38=; do
|
||||
n0 wg set wg0 peer "$low_order_point" persistent-keepalive 1 endpoint 127.0.0.1:1111
|
||||
done
|
||||
[[ -n $(n0 wg show wg0 peers) ]]
|
||||
exec 4< <(n0 ncat -l -u -p 1111)
|
||||
ncat_pid=$!
|
||||
waitncatudp $jail0 $ncat_pid
|
||||
ip0 link set wg0 up
|
||||
! read -r -n 1 -t 2 <&4 || false
|
||||
kill $ncat_pid
|
||||
ip0 link del wg0
|
||||
|
||||
# Ensure there aren't circular reference loops
|
||||
ip1 link add wg1 type wireguard
|
||||
ip2 link add wg2 type wireguard
|
||||
ip1 link set wg1 netns $jail2
|
||||
ip2 link set wg2 netns $jail1
|
||||
pp ip netns delete $jail1
|
||||
pp ip netns delete $jail2
|
||||
pp ip netns add $jail1
|
||||
pp ip netns add $jail2
|
||||
|
||||
sleep 2 # Wait for cleanup and grace periods
|
||||
declare -A objects
|
||||
while read -t 0.1 -r line 2>/dev/null || [[ $? -ne 142 ]]; do
|
||||
[[ $line =~ .*(wg[0-9]+:\ [A-Z][a-z]+\ ?[0-9]*)\ .*(created|destroyed).* ]] || continue
|
||||
objects["${BASH_REMATCH[1]}"]+="${BASH_REMATCH[2]}"
|
||||
done < /dev/kmsg
|
||||
alldeleted=1
|
||||
for object in "${!objects[@]}"; do
|
||||
if [[ ${objects["$object"]} != *createddestroyed ]]; then
|
||||
echo "Error: $object: merely ${objects["$object"]}" >&3
|
||||
alldeleted=0
|
||||
fi
|
||||
done
|
||||
[[ $alldeleted -eq 1 ]]
|
||||
pretty "" "Objects that were created were also destroyed."
|
Loading…
Reference in New Issue