├── .gitignore ├── go.mod ├── internal ├── README.md └── x25519ell2 │ ├── x25519ell2_test.go │ └── x25519ell2.go ├── obfs4proxy ├── termmon_linux.go ├── termmon.go ├── pt_extras.go ├── proxy_http.go └── proxy_socks4.go ├── common ├── probdist │ ├── weighted_dist_test.go │ └── weighted_dist.go ├── socks5 │ ├── args.go │ ├── args_test.go │ ├── rfc1929.go │ └── socks5.go ├── replayfilter │ ├── replay_filter_test.go │ └── replay_filter.go ├── csrand │ └── csrand.go ├── drbg │ └── hash_drbg.go ├── uniformdh │ ├── uniformdh.go │ └── uniformdh_test.go └── log │ └── log.go ├── LICENSE ├── .golangci.yml ├── doc └── obfs4proxy.1 ├── transports ├── meeklite │ ├── base.go │ └── meek.go ├── transports.go ├── scramblesuit │ ├── base.go │ ├── handshake_uniformdh.go │ └── handshake_ticket.go ├── base │ └── base.go ├── obfs4 │ ├── framing │ │ ├── framing_test.go │ │ └── framing.go │ ├── packet.go │ ├── statefile.go │ └── handshake_ntor_test.go └── obfs2 │ └── obfs2.go ├── README.md ├── ChangeLog └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | 4 | obfs4proxy/obfs4proxy 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gitlab.com/yawning/obfs4.git 2 | 3 | require ( 4 | filippo.io/edwards25519 v1.0.0 5 | github.com/dchest/siphash v1.2.3 6 | gitlab.com/yawning/edwards25519-extra v0.0.0-20231005122941-2149dcafc266 7 | gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 8 | golang.org/x/crypto v0.14.0 9 | golang.org/x/net v0.17.0 10 | ) 11 | 12 | require golang.org/x/sys v0.13.0 // indirect 13 | 14 | go 1.20 15 | -------------------------------------------------------------------------------- /internal/README.md: -------------------------------------------------------------------------------- 1 | The x25519ell2 package provides X25519 obfuscated with Elligator 2, with 2 | special care taken to handle cofactor related issues, and fixes for the 3 | bugs in agl's original Elligator2 implementation. 4 | 5 | All existing versions prior to the migration to the new code (anything 6 | that uses agl's code) are fatally broken, and trivial to distinguish via 7 | some simple math. For more details see Loup Vaillant's writings on the 8 | subject. Any bugs in the implementation are mine, and not his. 9 | 10 | Representatives created by this implementation will correctly be decoded 11 | by existing implementations. Public keys created by this implementation 12 | be it via the modified scalar basepoint multiply or via decoding a 13 | representative will be somewhat non-standard, but will interoperate with 14 | a standard X25519 scalar-multiply. 15 | 16 | As the representative to public key transform should be identical, 17 | this change is fully-backward compatible (though the non-upgraded side 18 | of the connection will still be trivially distinguishable from random). 19 | 20 | ##### Maintainer's rant 21 | 22 | Honestly, it is possible to create a better obfuscation protocol than 23 | obfs4, and it's shelf-life expired years ago. No one should be using 24 | it for anything at this point, and no one should have been using it 25 | for anything for the past however many years since I first started 26 | telling people to stop using it. 27 | 28 | People should also have listened when I told them repeatedly that there 29 | are massive issues in the protocol. 30 | 31 | * Do not ask me questions about this. 32 | * Do not use it in other projects. 33 | * Do not use it in anything new. 34 | * Use a prime order group instead of this nonsense especially if you 35 | are doing something new. 36 | * All I want is to be left alone. 37 | -------------------------------------------------------------------------------- /obfs4proxy/termmon_linux.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package main 29 | 30 | import ( 31 | "fmt" 32 | "syscall" 33 | ) 34 | 35 | func termMonitorInitLinux(_ *termMonitor) error { 36 | // Use prctl() to have the kernel deliver a SIGTERM if the parent 37 | // process dies. This beats anything else that can be done before 38 | // #15435 is implemented. 39 | _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGTERM), 0) 40 | if errno != 0 { 41 | var err error = errno 42 | return fmt.Errorf("prctl(PR_SET_PDEATHSIG, SIGTERM) returned: %w", err) 43 | } 44 | return nil 45 | } 46 | 47 | func init() { //nolint:gochecknoinits 48 | termMonitorOSInit = termMonitorInitLinux 49 | } 50 | -------------------------------------------------------------------------------- /common/probdist/weighted_dist_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package probdist 29 | 30 | import ( 31 | "testing" 32 | 33 | "gitlab.com/yawning/obfs4.git/common/drbg" 34 | ) 35 | 36 | const debug = false 37 | 38 | func TestWeightedDist(t *testing.T) { 39 | seed, err := drbg.NewSeed() 40 | if err != nil { 41 | t.Fatal("failed to generate a DRBG seed:", err) 42 | } 43 | 44 | const nrTrials = 1000000 45 | 46 | hist := make([]int, 1000) 47 | 48 | w := New(seed, 0, 999, true) 49 | if debug { 50 | // Dump a string representation of the probability table. 51 | t.Logf("Table:") 52 | var sum float64 53 | for _, weight := range w.weights { 54 | sum += weight 55 | } 56 | for i, weight := range w.weights { 57 | p := weight / sum 58 | if p > 0.000001 { // Filter out tiny values. 59 | t.Logf(" [%d]: %f", w.minValue+w.values[i], p) 60 | } 61 | } 62 | } 63 | 64 | for i := 0; i < nrTrials; i++ { 65 | value := w.Sample() 66 | hist[value]++ 67 | } 68 | 69 | if debug { 70 | t.Logf("Generated:") 71 | for value, count := range hist { 72 | if count != 0 { 73 | p := float64(count) / float64(nrTrials) 74 | t.Logf(" [%d]: %f (%d)", value, p, count) 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Yawning Angel 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | 26 | ============================================================================== 27 | 28 | Copyright (c) 2012 The Go Authors. All rights reserved. 29 | 30 | Redistribution and use in source and binary forms, with or without 31 | modification, are permitted provided that the following conditions are 32 | met: 33 | 34 | * Redistributions of source code must retain the above copyright 35 | notice, this list of conditions and the following disclaimer. 36 | * Redistributions in binary form must reproduce the above 37 | copyright notice, this list of conditions and the following disclaimer 38 | in the documentation and/or other materials provided with the 39 | distribution. 40 | * Neither the name of Google Inc. nor the names of its 41 | contributors may be used to endorse or promote products derived from 42 | this software without specific prior written permission. 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 45 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 46 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 47 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 48 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 49 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 50 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 51 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 52 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 53 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 54 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 55 | 56 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | # Re-enable the default linters 5 | - errcheck 6 | - gosimple 7 | - govet 8 | - ineffassign 9 | - staticcheck 10 | - typecheck 11 | - unused 12 | 13 | # Enable the "always useful" linters as of 1.53.3 14 | - asasalint 15 | - asciicheck 16 | - bidichk 17 | - decorder 18 | - dogsled 19 | - dupl 20 | - dupword 21 | - errchkjson 22 | - errname 23 | - errorlint 24 | - exhaustive 25 | - exportloopref 26 | - forbidigo 27 | - forcetypeassert 28 | - gci 29 | - gocheckcompilerdirectives 30 | - gochecknoinits 31 | - goconst 32 | - gocritic 33 | - godot 34 | - godox 35 | - gofumpt 36 | - gomoddirectives 37 | - goprintffuncname 38 | - gosec 39 | - gosmopolitan 40 | - importas 41 | - interfacebloat 42 | - makezero 43 | - mirror 44 | - misspell 45 | - musttag 46 | - nakedret 47 | - nestif 48 | - nilerr 49 | - nilnil 50 | - nolintlint 51 | - nonamedreturns 52 | - prealloc 53 | - predeclared 54 | - reassign 55 | - revive 56 | - tagalign 57 | - tenv 58 | - testableexamples 59 | - unconvert 60 | - unparam 61 | - usestdlibvars 62 | - wastedassign 63 | - whitespace 64 | 65 | # Disabled: Run periodically, but too many places to annotate 66 | # - gomnd 67 | 68 | # Disabled: Not how I do things 69 | # - exhaustruct # Zero value is fine. 70 | # - funlen # I'm not breaking up my math. 71 | # - gochecknoglobals # How else am I supposed to declare constants. 72 | # - lll # The 70s called and wants their ttys back. 73 | # - paralleltest 74 | # - varnamelen # The papers use short variable names. 75 | # - tagliatelle # I want my tags to match the files. 76 | # - thelper 77 | # - tparallel 78 | # - testpackage 79 | # - wsl # Nice idea, not how I like to write code. 80 | # - goerr113 # Nice idea, this package has too much legacy bs. 81 | # - ireturn # By virtue of the PT API we are interface heavy. 82 | 83 | # Disabled: Annoying/Useless 84 | # - cyclop 85 | # - gocognit 86 | # - gocyclo 87 | # - maintidx 88 | # - wrapcheck 89 | 90 | # Disabled: Irrelevant/redundant 91 | # - bodyclose 92 | # - containedctx 93 | # - contextcheck 94 | # - depguard 95 | # - durationcheck 96 | # - execinquery 97 | # - ginkgolinter 98 | # - gofmt 99 | # - goheader 100 | # - goimports 101 | # - gomodguard 102 | # - grouper 103 | # - loggercheck 104 | # - nlreturn 105 | # - noctx 106 | # - nosprintfhostport 107 | # - promlinter 108 | # - rowserrcheck 109 | # - sqlclosecheck 110 | # - stylecheck 111 | # - zerologlint 112 | 113 | linters-settings: 114 | gci: 115 | sections: 116 | - standard 117 | - default 118 | - prefix(gitlab.com/yawning/obfs4.git) 119 | skip-generated: true 120 | custom-order: true 121 | -------------------------------------------------------------------------------- /doc/obfs4proxy.1: -------------------------------------------------------------------------------- 1 | .TH OBFS4PROXY 1 "2015-10-29" 2 | .SH NAME 3 | obfs4proxy \- pluggable transport proxy for Tor, implementing obfs4 4 | .SH SYNOPSIS 5 | .B obfs4proxy 6 | [\fIoptions\fR] 7 | .SH DESCRIPTION 8 | obfs4proxy is a tool that attempts to circumvent censorship by 9 | transforming the Tor traffic between the client and the bridge. This way 10 | censors, who usually monitor traffic between the client and the bridge, 11 | will see innocent-looking transformed traffic instead of the actual Tor 12 | traffic. 13 | .PP 14 | obfs4proxy implements the obfuscation protocols obfs2, obfs3, 15 | ScrambleSuit (client only), meek (client only) and obfs4. 16 | .PP 17 | obfs4proxy is currently only supported as a managed pluggable transport 18 | spawned as a helper process via the \fBtor\fR daemon. 19 | .SH OPTIONS 20 | .TP 21 | \fB\-h\fR, \fB\-\-help\fR 22 | Display usage information and exit. 23 | .TP 24 | \fB\-\-version\fR 25 | Display version information and exit. 26 | .TP 27 | \fB\-\-enableLogging\fR 28 | Enable logging. 29 | .TP 30 | \fB\-\-logLevel\fR=\fIlevel\fR 31 | Specify the maximum log severity to log out of "\fBERROR\fR", "\fBWARN\fR", 32 | "\fBINFO\fR", and "\fBDEBUG\fR". 33 | .TP 34 | \fB\-\-unsafeLogging\fR 35 | Disable the IP address scrubber when logging, storing personally identifiable 36 | information in the logs. 37 | .TP 38 | \fB\-\-obfs4\-distBias\fR 39 | When generating probability distributions for the obfs4 length and timing 40 | obfuscation, generate biased distributions similar to ScrambleSuit. 41 | .SH ENVIORNMENT 42 | obfs4proxy honors all of the enviornment variables as specified in the Tor 43 | Pluggable Transport Specification. 44 | .SH FILES 45 | .PP 46 | \fIDataDirectory\fR\fB/pt_state/obfs4proxy.log\fR 47 | .RS 4 48 | The log file, assuming logging is enabled. 49 | .RE 50 | .PP 51 | \fIDataDirectory\fR\fB/pt_state/obfs4_state.json\fR 52 | .RS 4 53 | The Bridge (server) auto-generated obfs4 bridge parameters file. This file 54 | will not be created if the administrator specifies them in the \fBtorrc\fR 55 | via a \fBServerTransportOptions\fR directive. 56 | .RE 57 | .PP 58 | \fIDataDirectory\fR\fB/pt_state/obfs4_bridgeline.txt\fR 59 | .RS 4 60 | The Bridge (server) obfs4 bridge's client parameters. This file is created 61 | and contains the \fBBridge\fR directive a client should add to their 62 | \fBtorrc\fR to connect to the running server's obfs4 instance. 63 | .RE 64 | .SH "CONFORMING TO" 65 | Tor Pluggable Transport Specification 66 | .SH NOTES 67 | Using the obfs4 protocol requires tor-0.2.5.x or later. 68 | .PP 69 | The obfs2 protocol is included for backwards compatibility purposes only, and 70 | should not be used in new deployments. 71 | .SH EXAMPLE 72 | To configure tor to be able to use obfs4 bridges (as a client), add obfs4proxy 73 | to the \fBtorrc\fR like thus: 74 | .PP 75 | .nf 76 | .RS 77 | # Use obfs4proxy to provide the obfs4 protocol. 78 | ClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy 79 | .RE 80 | .fi 81 | .PP 82 | To configure tor to act as an obfs4 bridge (as the server), add obfs4proxy 83 | to the \fBtorrc\fR like thus: 84 | .PP 85 | .nf 86 | .RS 87 | # 88 | # In addition to the standard tor bridge configuration, add: 89 | # 90 | 91 | # Use obfs4proxy to provide the obfs4 protocol. 92 | ServerTransportPlugin obfs4 exec /usr/bin/obfs4proxy 93 | .RE 94 | .fi 95 | .SH "SEE ALSO" 96 | \fBtor (1), \fBtorrc (5), \fBobfsproxy (1) 97 | -------------------------------------------------------------------------------- /common/socks5/args.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package socks5 29 | 30 | import ( 31 | "fmt" 32 | 33 | "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" 34 | ) 35 | 36 | // parseClientParameters takes a client parameter string formatted according to 37 | // "Passing PT-specific parameters to a client PT" in the pluggable transport 38 | // specification, and returns it as a goptlib Args structure. 39 | // 40 | // This is functionally identical to the equivalently named goptlib routine. 41 | func parseClientParameters(argStr string) (pt.Args, error) { 42 | args := make(pt.Args) 43 | if len(argStr) == 0 { 44 | return args, nil 45 | } 46 | 47 | var key string 48 | acc := make([]byte, 0, len(argStr)) 49 | prevIsEscape := false 50 | for idx, ch := range []byte(argStr) { 51 | switch ch { 52 | case '\\': 53 | prevIsEscape = !prevIsEscape 54 | if prevIsEscape { 55 | continue 56 | } 57 | case '=': 58 | if !prevIsEscape { 59 | if key != "" { 60 | break 61 | } 62 | if len(acc) == 0 { 63 | return nil, fmt.Errorf("unexpected '=' at %d", idx) 64 | } 65 | key = string(acc) 66 | acc = nil 67 | continue 68 | } 69 | case ';': 70 | if !prevIsEscape { 71 | if key == "" || idx == len(argStr)-1 { 72 | return nil, fmt.Errorf("unexpected ';' at %d", idx) 73 | } 74 | args.Add(key, string(acc)) 75 | key = "" 76 | acc = nil 77 | continue 78 | } 79 | default: 80 | if prevIsEscape { 81 | return nil, fmt.Errorf("unexpected '\\' at %d", idx-1) 82 | } 83 | } 84 | prevIsEscape = false 85 | acc = append(acc, ch) 86 | } 87 | if prevIsEscape { 88 | return nil, fmt.Errorf("underminated escape character") 89 | } 90 | // Handle the final k,v pair if any. 91 | if key == "" { 92 | return nil, fmt.Errorf("final key with no value") 93 | } 94 | args.Add(key, string(acc)) 95 | 96 | return args, nil 97 | } 98 | -------------------------------------------------------------------------------- /common/socks5/args_test.go: -------------------------------------------------------------------------------- 1 | // Shamelessly stolen from goptlib's args_test.go. 2 | 3 | package socks5 4 | 5 | import ( 6 | "testing" 7 | 8 | "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" 9 | ) 10 | 11 | func stringSlicesEqual(a, b []string) bool { 12 | if len(a) != len(b) { 13 | return false 14 | } 15 | for i := range a { 16 | if a[i] != b[i] { 17 | return false 18 | } 19 | } 20 | return true 21 | } 22 | 23 | func argsEqual(a, b pt.Args) bool { 24 | for k, av := range a { 25 | bv := b[k] 26 | if !stringSlicesEqual(av, bv) { 27 | return false 28 | } 29 | } 30 | for k, bv := range b { 31 | av := a[k] 32 | if !stringSlicesEqual(av, bv) { 33 | return false 34 | } 35 | } 36 | return true 37 | } 38 | 39 | func TestParseClientParameters(t *testing.T) { 40 | badTests := [...]string{ 41 | "key", 42 | "key\\", 43 | "=value", 44 | "==value", 45 | "==key=value", 46 | "key=value\\", 47 | "a=b;key=value\\", 48 | "a;b=c", 49 | ";", 50 | "key=value;", 51 | ";key=value", 52 | "key\\=value", 53 | } 54 | goodTests := [...]struct { 55 | input string 56 | expected pt.Args 57 | }{ 58 | { 59 | "", 60 | pt.Args{}, 61 | }, 62 | { 63 | "key=", 64 | pt.Args{"key": []string{""}}, 65 | }, 66 | { 67 | "key==", 68 | pt.Args{"key": []string{"="}}, 69 | }, 70 | { 71 | "key=value", 72 | pt.Args{"key": []string{"value"}}, 73 | }, 74 | { 75 | "a=b=c", 76 | pt.Args{"a": []string{"b=c"}}, 77 | }, 78 | { 79 | "key=a\nb", 80 | pt.Args{"key": []string{"a\nb"}}, 81 | }, 82 | { 83 | "key=value\\;", 84 | pt.Args{"key": []string{"value;"}}, 85 | }, 86 | { 87 | "key=\"value\"", 88 | pt.Args{"key": []string{"\"value\""}}, 89 | }, 90 | { 91 | "key=\"\"value\"\"", 92 | pt.Args{"key": []string{"\"\"value\"\""}}, 93 | }, 94 | { 95 | "\"key=value\"", 96 | pt.Args{"\"key": []string{"value\""}}, 97 | }, 98 | { 99 | "key=value;key=value", 100 | pt.Args{"key": []string{"value", "value"}}, 101 | }, 102 | { 103 | "key=value1;key=value2", 104 | pt.Args{"key": []string{"value1", "value2"}}, 105 | }, 106 | { 107 | "key1=value1;key2=value2;key1=value3", 108 | pt.Args{"key1": []string{"value1", "value3"}, "key2": []string{"value2"}}, 109 | }, 110 | { 111 | "\\;=\\;;\\\\=\\;", 112 | pt.Args{";": []string{";"}, "\\": []string{";"}}, 113 | }, 114 | { 115 | "a\\=b=c", 116 | pt.Args{"a=b": []string{"c"}}, 117 | }, 118 | { 119 | "shared-secret=rahasia;secrets-file=/tmp/blob", 120 | pt.Args{"shared-secret": []string{"rahasia"}, "secrets-file": []string{"/tmp/blob"}}, 121 | }, 122 | { 123 | "rocks=20;height=5.6", 124 | pt.Args{"rocks": []string{"20"}, "height": []string{"5.6"}}, 125 | }, 126 | } 127 | 128 | for _, input := range badTests { 129 | _, err := parseClientParameters(input) 130 | if err == nil { 131 | t.Errorf("%q unexpectedly succeeded", input) 132 | } 133 | } 134 | 135 | for _, test := range goodTests { 136 | args, err := parseClientParameters(test.input) 137 | if err != nil { 138 | t.Errorf("%q unexpectedly returned an error: %s", test.input, err) 139 | } 140 | if !argsEqual(args, test.expected) { 141 | t.Errorf("%q → %q (expected %q)", test.input, args, test.expected) 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /transports/meeklite/base.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package meeklite provides an implementation of the Meek circumvention 29 | // protocol. Only a client implementation is provided, and no effort is 30 | // made to normalize the TLS fingerprint. 31 | // 32 | // It borrows quite liberally from the real meek-client code. 33 | package meeklite // import "gitlab.com/yawning/obfs4.git/transports/meeklite" 34 | 35 | import ( 36 | "fmt" 37 | "net" 38 | 39 | "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" 40 | 41 | "gitlab.com/yawning/obfs4.git/transports/base" 42 | ) 43 | 44 | const transportName = "meek_lite" 45 | 46 | // Transport is the Meek implementation of the base.Transport interface. 47 | type Transport struct{} 48 | 49 | // Name returns the name of the Meek transport protocol. 50 | func (t *Transport) Name() string { 51 | return transportName 52 | } 53 | 54 | // ClientFactory returns a new meekClientFactory instance. 55 | func (t *Transport) ClientFactory(_ string) (base.ClientFactory, error) { 56 | cf := &meekClientFactory{transport: t} 57 | return cf, nil 58 | } 59 | 60 | // ServerFactory will one day return a new meekServerFactory instance. 61 | func (t *Transport) ServerFactory(_ string, _ *pt.Args) (base.ServerFactory, error) { 62 | return nil, fmt.Errorf("server not supported") 63 | } 64 | 65 | type meekClientFactory struct { 66 | transport base.Transport 67 | } 68 | 69 | func (cf *meekClientFactory) Transport() base.Transport { 70 | return cf.transport 71 | } 72 | 73 | func (cf *meekClientFactory) ParseArgs(args *pt.Args) (any, error) { 74 | return newClientArgs(args) 75 | } 76 | 77 | func (cf *meekClientFactory) Dial(_, _ string, dialFn base.DialFunc, args any) (net.Conn, error) { 78 | // Validate args before opening outgoing connection. 79 | ca, ok := args.(*meekClientArgs) 80 | if !ok { 81 | return nil, fmt.Errorf("invalid argument type for args") 82 | } 83 | 84 | return newMeekConn(dialFn, ca) 85 | } 86 | 87 | var ( 88 | _ base.ClientFactory = (*meekClientFactory)(nil) 89 | _ base.Transport = (*Transport)(nil) 90 | ) 91 | -------------------------------------------------------------------------------- /common/replayfilter/replay_filter_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package replayfilter 29 | 30 | import ( 31 | "testing" 32 | "time" 33 | ) 34 | 35 | func TestReplayFilter(t *testing.T) { 36 | ttl := 10 * time.Second 37 | 38 | f, err := New(ttl) 39 | if err != nil { 40 | t.Fatal("newReplayFilter failed:", err) 41 | } 42 | 43 | buf := []byte("This is a test of the Emergency Broadcast System.") 44 | now := time.Now() 45 | 46 | // testAndSet into empty filter, returns false (not present). 47 | set := f.TestAndSet(now, buf) 48 | if set { 49 | t.Fatal("TestAndSet empty filter returned true") 50 | } 51 | 52 | // testAndSet into filter containing entry, should return true(present). 53 | set = f.TestAndSet(now, buf) 54 | if !set { 55 | t.Fatal("testAndSet populated filter (replayed) returned false") 56 | } 57 | 58 | buf2 := []byte("This concludes this test of the Emergency Broadcast System.") 59 | now = now.Add(ttl) 60 | 61 | // testAndSet with time advanced. 62 | set = f.TestAndSet(now, buf2) 63 | if set { 64 | t.Fatal("testAndSet populated filter, 2nd entry returned true") 65 | } 66 | set = f.TestAndSet(now, buf2) 67 | if !set { 68 | t.Fatal("testAndSet populated filter, 2nd entry (replayed) returned false") 69 | } 70 | 71 | // Ensure that the first entry has been removed by compact. 72 | set = f.TestAndSet(now, buf) 73 | if set { 74 | t.Fatal("testAndSet populated filter, compact check returned true") 75 | } 76 | 77 | // Ensure that the filter gets reaped if the clock jumps backwards. 78 | now = time.Time{} 79 | set = f.TestAndSet(now, buf) 80 | if set { 81 | t.Fatal("testAndSet populated filter, backward time jump returned true") 82 | } 83 | if len(f.filter) != 1 { 84 | t.Fatal("filter map has a unexpected number of entries:", len(f.filter)) 85 | } 86 | if f.fifo.Len() != 1 { 87 | t.Fatal("filter fifo has a unexpected number of entries:", f.fifo.Len()) 88 | } 89 | 90 | // Ensure that the entry is properly added after reaping. 91 | set = f.TestAndSet(now, buf) 92 | if !set { 93 | t.Fatal("testAndSet populated filter, post-backward clock jump (replayed) returned false") 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /common/csrand/csrand.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package csrand implements the math/rand interface over crypto/rand, along 29 | // with some utility functions for common random number/byte related tasks. 30 | // 31 | // Not all of the convinience routines are replicated, only those that are 32 | // immediately useful. The Rand variable provides access to the full math/rand 33 | // API. 34 | package csrand // import "gitlab.com/yawning/obfs4.git/common/csrand" 35 | 36 | import ( 37 | cryptRand "crypto/rand" 38 | "encoding/binary" 39 | "fmt" 40 | "io" 41 | "math/rand" 42 | ) 43 | 44 | var ( 45 | csRandSourceInstance csRandSource 46 | 47 | // Rand is a math/rand instance backed by crypto/rand CSPRNG. 48 | Rand = rand.New(csRandSourceInstance) //nolint:gosec 49 | ) 50 | 51 | type csRandSource struct { 52 | // This does not keep any state as it is backed by crypto/rand. 53 | } 54 | 55 | func (r csRandSource) Int63() int64 { 56 | var src [8]byte 57 | if err := Bytes(src[:]); err != nil { 58 | panic(err) 59 | } 60 | val := binary.BigEndian.Uint64(src[:]) 61 | val &= (1<<63 - 1) 62 | 63 | return int64(val) 64 | } 65 | 66 | func (r csRandSource) Seed(_ int64) { 67 | // No-op. 68 | } 69 | 70 | // Intn returns, as a int, a pseudo random number in [0, n). 71 | func Intn(n int) int { 72 | return Rand.Intn(n) 73 | } 74 | 75 | // Float64 returns, as a float64, a pesudo random number in [0.0,1.0). 76 | func Float64() float64 { 77 | return Rand.Float64() 78 | } 79 | 80 | // IntRange returns a uniformly distributed int [min, max]. 81 | func IntRange(min, max int) int { 82 | if max < min { 83 | panic(fmt.Sprintf("IntRange: min > max (%d, %d)", min, max)) 84 | } 85 | 86 | r := (max + 1) - min 87 | ret := Rand.Intn(r) 88 | return ret + min 89 | } 90 | 91 | // Bytes fills the slice with random data. 92 | func Bytes(buf []byte) error { 93 | if _, err := io.ReadFull(cryptRand.Reader, buf); err != nil { 94 | return err 95 | } 96 | 97 | return nil 98 | } 99 | 100 | // Reader is a alias of rand.Reader. 101 | var Reader = cryptRand.Reader 102 | -------------------------------------------------------------------------------- /transports/transports.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package transports provides a interface to query supported pluggable 29 | // transports. 30 | package transports // import "gitlab.com/yawning/obfs4.git/transports" 31 | 32 | import ( 33 | "fmt" 34 | "sync" 35 | 36 | "gitlab.com/yawning/obfs4.git/transports/base" 37 | "gitlab.com/yawning/obfs4.git/transports/meeklite" 38 | "gitlab.com/yawning/obfs4.git/transports/obfs2" 39 | "gitlab.com/yawning/obfs4.git/transports/obfs3" 40 | "gitlab.com/yawning/obfs4.git/transports/obfs4" 41 | "gitlab.com/yawning/obfs4.git/transports/scramblesuit" 42 | ) 43 | 44 | var ( 45 | transportMapLock sync.Mutex 46 | transportMap map[string]base.Transport = make(map[string]base.Transport) 47 | ) 48 | 49 | // Register registers a transport protocol. 50 | func Register(transport base.Transport) error { 51 | transportMapLock.Lock() 52 | defer transportMapLock.Unlock() 53 | 54 | name := transport.Name() 55 | _, registered := transportMap[name] 56 | if registered { 57 | return fmt.Errorf("transport '%s' already registered", name) 58 | } 59 | transportMap[name] = transport 60 | 61 | return nil 62 | } 63 | 64 | // Transports returns the list of registered transport protocols. 65 | func Transports() []string { 66 | transportMapLock.Lock() 67 | defer transportMapLock.Unlock() 68 | 69 | ret := make([]string, 0, len(transportMap)) 70 | for name := range transportMap { 71 | ret = append(ret, name) 72 | } 73 | 74 | return ret 75 | } 76 | 77 | // Get returns a transport protocol implementation by name. 78 | func Get(name string) base.Transport { 79 | transportMapLock.Lock() 80 | defer transportMapLock.Unlock() 81 | 82 | t := transportMap[name] 83 | 84 | return t 85 | } 86 | 87 | // Init initializes all of the integrated transports. 88 | func Init() error { 89 | for _, v := range []base.Transport{ 90 | new(meeklite.Transport), 91 | new(obfs2.Transport), 92 | new(obfs3.Transport), 93 | new(obfs4.Transport), 94 | new(scramblesuit.Transport), 95 | } { 96 | if err := Register(v); err != nil { 97 | return err 98 | } 99 | } 100 | 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /common/socks5/rfc1929.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package socks5 29 | 30 | import "fmt" 31 | 32 | const ( 33 | authRFC1929Ver = 0x01 34 | authRFC1929Success = 0x00 35 | authRFC1929Fail = 0x01 36 | ) 37 | 38 | func (req *Request) authRFC1929() error { 39 | sendErrResp := func(err error) error { 40 | // Swallow write/flush errors, the auth failure is the relevant error. 41 | _, _ = req.rw.Write([]byte{authRFC1929Ver, authRFC1929Fail}) 42 | _ = req.flushBuffers() 43 | return err // Pass this through from the arg. 44 | } 45 | 46 | // The client sends a Username/Password request. 47 | // uint8_t ver (0x01) 48 | // uint8_t ulen (>= 1) 49 | // uint8_t uname[ulen] 50 | // uint8_t plen (>= 1) 51 | // uint8_t passwd[plen] 52 | 53 | if err := req.readByteVerify("auth version", authRFC1929Ver); err != nil { 54 | return sendErrResp(err) 55 | } 56 | 57 | // Read the username. 58 | var ( 59 | ulen byte 60 | err error 61 | ) 62 | if ulen, err = req.readByte(); err != nil { 63 | return sendErrResp(err) 64 | } else if ulen < 1 { 65 | return sendErrResp(fmt.Errorf("username with 0 length")) 66 | } 67 | var uname []byte 68 | if uname, err = req.readBytes(int(ulen)); err != nil { 69 | return sendErrResp(err) 70 | } 71 | 72 | // Read the password. 73 | var plen byte 74 | if plen, err = req.readByte(); err != nil { 75 | return sendErrResp(err) 76 | } else if plen < 1 { 77 | return sendErrResp(fmt.Errorf("password with 0 length")) 78 | } 79 | var passwd []byte 80 | if passwd, err = req.readBytes(int(plen)); err != nil { 81 | return sendErrResp(err) 82 | } 83 | 84 | // Pluggable transports use the username/password field to pass 85 | // per-connection arguments. The fields contain ASCII strings that 86 | // are combined and then parsed into key/value pairs. 87 | argStr := string(uname) 88 | if !(plen == 1 && passwd[0] == 0x00) { 89 | // tor will set the password to 'NUL', if the field doesn't contain any 90 | // actual argument data. 91 | argStr += string(passwd) 92 | } 93 | if req.Args, err = parseClientParameters(argStr); err != nil { 94 | return sendErrResp(err) 95 | } 96 | 97 | resp := []byte{authRFC1929Ver, authRFC1929Success} 98 | _, err = req.rw.Write(resp) 99 | return err 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## obfs4 - The obfourscator 2 | #### Yawning Angel (yawning at schwanenlied dot me) 3 | 4 | ### What? 5 | 6 | This is a look-like nothing obfuscation protocol that incorporates ideas and 7 | concepts from Philipp Winter's ScrambleSuit protocol. The obfs naming was 8 | chosen primarily because it was shorter, in terms of protocol ancestery obfs4 9 | is much closer to ScrambleSuit than obfs2/obfs3. 10 | 11 | The notable differences between ScrambleSuit and obfs4: 12 | 13 | * The handshake always does a full key exchange (no such thing as a Session 14 | Ticket Handshake). 15 | * The handshake uses the Tor Project's ntor handshake with public keys 16 | obfuscated via the Elligator 2 mapping. 17 | * The link layer encryption uses NaCl secret boxes (Poly1305/XSalsa20). 18 | 19 | As an added bonus, obfs4proxy also supports acting as an obfs2/3 client and 20 | bridge to ease the transition to the new protocol. 21 | 22 | ### Why not extend ScrambleSuit? 23 | 24 | It's my protocol and I'll obfuscate if I want to. 25 | 26 | Since a lot of the changes are to the handshaking process, it didn't make sense 27 | to extend ScrambleSuit as writing a server implementation that supported both 28 | handshake variants without being obscenely slow is non-trivial. 29 | 30 | ### Dependencies 31 | 32 | Build time library dependencies are handled by the Go module automatically. 33 | 34 | If you are on Go versions earlier than 1.11, you might need to run `go get -d 35 | ./...` to download all the dependencies. Note however, that modules always use 36 | the same dependency versions, while `go get -d` always downloads master. 37 | 38 | * Go 1.11.0 or later. Patches to support up to 2 prior major releases will 39 | be accepted if they are not overly intrusive and well written. 40 | * See `go.mod`, `go.sum` and `go list -m -u all` for build time dependencies. 41 | 42 | ### Installation 43 | 44 | To build: 45 | 46 | `go build -o obfs4proxy/obfs4proxy ./obfs4proxy` 47 | 48 | To install, copy `./obfs4proxy/obfsproxy` to a permanent location 49 | (Eg: `/usr/local/bin`) 50 | 51 | Client side torrc configuration: 52 | ``` 53 | ClientTransportPlugin obfs4 exec /usr/local/bin/obfs4proxy 54 | ``` 55 | 56 | Bridge side torrc configuration: 57 | ``` 58 | # Act as a bridge relay. 59 | BridgeRelay 1 60 | 61 | # Enable the Extended ORPort 62 | ExtORPort auto 63 | 64 | # Use obfs4proxy to provide the obfs4 protocol. 65 | ServerTransportPlugin obfs4 exec /usr/local/bin/obfs4proxy 66 | 67 | # (Optional) Listen on the specified address/port for obfs4 connections as 68 | # opposed to picking a port automatically. 69 | #ServerTransportListenAddr obfs4 0.0.0.0:443 70 | ``` 71 | 72 | ### Tips and tricks 73 | 74 | * On modern Linux systems it is possible to have obfs4proxy bind to reserved 75 | ports (<=1024) even when not running as root by granting the 76 | `CAP_NET_BIND_SERVICE` capability with setcap: 77 | 78 | `# setcap 'cap_net_bind_service=+ep' /usr/local/bin/obfs4proxy` 79 | 80 | * obfs4proxy can also act as an obfs2 and obfs3 client or server. Adjust the 81 | `ClientTransportPlugin` and `ServerTransportPlugin` lines in the torrc as 82 | appropriate. 83 | 84 | * obfs4proxy can also act as a ScrambleSuit client. Adjust the 85 | `ClientTransportPlugin` line in the torrc as appropriate. 86 | 87 | * The autogenerated obfs4 bridge parameters are placed in 88 | `DataDir/pt_state/obfs4_state.json`. To ease deployment, the client side 89 | bridge line is written to `DataDir/pt_state/obfs4_bridgeline.txt`. 90 | 91 | ### Thanks 92 | 93 | * Loup Vaillant for motivating me to replace the Elligator implementation 94 | and a body of code I could draw on to accelerate the replacement process. 95 | * David Fifield for goptlib. 96 | * Adam Langley for his initial Elligator implementation. 97 | * Philipp Winter for the ScrambleSuit protocol which provided much of the 98 | design. 99 | -------------------------------------------------------------------------------- /transports/scramblesuit/base.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package scramblesuit provides an implementation of the ScrambleSuit 29 | // obfuscation protocol. The implementation is client only. 30 | package scramblesuit // import "gitlab.com/yawning/obfs4.git/transports/scramblesuit" 31 | 32 | import ( 33 | "fmt" 34 | "net" 35 | 36 | "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" 37 | 38 | "gitlab.com/yawning/obfs4.git/transports/base" 39 | ) 40 | 41 | const transportName = "scramblesuit" 42 | 43 | // Transport is the ScrambleSuit implementation of the base.Transport interface. 44 | type Transport struct{} 45 | 46 | // Name returns the name of the ScrambleSuit transport protocol. 47 | func (t *Transport) Name() string { 48 | return transportName 49 | } 50 | 51 | // ClientFactory returns a new ssClientFactory instance. 52 | func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) { 53 | tStore, err := loadTicketStore(stateDir) 54 | if err != nil { 55 | return nil, err 56 | } 57 | cf := &ssClientFactory{transport: t, ticketStore: tStore} 58 | return cf, nil 59 | } 60 | 61 | // ServerFactory will one day return a new ssServerFactory instance. 62 | func (t *Transport) ServerFactory(_ string, _ *pt.Args) (base.ServerFactory, error) { 63 | return nil, fmt.Errorf("server not supported") 64 | } 65 | 66 | type ssClientFactory struct { 67 | transport base.Transport 68 | ticketStore *ssTicketStore 69 | } 70 | 71 | func (cf *ssClientFactory) Transport() base.Transport { 72 | return cf.transport 73 | } 74 | 75 | func (cf *ssClientFactory) ParseArgs(args *pt.Args) (any, error) { 76 | return newClientArgs(args) 77 | } 78 | 79 | func (cf *ssClientFactory) Dial(network, addr string, dialFn base.DialFunc, args any) (net.Conn, error) { 80 | // Validate args before opening outgoing connection. 81 | ca, ok := args.(*ssClientArgs) 82 | if !ok { 83 | return nil, fmt.Errorf("invalid argument type for args") 84 | } 85 | 86 | conn, err := dialFn(network, addr) 87 | if err != nil { 88 | return nil, err 89 | } 90 | dialConn := conn 91 | if conn, err = newScrambleSuitClientConn(conn, cf.ticketStore, ca); err != nil { 92 | dialConn.Close() 93 | return nil, err 94 | } 95 | return conn, nil 96 | } 97 | 98 | var ( 99 | _ base.ClientFactory = (*ssClientFactory)(nil) 100 | _ base.Transport = (*Transport)(nil) 101 | ) 102 | -------------------------------------------------------------------------------- /transports/base/base.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package base provides the common interface that each supported transport 29 | // protocol must implement. 30 | package base // import "gitlab.com/yawning/obfs4.git/transports/base" 31 | 32 | import ( 33 | "net" 34 | 35 | "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" 36 | ) 37 | 38 | type DialFunc func(string, string) (net.Conn, error) 39 | 40 | // ClientFactory is the interface that defines the factory for creating 41 | // pluggable transport protocol client instances. 42 | type ClientFactory interface { 43 | // Transport returns the Transport instance that this ClientFactory belongs 44 | // to. 45 | Transport() Transport 46 | 47 | // ParseArgs parses the supplied arguments into an internal representation 48 | // for use with WrapConn. This routine is called before the outgoing 49 | // TCP/IP connection is created to allow doing things (like keypair 50 | // generation) to be hidden from third parties. 51 | ParseArgs(args *pt.Args) (any, error) 52 | 53 | // Dial creates an outbound net.Conn, and does whatever is required 54 | // (eg: handshaking) to get the connection to the point where it is 55 | // ready to relay data. 56 | Dial(network, address string, dialFn DialFunc, args any) (net.Conn, error) 57 | } 58 | 59 | // ServerFactory is the interface that defines the factory for creating 60 | // plugable transport protocol server instances. As the arguments are the 61 | // property of the factory, validation is done at factory creation time. 62 | type ServerFactory interface { 63 | // Transport returns the Transport instance that this ServerFactory belongs 64 | // to. 65 | Transport() Transport 66 | 67 | // Args returns the Args required on the client side to handshake with 68 | // server connections created by this factory. 69 | Args() *pt.Args 70 | 71 | // WrapConn wraps the provided net.Conn with a transport protocol 72 | // implementation, and does whatever is required (eg: handshaking) to get 73 | // the connection to a point where it is ready to relay data. 74 | WrapConn(conn net.Conn) (net.Conn, error) 75 | } 76 | 77 | // Transport is an interface that defines a pluggable transport protocol. 78 | type Transport interface { 79 | // Name returns the name of the transport protocol. It MUST be a valid C 80 | // identifier. 81 | Name() string 82 | 83 | // ClientFactory returns a ClientFactory instance for this transport 84 | // protocol. 85 | ClientFactory(stateDir string) (ClientFactory, error) 86 | 87 | // ServerFactory returns a ServerFactory instance for this transport 88 | // protocol. This can fail if the provided arguments are invalid. 89 | ServerFactory(stateDir string, args *pt.Args) (ServerFactory, error) 90 | } 91 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | Changes in version 0.0.15 - UNRELEASED: 2 | - Bump the various dependencies. 3 | 4 | Changes in version 0.0.14 - 2022-09-04: 5 | - Fixed the incompete previous fix to the Elligator 2 subgroup issue (Thanks 6 | to David Fifield). 7 | 8 | Changes in version 0.0.13 - 2022-02-04: 9 | - Stop using utls entirely for TLS signature normalization (meek_lite). 10 | - Stop pinning the certificate chain for default bridges (meek_lite). 11 | 12 | Changes in version 0.0.12 - 2021-12-31: 13 | - Fix the long standing distinguishers associated with agl's Elligator2 14 | implementation (Thanks to Loup Vaillant). 15 | - Replace the extra25519 import with an internal package. 16 | - Update the Azure TLS certificate digest (Thanks to Philipp Winter). 17 | - Make the -unsafeLogging command line switch work. 18 | - Bump the version of the utls fork, add the Chrome 83 fingerprint. 19 | 20 | Changes in version 0.0.11 - 2019-06-21: 21 | - Update my e-mail address. 22 | - Change the obfs4 behavior for handling handshake failure to be more 23 | uniform. Thanks to Sergey Frolov for assistance. 24 | - Bump the version of the utls fork. 25 | 26 | Changes in version 0.0.10 - 2019-04-12: 27 | - Disable behavior distinctive to crypto/tls when using utls. 28 | - Bump the version of the utls fork. 29 | 30 | Changes in version 0.0.9 - 2019-02-05: 31 | - Various meek_lite code cleanups and bug fixes. 32 | - Bug 29077: uTLS for ClientHello camouflage (meek_lite). 33 | - More fixes to HTTP Basic auth. 34 | - (meek_lite) Pin the certificate chain public keys for the default 35 | Tor Browser Azure bridge (meek_lite). 36 | 37 | Changes in version 0.0.8 - 2019-01-20: 38 | - Bug 24793: Send the correct authorization HTTP header for basic auth. 39 | - (meek_lite) Explicitly set Content-Length to zero when there is no data 40 | to send. 41 | - Added optional support for building as a Go 1.11 module. Patch by mvdan. 42 | - Change the canonical upstream repo location to gitlab. 43 | 44 | Changes in version 0.0.7 - 2016-11-15: 45 | - Support configuring the obfs4 IAT parameter as the sole 46 | ServerTransportOption on bridges, and correctly checkpoint the argument 47 | to the state file. 48 | - Correctly use the derived epoch hour when generating the server obfs4 49 | ntor handshake response to be more tollerant of clock skew. 50 | - Reuse the read buffer when consuming obfs4 frames over the network to 51 | reduce memory consumption. Patch by oxtoacart. 52 | 53 | Changes in version 0.0.6 - 2016-01-25: 54 | - Delay transport factory initialization till after logging has been 55 | initialized. 56 | - Add a meek client implementation (WARNING: Does not support using a 57 | helper to normalize TLS signatures). The brave people that want to use 58 | it can do so as the "meek_lite" transport, with identical bridge lines 59 | to the real meek-client. 60 | 61 | Changes in version 0.0.5 - 2015-04-15: 62 | - Go vet/fmt fixes, and misc. code cleanups. Patches by mvdan. 63 | - Changed the go.net import path to the new location (golang.org/x/net). 64 | - Added limited support for detecting if the parent process crashes. 65 | - Support for tor feature #15335 (stdin based termination notification). 66 | - Moved the leveled logging wrappers into common/log so they are usable 67 | in transport implementations. 68 | - Added a DEBUG log level. 69 | - Use a bundled SOCKS 5 server instead of goptlib's SocksListener. 70 | 71 | Changes in version 0.0.4 - 2015-02-17 72 | - Improve the runtime performance of the obfs4 handshake tests. 73 | - Changed the go.crypto import path to the new location (golang.org/x/crypto). 74 | - Added client only support for ScrambleSuit. 75 | 76 | Changes in version 0.0.3 - 2014-10-01 77 | - Change the obfs4 bridge line format to use a "cert" argument instead of the 78 | previous "node-id" and "public-key" arguments. The "cert" consists of the 79 | Base64 encoded concatenation of the node ID and public key, with the 80 | trailing padding removed. Old style separated bridge lines are still valid, 81 | but the newer representation is slightly more compact. 82 | 83 | Changes in version 0.0.2 - 2014-09-26 84 | - Write an example client bridge line suitable for use with the running obfs4 85 | server instance to "obfs4_bridgeline.txt" for the convenience of bridge 86 | operators. 87 | - Add a man page for obfs4proxy. 88 | 89 | Changes in version 0.0.1 - 2014-09-03 90 | - Initial release. 91 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= 2 | filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= 3 | github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= 4 | github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= 5 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 6 | gitlab.com/yawning/edwards25519-extra v0.0.0-20231005122941-2149dcafc266 h1:IvjshROr8z24+UCiOe/90cUWt3QDr8Rt+VkUjZsn+i0= 7 | gitlab.com/yawning/edwards25519-extra v0.0.0-20231005122941-2149dcafc266/go.mod h1:K/3SQWdJL6udzwInHk1gaYaECYxMp9dDayniPq6gCSo= 8 | gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 h1:rzdY78Ox2T+VlXcxGxELF+6VyUXlZBhmRqZu5etLm+c= 9 | gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0/go.mod h1:70bhd4JKW/+1HLfm+TMrgHJsUHG4coelMWwiVEJ2gAg= 10 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 11 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 12 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 13 | golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= 14 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 15 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 16 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 17 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 18 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 19 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 20 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 21 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 22 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 23 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 24 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 25 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 26 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 27 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 30 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 36 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 38 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 39 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 40 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 41 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 42 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 43 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 44 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 45 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 46 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 47 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 48 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 49 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 50 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 51 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 52 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 53 | -------------------------------------------------------------------------------- /obfs4proxy/termmon.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package main 29 | 30 | import ( 31 | "io" 32 | "os" 33 | "os/signal" 34 | "runtime" 35 | "syscall" 36 | "time" 37 | 38 | "gitlab.com/yawning/obfs4.git/common/log" 39 | ) 40 | 41 | var termMonitorOSInit func(*termMonitor) error 42 | 43 | type termMonitor struct { 44 | sigChan chan os.Signal 45 | handlerChan chan int 46 | numHandlers int 47 | } 48 | 49 | func (m *termMonitor) onHandlerStart() { 50 | m.handlerChan <- 1 51 | } 52 | 53 | func (m *termMonitor) onHandlerFinish() { 54 | m.handlerChan <- -1 55 | } 56 | 57 | func (m *termMonitor) wait(termOnNoHandlers bool) os.Signal { 58 | // Block until a signal has been received, or (optionally) the 59 | // number of pending handlers has hit 0. In the case of the 60 | // latter, treat it as if a SIGTERM has been received. 61 | for { 62 | select { 63 | case n := <-m.handlerChan: 64 | m.numHandlers += n 65 | case sig := <-m.sigChan: 66 | return sig 67 | } 68 | if termOnNoHandlers && m.numHandlers == 0 { 69 | return syscall.SIGTERM 70 | } 71 | } 72 | } 73 | 74 | func (m *termMonitor) termOnStdinClose() { 75 | _, err := io.Copy(io.Discard, os.Stdin) 76 | 77 | // io.Copy() will return a nil on EOF, since reaching EOF is 78 | // expected behavior. No matter what, if this unblocks, assume 79 | // that stdin is closed, and treat that as having received a 80 | // SIGTERM. 81 | log.Noticef("Stdin is closed or unreadable: %v", err) 82 | m.sigChan <- syscall.SIGTERM 83 | } 84 | 85 | func (m *termMonitor) termOnPPIDChange(ppid int) { 86 | // Under most if not all U*IX systems, the parent PID will change 87 | // to that of init once the parent dies. There are several notable 88 | // exceptions (Slowlaris/Android), but the parent PID changes 89 | // under those platforms as well. 90 | // 91 | // Naturally we lose if the parent has died by the time when the 92 | // Getppid() call was issued in our parent, but, this is better 93 | // than nothing. 94 | const ppidPollInterval = 1 * time.Second 95 | for ppid == os.Getppid() { 96 | time.Sleep(ppidPollInterval) 97 | } 98 | 99 | // Treat the parent PID changing as the same as having received 100 | // a SIGTERM. 101 | log.Noticef("Parent pid changed: %d (was %d)", os.Getppid(), ppid) 102 | m.sigChan <- syscall.SIGTERM 103 | } 104 | 105 | func newTermMonitor() *termMonitor { 106 | ppid := os.Getppid() 107 | m := new(termMonitor) 108 | m.sigChan = make(chan os.Signal) 109 | m.handlerChan = make(chan int) 110 | signal.Notify(m.sigChan, syscall.SIGINT, syscall.SIGTERM) 111 | 112 | // If tor supports feature #15435, we can use Stdin being closed as an 113 | // indication that tor has died, or wants the PT to shutdown for any 114 | // reason. 115 | if ptShouldExitOnStdinClose() { //nolint:nestif 116 | go m.termOnStdinClose() 117 | } else { 118 | // Instead of feature #15435, use various kludges and hacks: 119 | // * Linux - Platform specific code that should always work. 120 | // * Other U*IX - Somewhat generic code, that works unless the 121 | // parent dies before the monitor is initialized. 122 | if termMonitorOSInit != nil { 123 | // Errors here are non-fatal, since it might still be 124 | // possible to fall back to a generic implementation. 125 | if err := termMonitorOSInit(m); err == nil { 126 | return m 127 | } 128 | } 129 | if runtime.GOOS != "windows" { 130 | go m.termOnPPIDChange(ppid) 131 | } 132 | } 133 | return m 134 | } 135 | -------------------------------------------------------------------------------- /common/drbg/hash_drbg.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package drbg implements a minimalistic DRBG based off SipHash-2-4 in OFB 29 | // mode. 30 | package drbg // import "gitlab.com/yawning/obfs4.git/common/drbg" 31 | 32 | import ( 33 | "bytes" 34 | "encoding/binary" 35 | "encoding/hex" 36 | "fmt" 37 | "hash" 38 | 39 | "github.com/dchest/siphash" 40 | 41 | "gitlab.com/yawning/obfs4.git/common/csrand" 42 | ) 43 | 44 | // Size is the length of the HashDrbg output. 45 | const Size = siphash.Size 46 | 47 | // SeedLength is the length of the HashDrbg seed. 48 | const SeedLength = 16 + Size 49 | 50 | // Seed is the initial state for a HashDrbg. It consists of a SipHash-2-4 51 | // key, and 8 bytes of initial data. 52 | type Seed [SeedLength]byte 53 | 54 | // Bytes returns a pointer to the raw HashDrbg seed. 55 | func (seed *Seed) Bytes() *[SeedLength]byte { 56 | return (*[SeedLength]byte)(seed) 57 | } 58 | 59 | // Hex returns the hexdecimal representation of the seed. 60 | func (seed *Seed) Hex() string { 61 | return hex.EncodeToString(seed.Bytes()[:]) 62 | } 63 | 64 | // NewSeed returns a Seed initialized with the runtime CSPRNG. 65 | func NewSeed() (*Seed, error) { 66 | seed := new(Seed) 67 | if err := csrand.Bytes(seed.Bytes()[:]); err != nil { 68 | return nil, err 69 | } 70 | 71 | return seed, nil 72 | } 73 | 74 | // SeedFromBytes creates a Seed from the raw bytes, truncating to SeedLength as 75 | // appropriate. 76 | func SeedFromBytes(src []byte) (*Seed, error) { 77 | if len(src) < SeedLength { 78 | return nil, InvalidSeedLengthError(len(src)) 79 | } 80 | 81 | seed := new(Seed) 82 | copy(seed.Bytes()[:], src) 83 | 84 | return seed, nil 85 | } 86 | 87 | // SeedFromHex creates a Seed from the hexdecimal representation, truncating to 88 | // SeedLength as appropriate. 89 | func SeedFromHex(encoded string) (*Seed, error) { 90 | raw, err := hex.DecodeString(encoded) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return SeedFromBytes(raw) 96 | } 97 | 98 | // InvalidSeedLengthError is the error returned when the seed provided to the 99 | // DRBG is an invalid length. 100 | type InvalidSeedLengthError int 101 | 102 | func (e InvalidSeedLengthError) Error() string { 103 | return fmt.Sprintf("invalid seed length: %d", int(e)) 104 | } 105 | 106 | // HashDrbg is a CSDRBG based off of SipHash-2-4 in OFB mode. 107 | type HashDrbg struct { 108 | sip hash.Hash64 109 | ofb [Size]byte 110 | } 111 | 112 | // NewHashDrbg makes a HashDrbg instance based off an optional seed. The seed 113 | // is truncated to SeedLength. 114 | func NewHashDrbg(seed *Seed) (*HashDrbg, error) { 115 | drbg := new(HashDrbg) 116 | if seed == nil { 117 | var err error 118 | if seed, err = NewSeed(); err != nil { 119 | return nil, err 120 | } 121 | } 122 | drbg.sip = siphash.New(seed.Bytes()[:16]) 123 | copy(drbg.ofb[:], seed.Bytes()[16:]) 124 | 125 | return drbg, nil 126 | } 127 | 128 | // Int63 returns a uniformly distributed random integer [0, 1 << 63). 129 | func (drbg *HashDrbg) Int63() int64 { 130 | block := drbg.NextBlock() 131 | ret := binary.BigEndian.Uint64(block) 132 | ret &= (1<<63 - 1) 133 | 134 | return int64(ret) 135 | } 136 | 137 | // Seed does nothing, call NewHashDrbg if you want to reseed. 138 | func (drbg *HashDrbg) Seed(_ int64) { 139 | // No-op. 140 | } 141 | 142 | // NextBlock returns the next 8 byte DRBG block. 143 | func (drbg *HashDrbg) NextBlock() []byte { 144 | _, _ = drbg.sip.Write(drbg.ofb[:]) 145 | copy(drbg.ofb[:], drbg.sip.Sum(nil)) 146 | 147 | return bytes.Clone(drbg.ofb[:]) 148 | } 149 | -------------------------------------------------------------------------------- /obfs4proxy/pt_extras.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package main 29 | 30 | import ( 31 | "errors" 32 | "fmt" 33 | "net" 34 | "net/url" 35 | "os" 36 | "strconv" 37 | 38 | "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" 39 | ) 40 | 41 | // This file contains things that probably should be in goptlib but are not 42 | // yet or not exposed. 43 | 44 | func ptEnvError(msg string) error { 45 | line := []byte(fmt.Sprintf("ENV-ERROR %s\n", msg)) 46 | _, _ = pt.Stdout.Write(line) 47 | return errors.New(msg) 48 | } 49 | 50 | func ptIsClient() (bool, error) { 51 | clientEnv := os.Getenv("TOR_PT_CLIENT_TRANSPORTS") 52 | serverEnv := os.Getenv("TOR_PT_SERVER_TRANSPORTS") 53 | switch { 54 | case clientEnv != "" && serverEnv != "": 55 | return false, ptEnvError("TOR_PT_[CLIENT,SERVER]_TRANSPORTS both set") 56 | case clientEnv != "": 57 | return true, nil 58 | case serverEnv != "": 59 | return false, nil 60 | } 61 | return false, errors.New("not launched as a managed transport") 62 | } 63 | 64 | func ptGetProxy(info *pt.ClientInfo) (*url.URL, error) { 65 | proxyURL := info.ProxyURL 66 | if proxyURL == nil { 67 | return nil, nil //nolint:nilnil 68 | } 69 | 70 | // Validate the arguments. 71 | switch proxyURL.Scheme { 72 | case "http": 73 | // The most forgiving of proxies. 74 | 75 | case "socks4a": 76 | if proxyURL.User != nil { 77 | _, isSet := proxyURL.User.Password() 78 | if isSet { 79 | return nil, pt.ProxyError("proxy URI proxyURLified SOCKS4a and a password") 80 | } 81 | } 82 | 83 | case "socks5": 84 | if proxyURL.User != nil { 85 | // UNAME/PASSWD both must be between 1 and 255 bytes long. (RFC1929) 86 | user := proxyURL.User.Username() 87 | passwd, isSet := proxyURL.User.Password() 88 | if len(user) < 1 || len(user) > 255 { 89 | return nil, pt.ProxyError("proxy URI proxyURLified a invalid SOCKS5 username") 90 | } 91 | if !isSet || len(passwd) < 1 || len(passwd) > 255 { 92 | return nil, pt.ProxyError("proxy URI proxyURLified a invalid SOCKS5 password") 93 | } 94 | } 95 | 96 | default: 97 | return nil, pt.ProxyError(fmt.Sprintf("proxy URI has invalid scheme: %s", proxyURL.Scheme)) 98 | } 99 | 100 | if _, err := resolveAddrStr(proxyURL.Host); err != nil { 101 | return nil, pt.ProxyError(fmt.Sprintf("proxy URI has invalid host: %s", err)) 102 | } 103 | 104 | return proxyURL, nil 105 | } 106 | 107 | // Sigh, pt.resolveAddr() isn't exported. Include our own getto version that 108 | // doesn't work around #7011, because we don't work with pre-0.2.5.x tor, and 109 | // all we care about is validation anyway. 110 | func resolveAddrStr(addrStr string) (*net.TCPAddr, error) { 111 | ipStr, portStr, err := net.SplitHostPort(addrStr) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | if ipStr == "" { 117 | return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a host part", addrStr)) 118 | } 119 | if portStr == "" { 120 | return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a port part", addrStr)) 121 | } 122 | ip := net.ParseIP(ipStr) 123 | if ip == nil { 124 | return nil, net.InvalidAddrError(fmt.Sprintf("not an IP string: %q", ipStr)) 125 | } 126 | port, err := strconv.ParseUint(portStr, 10, 16) 127 | if err != nil { 128 | return nil, net.InvalidAddrError(fmt.Sprintf("not a Port string: %q", portStr)) 129 | } 130 | 131 | return &net.TCPAddr{IP: ip, Port: int(port), Zone: ""}, nil 132 | } 133 | 134 | // Feature #15435 adds a new env var for determining if Tor keeps stdin 135 | // open for use in termination detection. 136 | func ptShouldExitOnStdinClose() bool { 137 | return os.Getenv("TOR_PT_EXIT_ON_STDIN_CLOSE") == "1" 138 | } 139 | -------------------------------------------------------------------------------- /obfs4proxy/proxy_http.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package main 29 | 30 | import ( 31 | "bufio" 32 | "encoding/base64" 33 | "errors" 34 | "fmt" 35 | "net" 36 | "net/http" 37 | "net/http/httputil" 38 | "net/url" 39 | "time" 40 | 41 | "golang.org/x/net/proxy" 42 | ) 43 | 44 | // httpProxy is a HTTP connect proxy. 45 | type httpProxy struct { 46 | hostPort string 47 | haveAuth bool 48 | username string 49 | password string 50 | forward proxy.Dialer 51 | } 52 | 53 | func newHTTP(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { 54 | s := new(httpProxy) 55 | s.hostPort = uri.Host 56 | s.forward = forward 57 | if uri.User != nil { 58 | s.haveAuth = true 59 | s.username = uri.User.Username() 60 | s.password, _ = uri.User.Password() 61 | } 62 | 63 | return s, nil 64 | } 65 | 66 | func (s *httpProxy) Dial(network, addr string) (net.Conn, error) { 67 | // Dial and create the http client connection. 68 | c, err := s.forward.Dial("tcp", s.hostPort) 69 | if err != nil { 70 | return nil, err 71 | } 72 | conn := new(httpConn) 73 | conn.httpConn = httputil.NewClientConn(c, nil) //nolint:staticcheck 74 | conn.remoteAddr, err = net.ResolveTCPAddr(network, addr) 75 | if err != nil { 76 | conn.httpConn.Close() 77 | return nil, err 78 | } 79 | 80 | // HACK: http.ReadRequest also does this. 81 | reqURL, err := url.Parse("http://" + addr) 82 | if err != nil { 83 | conn.httpConn.Close() 84 | return nil, err 85 | } 86 | reqURL.Scheme = "" 87 | 88 | req, err := http.NewRequest(http.MethodConnect, reqURL.String(), nil) 89 | if err != nil { 90 | conn.httpConn.Close() 91 | return nil, err 92 | } 93 | req.Close = false 94 | if s.haveAuth { 95 | // SetBasicAuth doesn't quite do what is appropriate, because 96 | // the correct header is `Proxy-Authorization`. 97 | req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(s.username+":"+s.password))) 98 | } 99 | req.Header.Set("User-Agent", "") 100 | 101 | resp, err := conn.httpConn.Do(req) 102 | if err != nil && !errors.Is(err, httputil.ErrPersistEOF) { //nolint:staticcheck 103 | conn.httpConn.Close() 104 | return nil, err 105 | } 106 | if resp.StatusCode != http.StatusOK { 107 | conn.httpConn.Close() 108 | return nil, fmt.Errorf("proxy error: %s", resp.Status) 109 | } 110 | 111 | conn.hijackedConn, conn.staleReader = conn.httpConn.Hijack() 112 | return conn, nil 113 | } 114 | 115 | type httpConn struct { 116 | remoteAddr *net.TCPAddr 117 | httpConn *httputil.ClientConn //nolint:staticcheck 118 | hijackedConn net.Conn 119 | staleReader *bufio.Reader 120 | } 121 | 122 | func (c *httpConn) Read(b []byte) (int, error) { 123 | if c.staleReader != nil { 124 | if c.staleReader.Buffered() > 0 { 125 | return c.staleReader.Read(b) 126 | } 127 | c.staleReader = nil 128 | } 129 | return c.hijackedConn.Read(b) 130 | } 131 | 132 | func (c *httpConn) Write(b []byte) (int, error) { 133 | return c.hijackedConn.Write(b) 134 | } 135 | 136 | func (c *httpConn) Close() error { 137 | return c.hijackedConn.Close() 138 | } 139 | 140 | func (c *httpConn) LocalAddr() net.Addr { 141 | return c.hijackedConn.LocalAddr() 142 | } 143 | 144 | func (c *httpConn) RemoteAddr() net.Addr { 145 | return c.remoteAddr 146 | } 147 | 148 | func (c *httpConn) SetDeadline(t time.Time) error { 149 | return c.hijackedConn.SetDeadline(t) 150 | } 151 | 152 | func (c *httpConn) SetReadDeadline(t time.Time) error { 153 | return c.hijackedConn.SetReadDeadline(t) 154 | } 155 | 156 | func (c *httpConn) SetWriteDeadline(t time.Time) error { 157 | return c.hijackedConn.SetWriteDeadline(t) 158 | } 159 | 160 | func init() { //nolint:gochecknoinits 161 | proxy.RegisterDialerType("http", newHTTP) 162 | } 163 | -------------------------------------------------------------------------------- /internal/x25519ell2/x25519ell2_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Yawning Angel 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | package x25519ell2 17 | 18 | import ( 19 | "bytes" 20 | "crypto/rand" 21 | "testing" 22 | 23 | "filippo.io/edwards25519/field" 24 | "golang.org/x/crypto/curve25519" 25 | ) 26 | 27 | func TestX25519Ell2(t *testing.T) { 28 | t.Run("Constants", testConstants) 29 | t.Run("KeyExchage", testKeyExchange) 30 | } 31 | 32 | func testConstants(t *testing.T) { 33 | // While the constants were calculated and serialized with a known 34 | // correct implementation of the field arithmetic, re-derive them 35 | // to be sure. 36 | 37 | t.Run("NegTwo", func(t *testing.T) { 38 | expected := new(field.Element).Add(feOne, feOne) 39 | expected.Negate(expected) 40 | 41 | if expected.Equal(feNegTwo) != 1 { 42 | t.Fatalf("invalid value for -2: %x", feNegTwo.Bytes()) 43 | } 44 | }) 45 | 46 | t.Run("LopX", func(t *testing.T) { 47 | // d = -121665/121666 48 | d := mustFeFromUint64(121666) 49 | d.Invert(d) 50 | d.Multiply(d, mustFeFromUint64(121665)) 51 | d.Negate(d) 52 | 53 | // lop_x = sqrt((sqrt(d + 1) + 1) / d) 54 | expected := new(field.Element).Add(d, feOne) 55 | expected.Invert(expected) 56 | expected.SqrtRatio(feOne, expected) 57 | expected.Add(expected, feOne) 58 | expected.SqrtRatio(expected, d) 59 | 60 | if expected.Equal(feLopX) != 1 { 61 | t.Fatalf("invalid value for low order point X: %x", feLopX.Bytes()) 62 | } 63 | }) 64 | 65 | t.Run("LopY", func(t *testing.T) { 66 | // lop_y = -lop_x * sqrtm1 67 | expected := new(field.Element).Negate(feLopX) 68 | expected.Multiply(expected, feSqrtM1) 69 | 70 | if expected.Equal(feLopY) != 1 { 71 | t.Fatalf("invalid value for low order point Y: %x", feLopY.Bytes()) 72 | } 73 | }) 74 | } 75 | 76 | func testKeyExchange(t *testing.T) { 77 | var randSk [32]byte 78 | _, _ = rand.Read(randSk[:]) 79 | 80 | var good, bad int 81 | for i := 0; i < 1000; i++ { 82 | var ( 83 | publicKey, privateKey, representative [32]byte 84 | publicKeyClean [32]byte 85 | tweak [1]byte 86 | ) 87 | _, _ = rand.Read(privateKey[:]) 88 | _, _ = rand.Read(tweak[:]) 89 | 90 | // This won't match the public key from the Elligator2-ed scalar 91 | // basepoint multiply, but we want to ensure that the public keys 92 | // we do happen to generate are interoperable (otherwise something 93 | // is badly broken). 94 | curve25519.ScalarBaseMult(&publicKeyClean, &privateKey) 95 | 96 | if !ScalarBaseMult(&publicKey, &representative, &privateKey, tweak[0]) { 97 | t.Logf("bad: %x", privateKey) 98 | bad++ 99 | continue 100 | } 101 | t.Logf("good: %x", privateKey) 102 | 103 | t.Logf("publicKey: %x, repr: %x", publicKey, representative) 104 | 105 | var shared, sharedRep, sharedClean, pkFromRep [32]byte 106 | RepresentativeToPublicKey(&pkFromRep, &representative) 107 | if !bytes.Equal(pkFromRep[:], publicKey[:]) { 108 | t.Fatalf("public key mismatch(repr): expected %x, actual: %x", publicKey, pkFromRep) 109 | } 110 | 111 | curve25519.ScalarMult(&sharedClean, &randSk, &publicKeyClean) //nolint: staticcheck 112 | curve25519.ScalarMult(&shared, &randSk, &publicKey) //nolint: staticcheck 113 | curve25519.ScalarMult(&sharedRep, &randSk, &pkFromRep) //nolint: staticcheck 114 | 115 | if !bytes.Equal(shared[:], sharedRep[:]) { 116 | t.Fatalf("shared secret mismatch: expected %x, actual: %x", shared, sharedRep) 117 | } 118 | if !bytes.Equal(shared[:], sharedClean[:]) { 119 | t.Fatalf("shared secret mismatch(clean): expected %x, actual: %x", shared, sharedClean) 120 | } 121 | 122 | good++ 123 | } 124 | 125 | t.Logf("good: %d, bad: %d", good, bad) 126 | } 127 | 128 | func BenchmarkKeyGeneration(b *testing.B) { 129 | var publicKey, representative, privateKey [32]byte 130 | 131 | // Find the private key that results in a point that's in the image of the map. 132 | for { 133 | _, _ = rand.Reader.Read(privateKey[:]) 134 | if ScalarBaseMult(&publicKey, &representative, &privateKey, 0) { 135 | break 136 | } 137 | } 138 | 139 | b.ResetTimer() 140 | for i := 0; i < b.N; i++ { 141 | ScalarBaseMult(&publicKey, &representative, &privateKey, 0) 142 | } 143 | } 144 | 145 | func BenchmarkMap(b *testing.B) { 146 | var publicKey, representative [32]byte 147 | _, _ = rand.Reader.Read(representative[:]) 148 | 149 | b.ResetTimer() 150 | for i := 0; i < b.N; i++ { 151 | RepresentativeToPublicKey(&publicKey, &representative) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /common/replayfilter/replay_filter.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package replayfilter implements a generic replay detection filter with a 29 | // caller specifiable time-to-live. It only detects if a given byte sequence 30 | // has been seen before based on the SipHash-2-4 digest of the sequence. 31 | // Collisions are treated as positive matches, though the probability of this 32 | // happening is negligible. 33 | package replayfilter // import "gitlab.com/yawning/obfs4.git/common/replayfilter" 34 | 35 | import ( 36 | "container/list" 37 | "encoding/binary" 38 | "sync" 39 | "time" 40 | 41 | "github.com/dchest/siphash" 42 | 43 | "gitlab.com/yawning/obfs4.git/common/csrand" 44 | ) 45 | 46 | // maxFilterSize is the maximum capacity of a replay filter. This value is 47 | // more as a safeguard to prevent runaway filter growth, and is sized to be 48 | // serveral orders of magnitude greater than the number of connections a busy 49 | // bridge sees in one day, so in practice should never be reached. 50 | const maxFilterSize = 100 * 1024 51 | 52 | type entry struct { 53 | digest uint64 54 | firstSeen time.Time 55 | element *list.Element 56 | } 57 | 58 | // ReplayFilter is a simple filter designed only to detect if a given byte 59 | // sequence has been seen before. 60 | type ReplayFilter struct { 61 | sync.Mutex 62 | 63 | filter map[uint64]*entry 64 | fifo *list.List 65 | 66 | key [2]uint64 67 | ttl time.Duration 68 | } 69 | 70 | // New creates a new ReplayFilter instance. 71 | func New(ttl time.Duration) (*ReplayFilter, error) { 72 | // Initialize the SipHash-2-4 instance with a random key. 73 | var key [16]byte 74 | if err := csrand.Bytes(key[:]); err != nil { 75 | return nil, err 76 | } 77 | 78 | filter := new(ReplayFilter) 79 | filter.filter = make(map[uint64]*entry) 80 | filter.fifo = list.New() 81 | filter.key[0] = binary.BigEndian.Uint64(key[0:8]) 82 | filter.key[1] = binary.BigEndian.Uint64(key[8:16]) 83 | filter.ttl = ttl 84 | 85 | return filter, nil 86 | } 87 | 88 | // TestAndSet queries the filter for a given byte sequence, inserts the 89 | // sequence, and returns if it was present before the insertion operation. 90 | func (f *ReplayFilter) TestAndSet(now time.Time, buf []byte) bool { 91 | digest := siphash.Hash(f.key[0], f.key[1], buf) 92 | 93 | f.Lock() 94 | defer f.Unlock() 95 | 96 | f.compactFilter(now) 97 | 98 | if e := f.filter[digest]; e != nil { 99 | // Hit. Just return. 100 | return true 101 | } 102 | 103 | // Miss. Add a new entry. 104 | e := new(entry) 105 | e.digest = digest 106 | e.firstSeen = now 107 | e.element = f.fifo.PushBack(e) 108 | f.filter[digest] = e 109 | 110 | return false 111 | } 112 | 113 | func (f *ReplayFilter) compactFilter(now time.Time) { 114 | e := f.fifo.Front() 115 | for e != nil { 116 | ent, _ := e.Value.(*entry) 117 | 118 | // If the filter is not full, only purge entries that exceed the TTL, 119 | // otherwise purge at least one entry, then revert to TTL based 120 | // compaction. 121 | if f.fifo.Len() < maxFilterSize && f.ttl > 0 { 122 | deltaT := now.Sub(ent.firstSeen) 123 | if deltaT < 0 { 124 | // Aeeeeeee, the system time jumped backwards, potentially by 125 | // a lot. This will eventually self-correct, but "eventually" 126 | // could be a long time. As much as this sucks, jettison the 127 | // entire filter. 128 | f.reset() 129 | return 130 | } else if deltaT < f.ttl { 131 | return 132 | } 133 | } 134 | 135 | // Remove the eldest entry. 136 | eNext := e.Next() 137 | delete(f.filter, ent.digest) 138 | f.fifo.Remove(ent.element) 139 | ent.element = nil 140 | e = eNext 141 | } 142 | } 143 | 144 | func (f *ReplayFilter) reset() { 145 | f.filter = make(map[uint64]*entry) 146 | f.fifo = list.New() 147 | } 148 | -------------------------------------------------------------------------------- /obfs4proxy/proxy_socks4.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | * 27 | * This is inspired by go.net/proxy/socks5.go: 28 | * 29 | * Copyright 2011 The Go Authors. All rights reserved. 30 | * Use of this source code is governed by a BSD-style 31 | * license that can be found in the LICENSE file. 32 | */ 33 | 34 | package main 35 | 36 | import ( 37 | "errors" 38 | "fmt" 39 | "io" 40 | "net" 41 | "net/url" 42 | "strconv" 43 | 44 | "golang.org/x/net/proxy" 45 | ) 46 | 47 | // socks4Proxy is a SOCKS4 proxy. 48 | type socks4Proxy struct { 49 | hostPort string 50 | username string 51 | forward proxy.Dialer 52 | } 53 | 54 | const ( 55 | socks4Version = 0x04 56 | socks4CommandConnect = 0x01 57 | socks4Null = 0x00 58 | socks4ReplyVersion = 0x00 59 | 60 | socks4Granted = 0x5a 61 | socks4Rejected = 0x5b 62 | socks4RejectedIdentdFailed = 0x5c 63 | socks4RejectedIdentdMismatch = 0x5d 64 | ) 65 | 66 | func newSOCKS4(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { 67 | s := new(socks4Proxy) 68 | s.hostPort = uri.Host 69 | s.forward = forward 70 | if uri.User != nil { 71 | s.username = uri.User.Username() 72 | } 73 | return s, nil 74 | } 75 | 76 | func (s *socks4Proxy) Dial(network, addr string) (net.Conn, error) { 77 | if network != "tcp" && network != "tcp4" { 78 | return nil, errors.New("invalid network type") 79 | } 80 | 81 | // Deal with the destination address/string. 82 | ipStr, portStr, err := net.SplitHostPort(addr) 83 | if err != nil { 84 | return nil, err 85 | } 86 | ip := net.ParseIP(ipStr) 87 | if ip == nil { 88 | return nil, errors.New("failed to parse destination IP") 89 | } 90 | ip4 := ip.To4() 91 | if ip4 == nil { 92 | return nil, errors.New("destination address is not IPv4") 93 | } 94 | port, err := strconv.ParseUint(portStr, 10, 16) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | // Connect to the proxy. 100 | c, err := s.forward.Dial("tcp", s.hostPort) 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | // Make/write the request: 106 | // +----+----+----+----+----+----+----+----+----+----+....+----+ 107 | // | VN | CD | DSTPORT | DSTIP | USERID |NULL| 108 | // +----+----+----+----+----+----+----+----+----+----+....+----+ 109 | 110 | req := make([]byte, 0, 9+len(s.username)) 111 | req = append(req, socks4Version) 112 | req = append(req, socks4CommandConnect) 113 | req = append(req, byte(port>>8), byte(port)) 114 | req = append(req, ip4...) 115 | if s.username != "" { 116 | req = append(req, s.username...) 117 | } 118 | req = append(req, socks4Null) 119 | _, err = c.Write(req) 120 | if err != nil { 121 | c.Close() 122 | return nil, err 123 | } 124 | 125 | // Read the response: 126 | // +----+----+----+----+----+----+----+----+ 127 | // | VN | CD | DSTPORT | DSTIP | 128 | // +----+----+----+----+----+----+----+----+ 129 | 130 | var resp [8]byte 131 | _, err = io.ReadFull(c, resp[:]) 132 | if err != nil { 133 | c.Close() 134 | return nil, err 135 | } 136 | if resp[0] != socks4ReplyVersion { 137 | c.Close() 138 | return nil, errors.New("proxy returned invalid SOCKS4 version") 139 | } 140 | if resp[1] != socks4Granted { 141 | c.Close() 142 | return nil, fmt.Errorf("proxy error: %s", socks4ErrorToString(resp[1])) 143 | } 144 | 145 | return c, nil 146 | } 147 | 148 | func socks4ErrorToString(code byte) string { 149 | switch code { 150 | case socks4Rejected: 151 | return "request rejected or failed" 152 | case socks4RejectedIdentdFailed: 153 | return "request rejected because SOCKS server cannot connect to identd on the client" 154 | case socks4RejectedIdentdMismatch: 155 | return "request rejected because the client program and identd report different user-ids" 156 | default: 157 | return fmt.Sprintf("unknown failure code %x", code) 158 | } 159 | } 160 | 161 | func init() { //nolint:gochecknoinits 162 | // Despite the scheme name, this really is SOCKS4. 163 | proxy.RegisterDialerType("socks4a", newSOCKS4) 164 | } 165 | -------------------------------------------------------------------------------- /transports/obfs4/framing/framing_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package framing 29 | 30 | import ( 31 | "bytes" 32 | "crypto/rand" 33 | "errors" 34 | "testing" 35 | ) 36 | 37 | func generateRandomKey() []byte { 38 | key := make([]byte, KeyLength) 39 | 40 | _, err := rand.Read(key) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | return key 46 | } 47 | 48 | func newEncoder(t *testing.T) *Encoder { 49 | // Generate a key to use. 50 | key := generateRandomKey() 51 | 52 | encoder := NewEncoder(key) 53 | if encoder == nil { 54 | t.Fatalf("NewEncoder returned nil") 55 | } 56 | 57 | return encoder 58 | } 59 | 60 | // TestNewEncoder tests the Encoder ctor. 61 | func TestNewEncoder(t *testing.T) { 62 | encoder := newEncoder(t) 63 | _ = encoder 64 | } 65 | 66 | // TestEncoder_Encode tests Encoder.Encode. 67 | func TestEncoder_Encode(t *testing.T) { 68 | encoder := newEncoder(t) 69 | 70 | buf := make([]byte, MaximumFramePayloadLength) 71 | _, _ = rand.Read(buf) // YOLO 72 | for i := 0; i <= MaximumFramePayloadLength; i++ { 73 | var frame [MaximumSegmentLength]byte 74 | n, err := encoder.Encode(frame[:], buf[0:i]) 75 | if err != nil { 76 | t.Fatalf("Encoder.encode([%d]byte), failed: %s", i, err) 77 | } 78 | if n != i+FrameOverhead { 79 | t.Fatalf("Unexpected encoded framesize: %d, expecting %d", n, i+ 80 | FrameOverhead) 81 | } 82 | } 83 | } 84 | 85 | // TestEncoder_Encode_Oversize tests oversized frame rejection. 86 | func TestEncoder_Encode_Oversize(t *testing.T) { 87 | encoder := newEncoder(t) 88 | 89 | var frame [MaximumSegmentLength]byte 90 | var buf [MaximumFramePayloadLength + 1]byte 91 | _, _ = rand.Read(buf[:]) // YOLO 92 | _, err := encoder.Encode(frame[:], buf[:]) 93 | 94 | var payloadErr InvalidPayloadLengthError 95 | if !errors.As(err, &payloadErr) { 96 | t.Error("Encoder.encode() returned unexpected error:", err) 97 | } 98 | } 99 | 100 | // TestNewDecoder tests the Decoder ctor. 101 | func TestNewDecoder(t *testing.T) { 102 | key := generateRandomKey() 103 | decoder := NewDecoder(key) 104 | if decoder == nil { 105 | t.Fatalf("NewDecoder returned nil") 106 | } 107 | } 108 | 109 | // TestDecoder_Decode tests Decoder.Decode. 110 | func TestDecoder_Decode(t *testing.T) { 111 | key := generateRandomKey() 112 | 113 | encoder := NewEncoder(key) 114 | decoder := NewDecoder(key) 115 | 116 | var buf [MaximumFramePayloadLength]byte 117 | _, _ = rand.Read(buf[:]) // YOLO 118 | for i := 0; i <= MaximumFramePayloadLength; i++ { 119 | var frame [MaximumSegmentLength]byte 120 | encLen, err := encoder.Encode(frame[:], buf[0:i]) 121 | if err != nil { 122 | t.Fatalf("Encoder.encode([%d]byte), failed: %s", i, err) 123 | } 124 | if encLen != i+FrameOverhead { 125 | t.Fatalf("Unexpected encoded framesize: %d, expecting %d", encLen, 126 | i+FrameOverhead) 127 | } 128 | 129 | var decoded [MaximumFramePayloadLength]byte 130 | 131 | decLen, err := decoder.Decode(decoded[:], bytes.NewBuffer(frame[:encLen])) 132 | if err != nil { 133 | t.Fatalf("Decoder.decode([%d]byte), failed: %s", i, err) 134 | } 135 | if decLen != i { 136 | t.Fatalf("Unexpected decoded framesize: %d, expecting %d", 137 | decLen, i) 138 | } 139 | 140 | if 0 != bytes.Compare(decoded[:decLen], buf[:i]) { 141 | t.Fatalf("Frame %d does not match encoder input", i) 142 | } 143 | } 144 | } 145 | 146 | // BencharkEncoder_Encode benchmarks Encoder.Encode processing 1 MiB 147 | // of payload. 148 | func BenchmarkEncoder_Encode(b *testing.B) { 149 | var chopBuf [MaximumFramePayloadLength]byte 150 | var frame [MaximumSegmentLength]byte 151 | payload := make([]byte, 1024*1024) 152 | encoder := NewEncoder(generateRandomKey()) 153 | b.ResetTimer() 154 | 155 | for i := 0; i < b.N; i++ { 156 | var xfered int 157 | buffer := bytes.NewBuffer(payload) 158 | for 0 < buffer.Len() { 159 | n, err := buffer.Read(chopBuf[:]) 160 | if err != nil { 161 | b.Fatal("buffer.Read() failed:", err) 162 | } 163 | 164 | n, _ = encoder.Encode(frame[:], chopBuf[:n]) 165 | xfered += n - FrameOverhead 166 | } 167 | if xfered != len(payload) { 168 | b.Fatalf("Xfered length mismatch: %d != %d", xfered, len(payload)) 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /transports/scramblesuit/handshake_uniformdh.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package scramblesuit 29 | 30 | import ( 31 | "bytes" 32 | "crypto/hmac" 33 | "crypto/sha256" 34 | "errors" 35 | "hash" 36 | "strconv" 37 | "time" 38 | 39 | "gitlab.com/yawning/obfs4.git/common/csrand" 40 | "gitlab.com/yawning/obfs4.git/common/uniformdh" 41 | ) 42 | 43 | const ( 44 | minHandshakeLength = uniformdh.Size + macLength*2 45 | maxHandshakeLength = 1532 46 | dhMinPadLength = 0 47 | dhMaxPadLength = 1308 48 | macLength = 128 / 8 // HMAC-SHA256-128() 49 | 50 | kdfSecretLength = keyLength * 2 51 | ) 52 | 53 | var ( 54 | errMarkNotFoundYet = errors.New("mark not found yet") 55 | 56 | // ErrInvalidHandshake is the error returned when the handshake fails. 57 | ErrInvalidHandshake = errors.New("invalid handshake") 58 | ) 59 | 60 | type ssDHClientHandshake struct { 61 | mac hash.Hash 62 | keypair *uniformdh.PrivateKey 63 | epochHour []byte 64 | padLen int 65 | 66 | serverPublicKey *uniformdh.PublicKey 67 | serverMark []byte 68 | } 69 | 70 | func (hs *ssDHClientHandshake) generateHandshake() ([]byte, error) { 71 | var buf bytes.Buffer 72 | hs.mac.Reset() 73 | 74 | // The client handshake is X | P_C | M_C | MAC(X | P_C | M_C | E) 75 | x, err := hs.keypair.PublicKey.Bytes() 76 | if err != nil { 77 | return nil, err 78 | } 79 | _, _ = hs.mac.Write(x) 80 | mC := hs.mac.Sum(nil)[:macLength] 81 | pC, err := makePad(hs.padLen) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | // Write X, P_C, M_C. 87 | buf.Write(x) 88 | buf.Write(pC) 89 | buf.Write(mC) 90 | 91 | // Calculate and write the MAC. 92 | hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10)) 93 | _, _ = hs.mac.Write(pC) 94 | _, _ = hs.mac.Write(mC) 95 | _, _ = hs.mac.Write(hs.epochHour) 96 | buf.Write(hs.mac.Sum(nil)[:macLength]) 97 | 98 | return buf.Bytes(), nil 99 | } 100 | 101 | func (hs *ssDHClientHandshake) parseServerHandshake(resp []byte) (int, []byte, error) { 102 | if len(resp) < minHandshakeLength { 103 | return 0, nil, errMarkNotFoundYet 104 | } 105 | 106 | // The server response is Y | P_S | M_S | MAC(Y | P_S | M_S | E). 107 | if hs.serverPublicKey == nil { 108 | y := resp[:uniformdh.Size] 109 | 110 | // Pull out the public key, and derive the server mark. 111 | hs.serverPublicKey = &uniformdh.PublicKey{} 112 | if err := hs.serverPublicKey.SetBytes(y); err != nil { 113 | return 0, nil, err 114 | } 115 | hs.mac.Reset() 116 | _, _ = hs.mac.Write(y) 117 | hs.serverMark = hs.mac.Sum(nil)[:macLength] 118 | } 119 | 120 | // Find the mark+MAC, if it exits. 121 | endPos := len(resp) 122 | if endPos > maxHandshakeLength-macLength { 123 | endPos = maxHandshakeLength - macLength 124 | } 125 | pos := bytes.Index(resp[uniformdh.Size:endPos], hs.serverMark) 126 | if pos == -1 { 127 | if len(resp) >= maxHandshakeLength { 128 | // Couldn't find the mark in a maximum length response. 129 | return 0, nil, ErrInvalidHandshake 130 | } 131 | return 0, nil, errMarkNotFoundYet 132 | } else if len(resp) < pos+2*macLength { 133 | // Didn't receive the full M_S. 134 | return 0, nil, errMarkNotFoundYet 135 | } 136 | pos += uniformdh.Size 137 | 138 | // Validate the MAC. 139 | _, _ = hs.mac.Write(resp[uniformdh.Size : pos+macLength]) 140 | _, _ = hs.mac.Write(hs.epochHour) 141 | macCmp := hs.mac.Sum(nil)[:macLength] 142 | macRx := resp[pos+macLength : pos+2*macLength] 143 | if !hmac.Equal(macCmp, macRx) { 144 | return 0, nil, ErrInvalidHandshake 145 | } 146 | 147 | // Derive the shared secret. 148 | ss, err := uniformdh.Handshake(hs.keypair, hs.serverPublicKey) 149 | if err != nil { 150 | return 0, nil, err 151 | } 152 | seed := sha256.Sum256(ss) 153 | return pos + 2*macLength, seed[:], nil 154 | } 155 | 156 | func newDHClientHandshake(kB *ssSharedSecret, sessionKey *uniformdh.PrivateKey) *ssDHClientHandshake { 157 | hs := &ssDHClientHandshake{keypair: sessionKey} 158 | hs.mac = hmac.New(sha256.New, kB[:]) 159 | hs.padLen = csrand.IntRange(dhMinPadLength, dhMaxPadLength) 160 | return hs 161 | } 162 | 163 | func getEpochHour() int64 { 164 | return time.Now().Unix() / 3600 165 | } 166 | 167 | func makePad(padLen int) ([]byte, error) { 168 | pad := make([]byte, padLen) 169 | if err := csrand.Bytes(pad); err != nil { 170 | return nil, err 171 | } 172 | return pad, nil 173 | } 174 | -------------------------------------------------------------------------------- /transports/obfs4/packet.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package obfs4 29 | 30 | import ( 31 | "crypto/sha256" 32 | "encoding/binary" 33 | "errors" 34 | "fmt" 35 | "io" 36 | 37 | "gitlab.com/yawning/obfs4.git/common/drbg" 38 | "gitlab.com/yawning/obfs4.git/transports/obfs4/framing" 39 | ) 40 | 41 | const ( 42 | packetOverhead = 2 + 1 43 | maxPacketPayloadLength = framing.MaximumFramePayloadLength - packetOverhead 44 | maxPacketPaddingLength = maxPacketPayloadLength 45 | seedPacketPayloadLength = seedLength 46 | 47 | consumeReadSize = framing.MaximumSegmentLength * 16 48 | ) 49 | 50 | const ( 51 | packetTypePayload = iota 52 | packetTypePrngSeed 53 | ) 54 | 55 | // InvalidPacketLengthError is the error returned when decodePacket detects a 56 | // invalid packet length. 57 | type InvalidPacketLengthError int 58 | 59 | func (e InvalidPacketLengthError) Error() string { 60 | return fmt.Sprintf("packet: Invalid packet length: %d", int(e)) 61 | } 62 | 63 | // InvalidPayloadLengthError is the error returned when decodePacket rejects the 64 | // payload length. 65 | type InvalidPayloadLengthError int 66 | 67 | func (e InvalidPayloadLengthError) Error() string { 68 | return fmt.Sprintf("packet: Invalid payload length: %d", int(e)) 69 | } 70 | 71 | var zeroPadBytes [maxPacketPaddingLength]byte 72 | 73 | func (conn *obfs4Conn) makePacket(w io.Writer, pktType uint8, data []byte, padLen uint16) error { 74 | var pkt [framing.MaximumFramePayloadLength]byte 75 | 76 | if len(data)+int(padLen) > maxPacketPayloadLength { 77 | panic(fmt.Sprintf("BUG: makePacket() len(data) + padLen > maxPacketPayloadLength: %d + %d > %d", 78 | len(data), padLen, maxPacketPayloadLength)) 79 | } 80 | 81 | // Packets are: 82 | // uint8_t type packetTypePayload (0x00) 83 | // uint16_t length Length of the payload (Big Endian). 84 | // uint8_t[] payload Data payload. 85 | // uint8_t[] padding Padding. 86 | pkt[0] = pktType 87 | binary.BigEndian.PutUint16(pkt[1:], uint16(len(data))) 88 | if len(data) > 0 { 89 | copy(pkt[3:], data) 90 | } 91 | copy(pkt[3+len(data):], zeroPadBytes[:padLen]) 92 | 93 | pktLen := packetOverhead + len(data) + int(padLen) 94 | 95 | // Encode the packet in an AEAD frame. 96 | var frame [framing.MaximumSegmentLength]byte 97 | frameLen, err := conn.encoder.Encode(frame[:], pkt[:pktLen]) 98 | if err != nil { 99 | // All encoder errors are fatal. 100 | return err 101 | } 102 | wrLen, err := w.Write(frame[:frameLen]) 103 | if err != nil { 104 | return err 105 | } else if wrLen < frameLen { 106 | return io.ErrShortWrite 107 | } 108 | 109 | return nil 110 | } 111 | 112 | func (conn *obfs4Conn) readPackets() error { 113 | // Attempt to read off the network. 114 | rdLen, rdErr := conn.Conn.Read(conn.readBuffer) 115 | conn.receiveBuffer.Write(conn.readBuffer[:rdLen]) 116 | 117 | var ( 118 | decoded [framing.MaximumFramePayloadLength]byte 119 | err error 120 | ) 121 | bufferLoop: 122 | for conn.receiveBuffer.Len() > 0 { 123 | // Decrypt an AEAD frame. 124 | var decLen int 125 | decLen, err = conn.decoder.Decode(decoded[:], conn.receiveBuffer) 126 | switch { 127 | case errors.Is(err, framing.ErrAgain): 128 | break bufferLoop 129 | case err != nil: 130 | break bufferLoop 131 | case decLen < packetOverhead: 132 | err = InvalidPacketLengthError(decLen) 133 | break bufferLoop 134 | } 135 | 136 | // Decode the packet. 137 | pkt := decoded[0:decLen] 138 | pktType := pkt[0] 139 | payloadLen := binary.BigEndian.Uint16(pkt[1:]) 140 | if int(payloadLen) > len(pkt)-packetOverhead { 141 | err = InvalidPayloadLengthError(int(payloadLen)) 142 | break 143 | } 144 | payload := pkt[3 : 3+payloadLen] 145 | 146 | switch pktType { 147 | case packetTypePayload: 148 | if payloadLen > 0 { 149 | conn.receiveDecodedBuffer.Write(payload) 150 | } 151 | case packetTypePrngSeed: 152 | // Only regenerate the distribution if we are the client. 153 | if len(payload) == seedPacketPayloadLength && !conn.isServer { 154 | var seed *drbg.Seed 155 | seed, err = drbg.SeedFromBytes(payload) 156 | if err != nil { 157 | break 158 | } 159 | conn.lenDist.Reset(seed) 160 | if conn.iatDist != nil { 161 | iatSeedSrc := sha256.Sum256(seed.Bytes()[:]) 162 | iatSeed, err := drbg.SeedFromBytes(iatSeedSrc[:]) 163 | if err != nil { 164 | break 165 | } 166 | conn.iatDist.Reset(iatSeed) 167 | } 168 | } 169 | default: 170 | // Ignore unknown packet types. 171 | } 172 | } 173 | 174 | // Read errors (all fatal) take priority over various frame processing 175 | // errors. 176 | if rdErr != nil { 177 | return rdErr 178 | } 179 | 180 | return err 181 | } 182 | -------------------------------------------------------------------------------- /common/uniformdh/uniformdh.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package uniformdh implements the Tor Project's UniformDH key exchange 29 | // mechanism as defined in the obfs3 protocol specification. This 30 | // implementation is suitable for obfuscation but MUST NOT BE USED when strong 31 | // security is required as it is not constant time. 32 | package uniformdh // import "gitlab.com/yawning/obfs4.git/common/uniformdh" 33 | 34 | import ( 35 | "bytes" 36 | "fmt" 37 | "io" 38 | "math/big" 39 | ) 40 | 41 | const ( 42 | // Size is the size of a UniformDH key or shared secret in bytes. 43 | Size = 1536 / 8 44 | 45 | // modpStr is the RFC3526 1536-bit MODP Group (Group 5). 46 | modpStr = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + 47 | "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + 48 | "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + 49 | "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + 50 | "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + 51 | "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + 52 | "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + 53 | "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF" 54 | 55 | g = 2 56 | ) 57 | 58 | var ( 59 | modpGroup = func() *big.Int { 60 | n, ok := new(big.Int).SetString(modpStr, 16) 61 | if !ok { 62 | panic("Failed to load the RFC3526 MODP Group") 63 | } 64 | return n 65 | }() 66 | gen = big.NewInt(g) 67 | ) 68 | 69 | // A PrivateKey represents a UniformDH private key. 70 | type PrivateKey struct { 71 | PublicKey 72 | privateKey *big.Int 73 | } 74 | 75 | // A PublicKey represents a UniformDH public key. 76 | type PublicKey struct { 77 | bytes []byte 78 | publicKey *big.Int 79 | } 80 | 81 | // Bytes returns the byte representation of a PublicKey. 82 | func (pub *PublicKey) Bytes() ([]byte, error) { 83 | if len(pub.bytes) != Size || pub.bytes == nil { 84 | return nil, fmt.Errorf("public key is not initialized") 85 | } 86 | return bytes.Clone(pub.bytes), nil 87 | } 88 | 89 | // SetBytes sets the PublicKey from a byte slice. 90 | func (pub *PublicKey) SetBytes(pubBytes []byte) error { 91 | if len(pubBytes) != Size { 92 | return fmt.Errorf("public key length %d is not %d", len(pubBytes), Size) 93 | } 94 | pub.bytes = bytes.Clone(pubBytes) 95 | pub.publicKey = new(big.Int).SetBytes(pub.bytes) 96 | 97 | return nil 98 | } 99 | 100 | // GenerateKey generates a UniformDH keypair using the random source random. 101 | func GenerateKey(random io.Reader) (*PrivateKey, error) { 102 | var privBytes [Size]byte 103 | if _, err := io.ReadFull(random, privBytes[:]); err != nil { 104 | return nil, err 105 | } 106 | return generateKey(privBytes[:]) 107 | } 108 | 109 | func generateKey(privBytes []byte) (*PrivateKey, error) { 110 | // This function does all of the actual heavy lifting of creating a public 111 | // key from a raw 192 byte private key. It is split so that the KAT tests 112 | // can be written easily, and not exposed since non-ephemeral keys are a 113 | // terrible idea. 114 | 115 | if len(privBytes) != Size { 116 | return nil, fmt.Errorf("invalid private key size %d", len(privBytes)) 117 | } 118 | 119 | // To pick a private UniformDH key, we pick a random 1536-bit number, 120 | // and make it even by setting its low bit to 0 121 | privBn := new(big.Int).SetBytes(privBytes) 122 | wasEven := privBn.Bit(0) == 0 123 | privBn = privBn.SetBit(privBn, 0, 0) 124 | 125 | // Let x be that private key, and X = g^x (mod p). 126 | pubBn := new(big.Int).Exp(gen, privBn, modpGroup) 127 | pubAlt := new(big.Int).Sub(modpGroup, pubBn) 128 | 129 | // When someone sends her public key to the other party, she randomly 130 | // decides whether to send X or p-X. Use the lowest most bit of the 131 | // private key here as the random coin flip since it is masked out and not 132 | // used. 133 | // 134 | // Note: The spec doesn't explicitly specify it, but here we prepend zeros 135 | // to the key so that it is always exactly Size bytes. 136 | pubBytes := make([]byte, Size) 137 | if wasEven { 138 | pubBn.FillBytes(pubBytes) 139 | } else { 140 | pubAlt.FillBytes(pubBytes) 141 | } 142 | 143 | priv := new(PrivateKey) 144 | priv.PublicKey.bytes = pubBytes 145 | priv.PublicKey.publicKey = pubBn 146 | priv.privateKey = privBn 147 | 148 | return priv, nil 149 | } 150 | 151 | // Handshake generates a shared secret given a PrivateKey and PublicKey. 152 | func Handshake(privateKey *PrivateKey, publicKey *PublicKey) ([]byte, error) { 153 | // When a party wants to calculate the shared secret, she raises the 154 | // foreign public key to her private key. 155 | secretBn := new(big.Int).Exp(publicKey.publicKey, privateKey.privateKey, modpGroup) 156 | sharedSecret := make([]byte, Size) 157 | secretBn.FillBytes(sharedSecret) 158 | 159 | return sharedSecret, nil 160 | } 161 | -------------------------------------------------------------------------------- /transports/scramblesuit/handshake_ticket.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package scramblesuit 29 | 30 | import ( 31 | "bytes" 32 | "encoding/base32" 33 | "encoding/json" 34 | "errors" 35 | "fmt" 36 | "hash" 37 | "net" 38 | "os" 39 | "path" 40 | "strconv" 41 | "sync" 42 | "time" 43 | 44 | "gitlab.com/yawning/obfs4.git/common/csrand" 45 | ) 46 | 47 | const ( 48 | ticketFile = "scramblesuit_tickets.json" 49 | 50 | ticketKeyLength = 32 51 | ticketLength = 112 52 | ticketLifetime = 60 * 60 * 24 * 7 53 | 54 | ticketMinPadLength = 0 55 | ticketMaxPadLength = 1388 56 | ) 57 | 58 | var errInvalidTicket = errors.New("scramblesuit: invalid serialized ticket") 59 | 60 | type ssTicketStore struct { 61 | sync.Mutex 62 | 63 | filePath string 64 | store map[string]*ssTicket 65 | } 66 | 67 | type ssTicket struct { 68 | key [ticketKeyLength]byte 69 | ticket [ticketLength]byte 70 | issuedAt int64 71 | } 72 | 73 | type ssTicketJSON struct { 74 | KeyTicket string `json:"key-ticket"` 75 | IssuedAt int64 `json:"issuedAt"` 76 | } 77 | 78 | func (t *ssTicket) isValid() bool { 79 | return t.issuedAt+ticketLifetime > time.Now().Unix() 80 | } 81 | 82 | func newTicket(raw []byte) (*ssTicket, error) { 83 | if len(raw) != ticketKeyLength+ticketLength { 84 | return nil, errInvalidTicket 85 | } 86 | t := &ssTicket{issuedAt: time.Now().Unix()} 87 | copy(t.key[:], raw[0:]) 88 | copy(t.ticket[:], raw[ticketKeyLength:]) 89 | return t, nil 90 | } 91 | 92 | func (s *ssTicketStore) storeTicket(addr net.Addr, rawT []byte) { 93 | t, err := newTicket(rawT) 94 | if err != nil { 95 | // Silently ignore ticket store failures. 96 | return 97 | } 98 | 99 | s.Lock() 100 | defer s.Unlock() 101 | 102 | // Add the ticket to the map, and checkpoint to disk. Serialization errors 103 | // are ignored because the handshake code will just use UniformDH if a 104 | // ticket is not available. 105 | s.store[addr.String()] = t 106 | _ = s.serialize() 107 | } 108 | 109 | func (s *ssTicketStore) getTicket(addr net.Addr) (*ssTicket, error) { 110 | aStr := addr.String() 111 | 112 | s.Lock() 113 | defer s.Unlock() 114 | 115 | t, ok := s.store[aStr] 116 | if ok && t != nil { 117 | // Tickets are one use only, so remove tickets from the map, and 118 | // checkpoint the map to disk. 119 | delete(s.store, aStr) 120 | err := s.serialize() 121 | if !t.isValid() { 122 | // Expired ticket, ignore it. 123 | return nil, err 124 | } 125 | return t, err 126 | } 127 | 128 | // No ticket was found, that's fine. 129 | return nil, nil //nolint:nilnil 130 | } 131 | 132 | func (s *ssTicketStore) serialize() error { 133 | encMap := make(map[string]*ssTicketJSON) 134 | for k, v := range s.store { 135 | kt := make([]byte, 0, ticketKeyLength+ticketLength) 136 | kt = append(kt, v.key[:]...) 137 | kt = append(kt, v.ticket[:]...) 138 | ktStr := base32.StdEncoding.EncodeToString(kt) 139 | jsonObj := &ssTicketJSON{KeyTicket: ktStr, IssuedAt: v.issuedAt} 140 | encMap[k] = jsonObj 141 | } 142 | jsonStr, err := json.Marshal(encMap) 143 | if err != nil { 144 | return err 145 | } 146 | return os.WriteFile(s.filePath, jsonStr, 0o600) 147 | } 148 | 149 | func loadTicketStore(stateDir string) (*ssTicketStore, error) { 150 | fPath := path.Join(stateDir, ticketFile) 151 | s := &ssTicketStore{filePath: fPath} 152 | s.store = make(map[string]*ssTicket) 153 | 154 | f, err := os.ReadFile(fPath) 155 | if err != nil { 156 | // No ticket store is fine. 157 | if os.IsNotExist(err) { 158 | return s, nil 159 | } 160 | 161 | // But a file read error is not. 162 | return nil, err 163 | } 164 | 165 | encMap := make(map[string]*ssTicketJSON) 166 | if err = json.Unmarshal(f, &encMap); err != nil { 167 | return nil, fmt.Errorf("failed to load ticket store '%s': %w", fPath, err) 168 | } 169 | for k, v := range encMap { 170 | raw, err := base32.StdEncoding.DecodeString(v.KeyTicket) 171 | if err != nil || len(raw) != ticketKeyLength+ticketLength { 172 | // Just silently skip corrupted tickets. 173 | continue 174 | } 175 | t := &ssTicket{issuedAt: v.IssuedAt} 176 | if !t.isValid() { 177 | // Just ignore expired tickets. 178 | continue 179 | } 180 | copy(t.key[:], raw[0:]) 181 | copy(t.ticket[:], raw[ticketKeyLength:]) 182 | s.store[k] = t 183 | } 184 | return s, nil 185 | } 186 | 187 | type ssTicketClientHandshake struct { 188 | mac hash.Hash 189 | ticket *ssTicket 190 | padLen int 191 | } 192 | 193 | func (hs *ssTicketClientHandshake) generateHandshake() ([]byte, error) { 194 | var buf bytes.Buffer 195 | hs.mac.Reset() 196 | 197 | // The client handshake is T | P | M | MAC(T | P | M | E) 198 | _, _ = hs.mac.Write(hs.ticket.ticket[:]) 199 | m := hs.mac.Sum(nil)[:macLength] 200 | p, err := makePad(hs.padLen) 201 | if err != nil { 202 | return nil, err 203 | } 204 | 205 | // Write T, P, M. 206 | buf.Write(hs.ticket.ticket[:]) 207 | buf.Write(p) 208 | buf.Write(m) 209 | 210 | // Calculate and write the MAC. 211 | e := []byte(strconv.FormatInt(getEpochHour(), 10)) 212 | _, _ = hs.mac.Write(p) 213 | _, _ = hs.mac.Write(m) 214 | _, _ = hs.mac.Write(e) 215 | buf.Write(hs.mac.Sum(nil)[:macLength]) 216 | 217 | hs.mac.Reset() 218 | return buf.Bytes(), nil 219 | } 220 | 221 | func newTicketClientHandshake(mac hash.Hash, ticket *ssTicket) *ssTicketClientHandshake { 222 | hs := &ssTicketClientHandshake{mac: mac, ticket: ticket} 223 | hs.padLen = csrand.IntRange(ticketMinPadLength, ticketMaxPadLength) 224 | return hs 225 | } 226 | -------------------------------------------------------------------------------- /internal/x25519ell2/x25519ell2.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Yawning Angel 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | // Package x25519ell2 implements obfuscated X25519 ECDH, via the Elligator2 17 | // mapping. 18 | package x25519ell2 // import "gitlab.com/yawning/obfs4.git/internal/x25519ell2" 19 | 20 | import ( 21 | "encoding/binary" 22 | 23 | "filippo.io/edwards25519" 24 | "filippo.io/edwards25519/field" 25 | "gitlab.com/yawning/edwards25519-extra/elligator2" 26 | ) 27 | 28 | // The corrected version of this that solves the implementation errors 29 | // present in the historical implementation by agl is derived from 30 | // Monocypher (CC-0 or BSD-2) by Loup Vaillant. Without their efforts 31 | // and prodding, this would likely have stayed broken forever. 32 | 33 | var ( 34 | feOne = new(field.Element).One() 35 | 36 | feNegTwo = mustFeFromBytes([]byte{ 37 | 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 38 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 39 | }) 40 | 41 | feA = mustFeFromUint64(486662) 42 | 43 | feSqrtM1 = mustFeFromBytes([]byte{ 44 | 0xb0, 0xa0, 0x0e, 0x4a, 0x27, 0x1b, 0xee, 0xc4, 0x78, 0xe4, 0x2f, 0xad, 0x06, 0x18, 0x43, 0x2f, 45 | 0xa7, 0xd7, 0xfb, 0x3d, 0x99, 0x00, 0x4d, 0x2b, 0x0b, 0xdf, 0xc1, 0x4f, 0x80, 0x24, 0x83, 0x2b, 46 | }) 47 | 48 | // Low order point Edwards x-coordinate `sqrt((sqrt(d + 1) + 1) / d)`. 49 | feLopX = mustFeFromBytes([]byte{ 50 | 0x4a, 0xd1, 0x45, 0xc5, 0x46, 0x46, 0xa1, 0xde, 0x38, 0xe2, 0xe5, 0x13, 0x70, 0x3c, 0x19, 0x5c, 51 | 0xbb, 0x4a, 0xde, 0x38, 0x32, 0x99, 0x33, 0xe9, 0x28, 0x4a, 0x39, 0x06, 0xa0, 0xb9, 0xd5, 0x1f, 52 | }) 53 | 54 | // Low order point Edwards y-coordinate `-lop_x * sqrtm1`. 55 | feLopY = mustFeFromBytes([]byte{ 56 | 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, 57 | 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05, 58 | }) 59 | ) 60 | 61 | func mustFeFromBytes(b []byte) *field.Element { 62 | fe, err := new(field.Element).SetBytes(b) 63 | if err != nil { 64 | panic("internal/x25519ell2: failed to deserialize constant: " + err.Error()) 65 | } 66 | return fe 67 | } 68 | 69 | func mustFeFromUint64(x uint64) *field.Element { 70 | var b [32]byte 71 | binary.LittleEndian.PutUint64(b[:], x) 72 | return mustFeFromBytes(b[:]) 73 | } 74 | 75 | func selectLowOrderPoint(out, x, k *field.Element, cofactor uint8) { 76 | out.Zero() 77 | out.Select(k, out, int((cofactor>>1)&1)) // bit 1 78 | out.Select(x, out, int((cofactor>>0)&1)) // bit 0 79 | var tmp field.Element 80 | tmp.Negate(out) 81 | out.Select(&tmp, out, int((cofactor>>2)&1)) // bit 2 82 | } 83 | 84 | func scalarBaseMultDirty(privateKey *[32]byte) *field.Element { 85 | // Compute clean scalar multiplication 86 | scalar, err := new(edwards25519.Scalar).SetBytesWithClamping(privateKey[:]) 87 | if err != nil { 88 | panic("internal/x25519ell2: failed to deserialize scalar: " + err.Error()) 89 | } 90 | pk := new(edwards25519.Point).ScalarBaseMult(scalar) 91 | 92 | // Compute low order point 93 | var lopX, lopY, lopT field.Element 94 | selectLowOrderPoint(&lopX, feLopX, feSqrtM1, privateKey[0]) 95 | selectLowOrderPoint(&lopY, feLopY, feOne, privateKey[0]+2) 96 | // Z = one 97 | lopT.Multiply(&lopX, &lopY) 98 | lop, err := new(edwards25519.Point).SetExtendedCoordinates(&lopX, &lopY, feOne, &lopT) 99 | if err != nil { 100 | panic("interal/x25519ell2: failed to create edwards point from x, y: " + err.Error()) 101 | } 102 | 103 | // Add low order point to the public key 104 | pk.Add(pk, lop) 105 | 106 | // Convert to Montgomery u coordinate (we ignore the sign) 107 | _, yExt, zExt, _ := pk.ExtendedCoordinates() 108 | var t1, t2 field.Element 109 | t1.Add(zExt, yExt) 110 | t2.Subtract(zExt, yExt) 111 | t2.Invert(&t2) 112 | t1.Multiply(&t1, &t2) 113 | 114 | return &t1 115 | } 116 | 117 | func uToRepresentative(representative *[32]byte, u *field.Element, tweak byte) bool { 118 | t1 := new(field.Element).Set(u) 119 | 120 | t2 := new(field.Element).Add(t1, feA) 121 | t3 := new(field.Element).Multiply(t1, t2) 122 | t3.Multiply(t3, feNegTwo) 123 | if _, isSquare := t3.SqrtRatio(feOne, t3); isSquare == 1 { 124 | t1.Select(t2, t1, int(tweak&1)) 125 | t3.Multiply(t1, t3) 126 | t1.Mult32(t3, 2) 127 | t2.Negate(t3) 128 | tmp := t1.Bytes() 129 | t3.Select(t2, t3, int(tmp[0]&1)) 130 | copy(representative[:], t3.Bytes()) 131 | 132 | // Pad with two random bits 133 | representative[31] |= tweak & 0xc0 134 | 135 | return true 136 | } 137 | 138 | return false 139 | } 140 | 141 | // ScalarBaseMult computes a curve25519 public key from a private 142 | // key and also a uniform representative for that public key. 143 | // Note that this function will fail and return false for about 144 | // half of private keys. 145 | // 146 | // The `privateKey` input MUST be the full 32-bytes of entropy 147 | // (X25519-style "clamping" will result in non-uniformly distributed 148 | // representatives). 149 | // 150 | // WARNING: The underlying scalar multiply explicitly does not clear 151 | // the cofactor, and thus the public keys will be different from 152 | // those produced by normal implementations. 153 | func ScalarBaseMult(publicKey, representative, privateKey *[32]byte, tweak byte) bool { 154 | u := scalarBaseMultDirty(privateKey) 155 | if !uToRepresentative(representative, u, tweak) { 156 | // No representative. 157 | return false 158 | } 159 | copy(publicKey[:], u.Bytes()) 160 | return true 161 | } 162 | 163 | // RepresentativeToPublicKey converts a uniform representative value for 164 | // a curve25519 public key, as produced by ScalarBaseMult, to a curve25519 165 | // public key. 166 | func RepresentativeToPublicKey(publicKey, representative *[32]byte) { 167 | // Representatives are encoded in 254 bits. 168 | var clamped [32]byte 169 | copy(clamped[:], representative[:]) 170 | clamped[31] &= 63 171 | 172 | var fe field.Element 173 | if _, err := fe.SetBytes(clamped[:]); err != nil { 174 | // Panic is fine, the only way this fails is if the representative 175 | // is not 32-bytes. 176 | panic("internal/x25519ell2: failed to deserialize representative: " + err.Error()) 177 | } 178 | u, _ := elligator2.MontgomeryFlavor(&fe) 179 | copy(publicKey[:], u.Bytes()) 180 | } 181 | -------------------------------------------------------------------------------- /common/log/log.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package log implements a simple set of leveled logging wrappers around the 29 | // standard log package. 30 | package log // import "gitlab.com/yawning/obfs4.git/common/log" 31 | 32 | import ( 33 | "errors" 34 | "fmt" 35 | "io" 36 | "log" 37 | "net" 38 | "os" 39 | "strings" 40 | ) 41 | 42 | const ( 43 | elidedAddr = "[scrubbed]" 44 | 45 | // LevelError is the ERROR log level (NOTICE/ERROR). 46 | LevelError = iota 47 | 48 | // LevelWarn is the WARN log level, (NOTICE/ERROR/WARN). 49 | LevelWarn 50 | 51 | // LevelInfo is the INFO log level, (NOTICE/ERROR/WARN/INFO). 52 | LevelInfo 53 | 54 | // LevelDebug is the DEBUG log level, (NOTICE/ERROR/WARN/INFO/DEBUG). 55 | LevelDebug 56 | ) 57 | 58 | var ( 59 | logLevel = LevelInfo 60 | enableLogging bool 61 | unsafeLogging bool 62 | ) 63 | 64 | // Init initializes logging with the given path, and log safety options. 65 | func Init(enable bool, logFilePath string, unsafe bool) error { 66 | if enable { 67 | f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) 68 | if err != nil { 69 | return err 70 | } 71 | log.SetOutput(f) 72 | } else { 73 | log.SetOutput(io.Discard) 74 | } 75 | enableLogging = enable 76 | unsafeLogging = unsafe 77 | return nil 78 | } 79 | 80 | // Enabled returns if logging is enabled. 81 | func Enabled() bool { 82 | return enableLogging 83 | } 84 | 85 | // Unsafe returns if unsafe logging is allowed (the caller MAY skip eliding 86 | // addresses and other bits of sensitive information). 87 | func Unsafe() bool { 88 | return unsafeLogging 89 | } 90 | 91 | // Level returns the current log level. 92 | func Level() int { 93 | return logLevel 94 | } 95 | 96 | // SetLogLevel sets the log level to the value indicated by the given string 97 | // (case-insensitive). 98 | func SetLogLevel(logLevelStr string) error { 99 | switch strings.ToUpper(logLevelStr) { 100 | case "ERROR": 101 | logLevel = LevelError 102 | case "WARN": 103 | logLevel = LevelWarn 104 | case "INFO": 105 | logLevel = LevelInfo 106 | case "DEBUG": 107 | logLevel = LevelDebug 108 | default: 109 | return fmt.Errorf("invalid log level '%s'", logLevelStr) 110 | } 111 | return nil 112 | } 113 | 114 | // Noticef logs the given format string/arguments at the NOTICE log level. 115 | // Unless logging is disabled, Noticef logs are always emitted. 116 | func Noticef(format string, a ...interface{}) { 117 | if enableLogging { 118 | msg := fmt.Sprintf(format, a...) 119 | log.Print("[NOTICE]: " + msg) 120 | } 121 | } 122 | 123 | // Errorf logs the given format string/arguments at the ERROR log level. 124 | func Errorf(format string, a ...interface{}) { 125 | if enableLogging && logLevel >= LevelError { 126 | msg := fmt.Sprintf(format, a...) 127 | log.Print("[ERROR]: " + msg) 128 | } 129 | } 130 | 131 | // Warnf logs the given format string/arguments at the WARN log level. 132 | func Warnf(format string, a ...interface{}) { 133 | if enableLogging && logLevel >= LevelWarn { 134 | msg := fmt.Sprintf(format, a...) 135 | log.Print("[WARN]: " + msg) 136 | } 137 | } 138 | 139 | // Infof logs the given format string/arguments at the INFO log level. 140 | func Infof(format string, a ...interface{}) { 141 | if enableLogging && logLevel >= LevelInfo { 142 | msg := fmt.Sprintf(format, a...) 143 | log.Print("[INFO]: " + msg) 144 | } 145 | } 146 | 147 | // Debugf logs the given format string/arguments at the DEBUG log level. 148 | func Debugf(format string, a ...interface{}) { 149 | if enableLogging && logLevel >= LevelDebug { 150 | msg := fmt.Sprintf(format, a...) 151 | log.Print("[DEBUG]: " + msg) 152 | } 153 | } 154 | 155 | // ElideError transforms the string representation of the provided error 156 | // based on the unsafeLogging setting. Callers that wish to log errors 157 | // returned from Go's net package should use ElideError to sanitize the 158 | // contents first. 159 | func ElideError(err error) string { 160 | // Go's net package is somewhat rude and includes IP address and port 161 | // information in the string representation of net.Errors. Figure out if 162 | // this is the case here, and sanitize the error messages as needed. 163 | if unsafeLogging { 164 | return err.Error() 165 | } 166 | 167 | // If err is not a net.Error, just return the string representation, 168 | // presumably transport authors know what they are doing. 169 | var netErr net.Error 170 | if !errors.As(err, &netErr) { 171 | return err.Error() 172 | } 173 | 174 | switch t := netErr.(type) { 175 | case *net.AddrError: 176 | return t.Err + " " + elidedAddr 177 | case *net.DNSError: 178 | return "lookup " + elidedAddr + " on " + elidedAddr + ": " + t.Err 179 | case *net.InvalidAddrError: 180 | return "invalid address error" 181 | case *net.UnknownNetworkError: 182 | return "unknown network " + elidedAddr 183 | case *net.OpError: 184 | return t.Op + ": " + t.Err.Error() 185 | default: 186 | // For unknown error types, do the conservative thing and only log the 187 | // type of the error instead of assuming that the string representation 188 | // does not contain sensitive information. 189 | return fmt.Sprintf("network error: <%T>", t) 190 | } 191 | } 192 | 193 | // ElideAddr transforms the string representation of the provided address based 194 | // on the unsafeLogging setting. Callers that wish to log IP addreses should 195 | // use ElideAddr to sanitize the contents first. 196 | func ElideAddr(addrStr string) string { 197 | if unsafeLogging { 198 | return addrStr 199 | } 200 | 201 | // Only scrub off the address so that it's easier to track connections 202 | // in logs by looking at the port. 203 | if _, port, err := net.SplitHostPort(addrStr); err == nil { 204 | return elidedAddr + ":" + port 205 | } 206 | return elidedAddr 207 | } 208 | -------------------------------------------------------------------------------- /common/uniformdh/uniformdh_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package uniformdh 29 | 30 | import ( 31 | "bytes" 32 | "crypto/rand" 33 | "encoding/hex" 34 | "testing" 35 | ) 36 | 37 | const ( 38 | xPrivStr = "6f592d676f536874746f20686e6b776f" + 39 | "20736874206561676574202e6f592d67" + 40 | "6f536874746f20687369742065686720" + 41 | "74612e655920676f532d746f6f686874" + 42 | "6920207368742065656b20796e612064" + 43 | "7567726169646e616f20206668742065" + 44 | "61676574202e61507473202c72707365" + 45 | "6e652c746620747572752c6561206c6c" + 46 | "612065726f20656e6920206e6f592d67" + 47 | "6f536874746f2e68482020656e6b776f" + 48 | "2073687772652065687420656c4f2064" + 49 | "6e4f736562206f72656b74207268756f" 50 | 51 | xPubStr = "76a3d17d5c55b03e865fa3e8267990a7" + 52 | "24baa24b0bdd0cc4af93be8de30be120" + 53 | "d5533c91bf63ef923b02edcb84b74438" + 54 | "3f7de232cca6eb46d07cad83dcaa317f" + 55 | "becbc68ca13e2c4019e6a36531067450" + 56 | "04aecc0be1dff0a78733fb0e7d5cb7c4" + 57 | "97cab77b1331bf347e5f3a7847aa0bc0" + 58 | "f4bc64146b48407fed7b931d16972d25" + 59 | "fb4da5e6dc074ce2a58daa8de7624247" + 60 | "cdf2ebe4e4dfec6d5989aac778c87559" + 61 | "d3213d6040d4111ce3a2acae19f9ee15" + 62 | "32509e037f69b252fdc30243cbbce9d0" 63 | 64 | yPrivStr = "736562206f72656b74207268756f6867" + 65 | "6f2020666c6f2c646120646e77206568" + 66 | "657254206568207968736c61206c7262" + 67 | "6165206b68746f726775206867616961" + 68 | "2e6e482020656e6b776f207368777265" + 69 | "2065685479656820766120657274646f" + 70 | "652072616874732766206569646c2c73" + 71 | "6120646e772065686572542065682079" + 72 | "74736c69206c72746165206468746d65" + 73 | "202c6e612064687720796f6e6f20656e" + 74 | "63206e61622068656c6f206468546d65" + 75 | "61202073685479657420657264610a2e" 76 | 77 | yPubStr = "d04e156e554c37ffd7aba749df662350" + 78 | "1e4ff4466cb12be055617c1a36872237" + 79 | "36d2c3fdce9ee0f9b27774350849112a" + 80 | "a5aeb1f126811c9c2f3a9cb13d2f0c3a" + 81 | "7e6fa2d3bf71baf50d839171534f227e" + 82 | "fbb2ce4227a38c25abdc5ba7fc430111" + 83 | "3a2cb2069c9b305faac4b72bf21fec71" + 84 | "578a9c369bcac84e1a7dcf0754e342f5" + 85 | "bc8fe4917441b88254435e2abaf297e9" + 86 | "3e1e57968672d45bd7d4c8ba1bc3d314" + 87 | "889b5bc3d3e4ea33d4f2dfdd34e5e5a7" + 88 | "2ff24ee46316d4757dad09366a0b66b3" 89 | 90 | ssStr = "78afaf5f457f1fdb832bebc397644a33" + 91 | "038be9dba10ca2ce4a076f327f3a0ce3" + 92 | "151d477b869ee7ac467755292ad8a77d" + 93 | "b9bd87ffbbc39955bcfb03b1583888c8" + 94 | "fd037834ff3f401d463c10f899aa6378" + 95 | "445140b7f8386a7d509e7b9db19b677f" + 96 | "062a7a1a4e1509604d7a0839ccd5da61" + 97 | "73e10afd9eab6dda74539d60493ca37f" + 98 | "a5c98cd9640b409cd8bb3be2bc5136fd" + 99 | "42e764fc3f3c0ddb8db3d87abcf2e659" + 100 | "8d2b101bef7a56f50ebc658f9df1287d" + 101 | "a81359543e77e4a4cfa7598a4152e4c0" 102 | ) 103 | 104 | var ( 105 | // Load the test vectors into byte slices. 106 | xPriv, _ = hex.DecodeString(xPrivStr) 107 | xPub, _ = hex.DecodeString(xPubStr) 108 | yPriv, _ = hex.DecodeString(yPrivStr) 109 | yPub, _ = hex.DecodeString(yPubStr) 110 | ss, _ = hex.DecodeString(ssStr) 111 | ) 112 | 113 | // TestGenerateKeyOdd tests creating a UniformDH keypair with a odd private 114 | // key. 115 | func TestGenerateKeyOdd(t *testing.T) { 116 | xX, err := generateKey(xPriv) 117 | if err != nil { 118 | t.Fatal("generateKey(xPriv) failed:", err) 119 | } 120 | 121 | xPubGen, err := xX.PublicKey.Bytes() 122 | if err != nil { 123 | t.Fatal("xX.PublicKey.Bytes() failed:", err) 124 | } 125 | if 0 != bytes.Compare(xPubGen, xPub) { 126 | t.Fatal("Generated public key does not match known answer") 127 | } 128 | } 129 | 130 | // TestGenerateKeyEven tests creating a UniformDH keypair with a even private 131 | // key. 132 | func TestGenerateKeyEven(t *testing.T) { 133 | yY, err := generateKey(yPriv) 134 | if err != nil { 135 | t.Fatal("generateKey(yPriv) failed:", err) 136 | } 137 | 138 | yPubGen, err := yY.PublicKey.Bytes() 139 | if err != nil { 140 | t.Fatal("yY.PublicKey.Bytes() failed:", err) 141 | } 142 | if 0 != bytes.Compare(yPubGen, yPub) { 143 | t.Fatal("Generated public key does not match known answer") 144 | } 145 | } 146 | 147 | // TestHandshake tests conducting a UniformDH handshake with know values. 148 | func TestHandshake(t *testing.T) { 149 | xX, err := generateKey(xPriv) 150 | if err != nil { 151 | t.Fatal("generateKey(xPriv) failed:", err) 152 | } 153 | yY, err := generateKey(yPriv) 154 | if err != nil { 155 | t.Fatal("generateKey(yPriv) failed:", err) 156 | } 157 | 158 | xY, err := Handshake(xX, &yY.PublicKey) 159 | if err != nil { 160 | t.Fatal("Handshake(xX, yY.PublicKey) failed:", err) 161 | } 162 | yX, err := Handshake(yY, &xX.PublicKey) 163 | if err != nil { 164 | t.Fatal("Handshake(yY, xX.PublicKey) failed:", err) 165 | } 166 | 167 | if 0 != bytes.Compare(xY, yX) { 168 | t.Fatal("Generated shared secrets do not match between peers") 169 | } 170 | if 0 != bytes.Compare(xY, ss) { 171 | t.Fatal("Generated shared secret does not match known value") 172 | } 173 | } 174 | 175 | // Benchmark UniformDH key generation + exchange. THe actual time taken per 176 | // peer is half of the reported time as this does 2 key generation an 177 | // handshake operations. 178 | func BenchmarkHandshake(b *testing.B) { 179 | for i := 0; i < b.N; i++ { 180 | xX, err := GenerateKey(rand.Reader) 181 | if err != nil { 182 | b.Fatal("Failed to generate xX keypair", err) 183 | } 184 | 185 | yY, err := GenerateKey(rand.Reader) 186 | if err != nil { 187 | b.Fatal("Failed to generate yY keypair", err) 188 | } 189 | 190 | xY, err := Handshake(xX, &yY.PublicKey) 191 | if err != nil { 192 | b.Fatal("Handshake(xX, yY.PublicKey) failed:", err) 193 | } 194 | yX, err := Handshake(yY, &xX.PublicKey) 195 | if err != nil { 196 | b.Fatal("Handshake(yY, xX.PublicKey) failed:", err) 197 | } 198 | 199 | _ = xY 200 | _ = yX 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /common/probdist/weighted_dist.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package probdist implements a weighted probability distribution suitable for 29 | // protocol parameterization. To allow for easy reproduction of a given 30 | // distribution, the drbg package is used as the random number source. 31 | package probdist // import "gitlab.com/yawning/obfs4.git/common/probdist" 32 | 33 | import ( 34 | "bytes" 35 | "container/list" 36 | "fmt" 37 | "math/rand" 38 | "sync" 39 | 40 | "gitlab.com/yawning/obfs4.git/common/csrand" 41 | "gitlab.com/yawning/obfs4.git/common/drbg" 42 | ) 43 | 44 | const ( 45 | minValues = 1 46 | maxValues = 100 47 | ) 48 | 49 | // WeightedDist is a weighted distribution. 50 | type WeightedDist struct { 51 | sync.Mutex 52 | 53 | minValue int 54 | maxValue int 55 | biased bool 56 | values []int 57 | weights []float64 58 | 59 | alias []int 60 | prob []float64 61 | } 62 | 63 | // New creates a weighted distribution of values ranging from min to max 64 | // based on a HashDrbg initialized with seed. Optionally, bias the weight 65 | // generation to match the ScrambleSuit non-uniform distribution from 66 | // obfsproxy. 67 | func New(seed *drbg.Seed, min, max int, biased bool) *WeightedDist { 68 | w := &WeightedDist{minValue: min, maxValue: max, biased: biased} 69 | 70 | if max <= min { 71 | panic(fmt.Sprintf("wDist.Reset(): min >= max (%d, %d)", min, max)) 72 | } 73 | 74 | w.Reset(seed) 75 | 76 | return w 77 | } 78 | 79 | // genValues creates a slice containing a random number of random values 80 | // that when scaled by adding minValue will fall into [min, max]. 81 | func (w *WeightedDist) genValues(rng *rand.Rand) { 82 | nValues := (w.maxValue + 1) - w.minValue 83 | values := rng.Perm(nValues) 84 | if nValues < minValues { 85 | nValues = minValues 86 | } 87 | if nValues > maxValues { 88 | nValues = maxValues 89 | } 90 | nValues = rng.Intn(nValues) + 1 91 | w.values = values[:nValues] 92 | } 93 | 94 | // genBiasedWeights generates a non-uniform weight list, similar to the 95 | // ScrambleSuit prob_dist module. 96 | func (w *WeightedDist) genBiasedWeights(rng *rand.Rand) { 97 | w.weights = make([]float64, len(w.values)) 98 | 99 | culmProb := 0.0 100 | for i := range w.weights { 101 | p := (1.0 - culmProb) * rng.Float64() 102 | w.weights[i] = p 103 | culmProb += p 104 | } 105 | } 106 | 107 | // genUniformWeights generates a uniform weight list. 108 | func (w *WeightedDist) genUniformWeights(rng *rand.Rand) { 109 | w.weights = make([]float64, len(w.values)) 110 | for i := range w.weights { 111 | w.weights[i] = rng.Float64() 112 | } 113 | } 114 | 115 | // genTables calculates the alias and prob tables used for Vose's Alias method. 116 | // Algorithm taken from http://www.keithschwarz.com/darts-dice-coins/ 117 | func (w *WeightedDist) genTables() { 118 | n := len(w.weights) 119 | var sum float64 120 | for _, weight := range w.weights { 121 | sum += weight 122 | } 123 | 124 | // Create arrays $Alias$ and $Prob$, each of size $n$. 125 | alias := make([]int, n) 126 | prob := make([]float64, n) 127 | 128 | // Create two worklists, $Small$ and $Large$. 129 | small := list.New() 130 | large := list.New() 131 | 132 | scaled := make([]float64, n) 133 | for i, weight := range w.weights { 134 | // Multiply each probability by $n$. 135 | p_i := weight * float64(n) / sum //nolint:revive 136 | scaled[i] = p_i 137 | 138 | // For each scaled probability $p_i$: 139 | if scaled[i] < 1.0 { 140 | // If $p_i < 1$, add $i$ to $Small$. 141 | small.PushBack(i) 142 | } else { 143 | // Otherwise ($p_i \ge 1$), add $i$ to $Large$. 144 | large.PushBack(i) 145 | } 146 | } 147 | 148 | // While $Small$ and $Large$ are not empty: ($Large$ might be emptied first) 149 | for small.Len() > 0 && large.Len() > 0 { 150 | // Remove the first element from $Small$; call it $l$. 151 | l, _ := small.Remove(small.Front()).(int) 152 | // Remove the first element from $Large$; call it $g$. 153 | g, _ := large.Remove(large.Front()).(int) 154 | 155 | // Set $Prob[l] = p_l$. 156 | prob[l] = scaled[l] 157 | // Set $Alias[l] = g$. 158 | alias[l] = g 159 | 160 | // Set $p_g := (p_g + p_l) - 1$. (This is a more numerically stable option.) 161 | scaled[g] = (scaled[g] + scaled[l]) - 1.0 162 | 163 | if scaled[g] < 1.0 { 164 | // If $p_g < 1$, add $g$ to $Small$. 165 | small.PushBack(g) 166 | } else { 167 | // Otherwise ($p_g \ge 1$), add $g$ to $Large$. 168 | large.PushBack(g) 169 | } 170 | } 171 | 172 | // While $Large$ is not empty: 173 | for large.Len() > 0 { 174 | // Remove the first element from $Large$; call it $g$. 175 | g, _ := large.Remove(large.Front()).(int) 176 | // Set $Prob[g] = 1$. 177 | prob[g] = 1.0 178 | } 179 | 180 | // While $Small$ is not empty: This is only possible due to numerical instability. 181 | for small.Len() > 0 { 182 | // Remove the first element from $Small$; call it $l$. 183 | l, _ := small.Remove(small.Front()).(int) 184 | // Set $Prob[l] = 1$. 185 | prob[l] = 1.0 186 | } 187 | 188 | w.prob = prob 189 | w.alias = alias 190 | } 191 | 192 | // Reset generates a new distribution with the same min/max based on a new 193 | // seed. 194 | func (w *WeightedDist) Reset(seed *drbg.Seed) { 195 | // Initialize the deterministic random number generator. 196 | drbg, _ := drbg.NewHashDrbg(seed) 197 | rng := rand.New(drbg) //nolint:gosec 198 | 199 | w.Lock() 200 | defer w.Unlock() 201 | 202 | w.genValues(rng) 203 | if w.biased { 204 | w.genBiasedWeights(rng) 205 | } else { 206 | w.genUniformWeights(rng) 207 | } 208 | w.genTables() 209 | } 210 | 211 | // Sample generates a random value according to the distribution. 212 | func (w *WeightedDist) Sample() int { 213 | var idx int 214 | 215 | w.Lock() 216 | defer w.Unlock() 217 | 218 | // Generate a fair die roll from an $n$-sided die; call the side $i$. 219 | i := csrand.Intn(len(w.values)) 220 | // Flip a biased coin that comes up heads with probability $Prob[i]$. 221 | if csrand.Float64() <= w.prob[i] { 222 | // If the coin comes up "heads," return $i$. 223 | idx = i 224 | } else { 225 | // Otherwise, return $Alias[i]$. 226 | idx = w.alias[i] 227 | } 228 | 229 | return w.minValue + w.values[idx] 230 | } 231 | 232 | // String returns a dump of the distribution table. 233 | func (w *WeightedDist) String() string { 234 | var buf bytes.Buffer 235 | 236 | buf.WriteString("[ ") 237 | for i, v := range w.values { 238 | p := w.weights[i] 239 | if p > 0.01 { // Squelch tiny probabilities. 240 | buf.WriteString(fmt.Sprintf("%d: %f ", v, p)) 241 | } 242 | } 243 | buf.WriteString("]") 244 | return buf.String() 245 | } 246 | -------------------------------------------------------------------------------- /transports/obfs4/statefile.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package obfs4 29 | 30 | import ( 31 | "bytes" 32 | "encoding/base64" 33 | "encoding/json" 34 | "fmt" 35 | "os" 36 | "path" 37 | "strconv" 38 | "strings" 39 | 40 | "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" 41 | 42 | "gitlab.com/yawning/obfs4.git/common/csrand" 43 | "gitlab.com/yawning/obfs4.git/common/drbg" 44 | "gitlab.com/yawning/obfs4.git/common/ntor" 45 | ) 46 | 47 | const ( 48 | stateFile = "obfs4_state.json" 49 | bridgeFile = "obfs4_bridgeline.txt" 50 | 51 | certSuffix = "==" 52 | certLength = ntor.NodeIDLength + ntor.PublicKeyLength 53 | ) 54 | 55 | type jsonServerState struct { 56 | NodeID string `json:"node-id"` 57 | PrivateKey string `json:"private-key"` 58 | PublicKey string `json:"public-key"` 59 | DrbgSeed string `json:"drbg-seed"` 60 | IATMode int `json:"iat-mode"` 61 | } 62 | 63 | type obfs4ServerCert struct { 64 | raw []byte 65 | } 66 | 67 | func (cert *obfs4ServerCert) String() string { 68 | return strings.TrimSuffix(base64.StdEncoding.EncodeToString(cert.raw), certSuffix) 69 | } 70 | 71 | func (cert *obfs4ServerCert) unpack() (*ntor.NodeID, *ntor.PublicKey) { 72 | if len(cert.raw) != certLength { 73 | panic(fmt.Sprintf("cert length %d is invalid", len(cert.raw))) 74 | } 75 | 76 | nodeID, _ := ntor.NewNodeID(cert.raw[:ntor.NodeIDLength]) 77 | pubKey, _ := ntor.NewPublicKey(cert.raw[ntor.NodeIDLength:]) 78 | 79 | return nodeID, pubKey 80 | } 81 | 82 | func serverCertFromString(encoded string) (*obfs4ServerCert, error) { 83 | decoded, err := base64.StdEncoding.DecodeString(encoded + certSuffix) 84 | if err != nil { 85 | return nil, fmt.Errorf("failed to decode cert: %w", err) 86 | } 87 | 88 | if len(decoded) != certLength { 89 | return nil, fmt.Errorf("cert length %d is invalid", len(decoded)) 90 | } 91 | 92 | return &obfs4ServerCert{raw: decoded}, nil 93 | } 94 | 95 | func serverCertFromState(st *obfs4ServerState) *obfs4ServerCert { 96 | cert := new(obfs4ServerCert) 97 | 98 | cert.raw = bytes.Clone(st.nodeID.Bytes()[:]) 99 | cert.raw = append(cert.raw, st.identityKey.Public().Bytes()[:]...) 100 | 101 | return cert 102 | } 103 | 104 | type obfs4ServerState struct { 105 | nodeID *ntor.NodeID 106 | identityKey *ntor.Keypair 107 | drbgSeed *drbg.Seed 108 | iatMode int 109 | 110 | cert *obfs4ServerCert 111 | } 112 | 113 | func (st *obfs4ServerState) clientString() string { 114 | return fmt.Sprintf("%s=%s %s=%d", certArg, st.cert, iatArg, st.iatMode) 115 | } 116 | 117 | func serverStateFromArgs(stateDir string, args *pt.Args) (*obfs4ServerState, error) { 118 | var js jsonServerState 119 | var nodeIDOk, privKeyOk, seedOk bool 120 | 121 | js.NodeID, nodeIDOk = args.Get(nodeIDArg) 122 | js.PrivateKey, privKeyOk = args.Get(privateKeyArg) 123 | js.DrbgSeed, seedOk = args.Get(seedArg) 124 | iatStr, iatOk := args.Get(iatArg) 125 | 126 | // Either a private key, node id, and seed are ALL specified, or 127 | // they should be loaded from the state file. 128 | switch { 129 | case !privKeyOk && !nodeIDOk && !seedOk: 130 | if err := jsonServerStateFromFile(stateDir, &js); err != nil { 131 | return nil, err 132 | } 133 | case !privKeyOk: 134 | return nil, fmt.Errorf("missing argument '%s'", privateKeyArg) 135 | case !nodeIDOk: 136 | return nil, fmt.Errorf("missing argument '%s'", nodeIDArg) 137 | case !seedOk: 138 | return nil, fmt.Errorf("missing argument '%s'", seedArg) 139 | } 140 | 141 | // The IAT mode should be independently configurable. 142 | if iatOk { 143 | // If the IAT mode is specified, attempt to parse and apply it 144 | // as an override. 145 | iatMode, err := strconv.Atoi(iatStr) 146 | if err != nil { 147 | return nil, fmt.Errorf("malformed iat-mode '%s'", iatStr) 148 | } 149 | js.IATMode = iatMode 150 | } 151 | 152 | return serverStateFromJSONServerState(stateDir, &js) 153 | } 154 | 155 | func serverStateFromJSONServerState(stateDir string, js *jsonServerState) (*obfs4ServerState, error) { 156 | var err error 157 | 158 | st := new(obfs4ServerState) 159 | if st.nodeID, err = ntor.NodeIDFromHex(js.NodeID); err != nil { 160 | return nil, err 161 | } 162 | if st.identityKey, err = ntor.KeypairFromHex(js.PrivateKey); err != nil { 163 | return nil, err 164 | } 165 | if st.drbgSeed, err = drbg.SeedFromHex(js.DrbgSeed); err != nil { 166 | return nil, err 167 | } 168 | if js.IATMode < iatNone || js.IATMode > iatParanoid { 169 | return nil, fmt.Errorf("invalid iat-mode '%d'", js.IATMode) 170 | } 171 | st.iatMode = js.IATMode 172 | st.cert = serverCertFromState(st) 173 | 174 | // Generate a human readable summary of the configured endpoint. 175 | if err = newBridgeFile(stateDir, st); err != nil { 176 | return nil, err 177 | } 178 | 179 | // Write back the possibly updated server state. 180 | return st, writeJSONServerState(stateDir, js) 181 | } 182 | 183 | func jsonServerStateFromFile(stateDir string, js *jsonServerState) error { 184 | fPath := path.Join(stateDir, stateFile) 185 | f, err := os.ReadFile(fPath) 186 | if err != nil { 187 | if os.IsNotExist(err) { 188 | if err = newJSONServerState(stateDir, js); err == nil { 189 | return nil 190 | } 191 | } 192 | return err 193 | } 194 | 195 | if err := json.Unmarshal(f, js); err != nil { 196 | return fmt.Errorf("failed to load statefile '%s': %w", fPath, err) 197 | } 198 | 199 | return nil 200 | } 201 | 202 | func newJSONServerState(stateDir string, js *jsonServerState) error { 203 | // Generate everything a server needs, using the cryptographic PRNG. 204 | var st obfs4ServerState 205 | rawID := make([]byte, ntor.NodeIDLength) 206 | if err := csrand.Bytes(rawID); err != nil { 207 | return err 208 | } 209 | 210 | var err error 211 | if st.nodeID, err = ntor.NewNodeID(rawID); err != nil { 212 | return err 213 | } 214 | if st.identityKey, err = ntor.NewKeypair(false); err != nil { 215 | return err 216 | } 217 | if st.drbgSeed, err = drbg.NewSeed(); err != nil { 218 | return err 219 | } 220 | st.iatMode = iatNone 221 | 222 | // Encode it into JSON format and write the state file. 223 | js.NodeID = st.nodeID.Hex() 224 | js.PrivateKey = st.identityKey.Private().Hex() 225 | js.PublicKey = st.identityKey.Public().Hex() 226 | js.DrbgSeed = st.drbgSeed.Hex() 227 | js.IATMode = st.iatMode 228 | 229 | return writeJSONServerState(stateDir, js) 230 | } 231 | 232 | func writeJSONServerState(stateDir string, js *jsonServerState) error { 233 | var err error 234 | var encoded []byte 235 | if encoded, err = json.Marshal(js); err != nil { 236 | return err 237 | } 238 | return os.WriteFile(path.Join(stateDir, stateFile), encoded, 0o600) 239 | } 240 | 241 | func newBridgeFile(stateDir string, st *obfs4ServerState) error { 242 | const prefix = "# obfs4 torrc client bridge line\n" + 243 | "#\n" + 244 | "# This file is an automatically generated bridge line based on\n" + 245 | "# the current obfs4proxy configuration. EDITING IT WILL HAVE\n" + 246 | "# NO EFFECT.\n" + 247 | "#\n" + 248 | "# Before distributing this Bridge, edit the placeholder fields\n" + 249 | "# to contain the actual values:\n" + 250 | "# - The public IP address of your obfs4 bridge.\n" + 251 | "# - The TCP/IP port of your obfs4 bridge.\n" + 252 | "# - The bridge's fingerprint.\n\n" 253 | 254 | bridgeLine := fmt.Sprintf("Bridge obfs4 : %s\n", 255 | st.clientString()) 256 | 257 | tmp := []byte(prefix + bridgeLine) 258 | return os.WriteFile(path.Join(stateDir, bridgeFile), tmp, 0o600) 259 | } 260 | -------------------------------------------------------------------------------- /transports/obfs4/handshake_ntor_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package obfs4 29 | 30 | import ( 31 | "bytes" 32 | "testing" 33 | 34 | "gitlab.com/yawning/obfs4.git/common/ntor" 35 | "gitlab.com/yawning/obfs4.git/common/replayfilter" 36 | ) 37 | 38 | func TestHandshakeNtorClient(t *testing.T) { 39 | // Generate the server node id and id keypair, and ephemeral session keys. 40 | nodeID, _ := ntor.NewNodeID([]byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13")) 41 | idKeypair, _ := ntor.NewKeypair(false) 42 | serverFilter, _ := replayfilter.New(replayTTL) 43 | clientKeypair, err := ntor.NewKeypair(true) 44 | if err != nil { 45 | t.Fatalf("client: ntor.NewKeypair failed: %s", err) 46 | } 47 | serverKeypair, err := ntor.NewKeypair(true) 48 | if err != nil { 49 | t.Fatalf("server: ntor.NewKeypair failed: %s", err) 50 | } 51 | 52 | // Test client handshake padding. 53 | for l := clientMinPadLength; l <= clientMaxPadLength; l++ { 54 | // Generate the client state and override the pad length. 55 | clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair) 56 | clientHs.padLen = l 57 | 58 | // Generate what the client will send to the server. 59 | clientBlob, err := clientHs.generateHandshake() 60 | if err != nil { 61 | t.Fatalf("[%d:0] clientHandshake.generateHandshake() failed: %s", l, err) 62 | } 63 | if len(clientBlob) > maxHandshakeLength { 64 | t.Fatalf("[%d:0] Generated client body is oversized: %d", l, len(clientBlob)) 65 | } 66 | if len(clientBlob) < clientMinHandshakeLength { 67 | t.Fatalf("[%d:0] Generated client body is undersized: %d", l, len(clientBlob)) 68 | } 69 | if len(clientBlob) != clientMinHandshakeLength+l { 70 | t.Fatalf("[%d:0] Generated client body incorrect size: %d", l, len(clientBlob)) 71 | } 72 | 73 | // Generate the server state and override the pad length. 74 | serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair) 75 | serverHs.padLen = serverMinPadLength 76 | 77 | // Parse the client handshake message. 78 | serverSeed, err := serverHs.parseClientHandshake(serverFilter, clientBlob) 79 | if err != nil { 80 | t.Fatalf("[%d:0] serverHandshake.parseClientHandshake() failed: %s", l, err) 81 | } 82 | 83 | // Genrate what the server will send to the client. 84 | serverBlob, err := serverHs.generateHandshake() 85 | if err != nil { 86 | t.Fatalf("[%d:0]: serverHandshake.generateHandshake() failed: %s", l, err) 87 | } 88 | 89 | // Parse the server handshake message. 90 | clientHs.serverRepresentative = nil 91 | n, clientSeed, err := clientHs.parseServerHandshake(serverBlob) 92 | if err != nil { 93 | t.Fatalf("[%d:0] clientHandshake.parseServerHandshake() failed: %s", l, err) 94 | } 95 | if n != len(serverBlob) { 96 | t.Fatalf("[%d:0] clientHandshake.parseServerHandshake() has bytes remaining: %d", l, n) 97 | } 98 | 99 | // Ensure the derived shared secret is the same. 100 | if 0 != bytes.Compare(clientSeed, serverSeed) { 101 | t.Fatalf("[%d:0] client/server seed mismatch", l) 102 | } 103 | } 104 | 105 | // Test oversized client padding. 106 | clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair) 107 | if err != nil { 108 | t.Fatalf("newClientHandshake failed: %s", err) 109 | } 110 | clientHs.padLen = clientMaxPadLength + 1 111 | clientBlob, err := clientHs.generateHandshake() 112 | if err != nil { 113 | t.Fatalf("clientHandshake.generateHandshake() (forced oversize) failed: %s", err) 114 | } 115 | serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair) 116 | _, err = serverHs.parseClientHandshake(serverFilter, clientBlob) 117 | if err == nil { 118 | t.Fatalf("serverHandshake.parseClientHandshake() succeeded (oversized)") 119 | } 120 | 121 | // Test undersized client padding. 122 | clientHs.padLen = clientMinPadLength - 1 123 | clientBlob, err = clientHs.generateHandshake() 124 | if err != nil { 125 | t.Fatalf("clientHandshake.generateHandshake() (forced undersize) failed: %s", err) 126 | } 127 | serverHs = newServerHandshake(nodeID, idKeypair, serverKeypair) 128 | _, err = serverHs.parseClientHandshake(serverFilter, clientBlob) 129 | if err == nil { 130 | t.Fatalf("serverHandshake.parseClientHandshake() succeeded (undersized)") 131 | } 132 | } 133 | 134 | func TestHandshakeNtorServer(t *testing.T) { 135 | // Generate the server node id and id keypair, and ephemeral session keys. 136 | nodeID, _ := ntor.NewNodeID([]byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13")) 137 | idKeypair, _ := ntor.NewKeypair(false) 138 | serverFilter, _ := replayfilter.New(replayTTL) 139 | clientKeypair, err := ntor.NewKeypair(true) 140 | if err != nil { 141 | t.Fatalf("client: ntor.NewKeypair failed: %s", err) 142 | } 143 | serverKeypair, err := ntor.NewKeypair(true) 144 | if err != nil { 145 | t.Fatalf("server: ntor.NewKeypair failed: %s", err) 146 | } 147 | 148 | // Test server handshake padding. 149 | for l := serverMinPadLength; l <= serverMaxPadLength+inlineSeedFrameLength; l++ { 150 | // Generate the client state and override the pad length. 151 | clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair) 152 | clientHs.padLen = clientMinPadLength 153 | 154 | // Generate what the client will send to the server. 155 | clientBlob, err := clientHs.generateHandshake() 156 | if err != nil { 157 | t.Fatalf("[%d:1] clientHandshake.generateHandshake() failed: %s", l, err) 158 | } 159 | if len(clientBlob) > maxHandshakeLength { 160 | t.Fatalf("[%d:1] Generated client body is oversized: %d", l, len(clientBlob)) 161 | } 162 | 163 | // Generate the server state and override the pad length. 164 | serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair) 165 | serverHs.padLen = l 166 | 167 | // Parse the client handshake message. 168 | serverSeed, err := serverHs.parseClientHandshake(serverFilter, clientBlob) 169 | if err != nil { 170 | t.Fatalf("[%d:1] serverHandshake.parseClientHandshake() failed: %s", l, err) 171 | } 172 | 173 | // Genrate what the server will send to the client. 174 | serverBlob, err := serverHs.generateHandshake() 175 | if err != nil { 176 | t.Fatalf("[%d:1]: serverHandshake.generateHandshake() failed: %s", l, err) 177 | } 178 | 179 | // Parse the server handshake message. 180 | n, clientSeed, err := clientHs.parseServerHandshake(serverBlob) 181 | if err != nil { 182 | t.Fatalf("[%d:1] clientHandshake.parseServerHandshake() failed: %s", l, err) 183 | } 184 | if n != len(serverBlob) { 185 | t.Fatalf("[%d:1] clientHandshake.parseServerHandshake() has bytes remaining: %d", l, n) 186 | } 187 | 188 | // Ensure the derived shared secret is the same. 189 | if 0 != bytes.Compare(clientSeed, serverSeed) { 190 | t.Fatalf("[%d:1] client/server seed mismatch", l) 191 | } 192 | } 193 | 194 | // Test oversized client padding. 195 | clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair) 196 | if err != nil { 197 | t.Fatalf("newClientHandshake failed: %s", err) 198 | } 199 | clientHs.padLen = clientMaxPadLength + 1 200 | clientBlob, err := clientHs.generateHandshake() 201 | if err != nil { 202 | t.Fatalf("clientHandshake.generateHandshake() (forced oversize) failed: %s", err) 203 | } 204 | serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair) 205 | _, err = serverHs.parseClientHandshake(serverFilter, clientBlob) 206 | if err == nil { 207 | t.Fatalf("serverHandshake.parseClientHandshake() succeeded (oversized)") 208 | } 209 | 210 | // Test undersized client padding. 211 | clientHs.padLen = clientMinPadLength - 1 212 | clientBlob, err = clientHs.generateHandshake() 213 | if err != nil { 214 | t.Fatalf("clientHandshake.generateHandshake() (forced undersize) failed: %s", err) 215 | } 216 | serverHs = newServerHandshake(nodeID, idKeypair, serverKeypair) 217 | _, err = serverHs.parseClientHandshake(serverFilter, clientBlob) 218 | if err == nil { 219 | t.Fatalf("serverHandshake.parseClientHandshake() succeeded (undersized)") 220 | } 221 | 222 | // Test oversized server padding. 223 | // 224 | // NB: serverMaxPadLength isn't the real maxPadLength that triggers client 225 | // rejection, because the implementation is written with the asusmption 226 | // that the PRNG_SEED is also inlined with the response. Thus the client 227 | // actually accepts longer padding. The server handshake test and this 228 | // test adjust around that. 229 | clientHs.padLen = clientMinPadLength 230 | clientBlob, err = clientHs.generateHandshake() 231 | if err != nil { 232 | t.Fatalf("clientHandshake.generateHandshake() failed: %s", err) 233 | } 234 | serverHs = newServerHandshake(nodeID, idKeypair, serverKeypair) 235 | serverHs.padLen = serverMaxPadLength + inlineSeedFrameLength + 1 236 | _, err = serverHs.parseClientHandshake(serverFilter, clientBlob) 237 | if err != nil { 238 | t.Fatalf("serverHandshake.parseClientHandshake() failed: %s", err) 239 | } 240 | serverBlob, err := serverHs.generateHandshake() 241 | if err != nil { 242 | t.Fatalf("serverHandshake.generateHandshake() (forced oversize) failed: %s", err) 243 | } 244 | _, _, err = clientHs.parseServerHandshake(serverBlob) 245 | if err == nil { 246 | t.Fatalf("clientHandshake.parseServerHandshake() succeeded (oversized)") 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /transports/meeklite/meek.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | package meeklite 29 | 30 | import ( 31 | "bytes" 32 | "crypto/rand" 33 | "crypto/sha256" 34 | "encoding/hex" 35 | "errors" 36 | "fmt" 37 | "io" 38 | "net" 39 | "net/http" 40 | gourl "net/url" 41 | "os" 42 | "runtime" 43 | "sync" 44 | "time" 45 | 46 | "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" 47 | 48 | "gitlab.com/yawning/obfs4.git/transports/base" 49 | ) 50 | 51 | const ( 52 | urlArg = "url" 53 | frontArg = "front" 54 | 55 | maxChanBacklog = 16 56 | 57 | // Constants shamelessly stolen from meek-client.go... 58 | maxPayloadLength = 0x10000 59 | initPollInterval = 100 * time.Millisecond 60 | maxPollInterval = 5 * time.Second 61 | pollIntervalMultiplier = 1.5 62 | maxRetries = 10 63 | retryDelay = 30 * time.Second 64 | ) 65 | 66 | var ( 67 | // ErrNotSupported is the error returned for a unsupported operation. 68 | ErrNotSupported = errors.New("meek_lite: operation not supported") 69 | 70 | loopbackAddr = net.IPv4(127, 0, 0, 1) 71 | ) 72 | 73 | type meekClientArgs struct { 74 | url *gourl.URL 75 | front string 76 | } 77 | 78 | func (ca *meekClientArgs) Network() string { 79 | return transportName 80 | } 81 | 82 | func (ca *meekClientArgs) String() string { 83 | return transportName + ":" + ca.front + ":" + ca.url.String() 84 | } 85 | 86 | func newClientArgs(args *pt.Args) (*meekClientArgs, error) { 87 | var ( 88 | ca meekClientArgs 89 | err error 90 | ) 91 | 92 | // Parse the URL argument. 93 | str, ok := args.Get(urlArg) 94 | if !ok { 95 | return nil, fmt.Errorf("missing argument '%s'", urlArg) 96 | } 97 | ca.url, err = gourl.Parse(str) 98 | if err != nil { 99 | return nil, fmt.Errorf("malformed url: '%s'", str) 100 | } 101 | switch ca.url.Scheme { 102 | case "http", "https": 103 | default: 104 | return nil, fmt.Errorf("invalid scheme: '%s'", ca.url.Scheme) 105 | } 106 | 107 | // Parse the (optional) front argument. 108 | ca.front, _ = args.Get(frontArg) 109 | 110 | return &ca, nil 111 | } 112 | 113 | type meekConn struct { 114 | args *meekClientArgs 115 | sessionID string 116 | transport *http.Transport 117 | 118 | closeOnce sync.Once 119 | workerWrChan chan []byte 120 | workerRdChan chan []byte 121 | workerCloseChan chan struct{} 122 | rdBuf *bytes.Buffer 123 | } 124 | 125 | func (c *meekConn) Read(p []byte) (int, error) { 126 | // If there is data left over from the previous read, 127 | // service the request using the buffered data. 128 | if c.rdBuf != nil { 129 | if c.rdBuf.Len() == 0 { 130 | panic("empty read buffer") 131 | } 132 | n, err := c.rdBuf.Read(p) 133 | if c.rdBuf.Len() == 0 { 134 | c.rdBuf = nil 135 | } 136 | return n, err 137 | } 138 | 139 | // Wait for the worker to enqueue more incoming data. 140 | b, ok := <-c.workerRdChan 141 | if !ok { 142 | // Close() was called and the worker's shutting down. 143 | return 0, io.ErrClosedPipe 144 | } 145 | 146 | // Ew, an extra copy, but who am I kidding, it's meek. 147 | buf := bytes.NewBuffer(b) 148 | n, err := buf.Read(p) 149 | if buf.Len() > 0 { 150 | // If there's data pending, stash the buffer so the next 151 | // Read() call will use it to fulfuill the Read(). 152 | c.rdBuf = buf 153 | } 154 | return n, err 155 | } 156 | 157 | func (c *meekConn) Write(b []byte) (int, error) { 158 | // Check to see if the connection is actually open. 159 | select { 160 | case <-c.workerCloseChan: 161 | return 0, io.ErrClosedPipe 162 | default: 163 | } 164 | 165 | if len(b) == 0 { 166 | return 0, nil 167 | } 168 | 169 | // Copy the data to be written to a new slice, since 170 | // we return immediately after queuing and the peer can 171 | // happily reuse `b` before data has been sent. 172 | b2 := append([]byte{}, b...) 173 | if ok := c.enqueueWrite(b2); !ok { 174 | // Technically we did enqueue data, but the worker's 175 | // got closed out from under us. 176 | return 0, io.ErrClosedPipe 177 | } 178 | runtime.Gosched() 179 | return len(b), nil 180 | } 181 | 182 | func (c *meekConn) Close() error { 183 | err := os.ErrClosed 184 | 185 | c.closeOnce.Do(func() { 186 | // Tear down the worker, if it is still running. 187 | close(c.workerCloseChan) 188 | err = nil 189 | }) 190 | 191 | return err 192 | } 193 | 194 | func (c *meekConn) LocalAddr() net.Addr { 195 | return &net.IPAddr{IP: loopbackAddr} 196 | } 197 | 198 | func (c *meekConn) RemoteAddr() net.Addr { 199 | return c.args 200 | } 201 | 202 | func (c *meekConn) SetDeadline(_ time.Time) error { 203 | return ErrNotSupported 204 | } 205 | 206 | func (c *meekConn) SetReadDeadline(_ time.Time) error { 207 | return ErrNotSupported 208 | } 209 | 210 | func (c *meekConn) SetWriteDeadline(_ time.Time) error { 211 | return ErrNotSupported 212 | } 213 | 214 | func (c *meekConn) enqueueWrite(b []byte) (ok bool) { //nolint:nonamedreturns 215 | defer func() { 216 | if err := recover(); err != nil { 217 | ok = false 218 | } 219 | }() 220 | c.workerWrChan <- b 221 | return true 222 | } 223 | 224 | func (c *meekConn) roundTrip(sndBuf []byte) ([]byte, error) { 225 | var ( 226 | req *http.Request 227 | resp *http.Response 228 | err error 229 | ) 230 | 231 | url := *c.args.url 232 | host := url.Host 233 | if c.args.front != "" { 234 | url.Host = c.args.front 235 | } 236 | urlStr := url.String() 237 | 238 | for retries := 0; retries < maxRetries; retries++ { 239 | var body io.Reader 240 | if len(sndBuf) > 0 { 241 | body = bytes.NewReader(sndBuf) 242 | } 243 | req, err = http.NewRequest(http.MethodPost, urlStr, body) 244 | if err != nil { 245 | return nil, err 246 | } 247 | if c.args.front != "" { 248 | req.Host = host 249 | } 250 | req.Header.Set("X-Session-Id", c.sessionID) 251 | req.Header.Set("User-Agent", "") 252 | 253 | resp, err = c.transport.RoundTrip(req) 254 | if err != nil { 255 | return nil, err 256 | } 257 | 258 | if resp.StatusCode == http.StatusOK { 259 | var recvBuf []byte 260 | recvBuf, err = io.ReadAll(io.LimitReader(resp.Body, maxPayloadLength)) 261 | resp.Body.Close() 262 | return recvBuf, err 263 | } 264 | 265 | resp.Body.Close() 266 | err = fmt.Errorf("status code was %d, not %d", resp.StatusCode, http.StatusOK) 267 | time.Sleep(retryDelay) 268 | } 269 | return nil, err 270 | } 271 | 272 | func (c *meekConn) ioWorker() { 273 | interval := initPollInterval 274 | var sndBuf, leftBuf []byte 275 | 276 | loop: 277 | for { 278 | sndBuf = nil 279 | select { 280 | case <-time.After(interval): 281 | // If the poll interval has elapsed, issue a request. 282 | case sndBuf = <-c.workerWrChan: 283 | // If there is data pending a send, issue a request. 284 | case <-c.workerCloseChan: 285 | break loop 286 | } 287 | 288 | // Combine short writes as long as data is available to be 289 | // sent immediately and it will not put us over the max 290 | // payload limit. Any excess data is stored and dispatched 291 | // as the next request). 292 | sndBuf = append(leftBuf, sndBuf...) 293 | wrSz := len(sndBuf) 294 | for len(c.workerWrChan) > 0 && wrSz < maxPayloadLength { 295 | b := <-c.workerWrChan 296 | sndBuf = append(sndBuf, b...) 297 | wrSz = len(sndBuf) 298 | } 299 | if wrSz > maxPayloadLength { 300 | wrSz = maxPayloadLength 301 | } 302 | 303 | // Issue a request. 304 | rdBuf, err := c.roundTrip(sndBuf[:wrSz]) 305 | if err != nil { 306 | // Welp, something went horrifically wrong. 307 | break loop 308 | } 309 | 310 | // Stash the remaining payload if any. 311 | leftBuf = sndBuf[wrSz:] // Store the remaining data 312 | if len(leftBuf) == 0 { 313 | leftBuf = nil 314 | } 315 | 316 | // Determine the next poll interval. 317 | switch { 318 | case len(rdBuf) > 0: 319 | // Received data, enqueue the read. 320 | c.workerRdChan <- rdBuf 321 | 322 | // And poll immediately. 323 | interval = 0 324 | case wrSz > 0: 325 | // Sent data, poll immediately. 326 | interval = 0 327 | case interval == 0: 328 | // Neither sent nor received data after a poll, re-initialize the delay. 329 | interval = initPollInterval 330 | default: 331 | // Apply a multiplicative backoff. 332 | interval = time.Duration(float64(interval) * pollIntervalMultiplier) 333 | if interval > maxPollInterval { 334 | interval = maxPollInterval 335 | } 336 | } 337 | 338 | runtime.Gosched() 339 | } 340 | 341 | // Unblock callers waiting in Read() for data that will never arrive, 342 | // and callers waiting in Write() for data that will never get sent. 343 | close(c.workerRdChan) 344 | close(c.workerWrChan) 345 | 346 | // Close the connection (extra calls to Close() are harmless). 347 | _ = c.Close() 348 | } 349 | 350 | func newMeekConn(dialFn base.DialFunc, ca *meekClientArgs) (net.Conn, error) { 351 | id, err := newSessionID() 352 | if err != nil { 353 | return nil, err 354 | } 355 | 356 | conn := &meekConn{ 357 | args: ca, 358 | sessionID: id, 359 | transport: &http.Transport{Dial: dialFn}, 360 | workerWrChan: make(chan []byte, maxChanBacklog), 361 | workerRdChan: make(chan []byte, maxChanBacklog), 362 | workerCloseChan: make(chan struct{}), 363 | } 364 | 365 | // Start the I/O worker. 366 | go conn.ioWorker() 367 | 368 | return conn, nil 369 | } 370 | 371 | func newSessionID() (string, error) { 372 | var b [64]byte 373 | if _, err := rand.Read(b[:]); err != nil { 374 | return "", err 375 | } 376 | h := sha256.Sum256(b[:]) 377 | return hex.EncodeToString(h[:16]), nil 378 | } 379 | 380 | var ( 381 | _ net.Conn = (*meekConn)(nil) 382 | _ net.Addr = (*meekClientArgs)(nil) 383 | ) 384 | -------------------------------------------------------------------------------- /common/socks5/socks5.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package socks5 implements a SOCKS 5 server and the required pluggable 29 | // transport specific extensions. For more information see RFC 1928 and RFC 30 | // 1929. 31 | // 32 | // Notes: 33 | // - GSSAPI authentication, is NOT supported. 34 | // - Only the CONNECT command is supported. 35 | // - The authentication provided by the client is always accepted as it is 36 | // used as a channel to pass information rather than for authentication for 37 | // pluggable transports. 38 | package socks5 // import "gitlab.com/yawning/obfs4.git/common/socks5" 39 | 40 | import ( 41 | "bufio" 42 | "bytes" 43 | "errors" 44 | "fmt" 45 | "io" 46 | "net" 47 | "syscall" 48 | "time" 49 | 50 | "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" 51 | ) 52 | 53 | const ( 54 | version = 0x05 55 | rsv = 0x00 56 | 57 | cmdConnect = 0x01 58 | 59 | atypIPv4 = 0x01 60 | atypDomainName = 0x03 61 | atypIPv6 = 0x04 62 | 63 | authNoneRequired = 0x00 64 | authUsernamePassword = 0x02 65 | authNoAcceptableMethods = 0xff 66 | 67 | requestTimeout = 5 * time.Second 68 | ) 69 | 70 | // ReplyCode is a SOCKS 5 reply code. 71 | type ReplyCode byte 72 | 73 | // The various SOCKS 5 reply codes from RFC 1928. 74 | const ( 75 | ReplySucceeded ReplyCode = iota 76 | ReplyGeneralFailure 77 | ReplyConnectionNotAllowed 78 | ReplyNetworkUnreachable 79 | ReplyHostUnreachable 80 | ReplyConnectionRefused 81 | ReplyTTLExpired 82 | ReplyCommandNotSupported 83 | ReplyAddressNotSupported 84 | ) 85 | 86 | // Version returns a string suitable to be included in a call to Cmethod. 87 | func Version() string { 88 | return "socks5" 89 | } 90 | 91 | // ErrorToReplyCode converts an error to the "best" reply code. 92 | func ErrorToReplyCode(err error) ReplyCode { 93 | var opErr *net.OpError 94 | if !errors.As(err, &opErr) { 95 | return ReplyGeneralFailure 96 | } 97 | 98 | var errno syscall.Errno 99 | if !errors.As(opErr.Err, &errno) { 100 | return ReplyGeneralFailure 101 | } 102 | switch errno { //nolint:exhaustive 103 | case syscall.EADDRNOTAVAIL: 104 | return ReplyAddressNotSupported 105 | case syscall.ETIMEDOUT: 106 | return ReplyTTLExpired 107 | case syscall.ENETUNREACH: 108 | return ReplyNetworkUnreachable 109 | case syscall.EHOSTUNREACH: 110 | return ReplyHostUnreachable 111 | case syscall.ECONNREFUSED, syscall.ECONNRESET: 112 | return ReplyConnectionRefused 113 | default: 114 | return ReplyGeneralFailure 115 | } 116 | } 117 | 118 | // Request describes a SOCKS 5 request. 119 | type Request struct { 120 | Target string 121 | Args pt.Args 122 | rw *bufio.ReadWriter 123 | } 124 | 125 | // Handshake attempts to handle a incoming client handshake over the provided 126 | // connection and receive the SOCKS5 request. The routine handles sending 127 | // appropriate errors if applicable, but will not close the connection. 128 | func Handshake(conn net.Conn) (*Request, error) { 129 | // Arm the handshake timeout. 130 | var err error 131 | if err = conn.SetDeadline(time.Now().Add(requestTimeout)); err != nil { 132 | return nil, err 133 | } 134 | defer func() { 135 | // Disarm the handshake timeout, only propagate the error if 136 | // the handshake was successful. 137 | nerr := conn.SetDeadline(time.Time{}) 138 | if err == nil { 139 | err = nerr 140 | } 141 | }() 142 | 143 | req := new(Request) 144 | req.rw = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) 145 | 146 | // Negotiate the protocol version and authentication method. 147 | var method byte 148 | if method, err = req.negotiateAuth(); err != nil { 149 | return nil, err 150 | } 151 | 152 | // Authenticate if neccecary. 153 | if err = req.authenticate(method); err != nil { 154 | return nil, err 155 | } 156 | 157 | // Read the client command. 158 | if err = req.readCommand(); err != nil { 159 | return nil, err 160 | } 161 | 162 | return req, err 163 | } 164 | 165 | // Reply sends a SOCKS5 reply to the corresponding request. The BND.ADDR and 166 | // BND.PORT fields are always set to an address/port corresponding to 167 | // "0.0.0.0:0". 168 | func (req *Request) Reply(code ReplyCode) error { 169 | // The server sends a reply message. 170 | // uint8_t ver (0x05) 171 | // uint8_t rep 172 | // uint8_t rsv (0x00) 173 | // uint8_t atyp 174 | // uint8_t bnd_addr[] 175 | // uint16_t bnd_port 176 | 177 | var resp [4 + 4 + 2]byte 178 | resp[0] = version 179 | resp[1] = byte(code) 180 | resp[2] = rsv 181 | resp[3] = atypIPv4 182 | 183 | if _, err := req.rw.Write(resp[:]); err != nil { 184 | return err 185 | } 186 | 187 | return req.flushBuffers() 188 | } 189 | 190 | func (req *Request) negotiateAuth() (byte, error) { 191 | // The client sends a version identifier/selection message. 192 | // uint8_t ver (0x05) 193 | // uint8_t nmethods (>= 1). 194 | // uint8_t methods[nmethods] 195 | 196 | var err error 197 | if err = req.readByteVerify("version", version); err != nil { 198 | return 0, err 199 | } 200 | 201 | // Read the number of methods, and the methods. 202 | var nmethods byte 203 | method := byte(authNoAcceptableMethods) 204 | if nmethods, err = req.readByte(); err != nil { 205 | return method, err 206 | } 207 | var methods []byte 208 | if methods, err = req.readBytes(int(nmethods)); err != nil { 209 | return 0, err 210 | } 211 | 212 | // Pick the best authentication method, prioritizing authenticating 213 | // over not if both options are present. 214 | if bytes.IndexByte(methods, authUsernamePassword) != -1 { 215 | method = authUsernamePassword 216 | } else if bytes.IndexByte(methods, authNoneRequired) != -1 { 217 | method = authNoneRequired 218 | } 219 | 220 | // The server sends a method selection message. 221 | // uint8_t ver (0x05) 222 | // uint8_t method 223 | msg := []byte{version, method} 224 | if _, err = req.rw.Write(msg); err != nil { 225 | return 0, err 226 | } 227 | 228 | return method, req.flushBuffers() 229 | } 230 | 231 | func (req *Request) authenticate(method byte) error { 232 | switch method { 233 | case authNoneRequired: 234 | // No authentication required. 235 | case authUsernamePassword: 236 | if err := req.authRFC1929(); err != nil { 237 | return err 238 | } 239 | case authNoAcceptableMethods: 240 | return fmt.Errorf("no acceptable authentication methods") 241 | default: 242 | // This should never happen as only supported auth methods should be 243 | // negotiated. 244 | return fmt.Errorf("negotiated unsupported method 0x%02x", method) 245 | } 246 | 247 | return req.flushBuffers() 248 | } 249 | 250 | func (req *Request) readCommand() error { 251 | // The client sends the request details. 252 | // uint8_t ver (0x05) 253 | // uint8_t cmd 254 | // uint8_t rsv (0x00) 255 | // uint8_t atyp 256 | // uint8_t dst_addr[] 257 | // uint16_t dst_port 258 | 259 | var err error 260 | if err = req.readByteVerify("version", version); err != nil { 261 | _ = req.Reply(ReplyGeneralFailure) 262 | return err 263 | } 264 | if err = req.readByteVerify("command", cmdConnect); err != nil { 265 | _ = req.Reply(ReplyCommandNotSupported) 266 | return err 267 | } 268 | if err = req.readByteVerify("reserved", rsv); err != nil { 269 | _ = req.Reply(ReplyGeneralFailure) 270 | return err 271 | } 272 | 273 | // Read the destination address/port. 274 | var atyp byte 275 | var host string 276 | if atyp, err = req.readByte(); err != nil { 277 | _ = req.Reply(ReplyGeneralFailure) 278 | return err 279 | } 280 | switch atyp { 281 | case atypIPv4: 282 | var addr []byte 283 | if addr, err = req.readBytes(net.IPv4len); err != nil { 284 | _ = req.Reply(ReplyGeneralFailure) 285 | return err 286 | } 287 | host = net.IPv4(addr[0], addr[1], addr[2], addr[3]).String() 288 | case atypDomainName: 289 | var alen byte 290 | if alen, err = req.readByte(); err != nil { 291 | _ = req.Reply(ReplyGeneralFailure) 292 | return err 293 | } 294 | if alen == 0 { 295 | _ = req.Reply(ReplyGeneralFailure) 296 | return fmt.Errorf("domain name with 0 length") 297 | } 298 | var addr []byte 299 | if addr, err = req.readBytes(int(alen)); err != nil { 300 | _ = req.Reply(ReplyGeneralFailure) 301 | return err 302 | } 303 | host = string(addr) 304 | case atypIPv6: 305 | var rawAddr []byte 306 | if rawAddr, err = req.readBytes(net.IPv6len); err != nil { 307 | _ = req.Reply(ReplyGeneralFailure) 308 | return err 309 | } 310 | addr := make(net.IP, net.IPv6len) 311 | copy(addr[:], rawAddr) 312 | host = fmt.Sprintf("[%s]", addr.String()) 313 | default: 314 | _ = req.Reply(ReplyAddressNotSupported) 315 | return fmt.Errorf("unsupported address type 0x%02x", atyp) 316 | } 317 | var rawPort []byte 318 | if rawPort, err = req.readBytes(2); err != nil { 319 | _ = req.Reply(ReplyGeneralFailure) 320 | return err 321 | } 322 | port := int(rawPort[0])<<8 | int(rawPort[1]) 323 | req.Target = fmt.Sprintf("%s:%d", host, port) 324 | 325 | return req.flushBuffers() 326 | } 327 | 328 | func (req *Request) flushBuffers() error { 329 | if err := req.rw.Flush(); err != nil { 330 | return err 331 | } 332 | if req.rw.Reader.Buffered() > 0 { 333 | return fmt.Errorf("read buffer has %d bytes of trailing data", req.rw.Reader.Buffered()) 334 | } 335 | return nil 336 | } 337 | 338 | func (req *Request) readByte() (byte, error) { 339 | return req.rw.ReadByte() 340 | } 341 | 342 | func (req *Request) readByteVerify(descr string, expected byte) error { 343 | val, err := req.rw.ReadByte() 344 | if err != nil { 345 | return err 346 | } 347 | if val != expected { 348 | return fmt.Errorf("message field '%s' was 0x%02x (expected 0x%02x)", descr, val, expected) 349 | } 350 | return nil 351 | } 352 | 353 | func (req *Request) readBytes(n int) ([]byte, error) { 354 | b := make([]byte, n) 355 | if _, err := io.ReadFull(req.rw, b); err != nil { 356 | return nil, err 357 | } 358 | return b, nil 359 | } 360 | -------------------------------------------------------------------------------- /transports/obfs4/framing/framing.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package framing implements the obfs4 link framing and cryptography. 29 | // 30 | // The Encoder/Decoder shared secret format is: 31 | // 32 | // uint8_t[32] NaCl secretbox key 33 | // uint8_t[16] NaCl Nonce prefix 34 | // uint8_t[16] SipHash-2-4 key (used to obfsucate length) 35 | // uint8_t[8] SipHash-2-4 IV 36 | // 37 | // The frame format is: 38 | // 39 | // uint16_t length (obfsucated, big endian) 40 | // NaCl secretbox (Poly1305/XSalsa20) containing: 41 | // uint8_t[16] tag (Part of the secretbox construct) 42 | // uint8_t[] payload 43 | // 44 | // The length field is length of the NaCl secretbox XORed with the truncated 45 | // SipHash-2-4 digest ran in OFB mode. 46 | // 47 | // Initialize K, IV[0] with values from the shared secret. 48 | // On each packet, IV[n] = H(K, IV[n - 1]) 49 | // mask[n] = IV[n][0:2] 50 | // obfsLen = length ^ mask[n] 51 | // 52 | // The NaCl secretbox (Poly1305/XSalsa20) nonce format is: 53 | // 54 | // uint8_t[24] prefix (Fixed) 55 | // uint64_t counter (Big endian) 56 | // 57 | // The counter is initialized to 1, and is incremented on each frame. Since 58 | // the protocol is designed to be used over a reliable medium, the nonce is not 59 | // transmitted over the wire as both sides of the conversation know the prefix 60 | // and the initial counter value. It is imperative that the counter does not 61 | // wrap, and sessions MUST terminate before 2^64 frames are sent. 62 | package framing // import "gitlab.com/yawning/obfs4.git/transports/obfs4/framing" 63 | 64 | import ( 65 | "bytes" 66 | "encoding/binary" 67 | "errors" 68 | "fmt" 69 | "io" 70 | 71 | "golang.org/x/crypto/nacl/secretbox" 72 | 73 | "gitlab.com/yawning/obfs4.git/common/csrand" 74 | "gitlab.com/yawning/obfs4.git/common/drbg" 75 | ) 76 | 77 | const ( 78 | // MaximumSegmentLength is the length of the largest possible segment 79 | // including overhead. 80 | MaximumSegmentLength = 1500 - (40 + 12) 81 | 82 | // FrameOverhead is the length of the framing overhead. 83 | FrameOverhead = lengthLength + secretbox.Overhead 84 | 85 | // MaximumFramePayloadLength is the length of the maximum allowed payload 86 | // per frame. 87 | MaximumFramePayloadLength = MaximumSegmentLength - FrameOverhead 88 | 89 | // KeyLength is the length of the Encoder/Decoder secret key. 90 | KeyLength = keyLength + noncePrefixLength + drbg.SeedLength 91 | 92 | maxFrameLength = MaximumSegmentLength - lengthLength 93 | minFrameLength = FrameOverhead - lengthLength 94 | 95 | keyLength = 32 96 | 97 | noncePrefixLength = 16 98 | nonceCounterLength = 8 99 | nonceLength = noncePrefixLength + nonceCounterLength 100 | 101 | lengthLength = 2 102 | ) 103 | 104 | // Error returned when Decoder.Decode() requires more data to continue. 105 | var ErrAgain = errors.New("framing: More data needed to decode") 106 | 107 | // Error returned when Decoder.Decode() failes to authenticate a frame. 108 | var ErrTagMismatch = errors.New("framing: Poly1305 tag mismatch") 109 | 110 | // Error returned when the NaCl secretbox nonce's counter wraps (FATAL). 111 | var ErrNonceCounterWrapped = errors.New("framing: Nonce counter wrapped") 112 | 113 | // InvalidPayloadLengthError is the error returned when Encoder.Encode() 114 | // rejects the payload length. 115 | type InvalidPayloadLengthError int 116 | 117 | func (e InvalidPayloadLengthError) Error() string { 118 | return fmt.Sprintf("framing: Invalid payload length: %d", int(e)) 119 | } 120 | 121 | type boxNonce struct { 122 | prefix [noncePrefixLength]byte 123 | counter uint64 124 | } 125 | 126 | func (nonce *boxNonce) init(prefix []byte) { 127 | if noncePrefixLength != len(prefix) { 128 | panic(fmt.Sprintf("BUG: Nonce prefix length invalid: %d", len(prefix))) 129 | } 130 | 131 | copy(nonce.prefix[:], prefix) 132 | nonce.counter = 1 133 | } 134 | 135 | func (nonce boxNonce) bytes(out *[nonceLength]byte) error { 136 | // The security guarantee of Poly1305 is broken if a nonce is ever reused 137 | // for a given key. Detect this by checking for counter wraparound since 138 | // we start each counter at 1. If it ever happens that more than 2^64 - 1 139 | // frames are transmitted over a given connection, support for rekeying 140 | // will be neccecary, but that's unlikely to happen. 141 | if nonce.counter == 0 { 142 | return ErrNonceCounterWrapped 143 | } 144 | 145 | copy(out[:], nonce.prefix[:]) 146 | binary.BigEndian.PutUint64(out[noncePrefixLength:], nonce.counter) 147 | 148 | return nil 149 | } 150 | 151 | // Encoder is a frame encoder instance. 152 | type Encoder struct { 153 | key [keyLength]byte 154 | nonce boxNonce 155 | drbg *drbg.HashDrbg 156 | } 157 | 158 | // NewEncoder creates a new Encoder instance. It must be supplied a slice 159 | // containing exactly KeyLength bytes of keying material. 160 | func NewEncoder(key []byte) *Encoder { 161 | if len(key) != KeyLength { 162 | panic(fmt.Sprintf("BUG: Invalid encoder key length: %d", len(key))) 163 | } 164 | 165 | encoder := new(Encoder) 166 | copy(encoder.key[:], key[0:keyLength]) 167 | encoder.nonce.init(key[keyLength : keyLength+noncePrefixLength]) 168 | seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:]) 169 | if err != nil { 170 | panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err)) 171 | } 172 | encoder.drbg, _ = drbg.NewHashDrbg(seed) 173 | 174 | return encoder 175 | } 176 | 177 | // Encode encodes a single frame worth of payload and returns the encoded 178 | // length. InvalidPayloadLengthError is recoverable, all other errors MUST be 179 | // treated as fatal and the session aborted. 180 | func (encoder *Encoder) Encode(frame, payload []byte) (int, error) { 181 | payloadLen := len(payload) 182 | if MaximumFramePayloadLength < payloadLen { 183 | return 0, InvalidPayloadLengthError(payloadLen) 184 | } 185 | if len(frame) < payloadLen+FrameOverhead { 186 | return 0, io.ErrShortBuffer 187 | } 188 | 189 | // Generate a new nonce. 190 | var nonce [nonceLength]byte 191 | if err := encoder.nonce.bytes(&nonce); err != nil { 192 | return 0, err 193 | } 194 | encoder.nonce.counter++ 195 | 196 | // Encrypt and MAC payload. 197 | box := secretbox.Seal(frame[:lengthLength], payload, &nonce, &encoder.key) 198 | 199 | // Obfuscate the length. 200 | length := uint16(len(box) - lengthLength) 201 | lengthMask := encoder.drbg.NextBlock() 202 | length ^= binary.BigEndian.Uint16(lengthMask) 203 | binary.BigEndian.PutUint16(frame[:2], length) 204 | 205 | // Return the frame. 206 | return len(box), nil 207 | } 208 | 209 | // Decoder is a frame decoder instance. 210 | type Decoder struct { 211 | key [keyLength]byte 212 | nonce boxNonce 213 | drbg *drbg.HashDrbg 214 | 215 | nextNonce [nonceLength]byte 216 | nextLength uint16 217 | nextLengthInvalid bool 218 | } 219 | 220 | // NewDecoder creates a new Decoder instance. It must be supplied a slice 221 | // containing exactly KeyLength bytes of keying material. 222 | func NewDecoder(key []byte) *Decoder { 223 | if len(key) != KeyLength { 224 | panic(fmt.Sprintf("BUG: Invalid decoder key length: %d", len(key))) 225 | } 226 | 227 | decoder := new(Decoder) 228 | copy(decoder.key[:], key[0:keyLength]) 229 | decoder.nonce.init(key[keyLength : keyLength+noncePrefixLength]) 230 | seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:]) 231 | if err != nil { 232 | panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err)) 233 | } 234 | decoder.drbg, _ = drbg.NewHashDrbg(seed) 235 | 236 | return decoder 237 | } 238 | 239 | // Decode decodes a stream of data and returns the length if any. ErrAgain is 240 | // a temporary failure, all other errors MUST be treated as fatal and the 241 | // session aborted. 242 | func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) { 243 | // A length of 0 indicates that we do not know how big the next frame is 244 | // going to be. 245 | if decoder.nextLength == 0 { 246 | // Attempt to pull out the next frame length. 247 | if lengthLength > frames.Len() { 248 | return 0, ErrAgain 249 | } 250 | 251 | // Remove the length field from the buffer. 252 | var obfsLen [lengthLength]byte 253 | _, err := io.ReadFull(frames, obfsLen[:]) 254 | if err != nil { 255 | return 0, err 256 | } 257 | 258 | // Derive the nonce the peer used. 259 | if err = decoder.nonce.bytes(&decoder.nextNonce); err != nil { 260 | return 0, err 261 | } 262 | 263 | // Deobfuscate the length field. 264 | length := binary.BigEndian.Uint16(obfsLen[:]) 265 | lengthMask := decoder.drbg.NextBlock() 266 | length ^= binary.BigEndian.Uint16(lengthMask) 267 | if maxFrameLength < length || minFrameLength > length { 268 | // Per "Plaintext Recovery Attacks Against SSH" by 269 | // Martin R. Albrecht, Kenneth G. Paterson and Gaven J. Watson, 270 | // there are a class of attacks againt protocols that use similar 271 | // sorts of framing schemes. 272 | // 273 | // While obfs4 should not allow plaintext recovery (CBC mode is 274 | // not used), attempt to mitigate out of bound frame length errors 275 | // by pretending that the length was a random valid range as per 276 | // the countermeasure suggested by Denis Bider in section 6 of the 277 | // paper. 278 | 279 | decoder.nextLengthInvalid = true 280 | length = uint16(csrand.IntRange(minFrameLength, maxFrameLength)) 281 | } 282 | decoder.nextLength = length 283 | } 284 | 285 | if int(decoder.nextLength) > frames.Len() { 286 | return 0, ErrAgain 287 | } 288 | 289 | // Unseal the frame. 290 | var box [maxFrameLength]byte 291 | n, err := io.ReadFull(frames, box[:decoder.nextLength]) 292 | if err != nil { 293 | return 0, err 294 | } 295 | out, ok := secretbox.Open(data[:0], box[:n], &decoder.nextNonce, &decoder.key) 296 | if !ok || decoder.nextLengthInvalid { 297 | // When a random length is used (on length error) the tag should always 298 | // mismatch, but be paranoid. 299 | return 0, ErrTagMismatch 300 | } 301 | 302 | // Clean up and prepare for the next frame. 303 | decoder.nextLength = 0 304 | decoder.nonce.counter++ 305 | 306 | return len(out), nil 307 | } 308 | -------------------------------------------------------------------------------- /transports/obfs2/obfs2.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Yawning Angel 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | * POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Package obfs2 provides an implementation of the Tor Project's obfs2 29 | // obfuscation protocol. This protocol is considered trivially broken by most 30 | // sophisticated adversaries. 31 | package obfs2 // import "gitlab.com/yawning/obfs4.git/transports/obfs2" 32 | 33 | import ( 34 | "crypto/aes" 35 | "crypto/cipher" 36 | "crypto/sha256" 37 | "encoding/binary" 38 | "fmt" 39 | "io" 40 | "net" 41 | "time" 42 | 43 | "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" 44 | 45 | "gitlab.com/yawning/obfs4.git/common/csrand" 46 | "gitlab.com/yawning/obfs4.git/transports/base" 47 | ) 48 | 49 | const ( 50 | transportName = "obfs2" 51 | sharedSecretArg = "shared-secret" 52 | 53 | clientHandshakeTimeout = time.Duration(30) * time.Second 54 | serverHandshakeTimeout = time.Duration(30) * time.Second 55 | 56 | magicValue = 0x2bf5ca7e 57 | initiatorPadString = "Initiator obfuscation padding" 58 | responderPadString = "Responder obfuscation padding" 59 | initiatorKdfString = "Initiator obfuscated data" 60 | responderKdfString = "Responder obfuscated data" 61 | maxPadding = 8192 62 | keyLen = 16 63 | seedLen = 16 64 | hsLen = 4 + 4 65 | ) 66 | 67 | func validateArgs(args *pt.Args) error { 68 | if _, ok := args.Get(sharedSecretArg); ok { 69 | // "shared-secret" is something no bridges use in practice and is thus 70 | // unimplemented. 71 | return fmt.Errorf("unsupported argument '%s'", sharedSecretArg) 72 | } 73 | return nil 74 | } 75 | 76 | // Transport is the obfs2 implementation of the base.Transport interface. 77 | type Transport struct{} 78 | 79 | // Name returns the name of the obfs2 transport protocol. 80 | func (t *Transport) Name() string { 81 | return transportName 82 | } 83 | 84 | // ClientFactory returns a new obfs2ClientFactory instance. 85 | func (t *Transport) ClientFactory(_ string) (base.ClientFactory, error) { 86 | cf := &obfs2ClientFactory{transport: t} 87 | return cf, nil 88 | } 89 | 90 | // ServerFactory returns a new obfs2ServerFactory instance. 91 | func (t *Transport) ServerFactory(_ string, args *pt.Args) (base.ServerFactory, error) { 92 | if err := validateArgs(args); err != nil { 93 | return nil, err 94 | } 95 | 96 | sf := &obfs2ServerFactory{t} 97 | return sf, nil 98 | } 99 | 100 | type obfs2ClientFactory struct { 101 | transport base.Transport 102 | } 103 | 104 | func (cf *obfs2ClientFactory) Transport() base.Transport { 105 | return cf.transport 106 | } 107 | 108 | func (cf *obfs2ClientFactory) ParseArgs(args *pt.Args) (any, error) { 109 | return nil, validateArgs(args) 110 | } 111 | 112 | func (cf *obfs2ClientFactory) Dial(network, addr string, dialFn base.DialFunc, _ any) (net.Conn, error) { 113 | conn, err := dialFn(network, addr) 114 | if err != nil { 115 | return nil, err 116 | } 117 | dialConn := conn 118 | if conn, err = newObfs2ClientConn(conn); err != nil { 119 | dialConn.Close() 120 | return nil, err 121 | } 122 | return conn, nil 123 | } 124 | 125 | type obfs2ServerFactory struct { 126 | transport base.Transport 127 | } 128 | 129 | func (sf *obfs2ServerFactory) Transport() base.Transport { 130 | return sf.transport 131 | } 132 | 133 | func (sf *obfs2ServerFactory) Args() *pt.Args { 134 | return nil 135 | } 136 | 137 | func (sf *obfs2ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) { 138 | return newObfs2ServerConn(conn) 139 | } 140 | 141 | type obfs2Conn struct { 142 | net.Conn 143 | 144 | isInitiator bool 145 | 146 | rx *cipher.StreamReader 147 | tx *cipher.StreamWriter 148 | } 149 | 150 | func (conn *obfs2Conn) Read(b []byte) (int, error) { 151 | return conn.rx.Read(b) 152 | } 153 | 154 | func (conn *obfs2Conn) Write(b []byte) (int, error) { 155 | return conn.tx.Write(b) 156 | } 157 | 158 | func newObfs2ClientConn(conn net.Conn) (*obfs2Conn, error) { 159 | // Initialize a client connection, and start the handshake timeout. 160 | c := &obfs2Conn{conn, true, nil, nil} 161 | deadline := time.Now().Add(clientHandshakeTimeout) 162 | if err := c.SetDeadline(deadline); err != nil { 163 | return nil, err 164 | } 165 | 166 | // Handshake. 167 | if err := c.handshake(); err != nil { 168 | return nil, err 169 | } 170 | 171 | // Disarm the handshake timer. 172 | if err := c.SetDeadline(time.Time{}); err != nil { 173 | return nil, err 174 | } 175 | 176 | return c, nil 177 | } 178 | 179 | func newObfs2ServerConn(conn net.Conn) (*obfs2Conn, error) { 180 | // Initialize a server connection, and start the handshake timeout. 181 | c := &obfs2Conn{conn, false, nil, nil} 182 | deadline := time.Now().Add(serverHandshakeTimeout) 183 | if err := c.SetDeadline(deadline); err != nil { 184 | return nil, err 185 | } 186 | 187 | // Handshake. 188 | if err := c.handshake(); err != nil { 189 | return nil, err 190 | } 191 | 192 | // Disarm the handshake timer. 193 | if err := c.SetDeadline(time.Time{}); err != nil { 194 | return nil, err 195 | } 196 | 197 | return c, nil 198 | } 199 | 200 | func (conn *obfs2Conn) handshake() error { 201 | // Each begins by generating a seed and a padding key as follows. 202 | // The initiator generates: 203 | // 204 | // INIT_SEED = SR(SEED_LENGTH) 205 | // INIT_PAD_KEY = MAC("Initiator obfuscation padding", INIT_SEED)[:KEYLEN] 206 | // 207 | // And the responder generates: 208 | // 209 | // RESP_SEED = SR(SEED_LENGTH) 210 | // RESP_PAD_KEY = MAC("Responder obfuscation padding", INIT_SEED)[:KEYLEN] 211 | // 212 | // Each then generates a random number PADLEN in range from 0 through 213 | // MAX_PADDING (inclusive). 214 | var seed [seedLen]byte 215 | if err := csrand.Bytes(seed[:]); err != nil { 216 | return err 217 | } 218 | var padMagic []byte 219 | if conn.isInitiator { 220 | padMagic = []byte(initiatorPadString) 221 | } else { 222 | padMagic = []byte(responderPadString) 223 | } 224 | padKey, padIV := hsKdf(padMagic, seed[:]) 225 | padLen := uint32(csrand.IntRange(0, maxPadding)) 226 | 227 | hsBlob := make([]byte, hsLen+padLen) 228 | binary.BigEndian.PutUint32(hsBlob[0:4], magicValue) 229 | binary.BigEndian.PutUint32(hsBlob[4:8], padLen) 230 | if padLen > 0 { 231 | if err := csrand.Bytes(hsBlob[8:]); err != nil { 232 | return err 233 | } 234 | } 235 | 236 | // The initiator then sends: 237 | // 238 | // INIT_SEED | E(INIT_PAD_KEY, UINT32(MAGIC_VALUE) | UINT32(PADLEN) | WR(PADLEN)) 239 | // 240 | // and the responder sends: 241 | // 242 | // RESP_SEED | E(RESP_PAD_KEY, UINT32(MAGIC_VALUE) | UINT32(PADLEN) | WR(PADLEN)) 243 | txBlock, err := aes.NewCipher(padKey) 244 | if err != nil { 245 | return err 246 | } 247 | txStream := cipher.NewCTR(txBlock, padIV) 248 | conn.tx = &cipher.StreamWriter{S: txStream, W: conn.Conn} 249 | if _, err := conn.Conn.Write(seed[:]); err != nil { 250 | return err 251 | } 252 | if _, err := conn.Write(hsBlob); err != nil { 253 | return err 254 | } 255 | 256 | // Upon receiving the SEED from the other party, each party derives 257 | // the other party's padding key value as above, and decrypts the next 258 | // 8 bytes of the key establishment message. 259 | var peerSeed [seedLen]byte 260 | if _, err := io.ReadFull(conn.Conn, peerSeed[:]); err != nil { 261 | return err 262 | } 263 | var peerPadMagic []byte 264 | if conn.isInitiator { 265 | peerPadMagic = []byte(responderPadString) 266 | } else { 267 | peerPadMagic = []byte(initiatorPadString) 268 | } 269 | peerKey, peerIV := hsKdf(peerPadMagic, peerSeed[:]) 270 | rxBlock, err := aes.NewCipher(peerKey) 271 | if err != nil { 272 | return err 273 | } 274 | rxStream := cipher.NewCTR(rxBlock, peerIV) 275 | conn.rx = &cipher.StreamReader{S: rxStream, R: conn.Conn} 276 | hsHdr := make([]byte, hsLen) 277 | if _, err := io.ReadFull(conn, hsHdr); err != nil { 278 | return err 279 | } 280 | 281 | // If the MAGIC_VALUE does not match, or the PADLEN value is greater than 282 | // MAX_PADDING, the party receiving it should close the connection 283 | // immediately. 284 | if peerMagic := binary.BigEndian.Uint32(hsHdr[0:4]); peerMagic != magicValue { 285 | return fmt.Errorf("invalid magic value: %x", peerMagic) 286 | } 287 | padLen = binary.BigEndian.Uint32(hsHdr[4:8]) 288 | if padLen > maxPadding { 289 | return fmt.Errorf("padlen too long: %d", padLen) 290 | } 291 | 292 | // Otherwise, it should read the remaining PADLEN bytes of padding data 293 | // and discard them. 294 | tmp := make([]byte, padLen) 295 | if _, err := io.ReadFull(conn.Conn, tmp); err != nil { // Note: Skips AES. 296 | return err 297 | } 298 | 299 | // Derive the actual keys. 300 | return conn.kdf(seed[:], peerSeed[:]) 301 | } 302 | 303 | func (conn *obfs2Conn) kdf(seed, peerSeed []byte) error { 304 | // Additional keys are then derived as: 305 | // 306 | // INIT_SECRET = MAC("Initiator obfuscated data", INIT_SEED|RESP_SEED) 307 | // RESP_SECRET = MAC("Responder obfuscated data", INIT_SEED|RESP_SEED) 308 | // INIT_KEY = INIT_SECRET[:KEYLEN] 309 | // INIT_IV = INIT_SECRET[KEYLEN:] 310 | // RESP_KEY = RESP_SECRET[:KEYLEN] 311 | // RESP_IV = RESP_SECRET[KEYLEN:] 312 | combSeed := make([]byte, 0, seedLen*2) 313 | if conn.isInitiator { 314 | combSeed = append(combSeed, seed...) 315 | combSeed = append(combSeed, peerSeed...) 316 | } else { 317 | combSeed = append(combSeed, peerSeed...) 318 | combSeed = append(combSeed, seed...) 319 | } 320 | 321 | initKey, initIV := hsKdf([]byte(initiatorKdfString), combSeed) 322 | initBlock, err := aes.NewCipher(initKey) 323 | if err != nil { 324 | return err 325 | } 326 | initStream := cipher.NewCTR(initBlock, initIV) 327 | 328 | respKey, respIV := hsKdf([]byte(responderKdfString), combSeed) 329 | respBlock, err := aes.NewCipher(respKey) 330 | if err != nil { 331 | return err 332 | } 333 | respStream := cipher.NewCTR(respBlock, respIV) 334 | 335 | if conn.isInitiator { 336 | conn.tx.S = initStream 337 | conn.rx.S = respStream 338 | } else { 339 | conn.tx.S = respStream 340 | conn.rx.S = initStream 341 | } 342 | 343 | return nil 344 | } 345 | 346 | func hsKdf(magic, seed []byte) ([]byte, []byte) { 347 | // The actual key/IV is derived in the form of: 348 | // m = MAC(magic, seed) 349 | // KEY = m[:KEYLEN] 350 | // IV = m[KEYLEN:] 351 | m := mac(magic, seed) 352 | padKey := m[:keyLen] 353 | padIV := m[keyLen:] 354 | 355 | return padKey, padIV 356 | } 357 | 358 | func mac(s, x []byte) []byte { 359 | // H(x) is SHA256 of x. 360 | // MAC(s, x) = H(s | x | s) 361 | h := sha256.New() 362 | _, _ = h.Write(s) 363 | _, _ = h.Write(x) 364 | _, _ = h.Write(s) 365 | return h.Sum(nil) 366 | } 367 | 368 | var ( 369 | _ base.ClientFactory = (*obfs2ClientFactory)(nil) 370 | _ base.ServerFactory = (*obfs2ServerFactory)(nil) 371 | _ base.Transport = (*Transport)(nil) 372 | _ net.Conn = (*obfs2Conn)(nil) 373 | ) 374 | --------------------------------------------------------------------------------