diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index db0f800..04e0310 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -37,6 +37,7 @@ import ( "github.com/pion/ice/v4" "github.com/pion/webrtc/v4" + "github.com/theodorsm/covert-dtls/pkg/fingerprints" "github.com/xtaci/kcp-go/v5" "github.com/xtaci/smux" @@ -118,8 +119,9 @@ type ClientConfig struct { // connect to, as specified in the Bridge line of the torrc. BridgeFingerprint string // CommunicationProxy is the proxy address for network communication - CommunicationProxy *url.URL - CovertDTLSConfig string + CommunicationProxy *url.URL + CovertDTLSConfig string + CovertDTLSFingerprint string } // NewSnowflakeClient creates a new Snowflake transport client that can spawn multiple @@ -165,9 +167,12 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) { eventsLogger := event.NewSnowflakeEventDispatcher() var transport *Transport - // TODO: Add fingerprint config + if config.CovertDTLSConfig != "" { covertDTLSConfig := covertdtls.ParseConfigString(config.CovertDTLSConfig) + if config.CovertDTLSFingerprint != "" { + covertDTLSConfig.Fingerprint = fingerprints.ClientHelloFingerprint(*&config.CovertDTLSFingerprint) + } transport = &Transport{dialer: NewCovertWebRTCDialerWithEventsAndProxy(broker, iceServers, max, eventsLogger, config.CommunicationProxy, &covertDTLSConfig), eventDispatcher: eventsLogger} } else { transport = &Transport{dialer: NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, eventsLogger, config.CommunicationProxy), eventDispatcher: eventsLogger} diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go index d6a40e4..606a0b2 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -287,7 +287,17 @@ func (c *WebRTCPeer) preparePeerConnection( s.SetNet(vnet) - if covertDTLSConfig.Mimic { + if covertDTLSConfig.Fingerprint != "" { + mimic := &mimicry.MimickedClientHello{} + err = mimic.LoadFingerprint(covertDTLSConfig.Fingerprint) + if err != nil { + log.Printf("NewPeerConnection ERROR: %s", err) + return err + } + profiles := utils.DefaultSRTPProtectionProfiles() + s.SetSRTPProtectionProfiles(profiles...) + s.SetDTLSClientHelloMessageHook(mimic.Hook) + } else if covertDTLSConfig.Mimic { mimic := &mimicry.MimickedClientHello{} if covertDTLSConfig.Randomize { err = mimic.LoadRandomFingerprint() diff --git a/client/snowflake.go b/client/snowflake.go index 3a43d51..9e78938 100644 --- a/client/snowflake.go +++ b/client/snowflake.go @@ -126,6 +126,9 @@ func socksAcceptLoop(ln *pt.SocksListener, config sf.ClientConfig, shutdown chan if arg, ok := conn.Req.Args.Get("covertdtls-config"); ok { config.CovertDTLSConfig = arg } + if arg, ok := conn.Req.Args.Get("covertdtls-fingerprint"); ok { + config.CovertDTLSFingerprint = arg + } transport, err := sf.NewSnowflakeClient(config) if err != nil { conn.Reject() @@ -177,7 +180,8 @@ func main() { max := flag.Int("max", DefaultSnowflakeCapacity, "capacity for number of multiplexed WebRTC peers") versionFlag := flag.Bool("version", false, "display version info to stderr and quit") - covertDTLSConfig := flag.String("covertdtls-config", "", "Configuration of dtls mimicking and randomization: mimic, randomize, randomizemimic") + covertDTLSConfig := flag.String("covertdtls-config", "", "Configuration of DTLS mimicking and randomization: mimic, randomize, randomizemimic") + covertDTLSfingerprint := flag.String("covertdtls-fingerprint", "", "Mimicking of a raw DTLS fingerprint") // Deprecated oldLogToStateDir := flag.Bool("logToStateDir", false, "use -log-to-state-dir instead") @@ -236,15 +240,16 @@ func main() { } config := sf.ClientConfig{ - BrokerURL: *brokerURL, - AmpCacheURL: *ampCacheURL, - SQSQueueURL: *sqsQueueURL, - SQSCredsStr: *sqsCredsStr, - FrontDomains: frontDomains, - ICEAddresses: iceAddresses, - KeepLocalAddresses: *keepLocalAddresses || *oldKeepLocalAddresses, - Max: *max, - CovertDTLSConfig: *covertDTLSConfig, + BrokerURL: *brokerURL, + AmpCacheURL: *ampCacheURL, + SQSQueueURL: *sqsQueueURL, + SQSCredsStr: *sqsCredsStr, + FrontDomains: frontDomains, + ICEAddresses: iceAddresses, + KeepLocalAddresses: *keepLocalAddresses || *oldKeepLocalAddresses, + Max: *max, + CovertDTLSConfig: *covertDTLSConfig, + CovertDTLSFingerprint: *covertDTLSfingerprint, } // Begin goptlib client process. diff --git a/proxy/README.md b/proxy/README.md index 169ff65..d14ae41 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -48,6 +48,8 @@ Usage of ./proxy: maximum concurrent clients (default is to accept an unlimited number of clients) -covertdtls-config string Configuration of dtls mimicking and randomization: mimic, randomize, randomizemimic + -covertdtls-fingerprint string + Mimicking of a raw DTLS fingerprint -disable-stats-logger disable the exposing mechanism for stats using logs -dtls-randomize diff --git a/proxy/lib/snowflake.go b/proxy/lib/snowflake.go index c649a6f..766a9b6 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -430,7 +430,17 @@ func (sf *SnowflakeProxy) makeWebRTCAPI() *webrtc.API { settingsEngine.SetDTLSInsecureSkipHelloVerify(true) - if sf.CovertDTLSConfig.Mimic { + if sf.CovertDTLSConfig.Fingerprint != "" { + mimic := &mimicry.MimickedClientHello{} + err := mimic.LoadFingerprint(sf.CovertDTLSConfig.Fingerprint) + if err != nil { + log.Printf("NewPeerConnection ERROR: %s", err) + return nil + } + profiles := utils.DefaultSRTPProtectionProfiles() + settingsEngine.SetSRTPProtectionProfiles(profiles...) + settingsEngine.SetDTLSClientHelloMessageHook(mimic.Hook) + } else if sf.CovertDTLSConfig.Mimic { mimic := &mimicry.MimickedClientHello{} if sf.CovertDTLSConfig.Randomize { err := mimic.LoadRandomFingerprint() diff --git a/proxy/main.go b/proxy/main.go index d031d0e..16b89c3 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/theodorsm/covert-dtls/pkg/fingerprints" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/ptutil/safelog" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/covertdtls" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" @@ -47,7 +48,8 @@ func main() { verboseLogging := flag.Bool("verbose", false, "increase log verbosity") ephemeralPortsRangeFlag := flag.String("ephemeral-ports-range", "", "Set the `range` of ports used for client connections (format:\":\").\nIf omitted, the ports will be chosen automatically.") versionFlag := flag.Bool("version", false, "display version info to stderr and quit") - covertDTLSConfig := flag.String("covertdtls-config", "", "Configuration of dtls mimicking and randomization: mimic, randomize, randomizemimic") + covertDTLSConfig := flag.String("covertdtls-config", "", "Configuration of DTLS mimicking and randomization: mimic, randomize, randomizemimic") + covertDTLSfingerprint := flag.String("covertdtls-fingerprint", "", "Mimicking of a raw DTLS fingerprint") var ephemeralPortsRange []uint16 = []uint16{0, 0} @@ -94,6 +96,15 @@ func main() { } } + var cDTLSconfig covertdtls.CovertDTLSConfig + + if *covertDTLSConfig != "" { + cDTLSconfig = covertdtls.ParseConfigString(*covertDTLSConfig) + } + if *covertDTLSfingerprint != "" { + cDTLSconfig.Fingerprint = fingerprints.ClientHelloFingerprint(*covertDTLSfingerprint) + } + proxy := sf.SnowflakeProxy{ PollInterval: *pollInterval, Capacity: uint(*capacity), @@ -114,7 +125,7 @@ func main() { AllowNonTLSRelay: *allowNonTLSRelay, SummaryInterval: *summaryInterval, - CovertDTLSConfig: covertdtls.ParseConfigString(*covertDTLSConfig), + CovertDTLSConfig: cDTLSconfig, } var logOutput = io.Discard