Browse Source

More explicitly replace gotapdance and obsf4

Rod Hynes 3 năm trước cách đây
mục cha
commit
1d167a5601
42 tập tin đã thay đổi với 3046 bổ sung11 xóa
  1. 11 1
      go.mod
  2. 0 6
      go.sum
  3. 0 0
      replace/gotapdance/LICENSE
  4. 0 0
      replace/gotapdance/README.md
  5. 0 0
      replace/gotapdance/build-apk.sh
  6. 0 0
      replace/gotapdance/ed25519/LICENSE
  7. 0 0
      replace/gotapdance/ed25519/edwards25519/const.go
  8. 0 0
      replace/gotapdance/ed25519/edwards25519/edwards25519.go
  9. 0 0
      replace/gotapdance/ed25519/extra25519/extra25519.go
  10. 0 0
      replace/gotapdance/go.mod
  11. 0 0
      replace/gotapdance/protobuf/Makefile
  12. 0 0
      replace/gotapdance/protobuf/README.md
  13. 0 0
      replace/gotapdance/protobuf/extensions.go
  14. 0 0
      replace/gotapdance/protobuf/signalling.pb.go
  15. 0 0
      replace/gotapdance/protobuf/signalling.proto
  16. 0 0
      replace/gotapdance/tapdance/TODO
  17. 0 0
      replace/gotapdance/tapdance/assets.go
  18. 0 0
      replace/gotapdance/tapdance/common.go
  19. 0 0
      replace/gotapdance/tapdance/conjure.go
  20. 0 0
      replace/gotapdance/tapdance/conn_flow.go
  21. 0 0
      replace/gotapdance/tapdance/conn_raw.go
  22. 0 0
      replace/gotapdance/tapdance/counter.go
  23. 0 0
      replace/gotapdance/tapdance/dialer.go
  24. 0 0
      replace/gotapdance/tapdance/logger.go
  25. 0 0
      replace/gotapdance/tapdance/phantoms/phantoms.go
  26. 0 0
      replace/gotapdance/tapdance/registrar_bidirectional.go
  27. 0 0
      replace/gotapdance/tapdance/tapdance.go
  28. 0 0
      replace/gotapdance/tapdance/utils.go
  29. 55 0
      replace/obfs4.git/LICENSE
  30. 101 0
      replace/obfs4.git/common/csrand/csrand.go
  31. 148 0
      replace/obfs4.git/common/drbg/hash_drbg.go
  32. 431 0
      replace/obfs4.git/common/ntor/ntor.go
  33. 245 0
      replace/obfs4.git/common/probdist/weighted_dist.go
  34. 146 0
      replace/obfs4.git/common/replayfilter/replay_filter.go
  35. 3 0
      replace/obfs4.git/go.mod
  36. 90 0
      replace/obfs4.git/transports/base/base.go
  37. 305 0
      replace/obfs4.git/transports/obfs4/framing/framing.go
  38. 424 0
      replace/obfs4.git/transports/obfs4/handshake_ntor.go
  39. 647 0
      replace/obfs4.git/transports/obfs4/obfs4.go
  40. 175 0
      replace/obfs4.git/transports/obfs4/packet.go
  41. 260 0
      replace/obfs4.git/transports/obfs4/statefile.go
  42. 5 4
      vendor/modules.txt

+ 11 - 1
go.mod

@@ -2,7 +2,17 @@ module github.com/Psiphon-Labs/psiphon-tunnel-core
 
 go 1.19
 
-replace github.com/refraction-networking/gotapdance => ./psiphon/common/refraction/gotapdance
+// When this is the main module, use a patched version of
+// refraction/gotapdance with
+// https://github.com/Psiphon-Labs/psiphon-tunnel-core/commit/2a4121d9
+replace github.com/refraction-networking/gotapdance => ./replace/gotapdance
+
+// When this is the main module, gitlab.com/yawning/obfs4, used by
+// refraction-networking/gotapdance, is pinned at 816cff15 the last revision
+// published without a GPL license. This version lacks obfuscation
+// improvements added in revision 1a6129b6, but these changes apply only on
+// the server side.
+replace gitlab.com/yawning/obfs4.git => ./replace/obfs4.git
 
 require (
 	github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e

+ 0 - 6
go.sum

@@ -1,4 +1,3 @@
-git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q=
 git.torproject.org/pluggable-transports/goptlib.git v1.2.0 h1:0qRF7Dw5qXd0FtZkjWUiAh5GTutRtDGL4GXUDJ4qMHs=
 git.torproject.org/pluggable-transports/goptlib.git v1.2.0/go.mod h1:4PBMl1dg7/3vMWSoWb46eGWlrxkUyn/CAJmxhDLAlDs=
 github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57 h1:CVuXDbdzPW0XCNYTldy5dQues57geAs+vfwz3FTTpy8=
@@ -37,7 +36,6 @@ github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea/go.mod h1:M
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dchest/siphash v1.2.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
 github.com/dchest/siphash v1.2.3-0.20201109081723-a21c2e7914a8 h1:/3Ns/G7byyjzX76ElLmWJzmr4Ln2xAJy5mXdyowmGck=
 github.com/dchest/siphash v1.2.3-0.20201109081723-a21c2e7914a8/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
 github.com/deckarep/golang-set v0.0.0-20171013212420-1d4478f51bed h1:njG8LmGD6JCWJu4bwIKmkOHvch70UOEIqczl5vp7Gok=
@@ -204,9 +202,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea h1:CyhwejzVGvZ3Q2PSbQ4NRRYn+ZWv5eS1vlaEusT+bAI=
 github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea/go.mod h1:eNr558nEUjP8acGw8FFjTeWvSgU1stO7FAO6eknhHe4=
-gitlab.com/yawning/obfs4.git v0.0.0-20190120164510-816cff15f425 h1:aa4G2gzp5dbnBKJU2GfXyuFAA0xLMCcy7JoeMpDHDp0=
-gitlab.com/yawning/obfs4.git v0.0.0-20190120164510-816cff15f425/go.mod h1:uVzlS2REpJUn1Ey9SUKePjW2Qk9yNfNSxYUlbaefaDs=
-golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -222,7 +217,6 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=

+ 0 - 0
psiphon/common/refraction/gotapdance/LICENSE → replace/gotapdance/LICENSE


+ 0 - 0
psiphon/common/refraction/gotapdance/README.md → replace/gotapdance/README.md


+ 0 - 0
psiphon/common/refraction/gotapdance/build-apk.sh → replace/gotapdance/build-apk.sh


+ 0 - 0
psiphon/common/refraction/gotapdance/ed25519/LICENSE → replace/gotapdance/ed25519/LICENSE


+ 0 - 0
psiphon/common/refraction/gotapdance/ed25519/edwards25519/const.go → replace/gotapdance/ed25519/edwards25519/const.go


+ 0 - 0
psiphon/common/refraction/gotapdance/ed25519/edwards25519/edwards25519.go → replace/gotapdance/ed25519/edwards25519/edwards25519.go


+ 0 - 0
psiphon/common/refraction/gotapdance/ed25519/extra25519/extra25519.go → replace/gotapdance/ed25519/extra25519/extra25519.go


+ 0 - 0
psiphon/common/refraction/gotapdance/go.mod → replace/gotapdance/go.mod


+ 0 - 0
psiphon/common/refraction/gotapdance/protobuf/Makefile → replace/gotapdance/protobuf/Makefile


+ 0 - 0
psiphon/common/refraction/gotapdance/protobuf/README.md → replace/gotapdance/protobuf/README.md


+ 0 - 0
psiphon/common/refraction/gotapdance/protobuf/extensions.go → replace/gotapdance/protobuf/extensions.go


+ 0 - 0
psiphon/common/refraction/gotapdance/protobuf/signalling.pb.go → replace/gotapdance/protobuf/signalling.pb.go


+ 0 - 0
psiphon/common/refraction/gotapdance/protobuf/signalling.proto → replace/gotapdance/protobuf/signalling.proto


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/TODO → replace/gotapdance/tapdance/TODO


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/assets.go → replace/gotapdance/tapdance/assets.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/common.go → replace/gotapdance/tapdance/common.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/conjure.go → replace/gotapdance/tapdance/conjure.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/conn_flow.go → replace/gotapdance/tapdance/conn_flow.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/conn_raw.go → replace/gotapdance/tapdance/conn_raw.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/counter.go → replace/gotapdance/tapdance/counter.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/dialer.go → replace/gotapdance/tapdance/dialer.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/logger.go → replace/gotapdance/tapdance/logger.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/phantoms/phantoms.go → replace/gotapdance/tapdance/phantoms/phantoms.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/registrar_bidirectional.go → replace/gotapdance/tapdance/registrar_bidirectional.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/tapdance.go → replace/gotapdance/tapdance/tapdance.go


+ 0 - 0
psiphon/common/refraction/gotapdance/tapdance/utils.go → replace/gotapdance/tapdance/utils.go


+ 55 - 0
replace/obfs4.git/LICENSE

@@ -0,0 +1,55 @@
+Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+==============================================================================
+
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+

+ 101 - 0
replace/obfs4.git/common/csrand/csrand.go

@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Package csrand implements the math/rand interface over crypto/rand, along
+// with some utility functions for common random number/byte related tasks.
+//
+// Not all of the convinience routines are replicated, only those that are
+// immediately useful.  The Rand variable provides access to the full math/rand
+// API.
+package csrand // import "gitlab.com/yawning/obfs4.git/common/csrand"
+
+import (
+	cryptRand "crypto/rand"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"math/rand"
+)
+
+var (
+	csRandSourceInstance csRandSource
+
+	// Rand is a math/rand instance backed by crypto/rand CSPRNG.
+	Rand = rand.New(csRandSourceInstance)
+)
+
+type csRandSource struct {
+	// This does not keep any state as it is backed by crypto/rand.
+}
+
+func (r csRandSource) Int63() int64 {
+	var src [8]byte
+	if err := Bytes(src[:]); err != nil {
+		panic(err)
+	}
+	val := binary.BigEndian.Uint64(src[:])
+	val &= (1<<63 - 1)
+
+	return int64(val)
+}
+
+func (r csRandSource) Seed(seed int64) {
+	// No-op.
+}
+
+// Intn returns, as a int, a pseudo random number in [0, n).
+func Intn(n int) int {
+	return Rand.Intn(n)
+}
+
+// Float64 returns, as a float64, a pesudo random number in [0.0,1.0).
+func Float64() float64 {
+	return Rand.Float64()
+}
+
+// IntRange returns a uniformly distributed int [min, max].
+func IntRange(min, max int) int {
+	if max < min {
+		panic(fmt.Sprintf("IntRange: min > max (%d, %d)", min, max))
+	}
+
+	r := (max + 1) - min
+	ret := Rand.Intn(r)
+	return ret + min
+}
+
+// Bytes fills the slice with random data.
+func Bytes(buf []byte) error {
+	if _, err := io.ReadFull(cryptRand.Reader, buf); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Reader is a alias of rand.Reader.
+var Reader = cryptRand.Reader

+ 148 - 0
replace/obfs4.git/common/drbg/hash_drbg.go

@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Package drbg implements a minimalistic DRBG based off SipHash-2-4 in OFB
+// mode.
+package drbg // import "gitlab.com/yawning/obfs4.git/common/drbg"
+
+import (
+	"encoding/binary"
+	"encoding/hex"
+	"fmt"
+	"hash"
+
+	"github.com/dchest/siphash"
+	"gitlab.com/yawning/obfs4.git/common/csrand"
+)
+
+// Size is the length of the HashDrbg output.
+const Size = siphash.Size
+
+// SeedLength is the length of the HashDrbg seed.
+const SeedLength = 16 + Size
+
+// Seed is the initial state for a HashDrbg.  It consists of a SipHash-2-4
+// key, and 8 bytes of initial data.
+type Seed [SeedLength]byte
+
+// Bytes returns a pointer to the raw HashDrbg seed.
+func (seed *Seed) Bytes() *[SeedLength]byte {
+	return (*[SeedLength]byte)(seed)
+}
+
+// Hex returns the hexdecimal representation of the seed.
+func (seed *Seed) Hex() string {
+	return hex.EncodeToString(seed.Bytes()[:])
+}
+
+// NewSeed returns a Seed initialized with the runtime CSPRNG.
+func NewSeed() (seed *Seed, err error) {
+	seed = new(Seed)
+	if err = csrand.Bytes(seed.Bytes()[:]); err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+// SeedFromBytes creates a Seed from the raw bytes, truncating to SeedLength as
+// appropriate.
+func SeedFromBytes(src []byte) (seed *Seed, err error) {
+	if len(src) < SeedLength {
+		return nil, InvalidSeedLengthError(len(src))
+	}
+
+	seed = new(Seed)
+	copy(seed.Bytes()[:], src)
+
+	return
+}
+
+// SeedFromHex creates a Seed from the hexdecimal representation, truncating to
+// SeedLength as appropriate.
+func SeedFromHex(encoded string) (seed *Seed, err error) {
+	var raw []byte
+	if raw, err = hex.DecodeString(encoded); err != nil {
+		return nil, err
+	}
+
+	return SeedFromBytes(raw)
+}
+
+// InvalidSeedLengthError is the error returned when the seed provided to the
+// DRBG is an invalid length.
+type InvalidSeedLengthError int
+
+func (e InvalidSeedLengthError) Error() string {
+	return fmt.Sprintf("invalid seed length: %d", int(e))
+}
+
+// HashDrbg is a CSDRBG based off of SipHash-2-4 in OFB mode.
+type HashDrbg struct {
+	sip hash.Hash64
+	ofb [Size]byte
+}
+
+// NewHashDrbg makes a HashDrbg instance based off an optional seed.  The seed
+// is truncated to SeedLength.
+func NewHashDrbg(seed *Seed) (*HashDrbg, error) {
+	drbg := new(HashDrbg)
+	if seed == nil {
+		var err error
+		if seed, err = NewSeed(); err != nil {
+			return nil, err
+		}
+	}
+	drbg.sip = siphash.New(seed.Bytes()[:16])
+	copy(drbg.ofb[:], seed.Bytes()[16:])
+
+	return drbg, nil
+}
+
+// Int63 returns a uniformly distributed random integer [0, 1 << 63).
+func (drbg *HashDrbg) Int63() int64 {
+	block := drbg.NextBlock()
+	ret := binary.BigEndian.Uint64(block)
+	ret &= (1<<63 - 1)
+
+	return int64(ret)
+}
+
+// Seed does nothing, call NewHashDrbg if you want to reseed.
+func (drbg *HashDrbg) Seed(seed int64) {
+	// No-op.
+}
+
+// NextBlock returns the next 8 byte DRBG block.
+func (drbg *HashDrbg) NextBlock() []byte {
+	_, _ = drbg.sip.Write(drbg.ofb[:])
+	copy(drbg.ofb[:], drbg.sip.Sum(nil))
+
+	ret := make([]byte, Size)
+	copy(ret, drbg.ofb[:])
+	return ret
+}

+ 431 - 0
replace/obfs4.git/common/ntor/ntor.go

@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Package ntor implements the Tor Project's ntor handshake as defined in
+// proposal 216 "Improved circuit-creation key exchange".  It also supports
+// using Elligator to transform the Curve25519 public keys sent over the wire
+// to a form that is indistinguishable from random strings.
+//
+// Before using this package, it is strongly recommended that the specification
+// is read and understood.
+package ntor // import "gitlab.com/yawning/obfs4.git/common/ntor"
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/sha256"
+	"crypto/subtle"
+	"encoding/hex"
+	"fmt"
+	"io"
+
+	"github.com/agl/ed25519/extra25519"
+	"gitlab.com/yawning/obfs4.git/common/csrand"
+	"golang.org/x/crypto/curve25519"
+	"golang.org/x/crypto/hkdf"
+)
+
+const (
+	// PublicKeyLength is the length of a Curve25519 public key.
+	PublicKeyLength = 32
+
+	// RepresentativeLength is the length of an Elligator representative.
+	RepresentativeLength = 32
+
+	// PrivateKeyLength is the length of a Curve25519 private key.
+	PrivateKeyLength = 32
+
+	// SharedSecretLength is the length of a Curve25519 shared secret.
+	SharedSecretLength = 32
+
+	// NodeIDLength is the length of a ntor node identifier.
+	NodeIDLength = 20
+
+	// KeySeedLength is the length of the derived KEY_SEED.
+	KeySeedLength = sha256.Size
+
+	// AuthLength is the lenght of the derived AUTH.
+	AuthLength = sha256.Size
+)
+
+var protoID = []byte("ntor-curve25519-sha256-1")
+var tMac = append(protoID, []byte(":mac")...)
+var tKey = append(protoID, []byte(":key_extract")...)
+var tVerify = append(protoID, []byte(":key_verify")...)
+var mExpand = append(protoID, []byte(":key_expand")...)
+
+// PublicKeyLengthError is the error returned when the public key being
+// imported is an invalid length.
+type PublicKeyLengthError int
+
+func (e PublicKeyLengthError) Error() string {
+	return fmt.Sprintf("ntor: Invalid Curve25519 public key length: %d",
+		int(e))
+}
+
+// PrivateKeyLengthError is the error returned when the private key being
+// imported is an invalid length.
+type PrivateKeyLengthError int
+
+func (e PrivateKeyLengthError) Error() string {
+	return fmt.Sprintf("ntor: Invalid Curve25519 private key length: %d",
+		int(e))
+}
+
+// NodeIDLengthError is the error returned when the node ID being imported is
+// an invalid length.
+type NodeIDLengthError int
+
+func (e NodeIDLengthError) Error() string {
+	return fmt.Sprintf("ntor: Invalid NodeID length: %d", int(e))
+}
+
+// KeySeed is the key material that results from a handshake (KEY_SEED).
+type KeySeed [KeySeedLength]byte
+
+// Bytes returns a pointer to the raw key material.
+func (key_seed *KeySeed) Bytes() *[KeySeedLength]byte {
+	return (*[KeySeedLength]byte)(key_seed)
+}
+
+// Auth is the verifier that results from a handshake (AUTH).
+type Auth [AuthLength]byte
+
+// Bytes returns a pointer to the raw auth.
+func (auth *Auth) Bytes() *[AuthLength]byte {
+	return (*[AuthLength]byte)(auth)
+}
+
+// NodeID is a ntor node identifier.
+type NodeID [NodeIDLength]byte
+
+// NewNodeID creates a NodeID from the raw bytes.
+func NewNodeID(raw []byte) (*NodeID, error) {
+	if len(raw) != NodeIDLength {
+		return nil, NodeIDLengthError(len(raw))
+	}
+
+	nodeID := new(NodeID)
+	copy(nodeID[:], raw)
+
+	return nodeID, nil
+}
+
+// NodeIDFromHex creates a new NodeID from the hexdecimal representation.
+func NodeIDFromHex(encoded string) (*NodeID, error) {
+	raw, err := hex.DecodeString(encoded)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewNodeID(raw)
+}
+
+// Bytes returns a pointer to the raw NodeID.
+func (id *NodeID) Bytes() *[NodeIDLength]byte {
+	return (*[NodeIDLength]byte)(id)
+}
+
+// Hex returns the hexdecimal representation of the NodeID.
+func (id *NodeID) Hex() string {
+	return hex.EncodeToString(id[:])
+}
+
+// PublicKey is a Curve25519 public key in little-endian byte order.
+type PublicKey [PublicKeyLength]byte
+
+// Bytes returns a pointer to the raw Curve25519 public key.
+func (public *PublicKey) Bytes() *[PublicKeyLength]byte {
+	return (*[PublicKeyLength]byte)(public)
+}
+
+// Hex returns the hexdecimal representation of the Curve25519 public key.
+func (public *PublicKey) Hex() string {
+	return hex.EncodeToString(public.Bytes()[:])
+}
+
+// NewPublicKey creates a PublicKey from the raw bytes.
+func NewPublicKey(raw []byte) (*PublicKey, error) {
+	if len(raw) != PublicKeyLength {
+		return nil, PublicKeyLengthError(len(raw))
+	}
+
+	pubKey := new(PublicKey)
+	copy(pubKey[:], raw)
+
+	return pubKey, nil
+}
+
+// PublicKeyFromHex returns a PublicKey from the hexdecimal representation.
+func PublicKeyFromHex(encoded string) (*PublicKey, error) {
+	raw, err := hex.DecodeString(encoded)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewPublicKey(raw)
+}
+
+// Representative is an Elligator representative of a Curve25519 public key
+// in little-endian byte order.
+type Representative [RepresentativeLength]byte
+
+// Bytes returns a pointer to the raw Elligator representative.
+func (repr *Representative) Bytes() *[RepresentativeLength]byte {
+	return (*[RepresentativeLength]byte)(repr)
+}
+
+// ToPublic converts a Elligator representative to a Curve25519 public key.
+func (repr *Representative) ToPublic() *PublicKey {
+	pub := new(PublicKey)
+
+	extra25519.RepresentativeToPublicKey(pub.Bytes(), repr.Bytes())
+	return pub
+}
+
+// PrivateKey is a Curve25519 private key in little-endian byte order.
+type PrivateKey [PrivateKeyLength]byte
+
+// Bytes returns a pointer to the raw Curve25519 private key.
+func (private *PrivateKey) Bytes() *[PrivateKeyLength]byte {
+	return (*[PrivateKeyLength]byte)(private)
+}
+
+// Hex returns the hexdecimal representation of the Curve25519 private key.
+func (private *PrivateKey) Hex() string {
+	return hex.EncodeToString(private.Bytes()[:])
+}
+
+// Keypair is a Curve25519 keypair with an optional Elligator representative.
+// As only certain Curve25519 keys can be obfuscated with Elligator, the
+// representative must be generated along with the keypair.
+type Keypair struct {
+	public         *PublicKey
+	private        *PrivateKey
+	representative *Representative
+}
+
+// Public returns the Curve25519 public key belonging to the Keypair.
+func (keypair *Keypair) Public() *PublicKey {
+	return keypair.public
+}
+
+// Private returns the Curve25519 private key belonging to the Keypair.
+func (keypair *Keypair) Private() *PrivateKey {
+	return keypair.private
+}
+
+// Representative returns the Elligator representative of the public key
+// belonging to the Keypair.
+func (keypair *Keypair) Representative() *Representative {
+	return keypair.representative
+}
+
+// HasElligator returns true if the Keypair has an Elligator representative.
+func (keypair *Keypair) HasElligator() bool {
+	return nil != keypair.representative
+}
+
+// NewKeypair generates a new Curve25519 keypair, and optionally also generates
+// an Elligator representative of the public key.
+func NewKeypair(elligator bool) (*Keypair, error) {
+	keypair := new(Keypair)
+	keypair.private = new(PrivateKey)
+	keypair.public = new(PublicKey)
+	if elligator {
+		keypair.representative = new(Representative)
+	}
+
+	for {
+		// Generate a Curve25519 private key.  Like everyone who does this,
+		// run the CSPRNG output through SHA256 for extra tinfoil hattery.
+		priv := keypair.private.Bytes()[:]
+		if err := csrand.Bytes(priv); err != nil {
+			return nil, err
+		}
+		digest := sha256.Sum256(priv)
+		digest[0] &= 248
+		digest[31] &= 127
+		digest[31] |= 64
+		copy(priv, digest[:])
+
+		if elligator {
+			// Apply the Elligator transform.  This fails ~50% of the time.
+			if !extra25519.ScalarBaseMult(keypair.public.Bytes(),
+				keypair.representative.Bytes(),
+				keypair.private.Bytes()) {
+				continue
+			}
+		} else {
+			// Generate the corresponding Curve25519 public key.
+			curve25519.ScalarBaseMult(keypair.public.Bytes(),
+				keypair.private.Bytes())
+		}
+
+		return keypair, nil
+	}
+}
+
+// KeypairFromHex returns a Keypair from the hexdecimal representation of the
+// private key.
+func KeypairFromHex(encoded string) (*Keypair, error) {
+	raw, err := hex.DecodeString(encoded)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(raw) != PrivateKeyLength {
+		return nil, PrivateKeyLengthError(len(raw))
+	}
+
+	keypair := new(Keypair)
+	keypair.private = new(PrivateKey)
+	keypair.public = new(PublicKey)
+
+	copy(keypair.private[:], raw)
+	curve25519.ScalarBaseMult(keypair.public.Bytes(),
+		keypair.private.Bytes())
+
+	return keypair, nil
+}
+
+// ServerHandshake does the server side of a ntor handshake and returns status,
+// KEY_SEED, and AUTH.  If status is not true, the handshake MUST be aborted.
+func ServerHandshake(clientPublic *PublicKey, serverKeypair *Keypair, idKeypair *Keypair, id *NodeID) (ok bool, keySeed *KeySeed, auth *Auth) {
+	var notOk int
+	var secretInput bytes.Buffer
+
+	// Server side uses EXP(X,y) | EXP(X,b)
+	var exp [SharedSecretLength]byte
+	curve25519.ScalarMult(&exp, serverKeypair.private.Bytes(),
+		clientPublic.Bytes())
+	notOk |= constantTimeIsZero(exp[:])
+	secretInput.Write(exp[:])
+
+	curve25519.ScalarMult(&exp, idKeypair.private.Bytes(),
+		clientPublic.Bytes())
+	notOk |= constantTimeIsZero(exp[:])
+	secretInput.Write(exp[:])
+
+	keySeed, auth = ntorCommon(secretInput, id, idKeypair.public,
+		clientPublic, serverKeypair.public)
+	return notOk == 0, keySeed, auth
+}
+
+// ClientHandshake does the client side of a ntor handshake and returnes
+// status, KEY_SEED, and AUTH.  If status is not true or AUTH does not match
+// the value recieved from the server, the handshake MUST be aborted.
+func ClientHandshake(clientKeypair *Keypair, serverPublic *PublicKey, idPublic *PublicKey, id *NodeID) (ok bool, keySeed *KeySeed, auth *Auth) {
+	var notOk int
+	var secretInput bytes.Buffer
+
+	// Client side uses EXP(Y,x) | EXP(B,x)
+	var exp [SharedSecretLength]byte
+	curve25519.ScalarMult(&exp, clientKeypair.private.Bytes(),
+		serverPublic.Bytes())
+	notOk |= constantTimeIsZero(exp[:])
+	secretInput.Write(exp[:])
+
+	curve25519.ScalarMult(&exp, clientKeypair.private.Bytes(),
+		idPublic.Bytes())
+	notOk |= constantTimeIsZero(exp[:])
+	secretInput.Write(exp[:])
+
+	keySeed, auth = ntorCommon(secretInput, id, idPublic,
+		clientKeypair.public, serverPublic)
+	return notOk == 0, keySeed, auth
+}
+
+// CompareAuth does a constant time compare of a Auth and a byte slice
+// (presumably received over a network).
+func CompareAuth(auth1 *Auth, auth2 []byte) bool {
+	auth1Bytes := auth1.Bytes()
+	return hmac.Equal(auth1Bytes[:], auth2)
+}
+
+func ntorCommon(secretInput bytes.Buffer, id *NodeID, b *PublicKey, x *PublicKey, y *PublicKey) (*KeySeed, *Auth) {
+	keySeed := new(KeySeed)
+	auth := new(Auth)
+
+	// secret_input/auth_input use this common bit, build it once.
+	suffix := bytes.NewBuffer(b.Bytes()[:])
+	suffix.Write(b.Bytes()[:])
+	suffix.Write(x.Bytes()[:])
+	suffix.Write(y.Bytes()[:])
+	suffix.Write(protoID)
+	suffix.Write(id[:])
+
+	// At this point secret_input has the 2 exponents, concatenated, append the
+	// client/server common suffix.
+	secretInput.Write(suffix.Bytes())
+
+	// KEY_SEED = H(secret_input, t_key)
+	h := hmac.New(sha256.New, tKey)
+	_, _ = h.Write(secretInput.Bytes())
+	tmp := h.Sum(nil)
+	copy(keySeed[:], tmp)
+
+	// verify = H(secret_input, t_verify)
+	h = hmac.New(sha256.New, tVerify)
+	_, _ = h.Write(secretInput.Bytes())
+	verify := h.Sum(nil)
+
+	// auth_input = verify | ID | B | Y | X | PROTOID | "Server"
+	authInput := bytes.NewBuffer(verify)
+	_, _ = authInput.Write(suffix.Bytes())
+	_, _ = authInput.Write([]byte("Server"))
+	h = hmac.New(sha256.New, tMac)
+	_, _ = h.Write(authInput.Bytes())
+	tmp = h.Sum(nil)
+	copy(auth[:], tmp)
+
+	return keySeed, auth
+}
+
+func constantTimeIsZero(x []byte) int {
+	var ret byte
+	for _, v := range x {
+		ret |= v
+	}
+
+	return subtle.ConstantTimeByteEq(ret, 0)
+}
+
+// Kdf extracts and expands KEY_SEED via HKDF-SHA256 and returns `okm_len` bytes
+// of key material.
+func Kdf(keySeed []byte, okmLen int) []byte {
+	kdf := hkdf.New(sha256.New, keySeed, tKey, mExpand)
+	okm := make([]byte, okmLen)
+	n, err := io.ReadFull(kdf, okm)
+	if err != nil {
+		panic(fmt.Sprintf("BUG: Failed HKDF: %s", err.Error()))
+	} else if n != len(okm) {
+		panic(fmt.Sprintf("BUG: Got truncated HKDF output: %d", n))
+	}
+
+	return okm
+}

+ 245 - 0
replace/obfs4.git/common/probdist/weighted_dist.go

@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Package probdist implements a weighted probability distribution suitable for
+// protocol parameterization.  To allow for easy reproduction of a given
+// distribution, the drbg package is used as the random number source.
+package probdist // import "gitlab.com/yawning/obfs4.git/common/probdist"
+
+import (
+	"bytes"
+	"container/list"
+	"fmt"
+	"math/rand"
+	"sync"
+
+	"gitlab.com/yawning/obfs4.git/common/csrand"
+	"gitlab.com/yawning/obfs4.git/common/drbg"
+)
+
+const (
+	minValues = 1
+	maxValues = 100
+)
+
+// WeightedDist is a weighted distribution.
+type WeightedDist struct {
+	sync.Mutex
+
+	minValue int
+	maxValue int
+	biased   bool
+	values   []int
+	weights  []float64
+
+	alias []int
+	prob  []float64
+}
+
+// New creates a weighted distribution of values ranging from min to max
+// based on a HashDrbg initialized with seed.  Optionally, bias the weight
+// generation to match the ScrambleSuit non-uniform distribution from
+// obfsproxy.
+func New(seed *drbg.Seed, min, max int, biased bool) (w *WeightedDist) {
+	w = &WeightedDist{minValue: min, maxValue: max, biased: biased}
+
+	if max <= min {
+		panic(fmt.Sprintf("wDist.Reset(): min >= max (%d, %d)", min, max))
+	}
+
+	w.Reset(seed)
+
+	return
+}
+
+// genValues creates a slice containing a random number of random values
+// that when scaled by adding minValue will fall into [min, max].
+func (w *WeightedDist) genValues(rng *rand.Rand) {
+	nValues := (w.maxValue + 1) - w.minValue
+	values := rng.Perm(nValues)
+	if nValues < minValues {
+		nValues = minValues
+	}
+	if nValues > maxValues {
+		nValues = maxValues
+	}
+	nValues = rng.Intn(nValues) + 1
+	w.values = values[:nValues]
+}
+
+// genBiasedWeights generates a non-uniform weight list, similar to the
+// ScrambleSuit prob_dist module.
+func (w *WeightedDist) genBiasedWeights(rng *rand.Rand) {
+	w.weights = make([]float64, len(w.values))
+
+	culmProb := 0.0
+	for i := range w.weights {
+		p := (1.0 - culmProb) * rng.Float64()
+		w.weights[i] = p
+		culmProb += p
+	}
+}
+
+// genUniformWeights generates a uniform weight list.
+func (w *WeightedDist) genUniformWeights(rng *rand.Rand) {
+	w.weights = make([]float64, len(w.values))
+	for i := range w.weights {
+		w.weights[i] = rng.Float64()
+	}
+}
+
+// genTables calculates the alias and prob tables used for Vose's Alias method.
+// Algorithm taken from http://www.keithschwarz.com/darts-dice-coins/
+func (w *WeightedDist) genTables() {
+	n := len(w.weights)
+	var sum float64
+	for _, weight := range w.weights {
+		sum += weight
+	}
+
+	// Create arrays $Alias$ and $Prob$, each of size $n$.
+	alias := make([]int, n)
+	prob := make([]float64, n)
+
+	// Create two worklists, $Small$ and $Large$.
+	small := list.New()
+	large := list.New()
+
+	scaled := make([]float64, n)
+	for i, weight := range w.weights {
+		// Multiply each probability by $n$.
+		p_i := weight * float64(n) / sum
+		scaled[i] = p_i
+
+		// For each scaled probability $p_i$:
+		if scaled[i] < 1.0 {
+			// If $p_i < 1$, add $i$ to $Small$.
+			small.PushBack(i)
+		} else {
+			// Otherwise ($p_i \ge 1$), add $i$ to $Large$.
+			large.PushBack(i)
+		}
+	}
+
+	// While $Small$ and $Large$ are not empty: ($Large$ might be emptied first)
+	for small.Len() > 0 && large.Len() > 0 {
+		// Remove the first element from $Small$; call it $l$.
+		l := small.Remove(small.Front()).(int)
+		// Remove the first element from $Large$; call it $g$.
+		g := large.Remove(large.Front()).(int)
+
+		// Set $Prob[l] = p_l$.
+		prob[l] = scaled[l]
+		// Set $Alias[l] = g$.
+		alias[l] = g
+
+		// Set $p_g := (p_g + p_l) - 1$. (This is a more numerically stable option.)
+		scaled[g] = (scaled[g] + scaled[l]) - 1.0
+
+		if scaled[g] < 1.0 {
+			// If $p_g < 1$, add $g$ to $Small$.
+			small.PushBack(g)
+		} else {
+			// Otherwise ($p_g \ge 1$), add $g$ to $Large$.
+			large.PushBack(g)
+		}
+	}
+
+	// While $Large$ is not empty:
+	for large.Len() > 0 {
+		// Remove the first element from $Large$; call it $g$.
+		g := large.Remove(large.Front()).(int)
+		// Set $Prob[g] = 1$.
+		prob[g] = 1.0
+	}
+
+	// While $Small$ is not empty: This is only possible due to numerical instability.
+	for small.Len() > 0 {
+		// Remove the first element from $Small$; call it $l$.
+		l := small.Remove(small.Front()).(int)
+		// Set $Prob[l] = 1$.
+		prob[l] = 1.0
+	}
+
+	w.prob = prob
+	w.alias = alias
+}
+
+// Reset generates a new distribution with the same min/max based on a new
+// seed.
+func (w *WeightedDist) Reset(seed *drbg.Seed) {
+	// Initialize the deterministic random number generator.
+	drbg, _ := drbg.NewHashDrbg(seed)
+	rng := rand.New(drbg)
+
+	w.Lock()
+	defer w.Unlock()
+
+	w.genValues(rng)
+	if w.biased {
+		w.genBiasedWeights(rng)
+	} else {
+		w.genUniformWeights(rng)
+	}
+	w.genTables()
+}
+
+// Sample generates a random value according to the distribution.
+func (w *WeightedDist) Sample() int {
+	var idx int
+
+	w.Lock()
+	defer w.Unlock()
+
+	// Generate a fair die roll from an $n$-sided die; call the side $i$.
+	i := csrand.Intn(len(w.values))
+	// Flip a biased coin that comes up heads with probability $Prob[i]$.
+	if csrand.Float64() <= w.prob[i] {
+		// If the coin comes up "heads," return $i$.
+		idx = i
+	} else {
+		// Otherwise, return $Alias[i]$.
+		idx = w.alias[i]
+	}
+
+	return w.minValue + w.values[idx]
+}
+
+// String returns a dump of the distribution table.
+func (w *WeightedDist) String() string {
+	var buf bytes.Buffer
+
+	buf.WriteString("[ ")
+	for i, v := range w.values {
+		p := w.weights[i]
+		if p > 0.01 { // Squelch tiny probabilities.
+			buf.WriteString(fmt.Sprintf("%d: %f ", v, p))
+		}
+	}
+	buf.WriteString("]")
+	return buf.String()
+}

+ 146 - 0
replace/obfs4.git/common/replayfilter/replay_filter.go

@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Package replayfilter implements a generic replay detection filter with a
+// caller specifiable time-to-live.  It only detects if a given byte sequence
+// has been seen before based on the SipHash-2-4 digest of the sequence.
+// Collisions are treated as positive matches, though the probability of this
+// happening is negligible.
+package replayfilter // import "gitlab.com/yawning/obfs4.git/common/replayfilter"
+
+import (
+	"container/list"
+	"encoding/binary"
+	"sync"
+	"time"
+
+	"github.com/dchest/siphash"
+	"gitlab.com/yawning/obfs4.git/common/csrand"
+)
+
+// maxFilterSize is the maximum capacity of a replay filter.  This value is
+// more as a safeguard to prevent runaway filter growth, and is sized to be
+// serveral orders of magnitude greater than the number of connections a busy
+// bridge sees in one day, so in practice should never be reached.
+const maxFilterSize = 100 * 1024
+
+type entry struct {
+	digest    uint64
+	firstSeen time.Time
+	element   *list.Element
+}
+
+// ReplayFilter is a simple filter designed only to detect if a given byte
+// sequence has been seen before.
+type ReplayFilter struct {
+	sync.Mutex
+
+	filter map[uint64]*entry
+	fifo   *list.List
+
+	key [2]uint64
+	ttl time.Duration
+}
+
+// New creates a new ReplayFilter instance.
+func New(ttl time.Duration) (filter *ReplayFilter, err error) {
+	// Initialize the SipHash-2-4 instance with a random key.
+	var key [16]byte
+	if err = csrand.Bytes(key[:]); err != nil {
+		return
+	}
+
+	filter = new(ReplayFilter)
+	filter.filter = make(map[uint64]*entry)
+	filter.fifo = list.New()
+	filter.key[0] = binary.BigEndian.Uint64(key[0:8])
+	filter.key[1] = binary.BigEndian.Uint64(key[8:16])
+	filter.ttl = ttl
+
+	return
+}
+
+// TestAndSet queries the filter for a given byte sequence, inserts the
+// sequence, and returns if it was present before the insertion operation.
+func (f *ReplayFilter) TestAndSet(now time.Time, buf []byte) bool {
+	digest := siphash.Hash(f.key[0], f.key[1], buf)
+
+	f.Lock()
+	defer f.Unlock()
+
+	f.compactFilter(now)
+
+	if e := f.filter[digest]; e != nil {
+		// Hit.  Just return.
+		return true
+	}
+
+	// Miss.  Add a new entry.
+	e := new(entry)
+	e.digest = digest
+	e.firstSeen = now
+	e.element = f.fifo.PushBack(e)
+	f.filter[digest] = e
+
+	return false
+}
+
+func (f *ReplayFilter) compactFilter(now time.Time) {
+	e := f.fifo.Front()
+	for e != nil {
+		ent, _ := e.Value.(*entry)
+
+		// If the filter is not full, only purge entries that exceed the TTL,
+		// otherwise purge at least one entry, then revert to TTL based
+		// compaction.
+		if f.fifo.Len() < maxFilterSize && f.ttl > 0 {
+			deltaT := now.Sub(ent.firstSeen)
+			if deltaT < 0 {
+				// Aeeeeeee, the system time jumped backwards, potentially by
+				// a lot.  This will eventually self-correct, but "eventually"
+				// could be a long time.  As much as this sucks, jettison the
+				// entire filter.
+				f.reset()
+				return
+			} else if deltaT < f.ttl {
+				return
+			}
+		}
+
+		// Remove the eldest entry.
+		eNext := e.Next()
+		delete(f.filter, ent.digest)
+		f.fifo.Remove(ent.element)
+		ent.element = nil
+		e = eNext
+	}
+}
+
+func (f *ReplayFilter) reset() {
+	f.filter = make(map[uint64]*entry)
+	f.fifo = list.New()
+}

+ 3 - 0
replace/obfs4.git/go.mod

@@ -0,0 +1,3 @@
+module gitlab.com/yawning/obfs4.git
+
+go 1.19

+ 90 - 0
replace/obfs4.git/transports/base/base.go

@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Package base provides the common interface that each supported transport
+// protocol must implement.
+package base // import "gitlab.com/yawning/obfs4.git/transports/base"
+
+import (
+	"net"
+
+	"git.torproject.org/pluggable-transports/goptlib.git"
+)
+
+type DialFunc func(string, string) (net.Conn, error)
+
+// ClientFactory is the interface that defines the factory for creating
+// pluggable transport protocol client instances.
+type ClientFactory interface {
+	// Transport returns the Transport instance that this ClientFactory belongs
+	// to.
+	Transport() Transport
+
+	// ParseArgs parses the supplied arguments into an internal representation
+	// for use with WrapConn.  This routine is called before the outgoing
+	// TCP/IP connection is created to allow doing things (like keypair
+	// generation) to be hidden from third parties.
+	ParseArgs(args *pt.Args) (interface{}, error)
+
+	// Dial creates an outbound net.Conn, and does whatever is required
+	// (eg: handshaking) to get the connection to the point where it is
+	// ready to relay data.
+	Dial(network, address string, dialFn DialFunc, args interface{}) (net.Conn, error)
+}
+
+// ServerFactory is the interface that defines the factory for creating
+// plugable transport protocol server instances.  As the arguments are the
+// property of the factory, validation is done at factory creation time.
+type ServerFactory interface {
+	// Transport returns the Transport instance that this ServerFactory belongs
+	// to.
+	Transport() Transport
+
+	// Args returns the Args required on the client side to handshake with
+	// server connections created by this factory.
+	Args() *pt.Args
+
+	// WrapConn wraps the provided net.Conn with a transport protocol
+	// implementation, and does whatever is required (eg: handshaking) to get
+	// the connection to a point where it is ready to relay data.
+	WrapConn(conn net.Conn) (net.Conn, error)
+}
+
+// Transport is an interface that defines a pluggable transport protocol.
+type Transport interface {
+	// Name returns the name of the transport protocol.  It MUST be a valid C
+	// identifier.
+	Name() string
+
+	// ClientFactory returns a ClientFactory instance for this transport
+	// protocol.
+	ClientFactory(stateDir string) (ClientFactory, error)
+
+	// ServerFactory returns a ServerFactory instance for this transport
+	// protocol.  This can fail if the provided arguments are invalid.
+	ServerFactory(stateDir string, args *pt.Args) (ServerFactory, error)
+}

+ 305 - 0
replace/obfs4.git/transports/obfs4/framing/framing.go

@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+//
+// Package framing implements the obfs4 link framing and cryptography.
+//
+// The Encoder/Decoder shared secret format is:
+//    uint8_t[32] NaCl secretbox key
+//    uint8_t[16] NaCl Nonce prefix
+//    uint8_t[16] SipHash-2-4 key (used to obfsucate length)
+//    uint8_t[8]  SipHash-2-4 IV
+//
+// The frame format is:
+//   uint16_t length (obfsucated, big endian)
+//   NaCl secretbox (Poly1305/XSalsa20) containing:
+//     uint8_t[16] tag (Part of the secretbox construct)
+//     uint8_t[]   payload
+//
+// The length field is length of the NaCl secretbox XORed with the truncated
+// SipHash-2-4 digest ran in OFB mode.
+//
+//     Initialize K, IV[0] with values from the shared secret.
+//     On each packet, IV[n] = H(K, IV[n - 1])
+//     mask[n] = IV[n][0:2]
+//     obfsLen = length ^ mask[n]
+//
+// The NaCl secretbox (Poly1305/XSalsa20) nonce format is:
+//     uint8_t[24] prefix (Fixed)
+//     uint64_t    counter (Big endian)
+//
+// The counter is initialized to 1, and is incremented on each frame.  Since
+// the protocol is designed to be used over a reliable medium, the nonce is not
+// transmitted over the wire as both sides of the conversation know the prefix
+// and the initial counter value.  It is imperative that the counter does not
+// wrap, and sessions MUST terminate before 2^64 frames are sent.
+//
+package framing // import "gitlab.com/yawning/obfs4.git/transports/obfs4/framing"
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+
+	"gitlab.com/yawning/obfs4.git/common/csrand"
+	"gitlab.com/yawning/obfs4.git/common/drbg"
+	"golang.org/x/crypto/nacl/secretbox"
+)
+
+const (
+	// MaximumSegmentLength is the length of the largest possible segment
+	// including overhead.
+	MaximumSegmentLength = 1500 - (40 + 12)
+
+	// FrameOverhead is the length of the framing overhead.
+	FrameOverhead = lengthLength + secretbox.Overhead
+
+	// MaximumFramePayloadLength is the length of the maximum allowed payload
+	// per frame.
+	MaximumFramePayloadLength = MaximumSegmentLength - FrameOverhead
+
+	// KeyLength is the length of the Encoder/Decoder secret key.
+	KeyLength = keyLength + noncePrefixLength + drbg.SeedLength
+
+	maxFrameLength = MaximumSegmentLength - lengthLength
+	minFrameLength = FrameOverhead - lengthLength
+
+	keyLength = 32
+
+	noncePrefixLength  = 16
+	nonceCounterLength = 8
+	nonceLength        = noncePrefixLength + nonceCounterLength
+
+	lengthLength = 2
+)
+
+// Error returned when Decoder.Decode() requires more data to continue.
+var ErrAgain = errors.New("framing: More data needed to decode")
+
+// Error returned when Decoder.Decode() failes to authenticate a frame.
+var ErrTagMismatch = errors.New("framing: Poly1305 tag mismatch")
+
+// Error returned when the NaCl secretbox nonce's counter wraps (FATAL).
+var ErrNonceCounterWrapped = errors.New("framing: Nonce counter wrapped")
+
+// InvalidPayloadLengthError is the error returned when Encoder.Encode()
+// rejects the payload length.
+type InvalidPayloadLengthError int
+
+func (e InvalidPayloadLengthError) Error() string {
+	return fmt.Sprintf("framing: Invalid payload length: %d", int(e))
+}
+
+type boxNonce struct {
+	prefix  [noncePrefixLength]byte
+	counter uint64
+}
+
+func (nonce *boxNonce) init(prefix []byte) {
+	if noncePrefixLength != len(prefix) {
+		panic(fmt.Sprintf("BUG: Nonce prefix length invalid: %d", len(prefix)))
+	}
+
+	copy(nonce.prefix[:], prefix)
+	nonce.counter = 1
+}
+
+func (nonce boxNonce) bytes(out *[nonceLength]byte) error {
+	// The security guarantee of Poly1305 is broken if a nonce is ever reused
+	// for a given key.  Detect this by checking for counter wraparound since
+	// we start each counter at 1.  If it ever happens that more than 2^64 - 1
+	// frames are transmitted over a given connection, support for rekeying
+	// will be neccecary, but that's unlikely to happen.
+	if nonce.counter == 0 {
+		return ErrNonceCounterWrapped
+	}
+
+	copy(out[:], nonce.prefix[:])
+	binary.BigEndian.PutUint64(out[noncePrefixLength:], nonce.counter)
+
+	return nil
+}
+
+// Encoder is a frame encoder instance.
+type Encoder struct {
+	key   [keyLength]byte
+	nonce boxNonce
+	drbg  *drbg.HashDrbg
+}
+
+// NewEncoder creates a new Encoder instance.  It must be supplied a slice
+// containing exactly KeyLength bytes of keying material.
+func NewEncoder(key []byte) *Encoder {
+	if len(key) != KeyLength {
+		panic(fmt.Sprintf("BUG: Invalid encoder key length: %d", len(key)))
+	}
+
+	encoder := new(Encoder)
+	copy(encoder.key[:], key[0:keyLength])
+	encoder.nonce.init(key[keyLength : keyLength+noncePrefixLength])
+	seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:])
+	if err != nil {
+		panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err))
+	}
+	encoder.drbg, _ = drbg.NewHashDrbg(seed)
+
+	return encoder
+}
+
+// Encode encodes a single frame worth of payload and returns the encoded
+// length.  InvalidPayloadLengthError is recoverable, all other errors MUST be
+// treated as fatal and the session aborted.
+func (encoder *Encoder) Encode(frame, payload []byte) (n int, err error) {
+	payloadLen := len(payload)
+	if MaximumFramePayloadLength < payloadLen {
+		return 0, InvalidPayloadLengthError(payloadLen)
+	}
+	if len(frame) < payloadLen+FrameOverhead {
+		return 0, io.ErrShortBuffer
+	}
+
+	// Generate a new nonce.
+	var nonce [nonceLength]byte
+	if err = encoder.nonce.bytes(&nonce); err != nil {
+		return 0, err
+	}
+	encoder.nonce.counter++
+
+	// Encrypt and MAC payload.
+	box := secretbox.Seal(frame[:lengthLength], payload, &nonce, &encoder.key)
+
+	// Obfuscate the length.
+	length := uint16(len(box) - lengthLength)
+	lengthMask := encoder.drbg.NextBlock()
+	length ^= binary.BigEndian.Uint16(lengthMask)
+	binary.BigEndian.PutUint16(frame[:2], length)
+
+	// Return the frame.
+	return len(box), nil
+}
+
+// Decoder is a frame decoder instance.
+type Decoder struct {
+	key   [keyLength]byte
+	nonce boxNonce
+	drbg  *drbg.HashDrbg
+
+	nextNonce         [nonceLength]byte
+	nextLength        uint16
+	nextLengthInvalid bool
+}
+
+// NewDecoder creates a new Decoder instance.  It must be supplied a slice
+// containing exactly KeyLength bytes of keying material.
+func NewDecoder(key []byte) *Decoder {
+	if len(key) != KeyLength {
+		panic(fmt.Sprintf("BUG: Invalid decoder key length: %d", len(key)))
+	}
+
+	decoder := new(Decoder)
+	copy(decoder.key[:], key[0:keyLength])
+	decoder.nonce.init(key[keyLength : keyLength+noncePrefixLength])
+	seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:])
+	if err != nil {
+		panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err))
+	}
+	decoder.drbg, _ = drbg.NewHashDrbg(seed)
+
+	return decoder
+}
+
+// Decode decodes a stream of data and returns the length if any.  ErrAgain is
+// a temporary failure, all other errors MUST be treated as fatal and the
+// session aborted.
+func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) {
+	// A length of 0 indicates that we do not know how big the next frame is
+	// going to be.
+	if decoder.nextLength == 0 {
+		// Attempt to pull out the next frame length.
+		if lengthLength > frames.Len() {
+			return 0, ErrAgain
+		}
+
+		// Remove the length field from the buffer.
+		var obfsLen [lengthLength]byte
+		_, err := io.ReadFull(frames, obfsLen[:])
+		if err != nil {
+			return 0, err
+		}
+
+		// Derive the nonce the peer used.
+		if err = decoder.nonce.bytes(&decoder.nextNonce); err != nil {
+			return 0, err
+		}
+
+		// Deobfuscate the length field.
+		length := binary.BigEndian.Uint16(obfsLen[:])
+		lengthMask := decoder.drbg.NextBlock()
+		length ^= binary.BigEndian.Uint16(lengthMask)
+		if maxFrameLength < length || minFrameLength > length {
+			// Per "Plaintext Recovery Attacks Against SSH" by
+			// Martin R. Albrecht, Kenneth G. Paterson and Gaven J. Watson,
+			// there are a class of attacks againt protocols that use similar
+			// sorts of framing schemes.
+			//
+			// While obfs4 should not allow plaintext recovery (CBC mode is
+			// not used), attempt to mitigate out of bound frame length errors
+			// by pretending that the length was a random valid range as per
+			// the countermeasure suggested by Denis Bider in section 6 of the
+			// paper.
+
+			decoder.nextLengthInvalid = true
+			length = uint16(csrand.IntRange(minFrameLength, maxFrameLength))
+		}
+		decoder.nextLength = length
+	}
+
+	if int(decoder.nextLength) > frames.Len() {
+		return 0, ErrAgain
+	}
+
+	// Unseal the frame.
+	var box [maxFrameLength]byte
+	n, err := io.ReadFull(frames, box[:decoder.nextLength])
+	if err != nil {
+		return 0, err
+	}
+	out, ok := secretbox.Open(data[:0], box[:n], &decoder.nextNonce, &decoder.key)
+	if !ok || decoder.nextLengthInvalid {
+		// When a random length is used (on length error) the tag should always
+		// mismatch, but be paranoid.
+		return 0, ErrTagMismatch
+	}
+
+	// Clean up and prepare for the next frame.
+	decoder.nextLength = 0
+	decoder.nonce.counter++
+
+	return len(out), nil
+}

+ 424 - 0
replace/obfs4.git/transports/obfs4/handshake_ntor.go

@@ -0,0 +1,424 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package obfs4
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"hash"
+	"strconv"
+	"time"
+
+	"gitlab.com/yawning/obfs4.git/common/csrand"
+	"gitlab.com/yawning/obfs4.git/common/ntor"
+	"gitlab.com/yawning/obfs4.git/common/replayfilter"
+	"gitlab.com/yawning/obfs4.git/transports/obfs4/framing"
+)
+
+const (
+	maxHandshakeLength = 8192
+
+	clientMinPadLength = (serverMinHandshakeLength + inlineSeedFrameLength) -
+		clientMinHandshakeLength
+	clientMaxPadLength       = maxHandshakeLength - clientMinHandshakeLength
+	clientMinHandshakeLength = ntor.RepresentativeLength + markLength + macLength
+
+	serverMinPadLength = 0
+	serverMaxPadLength = maxHandshakeLength - (serverMinHandshakeLength +
+		inlineSeedFrameLength)
+	serverMinHandshakeLength = ntor.RepresentativeLength + ntor.AuthLength +
+		markLength + macLength
+
+	markLength = sha256.Size / 2
+	macLength  = sha256.Size / 2
+
+	inlineSeedFrameLength = framing.FrameOverhead + packetOverhead + seedPacketPayloadLength
+)
+
+// ErrMarkNotFoundYet is the error returned when the obfs4 handshake is
+// incomplete and requires more data to continue.  This error is non-fatal and
+// is the equivalent to EAGAIN/EWOULDBLOCK.
+var ErrMarkNotFoundYet = errors.New("handshake: M_[C,S] not found yet")
+
+// ErrInvalidHandshake is the error returned when the obfs4 handshake fails due
+// to the peer not sending the correct mark.  This error is fatal and the
+// connection MUST be dropped.
+var ErrInvalidHandshake = errors.New("handshake: Failed to find M_[C,S]")
+
+// ErrReplayedHandshake is the error returned when the obfs4 handshake fails
+// due it being replayed.  This error is fatal and the connection MUST be
+// dropped.
+var ErrReplayedHandshake = errors.New("handshake: Replay detected")
+
+// ErrNtorFailed is the error returned when the ntor handshake fails.  This
+// error is fatal and the connection MUST be dropped.
+var ErrNtorFailed = errors.New("handshake: ntor handshake failure")
+
+// InvalidMacError is the error returned when the handshake MACs do not match.
+// This error is fatal and the connection MUST be dropped.
+type InvalidMacError struct {
+	Derived  []byte
+	Received []byte
+}
+
+func (e *InvalidMacError) Error() string {
+	return fmt.Sprintf("handshake: MAC mismatch: Dervied: %s Received: %s.",
+		hex.EncodeToString(e.Derived), hex.EncodeToString(e.Received))
+}
+
+// InvalidAuthError is the error returned when the ntor AUTH tags do not match.
+// This error is fatal and the connection MUST be dropped.
+type InvalidAuthError struct {
+	Derived  *ntor.Auth
+	Received *ntor.Auth
+}
+
+func (e *InvalidAuthError) Error() string {
+	return fmt.Sprintf("handshake: ntor AUTH mismatch: Derived: %s Received:%s.",
+		hex.EncodeToString(e.Derived.Bytes()[:]),
+		hex.EncodeToString(e.Received.Bytes()[:]))
+}
+
+type clientHandshake struct {
+	keypair        *ntor.Keypair
+	nodeID         *ntor.NodeID
+	serverIdentity *ntor.PublicKey
+	epochHour      []byte
+
+	padLen int
+	mac    hash.Hash
+
+	serverRepresentative *ntor.Representative
+	serverAuth           *ntor.Auth
+	serverMark           []byte
+}
+
+func newClientHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.PublicKey, sessionKey *ntor.Keypair) *clientHandshake {
+	hs := new(clientHandshake)
+	hs.keypair = sessionKey
+	hs.nodeID = nodeID
+	hs.serverIdentity = serverIdentity
+	hs.padLen = csrand.IntRange(clientMinPadLength, clientMaxPadLength)
+	hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Bytes()[:], hs.nodeID.Bytes()[:]...))
+
+	return hs
+}
+
+func (hs *clientHandshake) generateHandshake() ([]byte, error) {
+	var buf bytes.Buffer
+
+	hs.mac.Reset()
+	_, _ = hs.mac.Write(hs.keypair.Representative().Bytes()[:])
+	mark := hs.mac.Sum(nil)[:markLength]
+
+	// The client handshake is X | P_C | M_C | MAC(X | P_C | M_C | E) where:
+	//  * X is the client's ephemeral Curve25519 public key representative.
+	//  * P_C is [clientMinPadLength,clientMaxPadLength] bytes of random padding.
+	//  * M_C is HMAC-SHA256-128(serverIdentity | NodeID, X)
+	//  * MAC is HMAC-SHA256-128(serverIdentity | NodeID, X .... E)
+	//  * E is the string representation of the number of hours since the UNIX
+	//    epoch.
+
+	// Generate the padding
+	pad, err := makePad(hs.padLen)
+	if err != nil {
+		return nil, err
+	}
+
+	// Write X, P_C, M_C.
+	buf.Write(hs.keypair.Representative().Bytes()[:])
+	buf.Write(pad)
+	buf.Write(mark)
+
+	// Calculate and write the MAC.
+	hs.mac.Reset()
+	_, _ = hs.mac.Write(buf.Bytes())
+	hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10))
+	_, _ = hs.mac.Write(hs.epochHour)
+	buf.Write(hs.mac.Sum(nil)[:macLength])
+
+	return buf.Bytes(), nil
+}
+
+func (hs *clientHandshake) parseServerHandshake(resp []byte) (int, []byte, error) {
+	// No point in examining the data unless the miminum plausible response has
+	// been received.
+	if serverMinHandshakeLength > len(resp) {
+		return 0, nil, ErrMarkNotFoundYet
+	}
+
+	if hs.serverRepresentative == nil || hs.serverAuth == nil {
+		// Pull out the representative/AUTH. (XXX: Add ctors to ntor)
+		hs.serverRepresentative = new(ntor.Representative)
+		copy(hs.serverRepresentative.Bytes()[:], resp[0:ntor.RepresentativeLength])
+		hs.serverAuth = new(ntor.Auth)
+		copy(hs.serverAuth.Bytes()[:], resp[ntor.RepresentativeLength:])
+
+		// Derive the mark.
+		hs.mac.Reset()
+		_, _ = hs.mac.Write(hs.serverRepresentative.Bytes()[:])
+		hs.serverMark = hs.mac.Sum(nil)[:markLength]
+	}
+
+	// Attempt to find the mark + MAC.
+	pos := findMarkMac(hs.serverMark, resp, ntor.RepresentativeLength+ntor.AuthLength+serverMinPadLength,
+		maxHandshakeLength, false)
+	if pos == -1 {
+		if len(resp) >= maxHandshakeLength {
+			return 0, nil, ErrInvalidHandshake
+		}
+		return 0, nil, ErrMarkNotFoundYet
+	}
+
+	// Validate the MAC.
+	hs.mac.Reset()
+	_, _ = hs.mac.Write(resp[:pos+markLength])
+	_, _ = hs.mac.Write(hs.epochHour)
+	macCmp := hs.mac.Sum(nil)[:macLength]
+	macRx := resp[pos+markLength : pos+markLength+macLength]
+	if !hmac.Equal(macCmp, macRx) {
+		return 0, nil, &InvalidMacError{macCmp, macRx}
+	}
+
+	// Complete the handshake.
+	serverPublic := hs.serverRepresentative.ToPublic()
+	ok, seed, auth := ntor.ClientHandshake(hs.keypair, serverPublic,
+		hs.serverIdentity, hs.nodeID)
+	if !ok {
+		return 0, nil, ErrNtorFailed
+	}
+	if !ntor.CompareAuth(auth, hs.serverAuth.Bytes()[:]) {
+		return 0, nil, &InvalidAuthError{auth, hs.serverAuth}
+	}
+
+	return pos + markLength + macLength, seed.Bytes()[:], nil
+}
+
+type serverHandshake struct {
+	keypair        *ntor.Keypair
+	nodeID         *ntor.NodeID
+	serverIdentity *ntor.Keypair
+	epochHour      []byte
+	serverAuth     *ntor.Auth
+
+	padLen int
+	mac    hash.Hash
+
+	clientRepresentative *ntor.Representative
+	clientMark           []byte
+}
+
+func newServerHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.Keypair, sessionKey *ntor.Keypair) *serverHandshake {
+	hs := new(serverHandshake)
+	hs.keypair = sessionKey
+	hs.nodeID = nodeID
+	hs.serverIdentity = serverIdentity
+	hs.padLen = csrand.IntRange(serverMinPadLength, serverMaxPadLength)
+	hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Public().Bytes()[:], hs.nodeID.Bytes()[:]...))
+
+	return hs
+}
+
+func (hs *serverHandshake) parseClientHandshake(filter *replayfilter.ReplayFilter, resp []byte) ([]byte, error) {
+	// No point in examining the data unless the miminum plausible response has
+	// been received.
+	if clientMinHandshakeLength > len(resp) {
+		return nil, ErrMarkNotFoundYet
+	}
+
+	if hs.clientRepresentative == nil {
+		// Pull out the representative/AUTH. (XXX: Add ctors to ntor)
+		hs.clientRepresentative = new(ntor.Representative)
+		copy(hs.clientRepresentative.Bytes()[:], resp[0:ntor.RepresentativeLength])
+
+		// Derive the mark.
+		hs.mac.Reset()
+		_, _ = hs.mac.Write(hs.clientRepresentative.Bytes()[:])
+		hs.clientMark = hs.mac.Sum(nil)[:markLength]
+	}
+
+	// Attempt to find the mark + MAC.
+	pos := findMarkMac(hs.clientMark, resp, ntor.RepresentativeLength+clientMinPadLength,
+		maxHandshakeLength, true)
+	if pos == -1 {
+		if len(resp) >= maxHandshakeLength {
+			return nil, ErrInvalidHandshake
+		}
+		return nil, ErrMarkNotFoundYet
+	}
+
+	// Validate the MAC.
+	macFound := false
+	for _, off := range []int64{0, -1, 1} {
+		// Allow epoch to be off by up to a hour in either direction.
+		epochHour := []byte(strconv.FormatInt(getEpochHour()+int64(off), 10))
+		hs.mac.Reset()
+		_, _ = hs.mac.Write(resp[:pos+markLength])
+		_, _ = hs.mac.Write(epochHour)
+		macCmp := hs.mac.Sum(nil)[:macLength]
+		macRx := resp[pos+markLength : pos+markLength+macLength]
+		if hmac.Equal(macCmp, macRx) {
+			// Ensure that this handshake has not been seen previously.
+			if filter.TestAndSet(time.Now(), macRx) {
+				// The client either happened to generate exactly the same
+				// session key and padding, or someone is replaying a previous
+				// handshake.  In either case, fuck them.
+				return nil, ErrReplayedHandshake
+			}
+
+			macFound = true
+			hs.epochHour = epochHour
+
+			// We could break out here, but in the name of reducing timing
+			// variation, evaluate all 3 MACs.
+		}
+	}
+	if !macFound {
+		// This probably should be an InvalidMacError, but conveying the 3 MACS
+		// that would be accepted is annoying so just return a generic fatal
+		// failure.
+		return nil, ErrInvalidHandshake
+	}
+
+	// Client should never sent trailing garbage.
+	if len(resp) != pos+markLength+macLength {
+		return nil, ErrInvalidHandshake
+	}
+
+	clientPublic := hs.clientRepresentative.ToPublic()
+	ok, seed, auth := ntor.ServerHandshake(clientPublic, hs.keypair,
+		hs.serverIdentity, hs.nodeID)
+	if !ok {
+		return nil, ErrNtorFailed
+	}
+	hs.serverAuth = auth
+
+	return seed.Bytes()[:], nil
+}
+
+func (hs *serverHandshake) generateHandshake() ([]byte, error) {
+	var buf bytes.Buffer
+
+	hs.mac.Reset()
+	_, _ = hs.mac.Write(hs.keypair.Representative().Bytes()[:])
+	mark := hs.mac.Sum(nil)[:markLength]
+
+	// The server handshake is Y | AUTH | P_S | M_S | MAC(Y | AUTH | P_S | M_S | E) where:
+	//  * Y is the server's ephemeral Curve25519 public key representative.
+	//  * AUTH is the ntor handshake AUTH value.
+	//  * P_S is [serverMinPadLength,serverMaxPadLength] bytes of random padding.
+	//  * M_S is HMAC-SHA256-128(serverIdentity | NodeID, Y)
+	//  * MAC is HMAC-SHA256-128(serverIdentity | NodeID, Y .... E)
+	//  * E is the string representation of the number of hours since the UNIX
+	//    epoch.
+
+	// Generate the padding
+	pad, err := makePad(hs.padLen)
+	if err != nil {
+		return nil, err
+	}
+
+	// Write Y, AUTH, P_S, M_S.
+	buf.Write(hs.keypair.Representative().Bytes()[:])
+	buf.Write(hs.serverAuth.Bytes()[:])
+	buf.Write(pad)
+	buf.Write(mark)
+
+	// Calculate and write the MAC.
+	hs.mac.Reset()
+	_, _ = hs.mac.Write(buf.Bytes())
+	_, _ = hs.mac.Write(hs.epochHour) // Set in hs.parseClientHandshake()
+	buf.Write(hs.mac.Sum(nil)[:macLength])
+
+	return buf.Bytes(), nil
+}
+
+// getEpochHour returns the number of hours since the UNIX epoch.
+func getEpochHour() int64 {
+	return time.Now().Unix() / 3600
+}
+
+func findMarkMac(mark, buf []byte, startPos, maxPos int, fromTail bool) (pos int) {
+	if len(mark) != markLength {
+		panic(fmt.Sprintf("BUG: Invalid mark length: %d", len(mark)))
+	}
+
+	endPos := len(buf)
+	if startPos > len(buf) {
+		return -1
+	}
+	if endPos > maxPos {
+		endPos = maxPos
+	}
+	if endPos-startPos < markLength+macLength {
+		return -1
+	}
+
+	if fromTail {
+		// The server can optimize the search process by only examining the
+		// tail of the buffer.  The client can't send valid data past M_C |
+		// MAC_C as it does not have the server's public key yet.
+		pos = endPos - (markLength + macLength)
+		if !hmac.Equal(buf[pos:pos+markLength], mark) {
+			return -1
+		}
+
+		return
+	}
+
+	// The client has to actually do a substring search since the server can
+	// and will send payload trailing the response.
+	//
+	// XXX: bytes.Index() uses a naive search, which kind of sucks.
+	pos = bytes.Index(buf[startPos:endPos], mark)
+	if pos == -1 {
+		return -1
+	}
+
+	// Ensure that there is enough trailing data for the MAC.
+	if startPos+pos+markLength+macLength > endPos {
+		return -1
+	}
+
+	// Return the index relative to the start of the slice.
+	pos += startPos
+	return
+}
+
+func makePad(padLen int) ([]byte, error) {
+	pad := make([]byte, padLen)
+	if err := csrand.Bytes(pad); err != nil {
+		return nil, err
+	}
+
+	return pad, nil
+}

+ 647 - 0
replace/obfs4.git/transports/obfs4/obfs4.go

@@ -0,0 +1,647 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Package obfs4 provides an implementation of the Tor Project's obfs4
+// obfuscation protocol.
+package obfs4 // import "gitlab.com/yawning/obfs4.git/transports/obfs4"
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"flag"
+	"fmt"
+	"math/rand"
+	"net"
+	"strconv"
+	"syscall"
+	"time"
+
+	"git.torproject.org/pluggable-transports/goptlib.git"
+	"gitlab.com/yawning/obfs4.git/common/drbg"
+	"gitlab.com/yawning/obfs4.git/common/ntor"
+	"gitlab.com/yawning/obfs4.git/common/probdist"
+	"gitlab.com/yawning/obfs4.git/common/replayfilter"
+	"gitlab.com/yawning/obfs4.git/transports/base"
+	"gitlab.com/yawning/obfs4.git/transports/obfs4/framing"
+)
+
+const (
+	transportName = "obfs4"
+
+	nodeIDArg     = "node-id"
+	publicKeyArg  = "public-key"
+	privateKeyArg = "private-key"
+	seedArg       = "drbg-seed"
+	iatArg        = "iat-mode"
+	certArg       = "cert"
+
+	biasCmdArg = "obfs4-distBias"
+
+	seedLength             = drbg.SeedLength
+	headerLength           = framing.FrameOverhead + packetOverhead
+	clientHandshakeTimeout = time.Duration(60) * time.Second
+	serverHandshakeTimeout = time.Duration(30) * time.Second
+	replayTTL              = time.Duration(3) * time.Hour
+
+	maxIATDelay        = 100
+	maxCloseDelayBytes = maxHandshakeLength
+	maxCloseDelay      = 60
+)
+
+const (
+	iatNone = iota
+	iatEnabled
+	iatParanoid
+)
+
+// biasedDist controls if the probability table will be ScrambleSuit style or
+// uniformly distributed.
+var biasedDist bool
+
+type obfs4ClientArgs struct {
+	nodeID     *ntor.NodeID
+	publicKey  *ntor.PublicKey
+	sessionKey *ntor.Keypair
+	iatMode    int
+}
+
+// Transport is the obfs4 implementation of the base.Transport interface.
+type Transport struct{}
+
+// Name returns the name of the obfs4 transport protocol.
+func (t *Transport) Name() string {
+	return transportName
+}
+
+// ClientFactory returns a new obfs4ClientFactory instance.
+func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
+	cf := &obfs4ClientFactory{transport: t}
+	return cf, nil
+}
+
+// ServerFactory returns a new obfs4ServerFactory instance.
+func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
+	st, err := serverStateFromArgs(stateDir, args)
+	if err != nil {
+		return nil, err
+	}
+
+	var iatSeed *drbg.Seed
+	if st.iatMode != iatNone {
+		iatSeedSrc := sha256.Sum256(st.drbgSeed.Bytes()[:])
+		var err error
+		iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:])
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// Store the arguments that should appear in our descriptor for the clients.
+	ptArgs := pt.Args{}
+	ptArgs.Add(certArg, st.cert.String())
+	ptArgs.Add(iatArg, strconv.Itoa(st.iatMode))
+
+	// Initialize the replay filter.
+	filter, err := replayfilter.New(replayTTL)
+	if err != nil {
+		return nil, err
+	}
+
+	// Initialize the close thresholds for failed connections.
+	drbg, err := drbg.NewHashDrbg(st.drbgSeed)
+	if err != nil {
+		return nil, err
+	}
+	rng := rand.New(drbg)
+
+	sf := &obfs4ServerFactory{t, &ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, st.iatMode, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)}
+	return sf, nil
+}
+
+type obfs4ClientFactory struct {
+	transport base.Transport
+}
+
+func (cf *obfs4ClientFactory) Transport() base.Transport {
+	return cf.transport
+}
+
+func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
+	var nodeID *ntor.NodeID
+	var publicKey *ntor.PublicKey
+
+	// The "new" (version >= 0.0.3) bridge lines use a unified "cert" argument
+	// for the Node ID and Public Key.
+	certStr, ok := args.Get(certArg)
+	if ok {
+		cert, err := serverCertFromString(certStr)
+		if err != nil {
+			return nil, err
+		}
+		nodeID, publicKey = cert.unpack()
+	} else {
+		// The "old" style (version <= 0.0.2) bridge lines use separate Node ID
+		// and Public Key arguments in Base16 encoding and are a UX disaster.
+		nodeIDStr, ok := args.Get(nodeIDArg)
+		if !ok {
+			return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
+		}
+		var err error
+		if nodeID, err = ntor.NodeIDFromHex(nodeIDStr); err != nil {
+			return nil, err
+		}
+
+		publicKeyStr, ok := args.Get(publicKeyArg)
+		if !ok {
+			return nil, fmt.Errorf("missing argument '%s'", publicKeyArg)
+		}
+		if publicKey, err = ntor.PublicKeyFromHex(publicKeyStr); err != nil {
+			return nil, err
+		}
+	}
+
+	// IAT config is common across the two bridge line formats.
+	iatStr, ok := args.Get(iatArg)
+	if !ok {
+		return nil, fmt.Errorf("missing argument '%s'", iatArg)
+	}
+	iatMode, err := strconv.Atoi(iatStr)
+	if err != nil || iatMode < iatNone || iatMode > iatParanoid {
+		return nil, fmt.Errorf("invalid iat-mode '%d'", iatMode)
+	}
+
+	// Generate the session key pair before connectiong to hide the Elligator2
+	// rejection sampling from network observers.
+	sessionKey, err := ntor.NewKeypair(true)
+	if err != nil {
+		return nil, err
+	}
+
+	return &obfs4ClientArgs{nodeID, publicKey, sessionKey, iatMode}, nil
+}
+
+func (cf *obfs4ClientFactory) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) {
+	// Validate args before bothering to open connection.
+	ca, ok := args.(*obfs4ClientArgs)
+	if !ok {
+		return nil, fmt.Errorf("invalid argument type for args")
+	}
+	conn, err := dialFn(network, addr)
+	if err != nil {
+		return nil, err
+	}
+	dialConn := conn
+	if conn, err = newObfs4ClientConn(conn, ca); err != nil {
+		dialConn.Close()
+		return nil, err
+	}
+	return conn, nil
+}
+
+type obfs4ServerFactory struct {
+	transport base.Transport
+	args      *pt.Args
+
+	nodeID       *ntor.NodeID
+	identityKey  *ntor.Keypair
+	lenSeed      *drbg.Seed
+	iatSeed      *drbg.Seed
+	iatMode      int
+	replayFilter *replayfilter.ReplayFilter
+
+	closeDelayBytes int
+	closeDelay      int
+}
+
+func (sf *obfs4ServerFactory) Transport() base.Transport {
+	return sf.transport
+}
+
+func (sf *obfs4ServerFactory) Args() *pt.Args {
+	return sf.args
+}
+
+func (sf *obfs4ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
+	// Not much point in having a separate newObfs4ServerConn routine when
+	// wrapping requires using values from the factory instance.
+
+	// Generate the session keypair *before* consuming data from the peer, to
+	// attempt to mask the rejection sampling due to use of Elligator2.  This
+	// might be futile, but the timing differential isn't very large on modern
+	// hardware, and there are far easier statistical attacks that can be
+	// mounted as a distinguisher.
+	sessionKey, err := ntor.NewKeypair(true)
+	if err != nil {
+		return nil, err
+	}
+
+	lenDist := probdist.New(sf.lenSeed, 0, framing.MaximumSegmentLength, biasedDist)
+	var iatDist *probdist.WeightedDist
+	if sf.iatSeed != nil {
+		iatDist = probdist.New(sf.iatSeed, 0, maxIATDelay, biasedDist)
+	}
+
+	c := &obfs4Conn{conn, true, lenDist, iatDist, sf.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), make([]byte, consumeReadSize), nil, nil}
+
+	startTime := time.Now()
+
+	if err = c.serverHandshake(sf, sessionKey); err != nil {
+		c.closeAfterDelay(sf, startTime)
+		return nil, err
+	}
+
+	return c, nil
+}
+
+type obfs4Conn struct {
+	net.Conn
+
+	isServer bool
+
+	lenDist *probdist.WeightedDist
+	iatDist *probdist.WeightedDist
+	iatMode int
+
+	receiveBuffer        *bytes.Buffer
+	receiveDecodedBuffer *bytes.Buffer
+	readBuffer           []byte
+
+	encoder *framing.Encoder
+	decoder *framing.Decoder
+}
+
+func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err error) {
+	// Generate the initial protocol polymorphism distribution(s).
+	var seed *drbg.Seed
+	if seed, err = drbg.NewSeed(); err != nil {
+		return
+	}
+	lenDist := probdist.New(seed, 0, framing.MaximumSegmentLength, biasedDist)
+	var iatDist *probdist.WeightedDist
+	if args.iatMode != iatNone {
+		var iatSeed *drbg.Seed
+		iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
+		if iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:]); err != nil {
+			return
+		}
+		iatDist = probdist.New(iatSeed, 0, maxIATDelay, biasedDist)
+	}
+
+	// Allocate the client structure.
+	c = &obfs4Conn{conn, false, lenDist, iatDist, args.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), make([]byte, consumeReadSize), nil, nil}
+
+	// Start the handshake timeout.
+	deadline := time.Now().Add(clientHandshakeTimeout)
+	if err = conn.SetDeadline(deadline); err != nil {
+		return nil, err
+	}
+
+	if err = c.clientHandshake(args.nodeID, args.publicKey, args.sessionKey); err != nil {
+		return nil, err
+	}
+
+	// Stop the handshake timeout.
+	if err = conn.SetDeadline(time.Time{}); err != nil {
+		return nil, err
+	}
+
+	return
+}
+
+func (conn *obfs4Conn) clientHandshake(nodeID *ntor.NodeID, peerIdentityKey *ntor.PublicKey, sessionKey *ntor.Keypair) error {
+	if conn.isServer {
+		return fmt.Errorf("clientHandshake called on server connection")
+	}
+
+	// Generate and send the client handshake.
+	hs := newClientHandshake(nodeID, peerIdentityKey, sessionKey)
+	blob, err := hs.generateHandshake()
+	if err != nil {
+		return err
+	}
+	if _, err = conn.Conn.Write(blob); err != nil {
+		return err
+	}
+
+	// Consume the server handshake.
+	var hsBuf [maxHandshakeLength]byte
+	for {
+		n, err := conn.Conn.Read(hsBuf[:])
+		if err != nil {
+			// The Read() could have returned data and an error, but there is
+			// no point in continuing on an EOF or whatever.
+			return err
+		}
+		conn.receiveBuffer.Write(hsBuf[:n])
+
+		n, seed, err := hs.parseServerHandshake(conn.receiveBuffer.Bytes())
+		if err == ErrMarkNotFoundYet {
+			continue
+		} else if err != nil {
+			return err
+		}
+		_ = conn.receiveBuffer.Next(n)
+
+		// Use the derived key material to intialize the link crypto.
+		okm := ntor.Kdf(seed, framing.KeyLength*2)
+		conn.encoder = framing.NewEncoder(okm[:framing.KeyLength])
+		conn.decoder = framing.NewDecoder(okm[framing.KeyLength:])
+
+		return nil
+	}
+}
+
+func (conn *obfs4Conn) serverHandshake(sf *obfs4ServerFactory, sessionKey *ntor.Keypair) error {
+	if !conn.isServer {
+		return fmt.Errorf("serverHandshake called on client connection")
+	}
+
+	// Generate the server handshake, and arm the base timeout.
+	hs := newServerHandshake(sf.nodeID, sf.identityKey, sessionKey)
+	if err := conn.Conn.SetDeadline(time.Now().Add(serverHandshakeTimeout)); err != nil {
+		return err
+	}
+
+	// Consume the client handshake.
+	var hsBuf [maxHandshakeLength]byte
+	for {
+		n, err := conn.Conn.Read(hsBuf[:])
+		if err != nil {
+			// The Read() could have returned data and an error, but there is
+			// no point in continuing on an EOF or whatever.
+			return err
+		}
+		conn.receiveBuffer.Write(hsBuf[:n])
+
+		seed, err := hs.parseClientHandshake(sf.replayFilter, conn.receiveBuffer.Bytes())
+		if err == ErrMarkNotFoundYet {
+			continue
+		} else if err != nil {
+			return err
+		}
+		conn.receiveBuffer.Reset()
+
+		if err := conn.Conn.SetDeadline(time.Time{}); err != nil {
+			return nil
+		}
+
+		// Use the derived key material to intialize the link crypto.
+		okm := ntor.Kdf(seed, framing.KeyLength*2)
+		conn.encoder = framing.NewEncoder(okm[framing.KeyLength:])
+		conn.decoder = framing.NewDecoder(okm[:framing.KeyLength])
+
+		break
+	}
+
+	// Since the current and only implementation always sends a PRNG seed for
+	// the length obfuscation, this makes the amount of data received from the
+	// server inconsistent with the length sent from the client.
+	//
+	// Rebalance this by tweaking the client mimimum padding/server maximum
+	// padding, and sending the PRNG seed unpadded (As in, treat the PRNG seed
+	// as part of the server response).  See inlineSeedFrameLength in
+	// handshake_ntor.go.
+
+	// Generate/send the response.
+	blob, err := hs.generateHandshake()
+	if err != nil {
+		return err
+	}
+	var frameBuf bytes.Buffer
+	if _, err = frameBuf.Write(blob); err != nil {
+		return err
+	}
+
+	// Send the PRNG seed as the first packet.
+	if err := conn.makePacket(&frameBuf, packetTypePrngSeed, sf.lenSeed.Bytes()[:], 0); err != nil {
+		return err
+	}
+	if _, err = conn.Conn.Write(frameBuf.Bytes()); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (conn *obfs4Conn) Read(b []byte) (n int, err error) {
+	// If there is no payload from the previous Read() calls, consume data off
+	// the network.  Not all data received is guaranteed to be usable payload,
+	// so do this in a loop till data is present or an error occurs.
+	for conn.receiveDecodedBuffer.Len() == 0 {
+		err = conn.readPackets()
+		if err == framing.ErrAgain {
+			// Don't proagate this back up the call stack if we happen to break
+			// out of the loop.
+			err = nil
+			continue
+		} else if err != nil {
+			break
+		}
+	}
+
+	// Even if err is set, attempt to do the read anyway so that all decoded
+	// data gets relayed before the connection is torn down.
+	if conn.receiveDecodedBuffer.Len() > 0 {
+		var berr error
+		n, berr = conn.receiveDecodedBuffer.Read(b)
+		if err == nil {
+			// Only propagate berr if there are not more important (fatal)
+			// errors from the network/crypto/packet processing.
+			err = berr
+		}
+	}
+
+	return
+}
+
+func (conn *obfs4Conn) Write(b []byte) (n int, err error) {
+	chopBuf := bytes.NewBuffer(b)
+	var payload [maxPacketPayloadLength]byte
+	var frameBuf bytes.Buffer
+
+	// Chop the pending data into payload frames.
+	for chopBuf.Len() > 0 {
+		// Send maximum sized frames.
+		rdLen := 0
+		rdLen, err = chopBuf.Read(payload[:])
+		if err != nil {
+			return 0, err
+		} else if rdLen == 0 {
+			panic(fmt.Sprintf("BUG: Write(), chopping length was 0"))
+		}
+		n += rdLen
+
+		err = conn.makePacket(&frameBuf, packetTypePayload, payload[:rdLen], 0)
+		if err != nil {
+			return 0, err
+		}
+	}
+
+	if conn.iatMode != iatParanoid {
+		// For non-paranoid IAT, pad once per burst.  Paranoid IAT handles
+		// things differently.
+		if err = conn.padBurst(&frameBuf, conn.lenDist.Sample()); err != nil {
+			return 0, err
+		}
+	}
+
+	// Write the pending data onto the network.  Partial writes are fatal,
+	// because the frame encoder state is advanced, and the code doesn't keep
+	// frameBuf around.  In theory, write timeouts and whatnot could be
+	// supported if this wasn't the case, but that complicates the code.
+	if conn.iatMode != iatNone {
+		var iatFrame [framing.MaximumSegmentLength]byte
+		for frameBuf.Len() > 0 {
+			iatWrLen := 0
+
+			switch conn.iatMode {
+			case iatEnabled:
+				// Standard (ScrambleSuit-style) IAT obfuscation optimizes for
+				// bulk transport and will write ~MTU sized frames when
+				// possible.
+				iatWrLen, err = frameBuf.Read(iatFrame[:])
+
+			case iatParanoid:
+				// Paranoid IAT obfuscation throws performance out of the
+				// window and will sample the length distribution every time a
+				// write is scheduled.
+				targetLen := conn.lenDist.Sample()
+				if frameBuf.Len() < targetLen {
+					// There's not enough data buffered for the target write,
+					// so padding must be inserted.
+					if err = conn.padBurst(&frameBuf, targetLen); err != nil {
+						return 0, err
+					}
+					if frameBuf.Len() != targetLen {
+						// Ugh, padding came out to a value that required more
+						// than one frame, this is relatively unlikely so just
+						// resample since there's enough data to ensure that
+						// the next sample will be written.
+						continue
+					}
+				}
+				iatWrLen, err = frameBuf.Read(iatFrame[:targetLen])
+			}
+			if err != nil {
+				return 0, err
+			} else if iatWrLen == 0 {
+				panic(fmt.Sprintf("BUG: Write(), iat length was 0"))
+			}
+
+			// Calculate the delay.  The delay resolution is 100 usec, leading
+			// to a maximum delay of 10 msec.
+			iatDelta := time.Duration(conn.iatDist.Sample() * 100)
+
+			// Write then sleep.
+			_, err = conn.Conn.Write(iatFrame[:iatWrLen])
+			if err != nil {
+				return 0, err
+			}
+			time.Sleep(iatDelta * time.Microsecond)
+		}
+	} else {
+		_, err = conn.Conn.Write(frameBuf.Bytes())
+	}
+
+	return
+}
+
+func (conn *obfs4Conn) SetDeadline(t time.Time) error {
+	return syscall.ENOTSUP
+}
+
+func (conn *obfs4Conn) SetWriteDeadline(t time.Time) error {
+	return syscall.ENOTSUP
+}
+
+func (conn *obfs4Conn) closeAfterDelay(sf *obfs4ServerFactory, startTime time.Time) {
+	// I-it's not like I w-wanna handshake with you or anything.  B-b-baka!
+	defer conn.Conn.Close()
+
+	delay := time.Duration(sf.closeDelay)*time.Second + serverHandshakeTimeout
+	deadline := startTime.Add(delay)
+	if time.Now().After(deadline) {
+		return
+	}
+
+	if err := conn.Conn.SetReadDeadline(deadline); err != nil {
+		return
+	}
+
+	// Consume and discard data on this connection until either the specified
+	// interval passes or a certain size has been reached.
+	discarded := 0
+	var buf [framing.MaximumSegmentLength]byte
+	for discarded < int(sf.closeDelayBytes) {
+		n, err := conn.Conn.Read(buf[:])
+		if err != nil {
+			return
+		}
+		discarded += n
+	}
+}
+
+func (conn *obfs4Conn) padBurst(burst *bytes.Buffer, toPadTo int) (err error) {
+	tailLen := burst.Len() % framing.MaximumSegmentLength
+
+	padLen := 0
+	if toPadTo >= tailLen {
+		padLen = toPadTo - tailLen
+	} else {
+		padLen = (framing.MaximumSegmentLength - tailLen) + toPadTo
+	}
+
+	if padLen > headerLength {
+		err = conn.makePacket(burst, packetTypePayload, []byte{},
+			uint16(padLen-headerLength))
+		if err != nil {
+			return
+		}
+	} else if padLen > 0 {
+		err = conn.makePacket(burst, packetTypePayload, []byte{},
+			maxPacketPayloadLength)
+		if err != nil {
+			return
+		}
+		err = conn.makePacket(burst, packetTypePayload, []byte{},
+			uint16(padLen))
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+func init() {
+	flag.BoolVar(&biasedDist, biasCmdArg, false, "Enable obfs4 using ScrambleSuit style table generation")
+}
+
+var _ base.ClientFactory = (*obfs4ClientFactory)(nil)
+var _ base.ServerFactory = (*obfs4ServerFactory)(nil)
+var _ base.Transport = (*Transport)(nil)
+var _ net.Conn = (*obfs4Conn)(nil)

+ 175 - 0
replace/obfs4.git/transports/obfs4/packet.go

@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package obfs4
+
+import (
+	"crypto/sha256"
+	"encoding/binary"
+	"fmt"
+	"io"
+
+	"gitlab.com/yawning/obfs4.git/common/drbg"
+	"gitlab.com/yawning/obfs4.git/transports/obfs4/framing"
+)
+
+const (
+	packetOverhead          = 2 + 1
+	maxPacketPayloadLength  = framing.MaximumFramePayloadLength - packetOverhead
+	maxPacketPaddingLength  = maxPacketPayloadLength
+	seedPacketPayloadLength = seedLength
+
+	consumeReadSize = framing.MaximumSegmentLength * 16
+)
+
+const (
+	packetTypePayload = iota
+	packetTypePrngSeed
+)
+
+// InvalidPacketLengthError is the error returned when decodePacket detects a
+// invalid packet length/
+type InvalidPacketLengthError int
+
+func (e InvalidPacketLengthError) Error() string {
+	return fmt.Sprintf("packet: Invalid packet length: %d", int(e))
+}
+
+// InvalidPayloadLengthError is the error returned when decodePacket rejects the
+// payload length.
+type InvalidPayloadLengthError int
+
+func (e InvalidPayloadLengthError) Error() string {
+	return fmt.Sprintf("packet: Invalid payload length: %d", int(e))
+}
+
+var zeroPadBytes [maxPacketPaddingLength]byte
+
+func (conn *obfs4Conn) makePacket(w io.Writer, pktType uint8, data []byte, padLen uint16) error {
+	var pkt [framing.MaximumFramePayloadLength]byte
+
+	if len(data)+int(padLen) > maxPacketPayloadLength {
+		panic(fmt.Sprintf("BUG: makePacket() len(data) + padLen > maxPacketPayloadLength: %d + %d > %d",
+			len(data), padLen, maxPacketPayloadLength))
+	}
+
+	// Packets are:
+	//   uint8_t type      packetTypePayload (0x00)
+	//   uint16_t length   Length of the payload (Big Endian).
+	//   uint8_t[] payload Data payload.
+	//   uint8_t[] padding Padding.
+	pkt[0] = pktType
+	binary.BigEndian.PutUint16(pkt[1:], uint16(len(data)))
+	if len(data) > 0 {
+		copy(pkt[3:], data[:])
+	}
+	copy(pkt[3+len(data):], zeroPadBytes[:padLen])
+
+	pktLen := packetOverhead + len(data) + int(padLen)
+
+	// Encode the packet in an AEAD frame.
+	var frame [framing.MaximumSegmentLength]byte
+	frameLen, err := conn.encoder.Encode(frame[:], pkt[:pktLen])
+	if err != nil {
+		// All encoder errors are fatal.
+		return err
+	}
+	wrLen, err := w.Write(frame[:frameLen])
+	if err != nil {
+		return err
+	} else if wrLen < frameLen {
+		return io.ErrShortWrite
+	}
+
+	return nil
+}
+
+func (conn *obfs4Conn) readPackets() (err error) {
+	// Attempt to read off the network.
+	rdLen, rdErr := conn.Conn.Read(conn.readBuffer)
+	conn.receiveBuffer.Write(conn.readBuffer[:rdLen])
+
+	var decoded [framing.MaximumFramePayloadLength]byte
+	for conn.receiveBuffer.Len() > 0 {
+		// Decrypt an AEAD frame.
+		decLen := 0
+		decLen, err = conn.decoder.Decode(decoded[:], conn.receiveBuffer)
+		if err == framing.ErrAgain {
+			break
+		} else if err != nil {
+			break
+		} else if decLen < packetOverhead {
+			err = InvalidPacketLengthError(decLen)
+			break
+		}
+
+		// Decode the packet.
+		pkt := decoded[0:decLen]
+		pktType := pkt[0]
+		payloadLen := binary.BigEndian.Uint16(pkt[1:])
+		if int(payloadLen) > len(pkt)-packetOverhead {
+			err = InvalidPayloadLengthError(int(payloadLen))
+			break
+		}
+		payload := pkt[3 : 3+payloadLen]
+
+		switch pktType {
+		case packetTypePayload:
+			if payloadLen > 0 {
+				conn.receiveDecodedBuffer.Write(payload)
+			}
+		case packetTypePrngSeed:
+			// Only regenerate the distribution if we are the client.
+			if len(payload) == seedPacketPayloadLength && !conn.isServer {
+				var seed *drbg.Seed
+				seed, err = drbg.SeedFromBytes(payload)
+				if err != nil {
+					break
+				}
+				conn.lenDist.Reset(seed)
+				if conn.iatDist != nil {
+					iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
+					iatSeed, err := drbg.SeedFromBytes(iatSeedSrc[:])
+					if err != nil {
+						break
+					}
+					conn.iatDist.Reset(iatSeed)
+				}
+			}
+		default:
+			// Ignore unknown packet types.
+		}
+	}
+
+	// Read errors (all fatal) take priority over various frame processing
+	// errors.
+	if rdErr != nil {
+		return rdErr
+	}
+
+	return
+}

+ 260 - 0
replace/obfs4.git/transports/obfs4/statefile.go

@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package obfs4
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+
+	"git.torproject.org/pluggable-transports/goptlib.git"
+	"gitlab.com/yawning/obfs4.git/common/csrand"
+	"gitlab.com/yawning/obfs4.git/common/drbg"
+	"gitlab.com/yawning/obfs4.git/common/ntor"
+)
+
+const (
+	stateFile  = "obfs4_state.json"
+	bridgeFile = "obfs4_bridgeline.txt"
+
+	certSuffix = "=="
+	certLength = ntor.NodeIDLength + ntor.PublicKeyLength
+)
+
+type jsonServerState struct {
+	NodeID     string `json:"node-id"`
+	PrivateKey string `json:"private-key"`
+	PublicKey  string `json:"public-key"`
+	DrbgSeed   string `json:"drbg-seed"`
+	IATMode    int    `json:"iat-mode"`
+}
+
+type obfs4ServerCert struct {
+	raw []byte
+}
+
+func (cert *obfs4ServerCert) String() string {
+	return strings.TrimSuffix(base64.StdEncoding.EncodeToString(cert.raw), certSuffix)
+}
+
+func (cert *obfs4ServerCert) unpack() (*ntor.NodeID, *ntor.PublicKey) {
+	if len(cert.raw) != certLength {
+		panic(fmt.Sprintf("cert length %d is invalid", len(cert.raw)))
+	}
+
+	nodeID, _ := ntor.NewNodeID(cert.raw[:ntor.NodeIDLength])
+	pubKey, _ := ntor.NewPublicKey(cert.raw[ntor.NodeIDLength:])
+
+	return nodeID, pubKey
+}
+
+func serverCertFromString(encoded string) (*obfs4ServerCert, error) {
+	decoded, err := base64.StdEncoding.DecodeString(encoded + certSuffix)
+	if err != nil {
+		return nil, fmt.Errorf("failed to decode cert: %s", err)
+	}
+
+	if len(decoded) != certLength {
+		return nil, fmt.Errorf("cert length %d is invalid", len(decoded))
+	}
+
+	return &obfs4ServerCert{raw: decoded}, nil
+}
+
+func serverCertFromState(st *obfs4ServerState) *obfs4ServerCert {
+	cert := new(obfs4ServerCert)
+	cert.raw = append(st.nodeID.Bytes()[:], st.identityKey.Public().Bytes()[:]...)
+	return cert
+}
+
+type obfs4ServerState struct {
+	nodeID      *ntor.NodeID
+	identityKey *ntor.Keypair
+	drbgSeed    *drbg.Seed
+	iatMode     int
+
+	cert *obfs4ServerCert
+}
+
+func (st *obfs4ServerState) clientString() string {
+	return fmt.Sprintf("%s=%s %s=%d", certArg, st.cert, iatArg, st.iatMode)
+}
+
+func serverStateFromArgs(stateDir string, args *pt.Args) (*obfs4ServerState, error) {
+	var js jsonServerState
+	var nodeIDOk, privKeyOk, seedOk bool
+
+	js.NodeID, nodeIDOk = args.Get(nodeIDArg)
+	js.PrivateKey, privKeyOk = args.Get(privateKeyArg)
+	js.DrbgSeed, seedOk = args.Get(seedArg)
+	iatStr, iatOk := args.Get(iatArg)
+
+	// Either a private key, node id, and seed are ALL specified, or
+	// they should be loaded from the state file.
+	if !privKeyOk && !nodeIDOk && !seedOk {
+		if err := jsonServerStateFromFile(stateDir, &js); err != nil {
+			return nil, err
+		}
+	} else if !privKeyOk {
+		return nil, fmt.Errorf("missing argument '%s'", privateKeyArg)
+	} else if !nodeIDOk {
+		return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
+	} else if !seedOk {
+		return nil, fmt.Errorf("missing argument '%s'", seedArg)
+	}
+
+	// The IAT mode should be independently configurable.
+	if iatOk {
+		// If the IAT mode is specified, attempt to parse and apply it
+		// as an override.
+		iatMode, err := strconv.Atoi(iatStr)
+		if err != nil {
+			return nil, fmt.Errorf("malformed iat-mode '%s'", iatStr)
+		}
+		js.IATMode = iatMode
+	}
+
+	return serverStateFromJSONServerState(stateDir, &js)
+}
+
+func serverStateFromJSONServerState(stateDir string, js *jsonServerState) (*obfs4ServerState, error) {
+	var err error
+
+	st := new(obfs4ServerState)
+	if st.nodeID, err = ntor.NodeIDFromHex(js.NodeID); err != nil {
+		return nil, err
+	}
+	if st.identityKey, err = ntor.KeypairFromHex(js.PrivateKey); err != nil {
+		return nil, err
+	}
+	if st.drbgSeed, err = drbg.SeedFromHex(js.DrbgSeed); err != nil {
+		return nil, err
+	}
+	if js.IATMode < iatNone || js.IATMode > iatParanoid {
+		return nil, fmt.Errorf("invalid iat-mode '%d'", js.IATMode)
+	}
+	st.iatMode = js.IATMode
+	st.cert = serverCertFromState(st)
+
+	// Generate a human readable summary of the configured endpoint.
+	if err = newBridgeFile(stateDir, st); err != nil {
+		return nil, err
+	}
+
+	// Write back the possibly updated server state.
+	return st, writeJSONServerState(stateDir, js)
+}
+
+func jsonServerStateFromFile(stateDir string, js *jsonServerState) error {
+	fPath := path.Join(stateDir, stateFile)
+	f, err := ioutil.ReadFile(fPath)
+	if err != nil {
+		if os.IsNotExist(err) {
+			if err = newJSONServerState(stateDir, js); err == nil {
+				return nil
+			}
+		}
+		return err
+	}
+
+	if err := json.Unmarshal(f, js); err != nil {
+		return fmt.Errorf("failed to load statefile '%s': %s", fPath, err)
+	}
+
+	return nil
+}
+
+func newJSONServerState(stateDir string, js *jsonServerState) (err error) {
+	// Generate everything a server needs, using the cryptographic PRNG.
+	var st obfs4ServerState
+	rawID := make([]byte, ntor.NodeIDLength)
+	if err = csrand.Bytes(rawID); err != nil {
+		return
+	}
+	if st.nodeID, err = ntor.NewNodeID(rawID); err != nil {
+		return
+	}
+	if st.identityKey, err = ntor.NewKeypair(false); err != nil {
+		return
+	}
+	if st.drbgSeed, err = drbg.NewSeed(); err != nil {
+		return
+	}
+	st.iatMode = iatNone
+
+	// Encode it into JSON format and write the state file.
+	js.NodeID = st.nodeID.Hex()
+	js.PrivateKey = st.identityKey.Private().Hex()
+	js.PublicKey = st.identityKey.Public().Hex()
+	js.DrbgSeed = st.drbgSeed.Hex()
+	js.IATMode = st.iatMode
+
+	return writeJSONServerState(stateDir, js)
+}
+
+func writeJSONServerState(stateDir string, js *jsonServerState) error {
+	var err error
+	var encoded []byte
+	if encoded, err = json.Marshal(js); err != nil {
+		return err
+	}
+	if err = ioutil.WriteFile(path.Join(stateDir, stateFile), encoded, 0600); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func newBridgeFile(stateDir string, st *obfs4ServerState) error {
+	const prefix = "# obfs4 torrc client bridge line\n" +
+		"#\n" +
+		"# This file is an automatically generated bridge line based on\n" +
+		"# the current obfs4proxy configuration.  EDITING IT WILL HAVE\n" +
+		"# NO EFFECT.\n" +
+		"#\n" +
+		"# Before distributing this Bridge, edit the placeholder fields\n" +
+		"# to contain the actual values:\n" +
+		"#  <IP ADDRESS>  - The public IP address of your obfs4 bridge.\n" +
+		"#  <PORT>        - The TCP/IP port of your obfs4 bridge.\n" +
+		"#  <FINGERPRINT> - The bridge's fingerprint.\n\n"
+
+	bridgeLine := fmt.Sprintf("Bridge obfs4 <IP ADDRESS>:<PORT> <FINGERPRINT> %s\n",
+		st.clientString())
+
+	tmp := []byte(prefix + bridgeLine)
+	if err := ioutil.WriteFile(path.Join(stateDir, bridgeFile), tmp, 0600); err != nil {
+		return err
+	}
+
+	return nil
+}

+ 5 - 4
vendor/modules.txt

@@ -183,7 +183,7 @@ github.com/pkg/errors
 # github.com/pmezard/go-difflib v1.0.0
 ## explicit
 github.com/pmezard/go-difflib/difflib
-# github.com/refraction-networking/gotapdance v1.2.0 => ./psiphon/common/refraction/gotapdance
+# github.com/refraction-networking/gotapdance v1.2.0 => ./replace/gotapdance
 ## explicit; go 1.16
 github.com/refraction-networking/gotapdance/ed25519/edwards25519
 github.com/refraction-networking/gotapdance/ed25519/extra25519
@@ -219,8 +219,8 @@ github.com/wader/filtertransport
 # github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea
 ## explicit
 github.com/zach-klippenstein/goregen
-# gitlab.com/yawning/obfs4.git v0.0.0-20190120164510-816cff15f425
-## explicit
+# gitlab.com/yawning/obfs4.git v0.0.0-20190120164510-816cff15f425 => ./replace/obfs4.git
+## explicit; go 1.19
 gitlab.com/yawning/obfs4.git/common/csrand
 gitlab.com/yawning/obfs4.git/common/drbg
 gitlab.com/yawning/obfs4.git/common/ntor
@@ -381,4 +381,5 @@ honnef.co/go/tools/staticcheck
 honnef.co/go/tools/stylecheck
 honnef.co/go/tools/unused
 honnef.co/go/tools/unused/typemap
-# github.com/refraction-networking/gotapdance => ./psiphon/common/refraction/gotapdance
+# github.com/refraction-networking/gotapdance => ./replace/gotapdance
+# gitlab.com/yawning/obfs4.git => ./replace/obfs4.git