diff --git a/tunsafe_config.h b/tunsafe_config.h index b08288a..5c7643e 100644 --- a/tunsafe_config.h +++ b/tunsafe_config.h @@ -2,21 +2,35 @@ // Copyright (C) 2018 Ludvig Strigeus . All Rights Reserved. #pragma once -#define TUNSAFE_VERSION_STRING "TunSafe 1.5-rc1" -#define TUNSAFE_VERSION_STRING_LONG "TunSafe 1.5-rc1" +#define TUNSAFE_VERSION_STRING "TunSafe 1.5-rc2" +#define TUNSAFE_VERSION_STRING_LONG "TunSafe 1.5-rc2" +// Enable support for handshake extensions #define WITH_HANDSHAKE_EXT 1 -#define WITH_CIPHER_SUITES 0 + +// Whether to enable the boolean features functionality #define WITH_BOOLEAN_FEATURES 1 + +// Enable support for header obfuscation +#define WITH_HEADER_OBFUSCATION 1 + +// Enable support for two-factor authentication (requires WITH_HANDSHAKE_EXT) +#define WITH_TWO_FACTOR_AUTHENTICATION 1 + +// Whether to enable the short MAC feature, that uses an 8-byte MAC instead of 16-byte (Saves overhead) +#define WITH_SHORT_MAC 0 + +// Enable support for the keypair->compress_handler_ feature #define WITH_PACKET_COMPRESSION 0 +// Enable support for short (down to 2 byte headers) instead of 16 bytes #define WITH_SHORT_HEADERS 0 -#define WITH_HEADER_OBFUSCATION 1 + +// Enable support for alternative cipher suites +#define WITH_CIPHER_SUITES 0 + #define WITH_AVX512_OPTIMIZATIONS 0 #define WITH_BENCHMARK 0 - - - // Use bytell hashmap instead. Only works in 64-bit builds #define WITH_BYTELL_HASHMAP 0 diff --git a/tunsafe_wg_plugin.cpp b/tunsafe_wg_plugin.cpp index 49e1493..a479110 100644 --- a/tunsafe_wg_plugin.cpp +++ b/tunsafe_wg_plugin.cpp @@ -15,10 +15,6 @@ enum { WG_SESSION_AUTH_LEN = 16, }; -enum { - WITH_TWO_FACTOR_AUTHENTICATION = 1, -}; - class PluginPeer; class TunsafePluginImpl; @@ -52,23 +48,21 @@ bool ExtFieldWriter::WriteField(uint8 code, const uint8 *data, uint32 size) { } enum { - kExtensionType_Padding = 0x00, // The other peer has no way of identifying a specific instance of // a connection. There's no way to distinguish a periodic handshake from // a new client connection. Add a session ID to the Peer to solve this. // We don't send the actual session id, instead we send: // Hash(plaintext ephemeral public key, session id) - kExtensionType_SessionIDAuth = 0x01, - kExtensionType_SetSessionID = 0x02, + kExtensionType_SessionIDAuth = 0x20, + kExtensionType_SetSessionID = 0x21, // This is sent by the server to request an additional token to allow // login, for example a TOTP token, or a password. // By cleverly using session ids, the server can avoid having to request // this for every new handshake, even when roaming. - kExtensionType_TokenRequest = 0x03, - + kExtensionType_TokenRequest = 0x22, // This holds the token reply. - kExtensionType_TokenReply = 0x04, + kExtensionType_TokenReply = 0x23, }; class TokenClientHandler { @@ -597,6 +591,10 @@ uint32 TunsafePluginImpl::OnHandshake1(WgPeer *peer, const uint8 *ext, uint32 ex PluginPeer *pp = GetPluginPeer(peer); ExtFieldWriter writer(extout, extout_size); + // Skip the version + if (ext_size >= 4) + ext += 4, ext_size -= 4; + bool has_valid_session_id = false; uint8 *token_reply = NULL; uint8 token_reply_size = 0; @@ -639,6 +637,10 @@ uint32 TunsafePluginImpl::OnHandshake1(WgPeer *peer, const uint8 *ext, uint32 ex uint32 TunsafePluginImpl::OnHandshake2(WgPeer *peer, const uint8 *ext, uint32 ext_size, const uint8 salt[WG_PUBLIC_KEY_LEN]) { PluginPeer *pp = GetPluginPeer(peer); + // Skip the version + if (ext_size >= 4) + ext += 4, ext_size -= 4; + bool has_valid_session_id = false; while (ext_size >= 2) { diff --git a/wireguard_config.cpp b/wireguard_config.cpp index c968f0b..0d0e5de 100644 --- a/wireguard_config.cpp +++ b/wireguard_config.cpp @@ -54,19 +54,19 @@ static int ParseFeature(const char *str) { else if (str[len - 1] == '!') what = WG_BOOLEAN_FEATURE_ENFORCES, len--; } - if (len == 5 && memcmp(str, "mac64", 5) == 0) + if (WITH_SHORT_MAC && len == 5 && memcmp(str, "mac64", 5) == 0) return what + WG_FEATURE_ID_SHORT_MAC * 16; - if (len == 5 && memcmp(str, "ipzip", 5) == 0) + if (WITH_PACKET_COMPRESSION && len == 5 && memcmp(str, "ipzip", 5) == 0) return what + WG_FEATURE_ID_IPZIP * 16; if (len == 10 && memcmp(str, "hybrid_tcp", 10) == 0) return what + WG_FEATURE_HYBRID_TCP * 16; - if (len == 10 && memcmp(str, "skip_keyid", 10) == 0) + if (WITH_SHORT_HEADERS && len == 10 && memcmp(str, "skip_keyid", 10) == 0) return what + WG_FEATURE_ID_SKIP_KEYID_IN * 16 + 1 * 4; - if (len == 12 && memcmp(str, "short_header", 12) == 0) + if (WITH_SHORT_HEADERS && len == 12 && memcmp(str, "short_header", 12) == 0) return what + WG_FEATURE_ID_SHORT_HEADER * 16; - if (len == 13 && memcmp(str, "skip_keyid_in", 13) == 0) + if (WITH_SHORT_HEADERS && len == 13 && memcmp(str, "skip_keyid_in", 13) == 0) return what + WG_FEATURE_ID_SKIP_KEYID_IN * 16; - if (len == 14 && memcmp(str, "skip_keyid_out", 14) == 0) + if (WITH_SHORT_HEADERS && len == 14 && memcmp(str, "skip_keyid_out", 14) == 0) return what + WG_FEATURE_ID_SKIP_KEYID_OUT * 16; return -1; } @@ -171,9 +171,9 @@ bool WgFileParser::ParseFlag(const char *group, const char *key, char *value) { } wg_->SetInternetBlocking((InternetBlockState)v); - } else if (strcmp(key, "ObfuscateKey") == 0) { + } else if (WITH_HEADER_OBFUSCATION && strcmp(key, "ObfuscateKey") == 0) { wg_->dev().packet_obfuscator().SetKey((uint8*)value, strlen(value)); - } else if (strcmp(key, "ObfuscateTCP") == 0) { + } else if (WITH_HEADER_OBFUSCATION && strcmp(key, "ObfuscateTCP") == 0) { bool flag; int v = 1; if (ParseBoolean(value, &flag)) { diff --git a/wireguard_proto.cpp b/wireguard_proto.cpp index 8b6264e..c1782b6 100644 --- a/wireguard_proto.cpp +++ b/wireguard_proto.cpp @@ -17,6 +17,10 @@ #include #include +enum { + kTunsafeExtensionClientID = ('T' | 'S' << 8) +}; + 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}; @@ -346,7 +350,6 @@ WgPeer::WgPeer(WgDevice *dev) { marked_for_delete_ = 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; @@ -545,15 +548,18 @@ void WgPeer::CreateMessageHandshakeInitiation(Packet *packet) { int extfield_size = 0; - if (WITH_HANDSHAKE_EXT && supports_handshake_extensions_) - extfield_size = WriteHandshakeExtension(dst->timestamp_enc + WG_TIMESTAMP_LEN, NULL); - - if (dev_->plugin_) { - uint32 rv = dev_->plugin_->OnHandshake0(this, dst->timestamp_enc + WG_TIMESTAMP_LEN + extfield_size, MAX_SIZE_OF_HANDSHAKE_EXTENSION - extfield_size, dst->ephemeral); - assert(!(rv & WgPlugin::kHandshakeResponseFail)); - extfield_size += rv; + if (WITH_HANDSHAKE_EXT) { + extfield_size = WriteHandshakeExtension(dst->timestamp_enc + WG_TIMESTAMP_LEN + 4, NULL); + if (dev_->plugin_) { + uint32 rv = dev_->plugin_->OnHandshake0(this, dst->timestamp_enc + WG_TIMESTAMP_LEN + 4 + extfield_size, MAX_SIZE_OF_HANDSHAKE_EXTENSION - 4 - extfield_size, dst->ephemeral); + assert(!(rv & WgPlugin::kHandshakeResponseFail)); + extfield_size += rv; + } + if (extfield_size) { + WriteLE32(dst->timestamp_enc + WG_TIMESTAMP_LEN, kTunsafeExtensionClientID); + extfield_size += 4; + } } - // 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) @@ -619,7 +625,7 @@ WgPeer *WgPeer::ParseMessageHandshakeInitiation(WgDevice *dev, Packet *packet) { // Hi2 := Hi memcpy(hi2, hi, sizeof(hi2)); extfield_size = packet->size - sizeof(MessageHandshakeInitiation); - if ((uint32)extfield_size > MAX_SIZE_OF_HANDSHAKE_EXTENSION || (extfield_size && !peer->supports_handshake_extensions_)) + if ((uint32)extfield_size > MAX_SIZE_OF_HANDSHAKE_EXTENSION) goto getout; // Hi := HASH(Hi || msg.timestamp) BlakeMix(hi, src->timestamp_enc, extfield_size + WG_TIMESTAMP_LEN + WG_MAC_LEN); @@ -668,20 +674,24 @@ WgPeer *WgPeer::ParseMessageHandshakeInitiation(WgDevice *dev, Packet *packet) { int extfield_out_size = 0; if (WITH_HANDSHAKE_EXT && extfield_size) - extfield_out_size = peer->WriteHandshakeExtension(dst->empty_enc, keypair); + extfield_out_size = peer->WriteHandshakeExtension(dst->empty_enc + 4, keypair); // Allow plugin to determine what to do with the packet, // it can append new headers to the response, and decide what to do. - if (dev->plugin_) { + if (WITH_HANDSHAKE_EXT && dev->plugin_) { uint32 rv = dev->plugin_->OnHandshake1(peer, extbuf + WG_TIMESTAMP_LEN, extfield_size, e_remote, - dst->empty_enc + extfield_out_size, MAX_SIZE_OF_HANDSHAKE_EXTENSION - extfield_out_size, dst->ephemeral); + dst->empty_enc + 4 + extfield_out_size, MAX_SIZE_OF_HANDSHAKE_EXTENSION - 4 - extfield_out_size, dst->ephemeral); if (rv == WgPlugin::kHandshakeResponseDrop) goto getout; if (rv & WgPlugin::kHandshakeResponseFail) delete exch_null(keypair); extfield_out_size += rv & ~WgPlugin::kHandshakeResponseFail; } - + if (extfield_out_size) { + WriteLE32(dst->empty_enc, kTunsafeExtensionClientID); + extfield_out_size += 4; + } + dst->sender_key_id = keypair ? dev->InsertInKeyIdLookup(peer, keypair) : 0; WG_ACQUIRE_LOCK(peer->mutex_); @@ -764,7 +774,7 @@ WgPeer *WgPeer::ParseMessageHandshakeResponse(WgDevice *dev, const Packet *packe // Allow plugin to determine what to do with the packet, // it can append new headers to the response, and decide what to do. - if (dev->plugin_) { + if (WITH_HANDSHAKE_EXT && dev->plugin_) { uint32 rv = dev->plugin_->OnHandshake2(peer, src->empty_enc, extfield_size, src->ephemeral); if (rv & WgPlugin::kHandshakeResponseFail) { delete keypair; @@ -841,7 +851,7 @@ int WgPeer::WriteHandshakeExtension(uint8 *dst, WgKeypair *keypair) { uint8 value = 0; // Include the supported features extension if (!IsOnlyZeros(features_, sizeof(features_))) { - *dst++ = EXT_BOOLEAN_FEATURES; + *dst++ = kExtensionType_Booleans; *dst++ = (WG_FEATURES_COUNT + 3) >> 2; for (size_t i = 0; i != WG_FEATURES_COUNT; i++) { if ((i & 3) == 0) @@ -857,7 +867,7 @@ int WgPeer::WriteHandshakeExtension(uint8 *dst, WgKeypair *keypair) { // Ordered list of cipher suites size_t ciphers = num_ciphers_; if (ciphers) { - *dst++ = EXT_CIPHER_SUITES + cipher_prio_; + *dst++ = kExtensionType_CipherSuites + cipher_prio_; if (keypair) { *dst++ = 1; *dst++ = keypair->cipher_suite; @@ -928,7 +938,14 @@ void WgPeer::DeleteKeypair(WgKeypair **kp) { } bool WgPeer::ParseExtendedHandshake(WgKeypair *kp, const uint8 *data, size_t data_size) { - assert(WITH_HANDSHAKE_EXT); + // Empty handshake is always OK + if (data_size == 0) + return true; + + // The first four bytes contain a client ID and major, minor version + if (data_size < 4 || (ReadLE32(data) & 0xFFFFFF) != kTunsafeExtensionClientID) + return false; + data += 4, data_size -= 4; while (data_size >= 2) { uint8 type = data[0], size = data[1]; @@ -936,15 +953,15 @@ bool WgPeer::ParseExtendedHandshake(WgKeypair *kp, const uint8 *data, size_t dat if (size > data_size) return false; switch (type) { - case EXT_CIPHER_SUITES_PRIO: - case EXT_CIPHER_SUITES: + case kExtensionType_CipherSuitesPrio: + case kExtensionType_CipherSuites: if (WITH_CIPHER_SUITES) { - kp->cipher_suite = ResolveCipherSuite(cipher_prio_ - (type - EXT_CIPHER_SUITES), + kp->cipher_suite = ResolveCipherSuite(cipher_prio_ - (type - kExtensionType_CipherSuites), ciphers_, num_ciphers_, data, size); } break; - case EXT_BOOLEAN_FEATURES: + case kExtensionType_Booleans: if (WITH_BOOLEAN_FEATURES) { for (uint32 i = 0, j = std::max(WG_FEATURES_COUNT, size * 4); i != j; i++) { uint8 value = (i < (uint32)size * 4) ? (data[i >> 2] >> ((i * 2) & 7)) & 3 : 0; @@ -960,10 +977,8 @@ bool WgPeer::ParseExtendedHandshake(WgKeypair *kp, const uint8 *data, size_t dat if (data_size != 0) return false; - if (WITH_BOOLEAN_FEATURES) + if (WITH_BOOLEAN_FEATURES && WITH_SHORT_MAC) kp->auth_tag_length = (kp->enabled_features[WG_FEATURE_ID_SHORT_MAC] ? 8 : CHACHA20POLY1305_AUTHTAGLEN); - return true; - if (WITH_CIPHER_SUITES && kp->cipher_suite >= EXT_CIPHER_SUITE_AES128_GCM && kp->cipher_suite <= EXT_CIPHER_SUITE_AES256_GCM) { #if WITH_AESGCM @@ -978,6 +993,7 @@ bool WgPeer::ParseExtendedHandshake(WgKeypair *kp, const uint8 *data, size_t dat #endif // WITH_AESGCM } + return true; } WgKeypair *WgPeer::CreateNewKeypair(bool is_initiator, const uint8 chaining_key[WG_HASH_LEN], uint32 remote_key_id) { @@ -1157,12 +1173,13 @@ void WgPeer::OnHandshakeFullyComplete() { 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" : ""); + RINFO("Using %s%s%s%s%s%s%s", kCipherSuites[curr_keypair_->cipher_suite], + curr_keypair_->enabled_features[WG_FEATURE_ID_SHORT_HEADER] ? ", short_header" : "", + curr_keypair_->enabled_features[WG_FEATURE_ID_SHORT_MAC] ? ", mac64" : "", + curr_keypair_->enabled_features[WG_FEATURE_ID_IPZIP] ? ", ipzip" : "", + curr_keypair_->enabled_features[WG_FEATURE_ID_SKIP_KEYID_IN] ? ", skip_keyid_in" : "", + curr_keypair_->enabled_features[WG_FEATURE_ID_SKIP_KEYID_OUT] ? ", skip_keyid_out" : "", + curr_keypair_->enabled_features[WG_FEATURE_HYBRID_TCP] ? ", hybrid_tcp" : ""); } } last_complete_handskake_timestamp_ = now; @@ -1572,7 +1589,7 @@ void WgPacketObfuscator::ObfuscatePacket(Packet *packet) { assert(data_size >= 48); data[35 + packet_type * 4] ^= data[15]; } - packet->size = data_size = InsertRandomBytesIntoPacket(data, data_size); + packet->size = (uint)(data_size = InsertRandomBytesIntoPacket(data, data_size)); } // Scramble the header bytes of the packet diff --git a/wireguard_proto.h b/wireguard_proto.h index 5b169f3..d2a26a4 100644 --- a/wireguard_proto.h +++ b/wireguard_proto.h @@ -165,10 +165,10 @@ struct MessageData { STATIC_ASSERT(sizeof(MessageData) == 16, MessageData_wrong_size); enum { - EXT_BOOLEAN_FEATURES = 0x16, - - EXT_CIPHER_SUITES = 0x18, - EXT_CIPHER_SUITES_PRIO = 0x19, + kExtensionType_Padding = 0x00, + kExtensionType_Booleans = 0x01, + kExtensionType_CipherSuites = 0x02, + kExtensionType_CipherSuitesPrio = 0x03, // The standard wireguard chacha EXT_CIPHER_SUITE_CHACHA20POLY1305 = 0x00, @@ -180,7 +180,6 @@ enum { EXT_CIPHER_SUITE_NONE_POLY1305 = 0x03, EXT_CIPHER_SUITE_COUNT = 4, - }; enum { @@ -610,9 +609,6 @@ private: // from being sent out over the VPN link. uint32 ipv4_broadcast_addr_; - // Whether the tunsafe specific handshake extensions are supported - bool supports_handshake_extensions_; - // Whether any data was sent since the keepalive timer was set bool pending_keepalive_;