diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 13e04d044455c5c160d4ba9430624435f3b99b3c..2f27b666a56c96ac8522097432673509082ada7e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,5 @@ variables: + DEBIAN_STABLE: bullseye DEBIAN_FRONTEND: noninteractive REPRODUCIBLE_FLAGS: -trimpath -ldflags=-buildid= @@ -31,14 +32,14 @@ variables: - go build $REPRODUCIBLE_FLAGS go-1.21: - image: golang:1.21-bookworm + image: containers.torproject.org/tpo/anti-censorship/duplicatedcontainerimages:golang-1.21-$DEBIAN_STABLE <<: *golang-docker-debian-template <<: *test-template script: - *go-test -go-1.22: - image: golang:1.22-bookworm +go-1.23: + image: containers.torproject.org/tpo/anti-censorship/duplicatedcontainerimages:golang-1.23-$DEBIAN_STABLE <<: *golang-docker-debian-template <<: *test-template script: diff --git a/client/conjure.go b/client/conjure.go index b9b969840d2de8fe27d6fac14a061193919e07b8..6dcf25f412d25da7c8bbed7567458528a49f3323 100644 --- a/client/conjure.go +++ b/client/conjure.go @@ -11,6 +11,7 @@ import ( "os" "os/signal" "path/filepath" + "strings" "sync" "syscall" "time" @@ -32,8 +33,17 @@ func getSOCKSArgs(conn *pt.SocksConn, config *conjure.ConjureConfig) { if arg, ok := conn.Req.Args.Get("front"); ok { config.Front = arg } - return - + if arg, ok := conn.Req.Args.Get("utls-nosni"); ok { + switch strings.ToLower(arg) { + case "true": + fallthrough + case "yes": + config.UTLSRemoveSNI = true + } + } + if arg, ok := conn.Req.Args.Get("utls-imitate"); ok { + config.UTLSClientID = arg + } } // handle the SOCKS conn @@ -141,6 +151,8 @@ func main() { unsafeLogging := flag.Bool("unsafe-logging", false, "prevent logs from being scrubbed") front := flag.String("front", "", "domain front") registerURL := flag.String("registerURL", "", "URL of the conjure registration station") + uTLSClientHelloID := flag.String("utls-imitate", "", "type of TLS client to imitate with utls") + uTLSRemoveSNI := flag.Bool("utls-nosni", false, "remove SNI from client hello(ignored if uTLS is not used)") flag.Parse() @@ -182,8 +194,10 @@ func main() { // Configure Conjure config := &conjure.ConjureConfig{ - RegisterURL: *registerURL, - Front: *front, + RegisterURL: *registerURL, + Front: *front, + UTLSClientID: *uTLSClientHelloID, + UTLSRemoveSNI: *uTLSRemoveSNI, } // Tor client-side transport setup diff --git a/client/conjure/registration.go b/client/conjure/registration.go index d10a3de9d51d180910a32fdea793ed2fe67046e0..c40ff74a65215e2125aa7cff160e4faa226c07f2 100644 --- a/client/conjure/registration.go +++ b/client/conjure/registration.go @@ -3,6 +3,7 @@ package conjure import ( "context" "crypto/tls" + "fmt" "log" "net" "net/http" @@ -12,6 +13,9 @@ import ( transports "github.com/refraction-networking/conjure/pkg/transports/client" "github.com/refraction-networking/conjure/proto" "github.com/refraction-networking/gotapdance/tapdance" + utls "github.com/refraction-networking/utls" + + utlsutil "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil/utls" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/certs" ) @@ -20,12 +24,16 @@ type ConjureConfig struct { RegisterURL string // URL of the conjure bidirectional registration API endpoint Front string BridgeAddress string // IP address of the Tor Conjure PT bridge + UTLSClientID string + UTLSRemoveSNI bool } type Rendezvous struct { - RegisterURL string - Front string - Transport *http.Transport + RegisterURL string + Front string + Transport http.RoundTripper + UTLSClientID string + UTLSRemoveSNI bool } func (r *Rendezvous) RoundTrip(req *http.Request) (*http.Response, error) { @@ -42,6 +50,19 @@ func (r *Rendezvous) RoundTrip(req *http.Request) (*http.Response, error) { return r.Transport.RoundTrip(req) } +// We make a copy of DefaultTransport because we want the default Dial +// and TLSHandshakeTimeout settings. But we want to disable the default +// ProxyFromEnvironment setting. +func createRegistrationTransport() http.RoundTripper { + tlsConfig := &tls.Config{ + RootCAs: certs.GetRootCAs(), + } + transport := &http.Transport{TLSClientConfig: tlsConfig} + transport.Proxy = nil + transport.ResponseHeaderTimeout = 15 * time.Second + return transport +} + func Register(config *ConjureConfig) (net.Conn, error) { dialer := &tapdance.Dialer{ @@ -57,11 +78,19 @@ func Register(config *ConjureConfig) (net.Conn, error) { Width: 0, } - tlsConfig := &tls.Config{ - RootCAs: certs.GetRootCAs(), + transport := createRegistrationTransport() + if config.UTLSClientID != "" { + utlsClienHelloID, err := utlsutil.NameToUTLSID(config.UTLSClientID) + if err != nil { + return nil, fmt.Errorf("unable to create ") + } + utlsConfig := &utls.Config{ + RootCAs: certs.GetRootCAs(), + } + + transport = utlsutil.NewUTLSHTTPRoundTripperWithProxy(utlsClienHelloID, utlsConfig, transport, config.UTLSRemoveSNI, nil) + } - transport := &http.Transport{TLSClientConfig: tlsConfig} - transport.Proxy = nil // APIRegistrarBidirectional expects an HTTP client for sending the registration request. // The http.RoundTripper associated with this client dictates the censorship-resistant @@ -70,9 +99,11 @@ func Register(config *ConjureConfig) (net.Conn, error) { // fronted connections. client := &http.Client{ Transport: &Rendezvous{ - RegisterURL: config.RegisterURL, - Front: config.Front, - Transport: transport, + RegisterURL: config.RegisterURL, + Front: config.Front, + Transport: transport, + UTLSClientID: config.UTLSClientID, + UTLSRemoveSNI: config.UTLSRemoveSNI, }, } diff --git a/go.mod b/go.mod index 23d44a86c653d197fc76fbe829f9fecff7fbc35c..1f33e1e1fe445c2538629b77ed51d6fdf1b99f77 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,9 @@ require ( github.com/pires/go-proxyproto v0.8.0 github.com/refraction-networking/conjure v0.7.10 github.com/refraction-networking/gotapdance v1.7.10 + github.com/refraction-networking/utls v1.6.7 gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 - gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil v0.0.0-20240710081135-6c4d8ed41027 + gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil v0.0.0-20250129175826-48a566259500 gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2 v2.10.1 ) @@ -34,7 +35,6 @@ require ( github.com/pion/transport/v2 v2.2.10 // indirect github.com/refraction-networking/ed25519 v0.1.2 // indirect github.com/refraction-networking/obfs4 v0.1.2 // indirect - github.com/refraction-networking/utls v1.6.7 // indirect github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/wlynxg/anet v0.0.3 // indirect diff --git a/go.sum b/go.sum index 8eaa1421f5b8b050e842a4bdf9c55ca29b6f1ca1..f86b91d1080ad3f90665bd66ecebfddec781e526 100644 --- a/go.sum +++ b/go.sum @@ -122,6 +122,8 @@ gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil v0.0.0-202 gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil v0.0.0-20240509090240-b27cf78add95/go.mod h1:ASIO5DzEHaAry1aSQDdhwoUy97E44kXqxya/LehoPKY= gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil v0.0.0-20240710081135-6c4d8ed41027 h1:zATW8o41V5jE5rkznMl85TbtNqRPMdexGevpjsNxQH4= gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil v0.0.0-20240710081135-6c4d8ed41027/go.mod h1:n/u74vECtThx3cvWkCD7j7PRtMb9oBTq33m74g4hL+c= +gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil v0.0.0-20250129175826-48a566259500 h1:lt8iyIWtJGIF2uOiPtcbhqNOmnrVmi+x04jNwudqPBA= +gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil v0.0.0-20250129175826-48a566259500/go.mod h1:PK7EvweKeypdelDyh1m7N922aldSeCAG8n0lJ7RAXWQ= gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2 v2.6.1 h1:PenLil49Ka399yxO9CfVpLFFsOLjwLCKMc/uMFTVGo4= gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2 v2.6.1/go.mod h1:Edotm7eSJgyaVDc0aQq3W7/cNNhWyWajm4DQgTKC5yI= gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2 v2.10.1 h1:Oik2tb1qbnbrxOlvRNul2FrBi4j2pnqFvPPEWCOSG1I=