Move out UDP obfuscator to a separate class

This commit is contained in:
Ludvig Strigeus 2018-12-10 23:31:22 +01:00
parent 6a55ec778b
commit 6e09191bf5
9 changed files with 157 additions and 74 deletions

View file

@ -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 {

View file

@ -961,6 +961,13 @@ void PacketProcessorUdpCb::OnQueuedItemDelete(QueuedItem *qi) {
FreePacket(static_cast<Packet*>(qi));
}
void PacketProcessorDeobfuscateUdpCb::OnQueuedItemEvent(QueuedItem *qi, uintptr_t extra) {
PacketProcessor::QueueContext *context = (PacketProcessor::QueueContext *)extra;
Packet *packet = static_cast<Packet*>(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.

View file

@ -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;

View file

@ -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};

View file

@ -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++;

View file

@ -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);

View file

@ -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) {

View file

@ -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));
}

View file

@ -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<WgPeer*, WgKeypair*> *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.