From e2e8a8e9bea9e645d0989f53832a14b7fdbd33f0 Mon Sep 17 00:00:00 2001 From: notsure2 Date: Sun, 11 Jun 2023 01:31:09 +0300 Subject: [PATCH] Support ServerName randomization (by setting ServerName=random) using the same algorithm as ProtonVPN https://github.com/ProtonVPN/wireguard-go/commit/bcf344b39b213c1f32147851af0d2a8da9266883 --- README.md | 2 +- internal/client/TLS.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 26b0d27..33fa3c3 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ random-like. **You may only leave it as `plain` if you are certain that your und encryption and authentication (via AEAD or similar techniques).** `ServerName` is the domain you want to make your ISP or firewall _think_ you are visiting. Ideally it should -match `RedirAddr` in the server's configuration, a major site the censor allows, but it doesn't have to. +match `RedirAddr` in the server's configuration, a major site the censor allows, but it doesn't have to. Use `random` to randomize the server name for every connection made. `AlternativeNames` is an array used alongside `ServerName` to shuffle between different ServerNames for every new connection. **This may conflict with `CDN` Transport mode** if the CDN provider prohibits domain fronting and rejects diff --git a/internal/client/TLS.go b/internal/client/TLS.go index 11cd8a5..dd248b6 100644 --- a/internal/client/TLS.go +++ b/internal/client/TLS.go @@ -1,9 +1,14 @@ package client import ( + cryptoRand "crypto/rand" "encoding/binary" "encoding/hex" + "math/big" + "math/rand" "net" + "strings" + "time" "github.com/cbeuw/Cloak/internal/common" log "github.com/sirupsen/logrus" @@ -60,6 +65,32 @@ type DirectTLS struct { browser browser } +var topLevelDomains = []string{"com", "net", "org", "it", "fr", "me", "ru", "cn", "es", "tr", "top", "xyz", "info"} + +// https://github.com/ProtonVPN/wireguard-go/commit/bcf344b39b213c1f32147851af0d2a8da9266883 +func randomServerName() string { + charNum := int('z') - int('a') + 1 + size := 3 + randInt(10) + name := make([]byte, size) + for i := range name { + name[i] = byte(int('a') + randInt(charNum)) + } + return string(name) + "." + randItem(topLevelDomains) +} + +func randItem(list []string) string { + return list[randInt(len(list))] +} + +func randInt(n int) int { + size, err := cryptoRand.Int(cryptoRand.Reader, big.NewInt(int64(n))) + if err == nil { + return int(size.Int64()) + } + rand.Seed(time.Now().UnixNano()) + return rand.Intn(n) +} + // NewClientTransport handles the TLS handshake for a given conn and returns the sessionKey // if the server proceed with Cloak authentication func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey [32]byte, err error) { @@ -73,6 +104,11 @@ func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey x25519KeyShare: payload.ciphertextWithTag[32:64], serverName: authInfo.MockDomain, } + + if strings.EqualFold(fields.serverName, "random") { + fields.serverName = randomServerName() + } + chOnly := tls.browser.composeClientHello(fields) chWithRecordLayer := common.AddRecordLayer(chOnly, common.Handshake, common.VersionTLS11) _, err = rawConn.Write(chWithRecordLayer)