From 6e09191bf5ecc61c2bd877287359f7560d30446d Mon Sep 17 00:00:00 2001 From: Ludvig Strigeus Date: Mon, 10 Dec 2018 23:31:22 +0100 Subject: [PATCH] Move out UDP obfuscator to a separate class --- network_bsd.cpp | 3 ++ network_win32.cpp | 7 +++ network_win32.h | 18 ++++++- tunsafe_bsd.cpp | 3 +- wireguard.cpp | 42 ---------------- wireguard.h | 1 - wireguard_config.cpp | 2 +- wireguard_proto.cpp | 113 +++++++++++++++++++++++++++++++++++++------ wireguard_proto.h | 42 +++++++++++----- 9 files changed, 157 insertions(+), 74 deletions(-) diff --git a/network_bsd.cpp b/network_bsd.cpp index 62d5e5f..01f03ca 100644 --- a/network_bsd.cpp +++ b/network_bsd.cpp @@ -480,6 +480,9 @@ bool UdpSocketBsd::DoRead() { read_packet->size = r; read_packet->protocol = kPacketProtocolUdp; network_->read_packet_ = NULL; + + if (processor_->dev().packet_obfuscator().enabled()) + processor_->dev().packet_obfuscator().DeobfuscatePacket(read_packet); processor_->HandleUdpPacket(read_packet, network_->overload_); return true; } else { diff --git a/network_win32.cpp b/network_win32.cpp index 51dce48..620764f 100644 --- a/network_win32.cpp +++ b/network_win32.cpp @@ -961,6 +961,13 @@ void PacketProcessorUdpCb::OnQueuedItemDelete(QueuedItem *qi) { FreePacket(static_cast(qi)); } +void PacketProcessorDeobfuscateUdpCb::OnQueuedItemEvent(QueuedItem *qi, uintptr_t extra) { + PacketProcessor::QueueContext *context = (PacketProcessor::QueueContext *)extra; + Packet *packet = static_cast(qi); + context->wg->dev().packet_obfuscator().DeobfuscatePacket(packet); + PacketProcessorUdpCb::OnQueuedItemEvent(qi, extra); +} + void PacketProcessor::PostExit(int exit_code) { mutex_.Acquire(); // Avoid race condition where mode_tun_failed is set during thread exit. diff --git a/network_win32.h b/network_win32.h index 4ba31c2..693beb3 100644 --- a/network_win32.h +++ b/network_win32.h @@ -30,6 +30,10 @@ struct PacketProcessorUdpCb : QueuedItemCallback { virtual void OnQueuedItemDelete(QueuedItem *ow) override; }; +struct PacketProcessorDeobfuscateUdpCb : PacketProcessorUdpCb { + virtual void OnQueuedItemEvent(QueuedItem *ow, uintptr_t extra) override; +}; + class PacketProcessor { public: explicit PacketProcessor(); @@ -41,11 +45,20 @@ public: void PostPackets(Packet *first, Packet **end, int count); void ForcePost(QueuedItem *item); void PostExit(int exit_code); + void EnableDeobfuscation() { + udp_cb_maybe_deobfuscate_ = &udp_cb_deobfuscate_; + } const uint32 *posted_exit_code() { return &exit_code_; } + // Handler for tun packets QueuedItemCallback *tun_queue() { return &tun_cb_; } - QueuedItemCallback *udp_queue() { return &udp_cb_; } + + // Handler for udp packets + QueuedItemCallback *udp_queue() { return udp_cb_maybe_deobfuscate_; } + + // Incoming queue for tcp packets that do not use deobfuscation + QueuedItemCallback *tcp_queue() { return &udp_cb_; } struct QueueContext { WireguardProcessor *wg; @@ -65,8 +78,11 @@ private: uint32 exit_code_; bool timer_interrupt_; + QueuedItemCallback *udp_cb_maybe_deobfuscate_; + PacketProcessorTunCb tun_cb_; PacketProcessorUdpCb udp_cb_; + PacketProcessorDeobfuscateUdpCb udp_cb_deobfuscate_; }; class NetworkWin32; diff --git a/tunsafe_bsd.cpp b/tunsafe_bsd.cpp index af55a46..34cbf93 100644 --- a/tunsafe_bsd.cpp +++ b/tunsafe_bsd.cpp @@ -773,6 +773,8 @@ void TunsafeBackendBsdImpl::WriteUdpPacket(Packet *packet) { if (packet->protocol & kPacketProtocolTcp) { WriteTcpPacket(packet); } else { + if (processor_.dev().packet_obfuscator().enabled()) + processor_.dev().packet_obfuscator().ObfuscatePacket(packet); udp_.WritePacket(packet); } } @@ -857,7 +859,6 @@ void TunsafeBackendBsdImpl::CloseOrphanTcpConnections() { for(const auto &it : lookup) delete (TcpSocketBsd *)it.second; } - int main(int argc, char **argv) { CommandLineOutput cmd = {0}; diff --git a/wireguard.cpp b/wireguard.cpp index 39be614..e4e5fa4 100644 --- a/wireguard.cpp +++ b/wireguard.cpp @@ -95,10 +95,6 @@ void WireguardProcessor::SetInternetBlocking(InternetBlockState internet_blockin internet_blocking_ = internet_blocking; } -void WireguardProcessor::SetHeaderObfuscation(const char *key) { - dev_.SetHeaderObfuscation(key); -} - const WgProcessorStats &WireguardProcessor::GetStats() { // todo: only supports one peer but i want this in the ui for now. stats_.endpoint.sin.sin_family = 0; @@ -573,42 +569,10 @@ getout_discard: return kPacketResult_Free; } -// This scrambles the initial 16 bytes of the packet with the -// next 8 bytes of the packet as a seed. -static void ScrambleUnscramblePacket(Packet *packet, ScramblerSiphashKeys *keys) { - uint8 *data = packet->data; - size_t data_size = packet->size; - - if (data_size <= 8) - return; - - uint64 last_uint64 = ReadLE64(data_size >= 24 ? data + 16 : data + data_size - 8); - uint64 a = siphash_u64_u32(last_uint64, (uint32)data_size, (siphash_key_t*)&keys->keys[0]); - uint64 b = siphash_u64_u32(last_uint64, (uint32)data_size, (siphash_key_t*)&keys->keys[2]); - a = ToLE64(a); - b = ToLE64(b); - if (data_size >= 24) { - ((uint64*)data)[0] ^= a; - ((uint64*)data)[1] ^= b; - } else { - union { - uint64 d[2]; - uint8 s[16]; - } scrambler = {{a,b}}; - for (size_t i = 0; i < data_size - 8; i++) - data[i] ^= scrambler.s[i]; - } -} - void WireguardProcessor::PrepareOutgoingHandshakePacket(WgPeer *peer, Packet *packet) { assert(dev_.IsMainThread()); - stats_.udp_packets_out++; stats_.udp_bytes_out += packet->size; -#if WITH_HEADER_OBFUSCATION - if (dev_.header_obfuscation_) - ScrambleUnscramblePacket(packet, &dev_.header_obfuscation_key_); -#endif // WITH_HEADER_OBFUSCATION } void WireguardProcessor::RunAllMainThreadScheduled() { @@ -694,12 +658,6 @@ WireguardProcessor::PacketResult WireguardProcessor::HandleUdpPacket2(Packet *pa uint32 type; assert(packet->protocol != 0xCD && (uint16)packet->addr.sin.sin_family != 0xCDCD); // catch msvc uninit mem - // Unscramble incoming packets -#if WITH_HEADER_OBFUSCATION - if (dev_.header_obfuscation_) - ScrambleUnscramblePacket(packet, &dev_.header_obfuscation_key_); -#endif // WITH_HEADER_OBFUSCATION - stats_.udp_bytes_in += packet->size; stats_.udp_packets_in++; diff --git a/wireguard.h b/wireguard.h index 1197215..5f16834 100644 --- a/wireguard.h +++ b/wireguard.h @@ -91,7 +91,6 @@ public: void SetAddRoutesMode(bool mode); void SetDnsBlocking(bool dns_blocking); void SetInternetBlocking(InternetBlockState internet_blocking); - void SetHeaderObfuscation(const char *key); void HandleTunPacket(Packet *packet); void HandleUdpPacket(Packet *packet, bool overload); diff --git a/wireguard_config.cpp b/wireguard_config.cpp index c82396f..d5e25c0 100644 --- a/wireguard_config.cpp +++ b/wireguard_config.cpp @@ -170,7 +170,7 @@ bool WgFileParser::ParseFlag(const char *group, const char *key, char *value) { wg_->SetInternetBlocking((InternetBlockState)v); } else if (strcmp(key, "HeaderObfuscation") == 0) { - wg_->SetHeaderObfuscation(value); + wg_->dev().packet_obfuscator().SetKey((uint8*)value, strlen(value)); } else if (strcmp(key, "PostUp") == 0) { wg_->prepost().post_up.emplace_back(value); } else if (strcmp(key, "PostDown") == 0) { diff --git a/wireguard_proto.cpp b/wireguard_proto.cpp index e6afc70..80d791a 100644 --- a/wireguard_proto.cpp +++ b/wireguard_proto.cpp @@ -55,7 +55,6 @@ WgDevice::WgDevice() { peers_ = NULL; last_peer_ptr_ = &peers_; plugin_ = NULL; - header_obfuscation_ = false; is_private_key_initialized_ = false; next_rng_slot_ = 0; main_thread_scheduled_ = NULL; @@ -331,18 +330,6 @@ void WgDevice::UpdateKeypairAddrEntry_Locked(const IpAddr &addr, WgKeypair *keyp 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; @@ -1056,7 +1043,7 @@ void WgPeer::WriteMacToPacket(const uint8 *data, MessageMacs *dst) { } else { has_mac2_cookie_ = false; - if (dev_->header_obfuscation_) { + if (dev_->packet_obfuscator().enabled()) { // when obfuscation is enabled just make the top bits random for (size_t i = 0; i < 4; i++) ((uint32*)dst->mac2)[i] = dev_->GetRandomNumber(); @@ -1482,10 +1469,104 @@ static RandomSiphashKey random_siphash_key; size_t WgAddrEntry::IpPortHasher::operator()(const WgAddrEntry::IpPort &a) const { uint32 xx = Read32(a.bytes + 16); - return siphash13_2u64(Read64(a.bytes) + xx, Read64(a.bytes + 8) + xx, &random_siphash_key.key); + return (size_t)siphash13_2u64(Read64(a.bytes) + xx, Read64(a.bytes + 8) + xx, &random_siphash_key.key); } size_t WgPublicKeyHasher::operator()(const WgPublicKey&a) const { - return siphash13_4u64(a.u64[0], a.u64[1], a.u64[2], a.u64[3], &random_siphash_key.key); + return (size_t)siphash13_4u64(a.u64[0], a.u64[1], a.u64[2], a.u64[3], &random_siphash_key.key); } +// This scrambles the initial 16 bytes of the packet with the +// last 8 bytes of the packet as a seed. +void WgPacketObfuscator::ScrambleUnscramble(uint8 *data, size_t data_size) { + uint64 last_uint64 = ReadLE64(data + data_size - 8); + uint64 a = siphash_u64_u32(last_uint64, (uint32)data_size, (siphash_key_t*)&key_[0]); + uint64 b = siphash_u64_u32(last_uint64, (uint32)data_size, (siphash_key_t*)&key_[2]); + a = ToLE64(a); + b = ToLE64(b); + if (data_size >= 24) { + ((uint64*)data)[0] ^= a; + ((uint64*)data)[1] ^= b; + } else { + uint64 d[2] = { a, b }; + for (size_t i = 0; i < data_size - 8; i++) + data[i] ^= ((uint8*)d)[i]; + } +} + +size_t WgPacketObfuscator::InsertRandomBytesIntoPacket(uint8 *data, size_t data_size) { + assert(data_size >= 24); + // The bytes at offset 16 are used as a seed to the prng + uint64 master_key = siphash_u64_u32(Read64(data + 16), (uint32)data_size, &random_siphash_key.key); + uint32 random_bytes = master_key & 0xFF; + data[3] = (uint8)random_bytes; + for (uint32 i = 0; i < random_bytes; i += 8) + *(uint64*)(data + data_size + i) = siphash_u64_u32(master_key + i, i, &random_siphash_key.key); + data_size += random_bytes; + return data_size; +} + +void WgPacketObfuscator::ObfuscatePacket(Packet *packet) { + uint8 *data = packet->data; + size_t data_size = packet->size; + + // Too short packets can't be obfuscated + if (data_size < 8) + return; + + // If the packet is type 1, 2 or 3, or a keepalive packet of type 4, add random bytes at + // the end. This is to make it harder to detect the protocol. Store the # of added bytes + // in the 3:rd byte of the packet. + uint32 packet_type = ReadLE32(data); + if ((packet_type == 4 && data_size <= 32) || packet_type < 4) { + if (packet_type != 4) { + // The 39:th and 43:rd bytes often have zero MSB because of curve25519 pubkey, + // so xor them with something in the header. + assert(data_size >= 44); + data[39] ^= data[12]; + data[43] ^= data[12]; + } + packet->size = data_size = InsertRandomBytesIntoPacket(data, data_size); + } + + // Scramble the header bytes of the packet + ScrambleUnscramble(data, data_size); +} + +void WgPacketObfuscator::DeobfuscatePacket(Packet *packet) { + uint8 *data = packet->data; + size_t data_size = packet->size; + + // Too short packets can't be obfuscated / deobfuscated + if (data_size < 8) + return; + + // Unscramble the header bytes of the packet + ScrambleUnscramble(data, data_size); + + // Check whether the packet type field says that we have + // extra bytes appended at the end. + if (data[0] <= 4) { + if (data[0] < 4 && data_size >= 44) { + // The 39:th and 43:rd bytes often have zero MSB because of curve25519 pubkey, + // so xor them with something in the header. + data[39] ^= data[12]; + data[43] ^= data[12]; + } + if (data[3] <= data_size) { + packet->size = (uint32)(data_size - data[3]); + data[3] = 0; + } + } +} + + +//>> > 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 WgPacketObfuscator::SetKey(const uint8 *key, size_t len) { + enabled_ = (key != NULL); + if (key) + blake2s((uint8*)&key_, sizeof(key_), key, len, kHeaderObfuscationKey, sizeof(kHeaderObfuscationKey)); +} diff --git a/wireguard_proto.h b/wireguard_proto.h index d602919..7049e82 100644 --- a/wireguard_proto.h +++ b/wireguard_proto.h @@ -272,10 +272,6 @@ struct WgAddrEntry { }; -struct ScramblerSiphashKeys { - uint64 keys[4]; -}; - union WgPublicKey { uint8 bytes[WG_PUBLIC_KEY_LEN]; uint64 u64[WG_PUBLIC_KEY_LEN / 8]; @@ -341,6 +337,32 @@ public: }; +// This class is used for scrambing / unscrambling of wireguard UDP/TCP packets, +// including adding random bytes at the end of the non-data packets. +class WgPacketObfuscator { +public: + WgPacketObfuscator() : enabled_(false) {} + + bool enabled() { return enabled_; } + void ObfuscatePacket(Packet *packet); + void DeobfuscatePacket(Packet *packet); + + void SetKey(const uint8 *key, size_t len); + + const uint8 *key() { return (uint8*)key_; } + + static size_t InsertRandomBytesIntoPacket(uint8 *data, size_t data_size); + +private: + void ScrambleUnscramble(uint8 *data, size_t data_size); + + // Whether packet obfuscation is enabled + bool enabled_; + + // Siphash keys for packet scrambling + uint64 key_[4]; +}; + class WgDevice { friend class WgPeer; friend class WireguardProcessor; @@ -359,9 +381,6 @@ public: // Remove all peers void RemoveAllPeers(); - // Setup header obfuscation - void SetHeaderObfuscation(const char *key); - // Check whether Mac1 appears to be valid bool CheckCookieMac1(Packet *packet); @@ -385,6 +404,8 @@ public: MultithreadedDelayedDelete *GetDelayedDelete() { return &delayed_delete_; } + WgPacketObfuscator &packet_obfuscator() { return packet_obfuscator_; } + private: std::pair *LookupPeerInKeyIdLookup(uint32 key_id); WgKeypair *LookupKeypairByKeyId(uint32 key_id); @@ -441,9 +462,6 @@ private: // Counter for generating new indices in |keypair_lookup_| uint8 next_rng_slot_; - // Whether packet obfuscation is enabled - bool header_obfuscation_; - // Whether a private key has been setup for the device bool is_private_key_initialized_; @@ -456,8 +474,6 @@ private: uint8 s_priv_[WG_PUBLIC_KEY_LEN]; uint8 s_pub_[WG_PUBLIC_KEY_LEN]; - // Siphash keys for packet scrambling - ScramblerSiphashKeys header_obfuscation_key_; uint8 precomputed_cookie_key_[WG_SYMMETRIC_KEY_LEN]; uint8 precomputed_mac1_key_[WG_SYMMETRIC_KEY_LEN]; @@ -465,6 +481,8 @@ private: uint64 random_number_input_[WG_HASH_LEN / 8 + 1]; uint32 random_number_output_[WG_HASH_LEN / 4]; + WgPacketObfuscator packet_obfuscator_; + WgRateLimit rate_limiter_; // For defering deletes until all worker threads are guaranteed not to use an object.