tunsafe-clang15/wireguard_proto.cpp
Ludvig Strigeus cf92ac7a0c Updates for TunSafe 1.4-rc1
1.Subfolders in the Config/ directory now show up as submenus.
2.Added a way to run TunSafe as a Windows Service.
  Foreground Mode: The service will disconnect when TunSafe closes.
  Background Mode: The service will stay connected in the background.
  No longer required to run the TunSafe client as Admin as long as
  the service is running.
3.New config setting [Interface].ExcludedIPs to configure IPs that
  should not be routed through TunSafe.
4.Can now automatically start TunSafe when Windows starts
5.New UI with tabs and graphs
6.Cache DNS queries to ensure DNS will succeed if connection fails
7.Recreate tray icon when explorer.exe restarts
8.Renamed window title to TunSafe instead of TunSafe VPN Client
9.Main window is now resizable
10.Disallow roaming endpoint when using AllowedIPs=0.0.0.0/0
   Only the original endpoint is added in the routing table so
   this would result in an endless loop of packets.
11.Display approximate Wireguard framing overhead in stats
12.Preparations for protocol handling with multiple threads
13.Delete the routes we made when disconnecting
14.Fix error message about unable to delete a route when connecting
2018-08-12 03:30:06 +02:00

1301 lines
46 KiB
C++

// SPDX-License-Identifier: AGPL-1.0-only
// Copyright (C) 2018 Ludvig Strigeus <info@tunsafe.com>. All Rights Reserved.
#include "stdafx.h"
#include "wireguard_proto.h"
#include "crypto/chacha20poly1305.h"
#include "crypto/blake2s.h"
#include "crypto/curve25519-donna.h"
#include "crypto/aesgcm/aes.h"
#include "crypto/siphash.h"
#include "tunsafe_endian.h"
#include "util.h"
#include "crypto_ops.h"
#include "bit_ops.h"
#include "tunsafe_cpu.h"
#include <algorithm>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
static const uint8 kLabelCookie[] = {'c', 'o', 'o', 'k', 'i', 'e', '-', '-'};
static const uint8 kLabelMac1[] = {'m', 'a', 'c', '1', '-', '-', '-', '-'};
static const uint8 kWgInitHash[WG_HASH_LEN] = {0x22,0x11,0xb3,0x61,0x08,0x1a,0xc5,0x66,0x69,0x12,0x43,0xdb,0x45,0x8a,0xd5,0x32,0x2d,0x9c,0x6c,0x66,0x22,0x93,0xe8,0xb7,0x0e,0xe1,0x9c,0x65,0xba,0x07,0x9e,0xf3};
static const uint8 kWgInitChainingKey[WG_HASH_LEN] = {0x60,0xe2,0x6d,0xae,0xf3,0x27,0xef,0xc0,0x2e,0xc3,0x35,0xe2,0xa0,0x25,0xd2,0xd0,0x16,0xeb,0x42,0x06,0xf8,0x72,0x77,0xf5,0x2d,0x38,0xd1,0x98,0x8b,0x78,0xcd,0x36};
static const uint8 kCurve25519Basepoint[32] = {9};
ReplayDetector::ReplayDetector() {
expected_seq_nr_ = 0;
memset(bitmap_, 0, sizeof(bitmap_));
}
ReplayDetector::~ReplayDetector() {
}
bool ReplayDetector::CheckReplay(uint64 seq_nr) {
uint64 slot = seq_nr / BITS_PER_ENTRY;
uint64 expected_seq_nr = expected_seq_nr_;
if (seq_nr >= expected_seq_nr) {
uint64 prev_slot = (expected_seq_nr + BITS_PER_ENTRY - 1) / BITS_PER_ENTRY - 1, n;
if ((n = slot - prev_slot) != 0) {
size_t nn = (size_t)std::min<uint64>(n, BITMAP_SIZE);
do {
bitmap_[(prev_slot + nn) & BITMAP_MASK] = 0;
} while (--nn);
}
expected_seq_nr_ = seq_nr + 1;
} else if (seq_nr + WINDOW_SIZE <= expected_seq_nr) {
return false;
}
uint32 mask = 1 << (seq_nr & (BITS_PER_ENTRY - 1)), prev;
prev = bitmap_[slot & BITMAP_MASK];
bitmap_[slot & BITMAP_MASK] = prev | mask;
return (prev & mask) == 0;
}
WgDevice::WgDevice() {
peers_ = NULL;
header_obfuscation_ = false;
next_rng_slot_ = 0;
memset(&compression_header_, 0, sizeof(compression_header_));
low_resolution_timestamp_ = cookie_secret_timestamp_ = OsGetMilliseconds();
OsGetRandomBytes(cookie_secret_, sizeof(cookie_secret_));
OsGetRandomBytes((uint8*)random_number_input_, sizeof(random_number_input_));
SetCurrentThreadAsMainThread();
}
WgDevice::~WgDevice() {
}
void WgDevice::SecondLoop(uint64 now) {
assert(IsMainThread());
low_resolution_timestamp_ = now;
if (rate_limiter_.is_used()) {
uint32 k[5];
for (size_t i = 0; i < ARRAY_SIZE(k); i++)
k[i] = GetRandomNumber();
rate_limiter_.Periodic(k);
}
}
uint32 WgDevice::InsertInKeyIdLookup(WgPeer *peer, WgKeypair *kp) {
assert(IsMainThread());
assert(peer);
for (;;) {
uint32 v = GetRandomNumber();
if (v == 0)
continue;
// Take the exclusive lock since we're modifying it.
WG_SCOPED_RWLOCK_EXCLUSIVE(key_id_lookup_lock_);
std::pair<WgPeer*, WgKeypair*> &peer_and_keypair = key_id_lookup_[v];
if (peer_and_keypair.first == NULL) {
peer_and_keypair = std::make_pair(peer, kp);
uint32 &x = (kp ? kp->local_key_id : peer->local_key_id_during_hs_);
uint32 old = x;
x = v;
if (old)
key_id_lookup_.erase(old);
return v;
}
}
}
std::pair<WgPeer*, WgKeypair*> *WgDevice::LookupPeerInKeyIdLookup(uint32 key_id) {
// This function is only ever called by the main thread, so no need to lock,
// since the main thread is the only mutator.
assert(IsMainThread());
auto it = key_id_lookup_.find(key_id);
return (it != key_id_lookup_.end() && it->second.second == NULL) ? &it->second : NULL;
}
WgKeypair *WgDevice::LookupKeypairByKeyId(uint32 key_id) {
// This function can be called from any thread, so make sure to
// lock using the shared lock.
WG_SCOPED_RWLOCK_SHARED(key_id_lookup_lock_);
auto it = key_id_lookup_.find(key_id);
return (it != key_id_lookup_.end()) ? it->second.second : NULL;
}
uint32 WgDevice::GetRandomNumber() {
assert(IsMainThread());
size_t slot;
if ((slot = next_rng_slot_) == 0) {
blake2s(random_number_output_, sizeof(random_number_output_), random_number_input_, sizeof(random_number_input_), NULL, 0);
random_number_input_[0]++;
slot = BLAKE2S_OUTBYTES / 4;
}
next_rng_slot_ = (uint8) --slot;
return random_number_output_[slot];
}
static void BlakeX2(uint8 *dst, size_t dst_size, const uint8 *a, size_t a_size, const uint8 *b, size_t b_size) {
blake2s_state b2s;
blake2s_init(&b2s, dst_size);
blake2s_update(&b2s, a, a_size);
blake2s_update(&b2s, b, b_size);
blake2s_final(&b2s, dst, dst_size);
}
static inline void BlakeMix(uint8 dst[WG_HASH_LEN], const uint8 *a, size_t a_size) {
BlakeX2(dst, WG_HASH_LEN, dst, WG_HASH_LEN, a, a_size);
}
static inline void ComputeHKDF2DH(uint8 ci[WG_HASH_LEN], uint8 k[WG_SYMMETRIC_KEY_LEN], const uint8 priv[WG_PUBLIC_KEY_LEN], const uint8 pub[WG_PUBLIC_KEY_LEN]) {
uint8 dh[WG_PUBLIC_KEY_LEN];
curve25519_donna(dh, priv, pub);
blake2s_hkdf(ci, WG_HASH_LEN, k, WG_SYMMETRIC_KEY_LEN, NULL, 32, dh, sizeof(dh), ci, WG_HASH_LEN);
memzero_crypto(dh, sizeof(dh));
}
void WgDevice::Initialize(const uint8 private_key[WG_PUBLIC_KEY_LEN]) {
// Derive the public key from the private key.
memcpy(s_priv_, private_key, sizeof(s_priv_));
curve25519_donna(s_pub_, s_priv_, kCurve25519Basepoint);
// Precompute: precomputed_cookie_label_hash_ := HASH(LABEL-COOKIE || Spub_m)
// precomputed_label_mac1_hash_ := HASH(MAC1-COOKIE || Spub_m)
BlakeX2(precomputed_cookie_key_, sizeof(precomputed_cookie_key_),
kLabelCookie, sizeof(kLabelCookie), s_pub_, sizeof(s_pub_));
BlakeX2(precomputed_mac1_key_, sizeof(precomputed_mac1_key_),
kLabelMac1, sizeof(kLabelMac1), s_pub_, sizeof(s_pub_));
}
WgPeer *WgDevice::AddPeer() {
assert(IsMainThread());
WgPeer *peer = new WgPeer(this);
WgPeer **pp = &peers_;
while (*pp)
pp = &(*pp)->next_peer_;
*pp = peer;
return peer;
}
WgPeer *WgDevice::GetPeerFromPublicKey(uint8 public_key[WG_PUBLIC_KEY_LEN]) {
assert(IsMainThread());
// todo: add O(1) lookup
for (WgPeer *peer = peers_; peer; peer = peer->next_peer_) {
if (memcmp(peer->s_remote_, public_key, WG_PUBLIC_KEY_LEN) == 0)
return peer;
}
return NULL;
}
bool WgDevice::CheckCookieMac1(Packet *packet) {
assert(IsMainThread());
uint8 mac[WG_COOKIE_LEN];
const uint8 *data = packet->data;
size_t data_size = packet->size;
blake2s(mac, sizeof(mac), data, data_size - WG_COOKIE_LEN * 2, precomputed_mac1_key_, sizeof(precomputed_mac1_key_));
return !memcmp_crypto(mac, data + data_size - WG_COOKIE_LEN * 2, WG_COOKIE_LEN);
}
void WgDevice::MakeCookie(uint8 cookie[WG_COOKIE_LEN], Packet *packet) {
assert(IsMainThread());
blake2s_state b2s;
uint64 now = OsGetMilliseconds();
if (now - cookie_secret_timestamp_ >= COOKIE_SECRET_MAX_AGE_MS) {
cookie_secret_timestamp_ = now;
OsGetRandomBytes(cookie_secret_, sizeof(cookie_secret_));
}
blake2s_init_key(&b2s, WG_COOKIE_LEN, cookie_secret_, sizeof(cookie_secret_));
if (packet->addr.sin.sin_family == AF_INET)
blake2s_update(&b2s, &packet->addr.sin.sin_addr, 4);
else if (packet->addr.sin.sin_family == AF_INET6)
blake2s_update(&b2s, &packet->addr.sin6.sin6_addr, sizeof(packet->addr.sin6.sin6_addr));
blake2s_update(&b2s, &packet->addr.sin6.sin6_port, 2);
blake2s_final(&b2s, cookie, WG_COOKIE_LEN);
}
bool WgDevice::CheckCookieMac2(Packet *packet) {
assert(IsMainThread());
uint8 cookie[WG_COOKIE_LEN];
uint8 mac[WG_COOKIE_LEN];
MakeCookie(cookie, packet);
blake2s(mac, sizeof(mac), packet->data, packet->size - WG_COOKIE_LEN, cookie, sizeof(cookie));
return !memcmp_crypto(mac, packet->data + packet->size - WG_COOKIE_LEN, WG_COOKIE_LEN);
}
void WgDevice::CreateCookieMessage(MessageHandshakeCookie *dst, Packet *packet, uint32 remote_key_id) {
assert(IsMainThread());
dst->type = MESSAGE_HANDSHAKE_COOKIE;
dst->receiver_key_id = remote_key_id;
MakeCookie(dst->cookie_enc, packet);
OsGetRandomBytes(dst->nonce, sizeof(dst->nonce));
MessageMacs *mac = (MessageMacs *)(packet->data + packet->size - sizeof(MessageMacs));
xchacha20poly1305_encrypt(dst->cookie_enc, dst->cookie_enc, WG_COOKIE_LEN, mac->mac1, WG_COOKIE_LEN, dst->nonce, precomputed_cookie_key_);
}
void WgDevice::EraseKeypairAddrEntry_Locked(WgKeypair *kp) {
WgAddrEntry *ae = kp->addr_entry;
assert(ae->ref_count >= 1);
assert(ae->ref_count == !!ae->keys[0] + !!ae->keys[1] + !!ae->keys[2]);
assert(ae->keys[kp->addr_entry_slot - 1] == kp);
kp->addr_entry = NULL;
ae->keys[kp->addr_entry_slot - 1] = NULL;
kp->addr_entry_slot = 0;
if (ae->ref_count-- == 1) {
addr_entry_lookup_.erase(ae->addr_entry_id);
delete ae;
}
}
WgKeypair *WgDevice::LookupKeypairInAddrEntryMap(uint64 addr, uint32 slot) {
WG_SCOPED_RWLOCK_SHARED(addr_entry_lookup_lock_);
auto it = addr_entry_lookup_.find(addr);
if (it == addr_entry_lookup_.end())
return NULL;
WgAddrEntry *addr_entry = it->second;
return addr_entry->keys[slot];
}
void WgDevice::UpdateKeypairAddrEntry_Locked(uint64 addr_id, WgKeypair *keypair) {
assert(keypair->peer->IsPeerLocked());
{
WG_SCOPED_RWLOCK_SHARED(addr_entry_lookup_lock_);
if (keypair->addr_entry != NULL && keypair->addr_entry->addr_entry_id == addr_id) {
keypair->broadcast_short_key = 1;
return;
}
}
WG_SCOPED_RWLOCK_EXCLUSIVE(addr_entry_lookup_lock_);
if (keypair->addr_entry != NULL)
EraseKeypairAddrEntry_Locked(keypair);
WgAddrEntry **aep = &addr_entry_lookup_[addr_id], *ae;
if ((ae = *aep) == NULL) {
*aep = ae = new WgAddrEntry(addr_id);
} else {
// Ensure we don't insert new things in this addr entry too often.
if (ae->time_of_last_insertion + 1000 * 60 > low_resolution_timestamp_)
return;
}
ae->time_of_last_insertion = low_resolution_timestamp_;
// Update slot #
uint32 next_slot = ae->next_slot;
ae->next_slot = (next_slot == 2) ? 0 : next_slot + 1;
WgKeypair *old_keypair = ae->keys[next_slot];
ae->keys[next_slot] = keypair;
keypair->addr_entry = ae;
keypair->addr_entry_slot = next_slot + 1;
if (old_keypair != NULL) {
old_keypair->addr_entry = NULL;
old_keypair->addr_entry_slot = 0;
} else {
ae->ref_count++;
}
assert(ae->ref_count == !!ae->keys[0] + !!ae->keys[1] + !!ae->keys[2]);
keypair->broadcast_short_key = 1;
}
//>> > hashlib.sha256('TunSafe Header Obfuscation Key').hexdigest()
//'2444423e33eb5bb875961224c6441f54c5dea95a3a4e1139509ffa6992bdb278'
static const uint8 kHeaderObfuscationKey[32] = {36, 68, 66, 62, 51, 235, 91, 184, 117, 150, 18, 36, 198, 68, 31, 84, 197, 222, 169, 90, 58, 78, 17, 57, 80, 159, 250, 105, 146, 189, 178, 120};
void WgDevice::SetHeaderObfuscation(const char *key) {
#if WITH_HEADER_OBFUSCATION
header_obfuscation_ = (key != NULL);
if (key)
blake2s_hmac((uint8*)&header_obfuscation_key_, sizeof(header_obfuscation_key_), (uint8*)key, strlen(key), kHeaderObfuscationKey, sizeof(kHeaderObfuscationKey));
#endif // WITH_HEADER_OBFUSCATION
}
WgPeer::WgPeer(WgDevice *dev) {
assert(dev->IsMainThread());
dev_ = dev;
endpoint_.sin.sin_family = 0;
next_peer_ = NULL;
curr_keypair_ = next_keypair_ = prev_keypair_ = NULL;
expect_cookie_reply_ = false;
has_mac2_cookie_ = false;
pending_keepalive_ = false;
allow_multicast_through_peer_ = false;
allow_endpoint_change_ = true;
supports_handshake_extensions_ = true;
local_key_id_during_hs_ = 0;
last_handshake_init_timestamp_ = -1000000ll;
last_handshake_init_recv_timestamp_ = 0;
last_complete_handskake_timestamp_ = 0;
persistent_keepalive_ms_ = 0;
timers_ = 0;
first_queued_packet_ = NULL;
last_queued_packet_ptr_ = &first_queued_packet_;
num_queued_packets_ = 0;
handshake_attempts_ = 0;
total_handshake_attempts_ = 0;
num_ciphers_ = 0;
cipher_prio_ = 0;
main_thread_scheduled_ = 0;
memset(last_timestamp_, 0, sizeof(last_timestamp_));
ipv4_broadcast_addr_ = 0xffffffff;
memset(features_, 0, sizeof(features_));
}
WgPeer::~WgPeer() {
assert(dev_->IsMainThread());
WG_ACQUIRE_LOCK(mutex_);
ClearKeys_Locked();
ClearHandshake_Locked();
ClearPacketQueue_Locked();
WG_RELEASE_LOCK(mutex_);
}
void WgPeer::ClearKeys_Locked() {
assert(dev_->IsMainThread() && IsPeerLocked());
DeleteKeypair(&curr_keypair_);
DeleteKeypair(&next_keypair_);
DeleteKeypair(&prev_keypair_);
}
void WgPeer::ClearHandshake_Locked() {
assert(dev_->IsMainThread() && IsPeerLocked());
uint32 v = local_key_id_during_hs_;
if (v != 0) {
local_key_id_during_hs_ = 0;
WG_SCOPED_RWLOCK_EXCLUSIVE(dev_->key_id_lookup_lock_);
dev_->key_id_lookup_.erase(v);
}
}
void WgPeer::ClearPacketQueue_Locked() {
assert(dev_->IsMainThread() && IsPeerLocked());
Packet *packet;
while ((packet = first_queued_packet_) != NULL) {
first_queued_packet_ = packet->next;
FreePacket(packet);
}
last_queued_packet_ptr_ = &first_queued_packet_;
num_queued_packets_ = 0;
}
void WgPeer::Initialize(const uint8 spub[WG_PUBLIC_KEY_LEN], const uint8 preshared_key[WG_SYMMETRIC_KEY_LEN]) {
// Optionally use a preshared key, it defaults to all zeros.
if (preshared_key)
memcpy(preshared_key_, preshared_key, sizeof(preshared_key_));
else
memset(preshared_key_, 0, sizeof(preshared_key_));
// Precompute: s_priv_pub_ := DH(sprivr, spubi)
memcpy(s_remote_, spub, sizeof(s_remote_));
curve25519_donna(s_priv_pub_, dev_->s_priv_, s_remote_);
// Precompute: precomputed_cookie_key_ := HASH(LABEL-COOKIE || Spub_m)
// precomputed_mac1_key_ := HASH(MAC1-COOKIE || Spub_m)
BlakeX2(precomputed_cookie_key_, sizeof(precomputed_cookie_key_),
kLabelCookie, sizeof(kLabelCookie), spub, WG_PUBLIC_KEY_LEN);
BlakeX2(precomputed_mac1_key_, sizeof(precomputed_mac1_key_),
kLabelMac1, sizeof(kLabelMac1), spub, WG_PUBLIC_KEY_LEN);
}
// run on the client
void WgPeer::CreateMessageHandshakeInitiation(Packet *packet) {
assert(dev_->IsMainThread());
uint8 k[WG_SYMMETRIC_KEY_LEN];
MessageHandshakeInitiation *dst = (MessageHandshakeInitiation *)packet->data;
// Ci := HASH(CONSTRUCTION)
memcpy(hs_.ci, kWgInitChainingKey, sizeof(hs_.ci));
// Hi := HASH(Ci || IDENTIFIER)
memcpy(hs_.hi, kWgInitHash, sizeof(hs_.hi));
// Hi := HASH(Hi || Spub_r)
BlakeMix(hs_.hi, s_remote_, sizeof(s_remote_));
// (Epriv_r, Epub_r) := DH-GENERATE()
// msg.ephemeral = Epub_r
OsGetRandomBytes(hs_.e_priv, sizeof(hs_.e_priv));
curve25519_normalize(hs_.e_priv);
curve25519_donna(dst->ephemeral, hs_.e_priv, kCurve25519Basepoint);
// Ci := KDF_1(Ci, msg.ephemeral)
blake2s_hkdf(hs_.ci, sizeof(hs_.ci), NULL, 32, NULL, 32, dst->ephemeral, sizeof(dst->ephemeral), hs_.ci, WG_HASH_LEN);
// Hi := HASH(Hi || msg.ephemeral)
BlakeMix(hs_.hi, dst->ephemeral, sizeof(dst->ephemeral));
// (Ci, K) := KDF2(Ci, DH(epriv, spub_r))
ComputeHKDF2DH(hs_.ci, k, hs_.e_priv, s_remote_);
// msg.static = AEAD(K, 0, Spub_i, Hi)
chacha20poly1305_encrypt(dst->static_enc, dev_->s_pub_, sizeof(dev_->s_pub_), hs_.hi, sizeof(hs_.hi), 0, k);
// Hi := HASH(Hi || msg.static)
BlakeMix(hs_.hi, dst->static_enc, sizeof(dst->static_enc));
// (Ci, K) := KDF2(Ci, DH(sprivr, spubi))
blake2s_hkdf(hs_.ci, sizeof(hs_.ci), k, sizeof(k), NULL, 32, s_priv_pub_, sizeof(s_priv_pub_), hs_.ci, WG_HASH_LEN);
// TAI64N
OsGetTimestampTAI64N(dst->timestamp_enc);
size_t extfield_size = 0;
#if WITH_HANDSHAKE_EXT
if (supports_handshake_extensions_)
extfield_size = WriteHandshakeExtension(dst->timestamp_enc + WG_TIMESTAMP_LEN, NULL);
#endif // WITH_HANDSHAKE_EXT
// msg.timestamp := AEAD(K, 0, timestamp, hi)
chacha20poly1305_encrypt(dst->timestamp_enc, dst->timestamp_enc, extfield_size + WG_TIMESTAMP_LEN, hs_.hi, sizeof(hs_.hi), 0, k);
// Hi := HASH(Hi || msg.timestamp)
BlakeMix(hs_.hi, dst->timestamp_enc, extfield_size + WG_TIMESTAMP_LEN + WG_MAC_LEN);
packet->size = (unsigned)(sizeof(MessageHandshakeInitiation) + extfield_size);
dst->sender_key_id = dev_->InsertInKeyIdLookup(this, NULL);
dst->type = MESSAGE_HANDSHAKE_INITIATION;
memzero_crypto(k, sizeof(k));
WriteMacToPacket((uint8*)dst, (MessageMacs*)((uint8*)&dst->mac + extfield_size));
}
// Parsed by server
WgPeer *WgPeer::ParseMessageHandshakeInitiation(WgDevice *dev, Packet *packet) { // const MessageHandshakeInitiation *src, MessageHandshakeResponse *dst) {
assert(dev->IsMainThread());
// Copy values into handshake once we've validated it all.
uint8 ci[WG_HASH_LEN];
uint8 hi[WG_HASH_LEN];
union {
uint8 k[WG_SYMMETRIC_KEY_LEN];
uint8 e_priv[WG_PUBLIC_KEY_LEN];
};
union {
uint8 spubi[WG_PUBLIC_KEY_LEN];
uint8 e_remote[WG_PUBLIC_KEY_LEN];
uint8 hi2[WG_HASH_LEN];
};
uint8 t[WG_HASH_LEN];
WgPeer *peer;
WgKeypair *keypair;
uint32 remote_key_id;
uint64 now;
uint8 extbuf[MAX_SIZE_OF_HANDSHAKE_EXTENSION + WG_TIMESTAMP_LEN];
MessageHandshakeInitiation *src = (MessageHandshakeInitiation *)packet->data;
MessageHandshakeResponse *dst;
size_t extfield_size;
// Ci := HASH(CONSTRUCTION)
memcpy(ci, kWgInitChainingKey, sizeof(ci));
// Hi := HASH(Ci || IDENTIFIER)
memcpy(hi, kWgInitHash, sizeof(hi));
// Hi := HASH(Hi || Spub_r)
BlakeMix(hi, dev->s_pub_, sizeof(dev->s_pub_));
// Ci := KDF_1(Ci, msg.ephemeral)
blake2s_hkdf(ci, sizeof(ci), NULL, 32, NULL, 32, src->ephemeral, sizeof(src->ephemeral), ci, WG_HASH_LEN);
// Hi := HASH(Hi || msg.ephemeral)
BlakeMix(hi, src->ephemeral, sizeof(src->ephemeral));
// (Ci, K) := KDF2(Ci, DH(spriv, msg.ephemeral))
ComputeHKDF2DH(ci, k, dev->s_priv_, src->ephemeral);
// Spub_i = AEAD_DEC(K, 0, msg.static, Hi)
if (!chacha20poly1305_decrypt(spubi, src->static_enc, sizeof(src->static_enc), hi, sizeof(hi), 0, k))
goto getout;
// Hi := HASH(Hi || msg.static)
BlakeMix(hi, src->static_enc, sizeof(src->static_enc));
// Lookup the peer with this ID
if (!(peer = dev->GetPeerFromPublicKey(spubi)))
goto getout;
// (Ci, K) := KDF2(Ci, DH(sprivr, spubi))
blake2s_hkdf(ci, sizeof(ci), k, sizeof(k), NULL, 32, peer->s_priv_pub_, sizeof(peer->s_priv_pub_), ci, WG_HASH_LEN);
// Hi2 := Hi
memcpy(hi2, hi, sizeof(hi2));
extfield_size = packet->size - sizeof(MessageHandshakeInitiation);
if (extfield_size > MAX_SIZE_OF_HANDSHAKE_EXTENSION || (extfield_size && !peer->supports_handshake_extensions_))
goto getout;
// Hi := HASH(Hi || msg.timestamp)
BlakeMix(hi, src->timestamp_enc, extfield_size + WG_TIMESTAMP_LEN + WG_MAC_LEN);
// TIMESTAMP := AEAD_DEC(K, 0, msg.timestamp, hi2)
if (!chacha20poly1305_decrypt(extbuf, src->timestamp_enc, extfield_size + WG_TIMESTAMP_LEN + WG_MAC_LEN, hi2, sizeof(hi2), 0, k))
goto getout;
// Replay attack?
if (memcmp(extbuf, peer->last_timestamp_, WG_TIMESTAMP_LEN) <= 0)
goto getout;
// Flood attack?
now = OsGetMilliseconds();
if (now < peer->last_handshake_init_recv_timestamp_ + MIN_HANDSHAKE_INTERVAL_MS)
goto getout;
// Remember all the information we need to produce a response cause we cannot touch src again
peer->last_handshake_init_recv_timestamp_ = now;
memcpy(peer->last_timestamp_, extbuf, sizeof(peer->last_timestamp_));
memcpy(e_remote, src->ephemeral, sizeof(e_remote));
remote_key_id = src->sender_key_id;
dst = (MessageHandshakeResponse *)src;
// (Epriv_r, Epub_r) := DH-GENERATE()
// msg.ephemeral = Epub_r
OsGetRandomBytes(e_priv, sizeof(e_priv));
curve25519_normalize(e_priv);
curve25519_donna(dst->ephemeral, e_priv, kCurve25519Basepoint);
// Hr := HASH(Hr || msg.ephemeral)
BlakeMix(hi, dst->ephemeral, sizeof(dst->ephemeral));
// Ci := KDF_1(Ci, msg.ephemeral)
blake2s_hkdf(ci, sizeof(ci), NULL, 32, NULL, 32, dst->ephemeral, sizeof(dst->ephemeral), ci, WG_HASH_LEN);
// Ci : = KDF2(Ci, DH(epriv, epub))
ComputeHKDF2DH(ci, NULL, e_priv, e_remote);
// Ci : = KDF2(Ci, DH(epriv, spub))
ComputeHKDF2DH(ci, NULL, e_priv, peer->s_remote_);
// (Ci, T, K) := KDF3(Ci, Q)
blake2s_hkdf(ci, sizeof(ci), t, sizeof(t), k, sizeof(k), peer->preshared_key_, sizeof(preshared_key_), ci, WG_HASH_LEN);
// Hr := HASH(Hr || T)
BlakeMix(hi, t, sizeof(t));
dst->receiver_key_id = remote_key_id;
keypair = WgPeer::CreateNewKeypair(false, ci, remote_key_id, extbuf + WG_TIMESTAMP_LEN, extfield_size);
if (keypair) {
WG_ACQUIRE_LOCK(peer->mutex_);
peer->InsertKeypairInPeer_Locked(keypair);
peer->OnHandshakeAuthComplete();
WG_RELEASE_LOCK(peer->mutex_);
dst->sender_key_id = dev->InsertInKeyIdLookup(peer, keypair);
size_t extfield_out_size = 0;
#if WITH_HANDSHAKE_EXT
if (extfield_size)
extfield_out_size = peer->WriteHandshakeExtension(dst->empty_enc, keypair);
#endif // WITH_HANDSHAKE_EXT
packet->size = (unsigned)(sizeof(MessageHandshakeResponse) + extfield_out_size);
// msg.empty := AEAD(K, 0, "", Hr)
chacha20poly1305_encrypt(dst->empty_enc, dst->empty_enc, extfield_out_size, hi, sizeof(hi), 0, k);
// Hr := HASH(Hr || "")
//BlakeMix(hi, dst->empty_enc, extfield_out_size);
dst->type = MESSAGE_HANDSHAKE_RESPONSE;
peer->WriteMacToPacket((uint8*)dst, (MessageMacs*)((uint8*)&dst->mac + extfield_out_size));
} else {
getout:
peer = NULL;
}
memzero_crypto(hi, sizeof(hi));
memzero_crypto(ci, sizeof(ci));
memzero_crypto(k, sizeof(k));
memzero_crypto(t, sizeof(t));
return peer;
}
WgPeer *WgPeer::ParseMessageHandshakeResponse(WgDevice *dev, const Packet *packet) {
assert(dev->IsMainThread());
MessageHandshakeResponse *src = (MessageHandshakeResponse *)packet->data;
uint8 t[WG_HASH_LEN];
uint8 k[WG_SYMMETRIC_KEY_LEN];
WgKeypair *keypair;
auto peer_and_keypair = dev->LookupPeerInKeyIdLookup(src->receiver_key_id);
if (peer_and_keypair == NULL)
return NULL;
WgPeer *peer = peer_and_keypair->first;
assert(src->receiver_key_id == peer->local_key_id_during_hs_);
HandshakeState hs = peer->hs_;
// Hr := HASH(Hr || msg.ephemeral)
BlakeMix(hs.hi, src->ephemeral, sizeof(src->ephemeral));
// Ci := KDF_1(Ci, msg.ephemeral)
blake2s_hkdf(hs.ci, sizeof(hs.ci), NULL, 32, NULL, 32, src->ephemeral, sizeof(src->ephemeral), hs.ci, sizeof(hs.ci));
// Ci : = KDF2(Ci, DH(epriv, epub))
ComputeHKDF2DH(hs.ci, NULL, hs.e_priv, src->ephemeral);
// Ci : = KDF2(Ci, DH(spriv, epub))
ComputeHKDF2DH(hs.ci, NULL, peer->dev_->s_priv_, src->ephemeral);
// (Ci, T, K) := KDF3(Ci, Q)
blake2s_hkdf(hs.ci, sizeof(hs.ci), t, sizeof(t), k, sizeof(k), peer->preshared_key_, sizeof(peer->preshared_key_), hs.ci, sizeof(hs.ci));
// Hr := HASH(Hr || T)
BlakeMix(hs.hi, t, sizeof(t));
size_t extfield_size = packet->size - sizeof(MessageHandshakeResponse);
if (extfield_size > MAX_SIZE_OF_HANDSHAKE_EXTENSION)
goto getout;
// "" := AEAD_DEC(K, 0, msg.empty, Hr)
if (!chacha20poly1305_decrypt(src->empty_enc, src->empty_enc, extfield_size + sizeof(src->empty_enc), hs.hi, sizeof(hs.hi), 0, k))
goto getout;
keypair = WgPeer::CreateNewKeypair(true, hs.ci, src->sender_key_id, src->empty_enc, extfield_size);
if (!keypair)
goto getout;
// Re-map the entry in the id table so it points at this keypair instead.
keypair->local_key_id = peer->local_key_id_during_hs_;
peer->local_key_id_during_hs_ = 0;
peer_and_keypair->second = keypair;
WG_ACQUIRE_LOCK(peer->mutex_);
peer->InsertKeypairInPeer_Locked(keypair);
WG_RELEASE_LOCK(peer->mutex_);
if (0) {
getout:
peer = NULL;
}
memzero_crypto(t, sizeof(t));
memzero_crypto(k, sizeof(k));
memzero_crypto(&hs, sizeof(hs));
return peer;
}
// This is parsed by the initiator, when it needs to re-send the handshake message with a better mac.
void WgPeer::ParseMessageHandshakeCookie(WgDevice *dev, const MessageHandshakeCookie *src) {
assert(dev->IsMainThread());
uint8 cookie[WG_COOKIE_LEN];
auto peer_and_keypair = dev->LookupPeerInKeyIdLookup(src->receiver_key_id);
if (!peer_and_keypair)
return;
WgPeer *peer = peer_and_keypair->first;
if (!peer->expect_cookie_reply_)
return;
if (!xchacha20poly1305_decrypt(cookie, src->cookie_enc, sizeof(src->cookie_enc),
peer->sent_mac1_, sizeof(peer->sent_mac1_), src->nonce, peer->precomputed_cookie_key_))
return;
peer->expect_cookie_reply_ = false;
peer->has_mac2_cookie_ = true;
peer->mac2_cookie_timestamp_ = OsGetMilliseconds();
memcpy(peer->mac2_cookie_, cookie, sizeof(peer->mac2_cookie_));
}
#if WITH_HANDSHAKE_EXT
size_t WgPeer::WriteHandshakeExtension(uint8 *dst, WgKeypair *keypair) {
uint8 *dst_org = dst, value = 0;
// Include the supported features extension
if (!IsOnlyZeros(features_, sizeof(features_))) {
*dst++ = EXT_BOOLEAN_FEATURES;
*dst++ = (WG_FEATURES_COUNT + 3) >> 2;
for (size_t i = 0; i != WG_FEATURES_COUNT; i++) {
if ((i & 3) == 0)
value = 0;
dst[i >> 2] = (value += (features_[i] << ((i * 2) & 7)));
}
// swap WG_FEATURE_ID_SKIP_KEYID_IN and WG_FEATURE_ID_SKIP_KEYID_OUT
dst[1] = (dst[1] & 0xF0) + ((dst[1] >> 2) & 0x03) + ((dst[1] << 2) & 0x0C);
dst += (WG_FEATURES_COUNT + 3) >> 2;
}
// Ordered list of cipher suites
size_t ciphers = num_ciphers_;
if (ciphers) {
*dst++ = EXT_CIPHER_SUITES + cipher_prio_;
if (keypair) {
*dst++ = 1;
*dst++ = keypair->cipher_suite;
} else {
*dst++ = (uint8)ciphers;
memcpy(dst, ciphers_, ciphers);
dst += ciphers;
}
}
if (features_[WG_FEATURE_ID_IPZIP]) {
// Include the packet compression extension
*dst++ = EXT_PACKET_COMPRESSION;
*dst++ = sizeof(WgPacketCompressionVer01);
memcpy(dst, &dev_->compression_header_, sizeof(WgPacketCompressionVer01));
dst += sizeof(WgPacketCompressionVer01);
}
return dst - dst_org;
}
static bool ResolveBooleanFeatureValue(uint8 other, uint8 self, bool *result) {
uint8 both = other * 4 + self;
*result = (0xfec0 >> both) & 1;
return (0xeff7 >> both) & 1;
}
static const uint8 cipher_strengths[EXT_CIPHER_SUITE_COUNT] = {4,2,3,1};
static uint32 ResolveCipherSuite(int tie, const uint8 *a, size_t a_size, const uint8 *b, size_t b_size) {
uint32 abits[8] = {0}, bbits[8] = {0}, found_a = 0, found_b = 0;
for (size_t i = 0; i < a_size; i++)
abits[a[i] >> 5] |= 1 << (a[i] & 31);
for (size_t i = 0; i < b_size; i++)
bbits[b[i] >> 5] |= 1 << (b[i] & 31);
for (size_t i = 0; i < a_size; i++)
if (bbits[a[i] >> 5] & (1 << (a[i] & 31))) {
found_a = a[i];
break;
}
for (size_t i = 0; i < b_size; i++)
if (abits[b[i] >> 5] & (1 << (b[i] & 31))) {
found_b = b[i];
break;
}
return (tie > 0 ||
(tie == 0 && cipher_strengths[found_a] > cipher_strengths[found_b])) ? found_a : found_b;
}
void WgKeypairSetupCompressionExtension(WgKeypair *keypair, const WgPacketCompressionVer01 *remotec) {
const WgPacketCompressionVer01 *localc = keypair->peer->dev_->compression_header();
IpzipState *state = &keypair->ipzip_state_;
// Use is_initiator as tie-breaker on who's going to be the client side.
int flags_xor = 0;
if ((localc->flags & ~3) + 2 * keypair->is_initiator - 1 <= (remotec->flags & ~3))
std::swap(localc, remotec), flags_xor = 1;
state->flags_xor = flags_xor;
memcpy(state->client_addr_v4, localc->ipv4_addr, 4);
memcpy(state->client_addr_v6, localc->ipv6_addr, 16);
state->guess_ttl[0] = localc->ttl;
state->client_addr_v4_subnet_bytes = (localc->flags & 3);
WriteLE32(&state->client_addr_v4_netmask, 0xffffffff >> ((localc->flags & 3) * 8));
memcpy(state->server_addr_v4, remotec->ipv4_addr, 4);
memcpy(state->server_addr_v6, remotec->ipv6_addr, 16);
state->guess_ttl[1] = remotec->ttl;
state->server_addr_v4_subnet_bytes = (remotec->flags & 3);
WriteLE32(&state->server_addr_v4_netmask, 0xffffffff >> ((remotec->flags & 3) * 8));
}
bool WgKeypairParseExtendedHandshake(WgKeypair *keypair, const uint8 *data, size_t data_size) {
bool did_setup_compression = false;
while (data_size >= 2) {
uint8 type = data[0], size = data[1];
data += 2, data_size -= 2;
if (size > data_size)
return false;
switch (type) {
case EXT_CIPHER_SUITES_PRIO:
case EXT_CIPHER_SUITES:
keypair->cipher_suite = ResolveCipherSuite(keypair->peer->cipher_prio_ - (type - EXT_CIPHER_SUITES),
keypair->peer->ciphers_, keypair->peer->num_ciphers_,
data, data_size);
break;
case EXT_BOOLEAN_FEATURES:
for (size_t i = 0, j = std::max<uint32>(WG_FEATURES_COUNT, size * 4); i != j; i++) {
uint8 value = (i < size * 4) ? (data[i >> 2] >> ((i * 2) & 7)) & 3 : 0;
if (i >= WG_FEATURES_COUNT ? (value == WG_BOOLEAN_FEATURE_ENFORCES) :
!ResolveBooleanFeatureValue(value, keypair->peer->features_[i], &keypair->enabled_features[i]))
return false;
}
break;
case EXT_PACKET_COMPRESSION:
if (size == sizeof(WgPacketCompressionVer01)) {
WgPacketCompressionVer01 *c = (WgPacketCompressionVer01*)data;
if (ReadLE16(&c->version) == EXT_PACKET_COMPRESSION_VER) {
WgKeypairSetupCompressionExtension(keypair, c);
did_setup_compression = true;
}
}
break;
}
data += size, data_size -= size;
}
if (data_size != 0)
return false;
keypair->enabled_features[WG_FEATURE_ID_IPZIP] &= did_setup_compression;
keypair->auth_tag_length = (keypair->enabled_features[WG_FEATURE_ID_SHORT_MAC] ? 8 : CHACHA20POLY1305_AUTHTAGLEN);
// RINFO("Cipher Suite = %d", keypair->cipher_suite);
return true;
}
#endif // WITH_HANDSHAKE_EXT
static void ActualFreeKeypair(void *x) {
WgKeypair *t = (WgKeypair*)x;
if (t->aes_gcm128_context_)
free(t->aes_gcm128_context_);
delete t;
}
void WgPeer::DeleteKeypair(WgKeypair **kp) {
WgKeypair *t = *kp;
*kp = NULL;
if (t) {
assert(t->peer->IsPeerLocked());
if (t->addr_entry) {
WG_SCOPED_RWLOCK_EXCLUSIVE(dev_->addr_entry_lookup_lock_);
dev_->EraseKeypairAddrEntry_Locked(t);
}
if (t->local_key_id) {
WG_SCOPED_RWLOCK_EXCLUSIVE(dev_->key_id_lookup_lock_);
dev_->key_id_lookup_.erase(t->local_key_id);
t->local_key_id = 0;
}
t->recv_key_state = WgKeypair::KEY_INVALID;
dev_->delayed_delete_.Add(&ActualFreeKeypair, t);
}
}
WgKeypair *WgPeer::CreateNewKeypair(bool is_initiator, const uint8 chaining_key[WG_HASH_LEN], uint32 remote_key_id, const uint8 *extfield, size_t extfield_size) {
WgKeypair *kp = new WgKeypair;
uint8 *first_key, *second_key;
if (!kp)
return NULL;
memset(kp, 0, offsetof(WgKeypair, replay_detector));
kp->is_initiator = is_initiator;
kp->remote_key_id = remote_key_id;
kp->auth_tag_length = CHACHA20POLY1305_AUTHTAGLEN;
#if WITH_HANDSHAKE_EXT
if (!WgKeypairParseExtendedHandshake(kp, extfield, extfield_size)) {
fail:
delete kp;
return NULL;
}
#endif // WITH_HANDSHAKE_EXT
first_key = kp->send_key, second_key = kp->recv_key;
if (!is_initiator)
std::swap(first_key, second_key);
blake2s_hkdf(first_key, sizeof(kp->send_key), second_key, sizeof(kp->recv_key),
kp->auth_tag_length != CHACHA20POLY1305_AUTHTAGLEN ? (uint8*)kp->compress_mac_keys : NULL, 32,
NULL, 0, chaining_key, WG_HASH_LEN);
if (!is_initiator) {
std::swap(kp->compress_mac_keys[0][0], kp->compress_mac_keys[1][0]);
std::swap(kp->compress_mac_keys[0][1], kp->compress_mac_keys[1][1]);
}
#if WITH_HANDSHAKE_EXT
if (kp->cipher_suite >= EXT_CIPHER_SUITE_AES128_GCM && kp->cipher_suite <= EXT_CIPHER_SUITE_AES256_GCM) {
#if WITH_AESGCM
kp->aes_gcm128_context_ = (AesGcm128StaticContext *)malloc(sizeof(*kp->aes_gcm128_context_) * 2);
if (!kp->aes_gcm128_context_)
goto fail;
int key_size = (kp->cipher_suite == EXT_CIPHER_SUITE_AES128_GCM) ? 128 : 256;
CRYPTO_gcm128_init(&kp->aes_gcm128_context_[0], kp->send_key, key_size);
CRYPTO_gcm128_init(&kp->aes_gcm128_context_[1], kp->recv_key, key_size);
#else // WITH_AESGCM
goto fail;
#endif // WITH_AESGCM
}
#endif // WITH_HANDSHAKE_EXT
kp->send_key_state = kp->recv_key_state = WgKeypair::KEY_VALID;
kp->key_timestamp = OsGetMilliseconds();
return kp;
}
void WgPeer::InsertKeypairInPeer_Locked(WgKeypair *kp) {
assert(dev_->IsMainThread() && IsPeerLocked());
assert(kp->peer == NULL);
kp->peer = this;
time_of_next_key_event_ = 0;
DeleteKeypair(&prev_keypair_);
if (kp->is_initiator) {
// When we're the initator then we got the handshake and we can
// use the keypair right away.
if (next_keypair_) {
prev_keypair_ = next_keypair_;
next_keypair_ = NULL;
DeleteKeypair(&curr_keypair_);
} else {
prev_keypair_ = curr_keypair_;
}
curr_keypair_ = kp;
} else {
// The keypair will be moved to curr when we get the first data packet.
DeleteKeypair(&next_keypair_);
next_keypair_ = kp;
}
}
bool WgPeer::CheckSwitchToNextKey_Locked(WgKeypair *keypair) {
assert(IsPeerLocked());
if (keypair != next_keypair_)
return false;
DeleteKeypair(&prev_keypair_);
prev_keypair_ = curr_keypair_;
curr_keypair_ = next_keypair_;
next_keypair_ = NULL;
time_of_next_key_event_ = 0;
return true;
}
bool WgPeer::CheckHandshakeRateLimit() {
assert(dev_->IsMainThread());
uint64 now = OsGetMilliseconds();
if (now - last_handshake_init_timestamp_ < REKEY_TIMEOUT_MS)
return false;
last_handshake_init_timestamp_ = now;
return true;
}
void WgPeer::WriteMacToPacket(const uint8 *data, MessageMacs *dst) {
assert(dev_->IsMainThread());
expect_cookie_reply_ = true;
blake2s(dst->mac1, sizeof(dst->mac1), data, (uint8*)dst->mac1 - data, precomputed_mac1_key_, sizeof(precomputed_mac1_key_));
memcpy(sent_mac1_, dst->mac1, sizeof(sent_mac1_));
if (has_mac2_cookie_ && OsGetMilliseconds() - mac2_cookie_timestamp_ < COOKIE_SECRET_MAX_AGE_MS - COOKIE_SECRET_LATENCY_MS) {
blake2s(dst->mac2, sizeof(dst->mac2), data, (uint8*)dst->mac2 - data, mac2_cookie_, sizeof(mac2_cookie_));
} else {
has_mac2_cookie_ = false;
if (dev_->header_obfuscation_) {
// when obfuscation is enabled just make the top bits random
for (size_t i = 0; i < 4; i++)
((uint32*)dst->mac2)[i] = dev_->GetRandomNumber();
} else {
memset(dst->mac2, 0, sizeof(dst->mac2));
}
}
}
enum {
// Timer for retransmitting the handshake if we don't hear back after REKEY_TIMEOUT_MS
TIMER_RETRANSMIT_HANDSHAKE = 0,
// Timer for sending keepalive if we received a packet if we don't send anything else for KEEPALIVE_TIMEOUT_MS
TIMER_SEND_KEEPALIVE = 1,
// Timer for initiating new handshake if we have sent a packet but after have not received one for KEEPALIVE_TIMEOUT_MS + REKEY_TIMEOUT_MS
TIMER_NEW_HANDSHAKE = 2,
// Timer for zeroing out all keys and handshake state after (REJECT_AFTER_TIME_MS * 3) if no new keys have been received
TIMER_ZERO_KEYS = 3,
// Timer for sending a keepalive packet every PERSISTENT_KEEPALIVE_MS
TIMER_PERSISTENT_KEEPALIVE = 4,
};
#define WgClearTimer(x) (timers_ &= ~(33 << x))
#define WgIsTimerActive(x) (timers_ & (33 << x))
#define WgSetTimer(x) (timers_ |= (32 << (x)))
void WgPeer::OnDataSent() {
assert(IsPeerLocked());
WgClearTimer(TIMER_SEND_KEEPALIVE);
if (!WgIsTimerActive(TIMER_NEW_HANDSHAKE))
WgSetTimer(TIMER_NEW_HANDSHAKE);
WgSetTimer(TIMER_PERSISTENT_KEEPALIVE);
}
void WgPeer::OnKeepaliveSent() {
assert(IsPeerLocked());
WgSetTimer(TIMER_PERSISTENT_KEEPALIVE);
}
void WgPeer::OnDataReceived() {
assert(IsPeerLocked());
WgClearTimer(TIMER_NEW_HANDSHAKE);
if (!WgIsTimerActive(TIMER_SEND_KEEPALIVE))
WgSetTimer(TIMER_SEND_KEEPALIVE);
else
pending_keepalive_ = true;
WgSetTimer(TIMER_PERSISTENT_KEEPALIVE);
}
void WgPeer::OnKeepaliveReceived() {
assert(IsPeerLocked());
WgClearTimer(TIMER_NEW_HANDSHAKE);
WgSetTimer(TIMER_PERSISTENT_KEEPALIVE);
}
void WgPeer::OnHandshakeInitSent() {
assert(IsPeerLocked());
WgClearTimer(TIMER_SEND_KEEPALIVE);
WgSetTimer(TIMER_RETRANSMIT_HANDSHAKE);
}
void WgPeer::OnHandshakeAuthComplete() {
assert(IsPeerLocked());
WgClearTimer(TIMER_NEW_HANDSHAKE);
WgSetTimer(TIMER_ZERO_KEYS);
WgSetTimer(TIMER_PERSISTENT_KEEPALIVE);
}
static const char * const kCipherSuites[] = {
"chacha20-poly1305",
"aes128-gcm",
"aes256-gcm",
"none"
};
void WgPeer::OnHandshakeFullyComplete() {
assert(IsPeerLocked());
WgClearTimer(TIMER_RETRANSMIT_HANDSHAKE);
total_handshake_attempts_ = handshake_attempts_ = 0;
uint64 now = OsGetMilliseconds();
if (last_complete_handskake_timestamp_ == 0) {
bool any_feature = false;
for(size_t i = 0; i < WG_FEATURES_COUNT; i++)
any_feature |= curr_keypair_->enabled_features[i];
if (curr_keypair_->cipher_suite != 0 || any_feature) {
RINFO("Using %s, %s %s %s %s %s", kCipherSuites[curr_keypair_->cipher_suite],
curr_keypair_->enabled_features[0] ? "short_header" : "",
curr_keypair_->enabled_features[1] ? "mac64" : "",
curr_keypair_->enabled_features[2] ? "ipzip" : "",
curr_keypair_->enabled_features[4] ? "skip_keyid_in" : "",
curr_keypair_->enabled_features[5] ? "skip_keyid_out" : "");
}
}
last_complete_handskake_timestamp_ = now;
// RINFO("Connection established.");
}
// Check if any of the timeouts have expired
uint32 WgPeer::CheckTimeouts(uint64 now) {
assert(IsPeerLocked());
uint32 t, rv = 0;
if (now >= time_of_next_key_event_)
CheckAndUpdateTimeOfNextKeyEvent(now);
if ((t = timers_) == 0)
return 0;
uint32 now32 = (uint32)now;
// Got any new timers?
if (t & (0x1f << 5)) {
if (t & (1 << (5+0))) timer_value_[0] = now32;
if (t & (1 << (5+1))) timer_value_[1] = now32;
if (t & (1 << (5+2))) timer_value_[2] = now32;
if (t & (1 << (5+3))) timer_value_[3] = now32;
if (t & (1 << (5+4))) timer_value_[4] = now32;
t |= (t >> 5);
t &= 0x1F;
}
// Got any expired timers?
if (t & 0x1F) {
if ((t & (1 << TIMER_RETRANSMIT_HANDSHAKE)) && (now32 - timer_value_[TIMER_RETRANSMIT_HANDSHAKE]) >= REKEY_TIMEOUT_MS) {
t ^= (1 << TIMER_RETRANSMIT_HANDSHAKE);
if (handshake_attempts_ > MAX_HANDSHAKE_ATTEMPTS) {
t &= ~(1 << TIMER_SEND_KEEPALIVE);
ClearPacketQueue_Locked();
} else {
handshake_attempts_++;
rv |= ACTION_SEND_HANDSHAKE;
}
}
if ((t & (1 << TIMER_SEND_KEEPALIVE)) && (now32 - timer_value_[TIMER_SEND_KEEPALIVE]) >= KEEPALIVE_TIMEOUT_MS) {
t &= ~(1 << TIMER_SEND_KEEPALIVE);
rv |= ACTION_SEND_KEEPALIVE;
if (pending_keepalive_) {
pending_keepalive_ = false;
timer_value_[TIMER_SEND_KEEPALIVE] = now32;
t |= (1 << TIMER_SEND_KEEPALIVE);
}
}
if ((t & (1 << TIMER_PERSISTENT_KEEPALIVE)) && (now32 - timer_value_[TIMER_PERSISTENT_KEEPALIVE]) >= (uint32)persistent_keepalive_ms_) {
t &= ~(1 << TIMER_PERSISTENT_KEEPALIVE);
if (persistent_keepalive_ms_) {
t &= ~(1 << TIMER_SEND_KEEPALIVE);
rv |= ACTION_SEND_KEEPALIVE;
}
}
if ((t & (1 << TIMER_NEW_HANDSHAKE)) && (now32 - timer_value_[TIMER_NEW_HANDSHAKE]) >= KEEPALIVE_TIMEOUT_MS + REKEY_TIMEOUT_MS) {
t &= ~(1 << TIMER_NEW_HANDSHAKE);
handshake_attempts_ = 0;
rv |= ACTION_SEND_HANDSHAKE;
}
if ((t & (1 << TIMER_ZERO_KEYS)) && (now32 - timer_value_[TIMER_ZERO_KEYS]) >= REJECT_AFTER_TIME_MS * 3) {
RINFO("Expiring all keys for peer");
t &= ~(1 << TIMER_ZERO_KEYS);
ClearKeys_Locked();
ClearHandshake_Locked();
}
}
timers_ = t;
return rv;
}
// Check all key stuff here to avoid calling possibly expensive timestamp routines in the packet handler
void WgPeer::CheckAndUpdateTimeOfNextKeyEvent(uint64 now) {
assert(IsPeerLocked());
uint64 next_time = UINT64_MAX;
uint32 rv = 0;
if (curr_keypair_ != NULL) {
if (now >= curr_keypair_->key_timestamp + REJECT_AFTER_TIME_MS) {
DeleteKeypair(&curr_keypair_);
} else if (curr_keypair_->is_initiator) {
// if a peer is the initiator of a current secure session, WireGuard will send a handshake initiation
// message to begin a new secure session if, after transmitting a transport data message, the current secure session
// is REKEY_AFTER_TIME_MS old, or if after receiving a transport data message, the current secure session is
// (REKEY_AFTER_TIME_MS - KEEPALIVE_TIMEOUT_MS - REKEY_TIMEOUT_MS) seconds old and it has not yet acted upon it.
if (now >= curr_keypair_->key_timestamp + (REJECT_AFTER_TIME_MS - KEEPALIVE_TIMEOUT_MS - REKEY_TIMEOUT_MS)) {
next_time = curr_keypair_->key_timestamp + REJECT_AFTER_TIME_MS;
if (curr_keypair_->recv_key_state == WgKeypair::KEY_VALID)
curr_keypair_->recv_key_state = WgKeypair::KEY_WANT_REFRESH;
} else if (now >= curr_keypair_->key_timestamp + REKEY_AFTER_TIME_MS) {
next_time = curr_keypair_->key_timestamp + (REJECT_AFTER_TIME_MS - KEEPALIVE_TIMEOUT_MS - REKEY_TIMEOUT_MS);
if (curr_keypair_->send_key_state == WgKeypair::KEY_VALID)
curr_keypair_->send_key_state = WgKeypair::KEY_WANT_REFRESH;
} else {
next_time = curr_keypair_->key_timestamp + REKEY_AFTER_TIME_MS;
}
} else {
next_time = curr_keypair_->key_timestamp + REJECT_AFTER_TIME_MS;
}
}
if (prev_keypair_ != NULL) {
if (now >= prev_keypair_->key_timestamp + REJECT_AFTER_TIME_MS)
DeleteKeypair(&prev_keypair_);
else
next_time = std::min<uint64>(next_time, prev_keypair_->key_timestamp + REJECT_AFTER_TIME_MS);
}
if (next_keypair_ != NULL) {
if (now >= next_keypair_->key_timestamp + REJECT_AFTER_TIME_MS)
DeleteKeypair(&next_keypair_);
else
next_time = std::min<uint64>(next_time, next_keypair_->key_timestamp + REJECT_AFTER_TIME_MS);
}
time_of_next_key_event_ = next_time;
}
void WgPeer::SetEndpoint(const IpAddr &sin) {
endpoint_ = sin;
}
void WgPeer::SetPersistentKeepalive(int persistent_keepalive_secs) {
if (persistent_keepalive_secs < 10 || persistent_keepalive_secs > 10000)
return;
persistent_keepalive_ms_ = persistent_keepalive_secs * 1000;
}
bool WgPeer::AddIp(const WgCidrAddr &cidr_addr) {
assert(dev_->IsMainThread());
if (cidr_addr.size == 32) {
if (cidr_addr.cidr > 32)
return false;
WG_ACQUIRE_RWLOCK_EXCLUSIVE(dev_->ip_to_peer_map_lock_);
dev_->ip_to_peer_map_.InsertV4(cidr_addr.addr, cidr_addr.cidr, this);
WG_RELEASE_RWLOCK_EXCLUSIVE(dev_->ip_to_peer_map_lock_);
allowed_ips_.push_back(cidr_addr);
return true;
} else if (cidr_addr.size == 128) {
if (cidr_addr.cidr > 128)
return false;
WG_ACQUIRE_RWLOCK_EXCLUSIVE(dev_->ip_to_peer_map_lock_);
dev_->ip_to_peer_map_.InsertV6(cidr_addr.addr, cidr_addr.cidr, this);
WG_RELEASE_RWLOCK_EXCLUSIVE(dev_->ip_to_peer_map_lock_);
allowed_ips_.push_back(cidr_addr);
return true;
} else {
return false;
}
}
void WgPeer::SetAllowMulticast(bool allow) {
allow_multicast_through_peer_ = allow;
}
void WgPeer::SetFeature(int feature, uint8 value) {
features_[feature] = value;
}
bool WgPeer::AddCipher(int cipher) {
if (num_ciphers_ == MAX_CIPHERS)
return false;
if (cipher == EXT_CIPHER_SUITE_AES128_GCM || cipher == EXT_CIPHER_SUITE_AES256_GCM) {
#if defined(ARCH_CPU_X86_FAMILY) && WITH_AESGCM
if (!X86_PCAP_AES)
return true;
#else
return true;
#endif // defined(ARCH_CPU_X86_FAMILY) && WITH_AESGCM
}
ciphers_[num_ciphers_++] = cipher;
return true;
}
WgRateLimit::WgRateLimit() {
key1_[0] = key1_[1] = 1;
key2_[0] = key2_[1] = 1;
bin1_ = bins_[0];
bin2_ = bins_[1];
rand_ = 0;
rand_xor_ = 0;
packets_per_sec_ = PACKETS_PER_SEC;
used_rate_limit_ = 0;
memset(bins_, 0, sizeof(bins_));
}
void WgRateLimit::Periodic(uint32 s[5]) {
unsigned int per_sec = PACKETS_PER_SEC;
if (used_rate_limit_ >= TOTAL_PACKETS_PER_SEC) {
per_sec = PACKETS_PER_SEC * TOTAL_PACKETS_PER_SEC / used_rate_limit_;
if (per_sec < 1)
per_sec = 1;
}
if ((unsigned)per_sec > packets_per_sec_)
per_sec = (per_sec + packets_per_sec_ + 1) >> 1;
packets_per_sec_ = per_sec;
used_rate_limit_ = 0;
rand_xor_ = s[4];
key2_[0] = key1_[0];
key2_[1] = key1_[1];
memcpy(key1_, s, sizeof(key1_));
std::swap(bin1_, bin2_);
memset(bin1_, 0, BINSIZE);
}
static inline size_t hashit(uint64 ip, const uint64 *key) {
uint64 x = ip * key[0] + rol64(ip, 32) * key[1];
uint32 a = (uint32)(x + (x >> 32) * 0x85ebca6b);
a -= a >> 16;
a ^= a >> 4;
return a;
}
WgRateLimit::RateLimitResult WgRateLimit::CheckRateLimit(uint64 ip) {
uint8 *a = &bin1_[hashit(ip, key1_) & (BINSIZE - 1)];
uint8 *b = &bin2_[hashit(ip, key2_) & (BINSIZE - 1)];
unsigned int old = std::max<int>(*a, *b - packets_per_sec_), v = 0;
if (old < PACKET_ACCUM / 2) {
v = 1;
} else if (old < PACKET_ACCUM) {
v = old < ((uint64)rand_ * ((PACKET_ACCUM / 2) + 1) >> 32) + (PACKET_ACCUM / 2);
rand_ = (rand_ * 0x1b873593 + 5) + rand_xor_;
}
RateLimitResult rr = {a, (uint8)(old + v), (uint8)v};
return rr;
}
void WgKeypairEncryptPayload(uint8 *dst, const size_t src_len,
const uint8 *ad, const size_t ad_len,
const uint64 nonce, WgKeypair *keypair) {
if (keypair->cipher_suite == EXT_CIPHER_SUITE_CHACHA20POLY1305) {
chacha20poly1305_encrypt(dst, dst, src_len, ad, ad_len, nonce, keypair->send_key);
} else if (keypair->cipher_suite >= EXT_CIPHER_SUITE_AES128_GCM && keypair->cipher_suite <= EXT_CIPHER_SUITE_AES256_GCM) {
#if WITH_AESGCM
aesgcm_encrypt(dst, dst, src_len, ad, ad_len, nonce, &keypair->aes_gcm128_context_[0]);
#endif // WITH_AESGCM
} else {
poly1305_get_mac(dst, src_len, ad, ad_len, nonce, keypair->send_key, dst + src_len);
}
// Convert MAC to 8 bytes if that's all we need.
if (keypair->auth_tag_length != WG_MAC_LEN) {
uint8 *mac = dst + src_len;
uint64 rv = siphash_2u64(ReadLE64(mac), ReadLE64(mac + 8), (siphash_key_t*)keypair->compress_mac_keys[0]);
WriteLE64(mac, rv);
}
}
bool WgKeypairDecryptPayload(uint8 *dst, size_t src_len,
const uint8 *ad, size_t ad_len,
const uint64 nonce, WgKeypair *keypair) {
__aligned(16) uint8 mac[16];
if (src_len < keypair->auth_tag_length)
return false;
src_len -= keypair->auth_tag_length;
if (keypair->cipher_suite == EXT_CIPHER_SUITE_CHACHA20POLY1305) {
chacha20poly1305_decrypt_get_mac(dst, dst, src_len, ad, ad_len, nonce, keypair->recv_key, mac);
} else if (keypair->cipher_suite >= EXT_CIPHER_SUITE_AES128_GCM && keypair->cipher_suite <= EXT_CIPHER_SUITE_AES256_GCM) {
#if WITH_AESGCM
aesgcm_decrypt_get_mac(dst, dst, src_len, ad, ad_len, nonce, &keypair->aes_gcm128_context_[1], mac);
#else // WITH_AESGCM
return false;
#endif // WITH_AESGCM
} else {
poly1305_get_mac(dst, src_len, ad, ad_len, nonce, keypair->recv_key, mac);
}
if (keypair->auth_tag_length == WG_MAC_LEN) {
return memcmp_crypto(mac, dst + src_len, WG_MAC_LEN) == 0;
} else {
uint64 rv = siphash_2u64(ReadLE64(mac), ReadLE64(mac + 8), (siphash_key_t*)keypair->compress_mac_keys[1]);
WriteLE64(mac, rv);
return memcmp_crypto(mac, dst + src_len, keypair->auth_tag_length) == 0;
}
}