diff options
author | kali kaneko (leap communications) <kali@leap.se> | 2021-04-14 16:54:42 +0200 |
---|---|---|
committer | kali kaneko (leap communications) <kali@leap.se> | 2021-04-14 16:54:42 +0200 |
commit | 67a0eb7111d3f89e4a0cb21e43aefe6d87d37e04 (patch) | |
tree | c9b18e0da6e06ac165a485ee957b7850adb12e86 /vendor/github.com/OperatorFoundation | |
parent | 2e8f2a2e8e83fd89f956cdde886d5d9d808132da (diff) |
[pkg] go mod vendor to build debian/ubuntu packages
Diffstat (limited to 'vendor/github.com/OperatorFoundation')
19 files changed, 4937 insertions, 0 deletions
diff --git a/vendor/github.com/OperatorFoundation/obfs4/LICENSE b/vendor/github.com/OperatorFoundation/obfs4/LICENSE new file mode 100644 index 0000000..6b89f8c --- /dev/null +++ b/vendor/github.com/OperatorFoundation/obfs4/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. + diff --git a/vendor/github.com/OperatorFoundation/obfs4/common/csrand/csrand.go b/vendor/github.com/OperatorFoundation/obfs4/common/csrand/csrand.go new file mode 100644 index 0000000..45849d3 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/obfs4/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 ( + 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 diff --git a/vendor/github.com/OperatorFoundation/obfs4/common/drbg/hash_drbg.go b/vendor/github.com/OperatorFoundation/obfs4/common/drbg/hash_drbg.go new file mode 100644 index 0000000..cc4e8b9 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/obfs4/common/drbg/hash_drbg.go @@ -0,0 +1,149 @@ +/* + * 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 ( + "encoding/binary" + "encoding/hex" + "fmt" + "hash" + + "github.com/dchest/siphash" + + "github.com/OperatorFoundation/obfs4/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 +} diff --git a/vendor/github.com/OperatorFoundation/obfs4/common/ntor/ntor.go b/vendor/github.com/OperatorFoundation/obfs4/common/ntor/ntor.go new file mode 100644 index 0000000..0994782 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/obfs4/common/ntor/ntor.go @@ -0,0 +1,433 @@ +/* + * 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 ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "crypto/subtle" + "encoding/hex" + "fmt" + "io" + + "golang.org/x/crypto/curve25519" + "golang.org/x/crypto/hkdf" + + "github.com/agl/ed25519/extra25519" + + "github.com/OperatorFoundation/obfs4/common/csrand" +) + +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 +} diff --git a/vendor/github.com/OperatorFoundation/obfs4/common/probdist/weighted_dist.go b/vendor/github.com/OperatorFoundation/obfs4/common/probdist/weighted_dist.go new file mode 100644 index 0000000..2822ce6 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/obfs4/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 ( + "bytes" + "container/list" + "fmt" + "math/rand" + "sync" + + "github.com/OperatorFoundation/obfs4/common/csrand" + "github.com/OperatorFoundation/obfs4/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() +} diff --git a/vendor/github.com/OperatorFoundation/obfs4/common/replayfilter/replay_filter.go b/vendor/github.com/OperatorFoundation/obfs4/common/replayfilter/replay_filter.go new file mode 100644 index 0000000..5513361 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/obfs4/common/replayfilter/replay_filter.go @@ -0,0 +1,147 @@ +/* + * 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 ( + "container/list" + "encoding/binary" + "sync" + "time" + + "github.com/dchest/siphash" + + "github.com/OperatorFoundation/obfs4/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() +} diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/.gitignore b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/.gitignore new file mode 100644 index 0000000..d4d5132 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/.gitignore @@ -0,0 +1,2 @@ +/examples/dummy-client/dummy-client +/examples/dummy-server/dummy-server diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/COPYING b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/COPYING new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/COPYING @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/README b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/README new file mode 100644 index 0000000..1e36247 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/README @@ -0,0 +1,27 @@ +goptlib is a library for writing Tor pluggable transports in Go. + +https://spec.torproject.org/pt-spec +https://gitweb.torproject.org/torspec.git/tree/proposals/196-transport-control-ports.txt +https://gitweb.torproject.org/torspec.git/tree/proposals/217-ext-orport-auth.txt +https://gitweb.torproject.org/torspec.git/tree/proposals/232-pluggable-transports-through-proxy.txt + +To download a copy of the library into $GOPATH: + go get github.com/OperatorFoundation/shapeshifter-ipc + +See the included example programs for examples of how to use the +library. To build them, enter their directory and run "go build". + examples/dummy-client/dummy-client.go + examples/dummy-server/dummy-server.go +The recommended way to start writing a new transport plugin is to copy +dummy-client or dummy-server and make changes to it. + +There is browseable documentation here: +https://godoc.org/github.com/OperatorFoundation/shapeshifter-ipc + +Report bugs to the tor-dev@lists.torproject.org mailing list or to the +bug tracker at https://trac.torproject.org/projects/tor. + +To the extent possible under law, the authors have dedicated all +copyright and related and neighboring rights to this software to the +public domain worldwide. This software is distributed without any +warranty. See COPYING. diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/args.go b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/args.go new file mode 100644 index 0000000..295efa4 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/args.go @@ -0,0 +1,265 @@ +package pt + +import ( + "bytes" + "encoding/json" + "fmt" + "sort" + "strings" +) + +// Key–value mappings for the representation of client and server options. + +// Args maps a string key to a list of values. It is similar to url.Values. +type Args map[string][]string + +// Get the first value associated with the given key. If there are any values +// associated with the key, the value return has the value and ok is set to +// true. If there are no values for the given key, value is "" and ok is false. +// If you need access to multiple values, use the map directly. +func (args Args) Get(key string) (value string, ok bool) { + if args == nil { + return "", false + } + vals, ok := args[key] + if !ok || len(vals) == 0 { + return "", false + } + return vals[0], true +} + +// Append value to the list of values for key. +func (args Args) Add(key, value string) { + args[key] = append(args[key], value) +} + +// Return the index of the next unescaped byte in s that is in the term set, or +// else the length of the string if no terminators appear. Additionally return +// the unescaped string up to the returned index. +func indexUnescaped(s string, term []byte) (int, string, error) { + var i int + unesc := make([]byte, 0) + for i = 0; i < len(s); i++ { + b := s[i] + // A terminator byte? + if bytes.IndexByte(term, b) != -1 { + break + } + if b == '\\' { + i++ + if i >= len(s) { + return 0, "", fmt.Errorf("nothing following final escape in %q", s) + } + b = s[i] + } + unesc = append(unesc, b) + } + return i, string(unesc), nil +} + +// Parse a name–value mapping as from an encoded SOCKS username/password. +// +// "If any [k=v] items are provided, they are configuration parameters for the +// proxy: Tor should separate them with semicolons ... If a key or value value +// must contain [an equals sign or] a semicolon or a backslash, it is escaped +// with a backslash." +func parseClientParameters(s string) (args Args, err error) { + args = make(Args) + if len(s) == 0 { + return + } + i := 0 + for { + var key, value string + var offset, begin int + + begin = i + // Read the key. + offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) + if err != nil { + return + } + i += offset + // End of string or no equals sign? + if i >= len(s) || s[i] != '=' { + err = fmt.Errorf("no equals sign in %q", s[begin:i]) + return + } + // Skip the equals sign. + i++ + // Read the value. + offset, value, err = indexUnescaped(s[i:], []byte{';'}) + if err != nil { + return + } + i += offset + if len(key) == 0 { + err = fmt.Errorf("empty key in %q", s[begin:i]) + return + } + args.Add(key, value) + if i >= len(s) { + break + } + // Skip the semicolon. + i++ + } + return args, nil +} + +func ParsePT2ClientParameters(s string) (args Args, err error) { + args = make(Args) + if len(s) == 0 { + return + } + + decoder := json.NewDecoder(strings.NewReader(s)) + var result map[string]string + if err := decoder.Decode(&result); err != nil { + fmt.Errorf("Error decoding JSON %q", err) + return nil, err + } + + for key, value := range result { + args.Add(key, value) + } + + return args, nil +} + +func ParsePT2ServerParameters(s string) (params map[string]Args, err error) { + opts := make(map[string]Args) + if len(s) == 0 { + return opts, nil + } + + decoder := json.NewDecoder(strings.NewReader(s)) + var result map[string]map[string]string + if err := decoder.Decode(&result); err != nil { + fmt.Errorf("Error decoding JSON %q", err) + return nil, err + } + + for key, sub := range result { + args := make(Args) + for key2, value := range sub { + args.Add(key2, value) + } + + opts[key] = args + } + + return opts, nil +} + +// Parse a transport–name–value mapping as from TOR_PT_SERVER_TRANSPORT_OPTIONS. +// +// "<value> is a k=v string value with options that are to be passed to the +// transport. Colons, semicolons, equal signs and backslashes must be escaped +// with a backslash." +// Example: trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes +func ParseServerTransportOptions(s string) (opts map[string]Args, err error) { + opts = make(map[string]Args) + if len(s) == 0 { + return + } + i := 0 + for { + var methodName, key, value string + var offset, begin int + + begin = i + // Read the method name. + offset, methodName, err = indexUnescaped(s[i:], []byte{':', '=', ';'}) + if err != nil { + return + } + i += offset + // End of string or no colon? + if i >= len(s) || s[i] != ':' { + err = fmt.Errorf("no colon in %q", s[begin:i]) + return + } + // Skip the colon. + i++ + // Read the key. + offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) + if err != nil { + return + } + i += offset + // End of string or no equals sign? + if i >= len(s) || s[i] != '=' { + err = fmt.Errorf("no equals sign in %q", s[begin:i]) + return + } + // Skip the equals sign. + i++ + // Read the value. + offset, value, err = indexUnescaped(s[i:], []byte{';'}) + if err != nil { + return + } + i += offset + if len(methodName) == 0 { + err = fmt.Errorf("empty method name in %q", s[begin:i]) + return + } + if len(key) == 0 { + err = fmt.Errorf("empty key in %q", s[begin:i]) + return + } + if opts[methodName] == nil { + opts[methodName] = make(Args) + } + opts[methodName].Add(key, value) + if i >= len(s) { + break + } + // Skip the semicolon. + i++ + } + return opts, nil +} + +// Escape backslashes and all the bytes that are in set. +func backslashEscape(s string, set []byte) string { + var buf bytes.Buffer + for _, b := range []byte(s) { + if b == '\\' || bytes.IndexByte(set, b) != -1 { + buf.WriteByte('\\') + } + buf.WriteByte(b) + } + return buf.String() +} + +// Encode a name–value mapping so that it is suitable to go in the ARGS option +// of an SMETHOD line. The output is sorted by key. The "ARGS:" prefix is not +// added. +// +// "Equal signs and commas [and backslashes] must be escaped with a backslash." +func encodeSmethodArgs(args Args) string { + if args == nil { + return "" + } + + keys := make([]string, 0, len(args)) + for key := range args { + keys = append(keys, key) + } + sort.Strings(keys) + + escape := func(s string) string { + return backslashEscape(s, []byte{'=', ','}) + } + + var pairs []string + for _, key := range keys { + for _, value := range args[key] { + pairs = append(pairs, escape(key)+"="+escape(value)) + } + } + + return strings.Join(pairs, ",") +} diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt.go b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt.go new file mode 100644 index 0000000..aedbbb6 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt.go @@ -0,0 +1,941 @@ +// Package pt implements the Tor pluggable transports specification. +// +// Sample client usage: +// var ptInfo pt.ClientInfo +// ... +// func handler(conn *pt.SocksConn) error { +// defer conn.Close() +// remote, err := net.Dial("tcp", conn.Req.Target) +// if err != nil { +// conn.Reject() +// return err +// } +// defer remote.Close() +// err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr)) +// if err != nil { +// return err +// } +// // do something with conn and remote. +// return nil +// } +// func acceptLoop(ln *pt.SocksListener) error { +// defer ln.Close() +// for { +// conn, err := ln.AcceptSocks() +// if err != nil { +// if e, ok := err.(net.Error); ok && e.Temporary() { +// continue +// } +// return err +// } +// go handler(conn) +// } +// return nil +// } +// ... +// func main() { +// var err error +// ptInfo, err = pt.ClientSetup(nil) +// if err != nil { +// os.Exit(1) +// } +// if ptInfo.ProxyURL != nil { +// // you need to interpret the proxy URL yourself +// // call pt.ProxyDone instead if it's a type you understand +// pt.ProxyError("proxy %s is not supported") +// os.Exit(1) +// } +// for _, methodName := range ptInfo.MethodNames { +// switch methodName { +// case "foo": +// ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") +// if err != nil { +// pt.CmethodError(methodName, err.Error()) +// break +// } +// go acceptLoop(ln) +// pt.Cmethod(methodName, ln.Version(), ln.Addr()) +// default: +// pt.CmethodError(methodName, "no such method") +// } +// } +// pt.CmethodsDone() +// } +// +// Sample server usage: +// var ptInfo pt.ServerInfo +// ... +// func handler(conn net.Conn) error { +// defer conn.Close() +// or, err := pt.DialOr(&ptInfo, conn.RemoteAddr().String(), "foo") +// if err != nil { +// return +// } +// defer or.Close() +// // do something with or and conn +// return nil +// } +// func acceptLoop(ln net.Listener) error { +// defer ln.Close() +// for { +// conn, err := ln.Accept() +// if err != nil { +// if e, ok := err.(net.Error); ok && e.Temporary() { +// continue +// } +// return err +// } +// go handler(conn) +// } +// return nil +// } +// ... +// func main() { +// var err error +// ptInfo, err = pt.ServerSetup(nil) +// if err != nil { +// os.Exit(1) +// } +// for _, bindaddr := range ptInfo.Bindaddrs { +// switch bindaddr.MethodName { +// case "foo": +// ln, err := net.ListenTCP("tcp", bindaddr.Addr) +// if err != nil { +// pt.SmethodError(bindaddr.MethodName, err.Error()) +// break +// } +// go acceptLoop(ln) +// pt.Smethod(bindaddr.MethodName, ln.Addr()) +// default: +// pt.SmethodError(bindaddr.MethodName, "no such method") +// } +// } +// pt.SmethodsDone() +// } +// +// Some additional care is needed to handle signals and shutdown properly. See +// the example programs dummy-client and dummy-server. +// +// Tor pluggable transports specification: +// https://spec.torproject.org/pt-spec +// +// Extended ORPort: +// https://gitweb.torproject.org/torspec.git/tree/proposals/196-transport-control-ports.txt. +// +// Extended ORPort Authentication: +// https://gitweb.torproject.org/torspec.git/tree/proposals/217-ext-orport-auth.txt. +// +// Pluggable Transport through SOCKS proxy: +// https://gitweb.torproject.org/torspec.git/tree/proposals/232-pluggable-transports-through-proxy.txt +// +// The package implements a SOCKS5 server sufficient for a Tor client transport +// plugin. +// +// https://www.ietf.org/rfc/rfc1928.txt +// https://www.ietf.org/rfc/rfc1929.txt +package pt + +import ( + "bytes" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/subtle" + "encoding/binary" + "fmt" + "io" + "net" + "net/url" + "os" + "strconv" + "strings" + "time" +) + +// This type wraps a Write method and calls Sync after each Write. +type syncWriter struct { + *os.File +} + +// Call File.Write and then Sync. An error is returned if either operation +// returns an error. +func (w syncWriter) Write(p []byte) (n int, err error) { + n, err = w.File.Write(p) + if err != nil { + return + } + err = w.Sync() + return +} + +// Writer to which pluggable transports negotiation messages are written. It +// defaults to a Writer that writes to os.Stdout and calls Sync after each +// write. +// +// You may, for example, log pluggable transports messages by defining a Writer +// that logs what is written to it: +// type logWriteWrapper struct { +// io.Writer +// } +// +// func (w logWriteWrapper) Write(p []byte) (int, error) { +// log.Print(string(p)) +// return w.Writer.Write(p) +// } +// and then redefining Stdout: +// pt.Stdout = logWriteWrapper{pt.Stdout} +var Stdout io.Writer = syncWriter{os.Stdout} + +// Represents an error that can happen during negotiation, for example +// ENV-ERROR. When an error occurs, we print it to stdout and also pass it up +// the return chain. +type ptErr struct { + Keyword string + Args []string +} + +// Implements the error interface. +func (err *ptErr) Error() string { + return formatline(err.Keyword, err.Args...) +} + +func Getenv(key string) string { + return os.Getenv(key) +} + +// Returns an ENV-ERROR if the environment variable isn't set. +func GetenvRequired(key string) (string, error) { + value := os.Getenv(key) + if value == "" { + return "", envError(fmt.Sprintf("no %s environment variable", key)) + } + return value, nil +} + +// Returns true iff keyword contains only bytes allowed in a PT→Tor output line +// keyword. +// <KeywordChar> ::= <any US-ASCII alphanumeric, dash, and underscore> +func keywordIsSafe(keyword string) bool { + for _, b := range []byte(keyword) { + if b >= '0' && b <= '9' { + continue + } + if b >= 'A' && b <= 'Z' { + continue + } + if b >= 'a' && b <= 'z' { + continue + } + if b == '-' || b == '_' { + continue + } + return false + } + return true +} + +// Returns true iff arg contains only bytes allowed in a PT→Tor output line arg. +// <ArgChar> ::= <any US-ASCII character but NUL or NL> +func argIsSafe(arg string) bool { + for _, b := range []byte(arg) { + if b >= '\x80' || b == '\x00' || b == '\n' { + return false + } + } + return true +} + +func formatline(keyword string, v ...string) string { + var buf bytes.Buffer + if !keywordIsSafe(keyword) { + panic(fmt.Sprintf("keyword %q contains forbidden bytes", keyword)) + } + buf.WriteString(keyword) + for _, x := range v { + if !argIsSafe(x) { + panic(fmt.Sprintf("arg %q contains forbidden bytes", x)) + } + buf.WriteString(" " + x) + } + return buf.String() +} + +// Print a pluggable transports protocol line to Stdout. The line consists of a +// keyword followed by any number of space-separated arg strings. Panics if +// there are forbidden bytes in the keyword or the args (pt-spec.txt 2.2.1). +func line(keyword string, v ...string) { + fmt.Fprintln(Stdout, formatline(keyword, v...)) +} + +// Emit and return the given error as a ptErr. +func doError(keyword string, v ...string) *ptErr { + line(keyword, v...) + return &ptErr{keyword, v} +} + +// Emit an ENV-ERROR line with explanation text. Returns a representation of the +// error. +func envError(msg string) error { + return doError("ENV-ERROR", msg) +} + +// Emit a VERSION-ERROR line with explanation text. Returns a representation of +// the error. +func versionError(msg string) error { + return doError("VERSION-ERROR", msg) +} + +// Emit a CMETHOD-ERROR line with explanation text. Returns a representation of +// the error. +func CmethodError(methodName, msg string) error { + return doError("CMETHOD-ERROR", methodName, msg) +} + +// Emit an SMETHOD-ERROR line with explanation text. Returns a representation of +// the error. +func SmethodError(methodName, msg string) error { + return doError("SMETHOD-ERROR", methodName, msg) +} + +// Emit a PROXY-ERROR line with explanation text. Returns a representation of +// the error. +func ProxyError(msg string) error { + return doError("PROXY-ERROR %s\n", msg) +} + +// Emit a CMETHOD line. socks must be "socks4" or "socks5". Call this once for +// each listening client SOCKS port. +func Cmethod(name string, socks string, addr net.Addr) { + line("CMETHOD", name, socks, addr.String()) +} + +// Emit a CMETHODS DONE line. Call this after opening all client listeners. +func CmethodsDone() { + line("CMETHODS", "DONE") +} + +// Emit an SMETHOD line. Call this once for each listening server port. +func Smethod(name string, addr net.Addr) { + line("SMETHOD", name, addr.String()) +} + +// Emit an SMETHOD line with an ARGS option. args is a name–value mapping that +// will be added to the server's extrainfo document. +// +// This is an example of how to check for a required option: +// secret, ok := bindaddr.Options.Get("shared-secret") +// if ok { +// args := pt.Args{} +// args.Add("shared-secret", secret) +// pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), args) +// } else { +// pt.SmethodError(bindaddr.MethodName, "need a shared-secret option") +// } +// Or, if you just want to echo back the options provided by Tor from the +// TransportServerOptions configuration, +// pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), bindaddr.Options) +func SmethodArgs(name string, addr net.Addr, args Args) { + line("SMETHOD", name, addr.String(), "ARGS:"+encodeSmethodArgs(args)) +} + +// Emit an SMETHODS DONE line. Call this after opening all server listeners. +func SmethodsDone() { + line("SMETHODS", "DONE") +} + +// Emit a PROXY DONE line. Call this after parsing ClientInfo.ProxyURL. +func ProxyDone() { + fmt.Fprintf(Stdout, "PROXY DONE\n") +} + +// Get a pluggable transports version offered by Tor and understood by us, if +// any. The only version we understand is "1". This function reads the +// environment variable TOR_PT_MANAGED_TRANSPORT_VER. +func getManagedTransportVer() (string, error) { + const transportVersion = "1" + managedTransportVer, err := GetenvRequired("TOR_PT_MANAGED_TRANSPORT_VER") + if err != nil { + return "", err + } + for _, offered := range strings.Split(managedTransportVer, ",") { + if offered == transportVersion { + return offered, nil + } + } + return "", versionError("no-version") +} + +// Return the directory name in the TOR_PT_STATE_LOCATION environment variable, +// creating it if it doesn't exist. Returns non-nil error if +// TOR_PT_STATE_LOCATION is not set or if there is an error creating the +// directory. +func MakeStateDir() (string, error) { + dir, err := GetenvRequired("TOR_PT_STATE_LOCATION") + if err != nil { + return "", err + } + err = os.MkdirAll(dir, 0700) + return dir, err +} + +// Get the list of method names requested by Tor. This function reads the +// environment variable TOR_PT_CLIENT_TRANSPORTS. +func getClientTransports() ([]string, error) { + clientTransports, err := GetenvRequired("TOR_PT_CLIENT_TRANSPORTS") + if err != nil { + return nil, err + } + return strings.Split(clientTransports, ","), nil +} + +// Get the upstream proxy URL. Returns nil if no proxy is requested. The +// function ensures that the Scheme and Host fields are set; i.e., that the URL +// is absolute. It additionally checks that the Host field contains both a host +// and a port part. This function reads the environment variable TOR_PT_PROXY. +// +// This function doesn't check that the scheme is one of Tor's supported proxy +// schemes; that is, one of "http", "socks5", or "socks4a". The caller must be +// able to handle any returned scheme (which may be by calling ProxyError if +// it doesn't know how to handle the scheme). +func getProxyURL() (*url.URL, error) { + rawurl := os.Getenv("TOR_PT_PROXY") + if rawurl == "" { + return nil, nil + } + u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + if u.Scheme == "" { + return nil, fmt.Errorf("missing scheme") + } + if u.Host == "" { + return nil, fmt.Errorf("missing authority") + } + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + return nil, err + } + if host == "" { + return nil, fmt.Errorf("missing host") + } + if port == "" { + return nil, fmt.Errorf("missing port") + } + return u, nil +} + +// This structure is returned by ClientSetup. It consists of a list of method +// names and the upstream proxy URL, if any. +type ClientInfo struct { + MethodNames []string + ProxyURL *url.URL +} + +// Check the client pluggable transports environment, emitting an error message +// and returning a non-nil error if any error is encountered. Returns a +// ClientInfo struct. +// +// If your program needs to know whether to call ClientSetup or ServerSetup +// (i.e., if the same program can be run as either a client or a server), check +// whether the TOR_PT_CLIENT_TRANSPORTS environment variable is set: +// if os.Getenv("TOR_PT_CLIENT_TRANSPORTS") != "" { +// // Client mode; call pt.ClientSetup. +// } else { +// // Server mode; call pt.ServerSetup. +// } +// +// Always pass nil for the unused single parameter. In the past, the parameter +// was a list of transport names to use in case Tor requested "*". That feature +// was never implemented and has been removed from the pluggable transports +// specification. +// https://trac.torproject.org/projects/tor/ticket/15612 +func ClientSetup(_ []string) (info ClientInfo, err error) { + ver, err := getManagedTransportVer() + if err != nil { + return + } + line("VERSION", ver) + + info.MethodNames, err = getClientTransports() + if err != nil { + return + } + + info.ProxyURL, err = getProxyURL() + if err != nil { + return + } + + return info, nil +} + +// A combination of a method name and an address, as extracted from +// TOR_PT_SERVER_BINDADDR. +type Bindaddr struct { + MethodName string + Addr *net.TCPAddr + // Options from TOR_PT_SERVER_TRANSPORT_OPTIONS that pertain to this + // transport. + Options Args +} + +func parsePort(portStr string) (int, error) { + port, err := strconv.ParseUint(portStr, 10, 16) + return int(port), err +} + +// Resolve an address string into a net.TCPAddr. We are a bit more strict than +// net.ResolveTCPAddr; we don't allow an empty host or port, and the host part +// must be a literal IP address. +func ResolveAddr(addrStr string) (*net.TCPAddr, error) { + ipStr, portStr, err := net.SplitHostPort(addrStr) + if err != nil { + // Before the fixing of bug #7011, tor doesn't put brackets around IPv6 + // addresses. Split after the last colon, assuming it is a port + // separator, and try adding the brackets. + parts := strings.Split(addrStr, ":") + if len(parts) <= 2 { + return nil, err + } + addrStr := "[" + strings.Join(parts[:len(parts)-1], ":") + "]:" + parts[len(parts)-1] + ipStr, portStr, err = net.SplitHostPort(addrStr) + } + if err != nil { + return nil, err + } + if ipStr == "" { + return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a host part", addrStr)) + } + if portStr == "" { + return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a port part", addrStr)) + } + ip := net.ParseIP(ipStr) + if ip == nil { + return nil, net.InvalidAddrError(fmt.Sprintf("not an IP string: %q", ipStr)) + } + port, err := parsePort(portStr) + if err != nil { + return nil, err + } + return &net.TCPAddr{IP: ip, Port: port}, nil +} + +// Return a new slice, the members of which are those members of addrs having a +// MethodName in methodNames. +func FilterBindaddrs(addrs []Bindaddr, methodNames []string) []Bindaddr { + var result []Bindaddr + + for _, ba := range addrs { + for _, methodName := range methodNames { + if ba.MethodName == methodName { + result = append(result, ba) + break + } + } + } + + return result +} + +// Return an array of Bindaddrs, being the contents of TOR_PT_SERVER_BINDADDR +// with keys filtered by TOR_PT_SERVER_TRANSPORTS. Transport-specific options +// from TOR_PT_SERVER_TRANSPORT_OPTIONS are assigned to the Options member. +func getServerBindaddrs() ([]Bindaddr, error) { + var result []Bindaddr + + // Parse the list of server transport options. + serverTransportOptions := Getenv("TOR_PT_SERVER_TRANSPORT_OPTIONS") + optionsMap, err := ParseServerTransportOptions(serverTransportOptions) + if err != nil { + return nil, envError(fmt.Sprintf("TOR_PT_SERVER_TRANSPORT_OPTIONS: %q: %s", serverTransportOptions, err.Error())) + } + + // Get the list of all requested bindaddrs. + serverBindaddr, err := GetenvRequired("TOR_PT_SERVER_BINDADDR") + if err != nil { + return nil, err + } + for _, spec := range strings.Split(serverBindaddr, ",") { + var bindaddr Bindaddr + + parts := strings.SplitN(spec, "-", 2) + if len(parts) != 2 { + return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: doesn't contain \"-\"", spec)) + } + bindaddr.MethodName = parts[0] + addr, err := ResolveAddr(parts[1]) + if err != nil { + return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: %s", spec, err.Error())) + } + bindaddr.Addr = addr + bindaddr.Options = optionsMap[bindaddr.MethodName] + result = append(result, bindaddr) + } + + // Filter by TOR_PT_SERVER_TRANSPORTS. + serverTransports, err := GetenvRequired("TOR_PT_SERVER_TRANSPORTS") + if err != nil { + return nil, err + } + result = FilterBindaddrs(result, strings.Split(serverTransports, ",")) + + return result, nil +} + +func readAuthCookie(f io.Reader) ([]byte, error) { + authCookieHeader := []byte("! Extended ORPort Auth Cookie !\x0a") + buf := make([]byte, 64) + + n, err := io.ReadFull(f, buf) + if err != nil { + return nil, err + } + // Check that the file ends here. + n, err = f.Read(make([]byte, 1)) + if n != 0 { + return nil, fmt.Errorf("file is longer than 64 bytes") + } else if err != io.EOF { + return nil, fmt.Errorf("did not find EOF at end of file") + } + header := buf[0:32] + cookie := buf[32:64] + if subtle.ConstantTimeCompare(header, authCookieHeader) != 1 { + return nil, fmt.Errorf("missing auth cookie header") + } + + return cookie, nil +} + +// Read and validate the contents of an auth cookie file. Returns the 32-byte +// cookie. See section 4.2.1.2 of pt-spec.txt. +func readAuthCookieFile(filename string) ([]byte, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + + return readAuthCookie(f) +} + +// This structure is returned by ServerSetup. It consists of a list of +// Bindaddrs, an address for the ORPort, an address for the extended ORPort (if +// any), and an authentication cookie (if any). +type ServerInfo struct { + Bindaddrs []Bindaddr + OrAddr *net.TCPAddr + ExtendedOrAddr *net.TCPAddr + AuthCookiePath string +} + +// Check the server pluggable transports environment, emitting an error message +// and returning a non-nil error if any error is encountered. Resolves the +// various requested bind addresses, the server ORPort and extended ORPort, and +// reads the auth cookie file. Returns a ServerInfo struct. +// +// If your program needs to know whether to call ClientSetup or ServerSetup +// (i.e., if the same program can be run as either a client or a server), check +// whether the TOR_PT_CLIENT_TRANSPORTS environment variable is set: +// if os.Getenv("TOR_PT_CLIENT_TRANSPORTS") != "" { +// // Client mode; call pt.ClientSetup. +// } else { +// // Server mode; call pt.ServerSetup. +// } +// +// Always pass nil for the unused single parameter. In the past, the parameter +// was a list of transport names to use in case Tor requested "*". That feature +// was never implemented and has been removed from the pluggable transports +// specification. +// https://trac.torproject.org/projects/tor/ticket/15612 +func ServerSetup(_ []string) (info ServerInfo, err error) { + ver, err := getManagedTransportVer() + if err != nil { + return + } + line("VERSION", ver) + + info.Bindaddrs, err = getServerBindaddrs() + if err != nil { + return + } + + orPort := Getenv("TOR_PT_ORPORT") + if orPort != "" { + info.OrAddr, err = ResolveAddr(orPort) + if err != nil { + err = envError(fmt.Sprintf("cannot resolve TOR_PT_ORPORT %q: %s", orPort, err.Error())) + return + } + } + + info.AuthCookiePath = Getenv("TOR_PT_AUTH_COOKIE_FILE") + + extendedOrPort := Getenv("TOR_PT_EXTENDED_SERVER_PORT") + if extendedOrPort != "" { + if info.AuthCookiePath == "" { + err = envError("need TOR_PT_AUTH_COOKIE_FILE environment variable with TOR_PT_EXTENDED_SERVER_PORT") + return + } + info.ExtendedOrAddr, err = ResolveAddr(extendedOrPort) + if err != nil { + err = envError(fmt.Sprintf("cannot resolve TOR_PT_EXTENDED_SERVER_PORT %q: %s", extendedOrPort, err.Error())) + return + } + } + + // Need either OrAddr or ExtendedOrAddr. + if info.OrAddr == nil && info.ExtendedOrAddr == nil { + err = envError("need TOR_PT_ORPORT or TOR_PT_EXTENDED_SERVER_PORT environment variable") + return + } + + return info, nil +} + +// See 217-ext-orport-auth.txt section 4.2.1.3. +func computeServerHash(authCookie, clientNonce, serverNonce []byte) []byte { + h := hmac.New(sha256.New, authCookie) + io.WriteString(h, "ExtORPort authentication server-to-client hash") + h.Write(clientNonce) + h.Write(serverNonce) + return h.Sum([]byte{}) +} + +// See 217-ext-orport-auth.txt section 4.2.1.3. +func computeClientHash(authCookie, clientNonce, serverNonce []byte) []byte { + h := hmac.New(sha256.New, authCookie) + io.WriteString(h, "ExtORPort authentication client-to-server hash") + h.Write(clientNonce) + h.Write(serverNonce) + return h.Sum([]byte{}) +} + +func extOrPortAuthenticate(s io.ReadWriter, info *ServerInfo) error { + // Read auth types. 217-ext-orport-auth.txt section 4.1. + var authTypes [256]bool + var count int + for count = 0; count < 256; count++ { + buf := make([]byte, 1) + _, err := io.ReadFull(s, buf) + if err != nil { + return err + } + b := buf[0] + if b == 0 { + break + } + authTypes[b] = true + } + if count >= 256 { + return fmt.Errorf("read 256 auth types without seeing \\x00") + } + + // We support only type 1, SAFE_COOKIE. + if !authTypes[1] { + return fmt.Errorf("server didn't offer auth type 1") + } + _, err := s.Write([]byte{1}) + if err != nil { + return err + } + + clientNonce := make([]byte, 32) + clientHash := make([]byte, 32) + serverNonce := make([]byte, 32) + serverHash := make([]byte, 32) + + _, err = io.ReadFull(rand.Reader, clientNonce) + if err != nil { + return err + } + _, err = s.Write(clientNonce) + if err != nil { + return err + } + + _, err = io.ReadFull(s, serverHash) + if err != nil { + return err + } + _, err = io.ReadFull(s, serverNonce) + if err != nil { + return err + } + + // Work around tor bug #15240 where the auth cookie is generated after + // pluggable transports are launched, leading to a stale cookie getting + // cached forever if it is only read once as part of ServerSetup. + authCookie, err := readAuthCookieFile(info.AuthCookiePath) + if err != nil { + return fmt.Errorf("error reading TOR_PT_AUTH_COOKIE_FILE %q: %s", info.AuthCookiePath, err.Error()) + } + + expectedServerHash := computeServerHash(authCookie, clientNonce, serverNonce) + if subtle.ConstantTimeCompare(serverHash, expectedServerHash) != 1 { + return fmt.Errorf("mismatch in server hash") + } + + clientHash = computeClientHash(authCookie, clientNonce, serverNonce) + _, err = s.Write(clientHash) + if err != nil { + return err + } + + status := make([]byte, 1) + _, err = io.ReadFull(s, status) + if err != nil { + return err + } + if status[0] != 1 { + return fmt.Errorf("server rejected authentication") + } + + return nil +} + +// See section 3.1 of 196-transport-control-ports.txt. +const ( + extOrCmdDone = 0x0000 + extOrCmdUserAddr = 0x0001 + extOrCmdTransport = 0x0002 + extOrCmdOkay = 0x1000 + extOrCmdDeny = 0x1001 +) + +func extOrPortSendCommand(s io.Writer, cmd uint16, body []byte) error { + var buf bytes.Buffer + if len(body) > 65535 { + return fmt.Errorf("body length %d exceeds maximum of 65535", len(body)) + } + err := binary.Write(&buf, binary.BigEndian, cmd) + if err != nil { + return err + } + err = binary.Write(&buf, binary.BigEndian, uint16(len(body))) + if err != nil { + return err + } + err = binary.Write(&buf, binary.BigEndian, body) + if err != nil { + return err + } + _, err = s.Write(buf.Bytes()) + if err != nil { + return err + } + + return nil +} + +// Send a USERADDR command on s. See section 3.1.2.1 of +// 196-transport-control-ports.txt. +func extOrPortSendUserAddr(s io.Writer, addr string) error { + return extOrPortSendCommand(s, extOrCmdUserAddr, []byte(addr)) +} + +// Send a TRANSPORT command on s. See section 3.1.2.2 of +// 196-transport-control-ports.txt. +func extOrPortSendTransport(s io.Writer, methodName string) error { + return extOrPortSendCommand(s, extOrCmdTransport, []byte(methodName)) +} + +// Send a DONE command on s. See section 3.1 of 196-transport-control-ports.txt. +func extOrPortSendDone(s io.Writer) error { + return extOrPortSendCommand(s, extOrCmdDone, []byte{}) +} + +func extOrPortRecvCommand(s io.Reader) (cmd uint16, body []byte, err error) { + var bodyLen uint16 + data := make([]byte, 4) + + _, err = io.ReadFull(s, data) + if err != nil { + return + } + buf := bytes.NewBuffer(data) + err = binary.Read(buf, binary.BigEndian, &cmd) + if err != nil { + return + } + err = binary.Read(buf, binary.BigEndian, &bodyLen) + if err != nil { + return + } + body = make([]byte, bodyLen) + _, err = io.ReadFull(s, body) + if err != nil { + return + } + + return cmd, body, err +} + +// Send USERADDR and TRANSPORT commands followed by a DONE command. Wait for an +// OKAY or DENY response command from the server. If addr or methodName is "", +// the corresponding command is not sent. Returns nil if and only if OKAY is +// received. +func extOrPortSetup(s io.ReadWriter, addr, methodName string) error { + var err error + + if addr != "" { + err = extOrPortSendUserAddr(s, addr) + if err != nil { + return err + } + } + if methodName != "" { + err = extOrPortSendTransport(s, methodName) + if err != nil { + return err + } + } + err = extOrPortSendDone(s) + if err != nil { + return err + } + cmd, _, err := extOrPortRecvCommand(s) + if err != nil { + return err + } + if cmd == extOrCmdDeny { + return fmt.Errorf("server returned DENY after our USERADDR and DONE") + } else if cmd != extOrCmdOkay { + return fmt.Errorf("server returned unknown command 0x%04x after our USERADDR and DONE", cmd) + } + + return nil +} + +// Dial info.ExtendedOrAddr if defined, or else info.OrAddr, and return an open +// *net.TCPConn. If connecting to the extended OR port, extended OR port +// authentication à la 217-ext-orport-auth.txt is done before returning; an +// error is returned if authentication fails. +// +// The addr and methodName arguments are put in USERADDR and TRANSPORT ExtOrPort +// commands, respectively. If either is "", the corresponding command is not +// sent. +func DialOr(info *ServerInfo, addr, methodName string) (*net.TCPConn, error) { + if info.ExtendedOrAddr == nil || info.AuthCookiePath == "" { + return net.DialTCP("tcp", nil, info.OrAddr) + } + + s, err := net.DialTCP("tcp", nil, info.ExtendedOrAddr) + if err != nil { + return nil, err + } + s.SetDeadline(time.Now().Add(5 * time.Second)) + err = extOrPortAuthenticate(s, info) + if err != nil { + s.Close() + return nil, err + } + err = extOrPortSetup(s, addr, methodName) + if err != nil { + s.Close() + return nil, err + } + s.SetDeadline(time.Time{}) + + return s, nil +} diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go new file mode 100644 index 0000000..eab74c6 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go @@ -0,0 +1,486 @@ +package pt + +import ( + "bufio" + "encoding/binary" + "fmt" + "io" + "net" + "time" +) + +const ( + socksVersion = 0x05 + + socksAuthNoneRequired = 0x00 + socksAuthPrivateMethodPT2 = 0x80 + socksAuthNoAcceptableMethods = 0xff + + socksCmdConnect = 0x01 + socksRsv = 0x00 + + socksAtypeV4 = 0x01 + socksAtypeDomainName = 0x03 + socksAtypeV6 = 0x04 + + socksRepSucceeded = 0x00 + // "general SOCKS server failure" + SocksRepGeneralFailure = 0x01 + // "connection not allowed by ruleset" + SocksRepConnectionNotAllowed = 0x02 + // "Network unreachable" + SocksRepNetworkUnreachable = 0x03 + // "Host unreachable" + SocksRepHostUnreachable = 0x04 + // "Connection refused" + SocksRepConnectionRefused = 0x05 + // "TTL expired" + SocksRepTTLExpired = 0x06 + // "Command not supported" + SocksRepCommandNotSupported = 0x07 + // "Address type not supported" + SocksRepAddressNotSupported = 0x08 +) + +// Put a sanity timeout on how long we wait for a SOCKS request. +const socksRequestTimeout = 5 * time.Second + +// SocksRequest describes a SOCKS request. +type SocksRequest struct { + // The endpoint requested by the client as a "host:port" string. + Target string + // The parsed contents of private authentication method as a key–value mapping. + Args Args +} + +// SocksConn encapsulates a net.Conn and information associated with a SOCKS request. +type SocksConn struct { + net.Conn + Req SocksRequest +} + +// Send a message to the proxy client that access to the given address is +// granted. Addr is ignored, and "0.0.0.0:0" is always sent back for +// BND.ADDR/BND.PORT in the SOCKS response. +func (conn *SocksConn) Grant(addr *net.TCPAddr) error { + return sendSocks5ResponseGranted(conn) +} + +// Send a message to the proxy client that access was rejected or failed. This +// sends back a "General Failure" error code. RejectReason should be used if +// more specific error reporting is desired. +func (conn *SocksConn) Reject() error { + return conn.RejectReason(SocksRepGeneralFailure) +} + +// Send a message to the proxy client that access was rejected, with the +// specific error code indicating the reason behind the rejection. +func (conn *SocksConn) RejectReason(reason byte) error { + return sendSocks5ResponseRejected(conn, reason) +} + +// SocksListener wraps a net.Listener in order to read a SOCKS request on Accept. +// +// func handleConn(conn *pt.SocksConn) error { +// defer conn.Close() +// remote, err := net.Dial("tcp", conn.Req.Target) +// if err != nil { +// conn.Reject() +// return err +// } +// defer remote.Close() +// err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr)) +// if err != nil { +// return err +// } +// // do something with conn and remote +// return nil +// } +// ... +// ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") +// if err != nil { +// panic(err.Error()) +// } +// for { +// conn, err := ln.AcceptSocks() +// if err != nil { +// log.Printf("accept error: %s", err) +// if e, ok := err.(net.Error); ok && e.Temporary() { +// continue +// } +// break +// } +// go handleConn(conn) +// } +type SocksListener struct { + net.Listener +} + +// Open a net.Listener according to network and laddr, and return it as a +// SocksListener. +func ListenSocks(network, laddr string) (*SocksListener, error) { + ln, err := net.Listen(network, laddr) + if err != nil { + return nil, err + } + return NewSocksListener(ln), nil +} + +// Create a new SocksListener wrapping the given net.Listener. +func NewSocksListener(ln net.Listener) *SocksListener { + return &SocksListener{ln} +} + +// Accept is the same as AcceptSocks, except that it returns a generic net.Conn. +// It is present for the sake of satisfying the net.Listener interface. +func (ln *SocksListener) Accept() (net.Conn, error) { + return ln.AcceptSocks() +} + +// Call Accept on the wrapped net.Listener, do SOCKS negotiation, and return a +// SocksConn. After accepting, you must call either conn.Grant or conn.Reject +// (presumably after trying to connect to conn.Req.Target). +// +// Errors returned by AcceptSocks may be temporary (for example, EOF while +// reading the request, or a badly formatted userid string), or permanent (e.g., +// the underlying socket is closed). You can determine whether an error is +// temporary and take appropriate action with a type conversion to net.Error. +// For example: +// +// for { +// conn, err := ln.AcceptSocks() +// if err != nil { +// if e, ok := err.(net.Error); ok && e.Temporary() { +// log.Printf("temporary accept error; trying again: %s", err) +// continue +// } +// log.Printf("permanent accept error; giving up: %s", err) +// break +// } +// go handleConn(conn) +// } +func (ln *SocksListener) AcceptSocks() (*SocksConn, error) { +retry: + c, err := ln.Listener.Accept() + if err != nil { + return nil, err + } + conn := new(SocksConn) + conn.Conn = c + err = conn.SetDeadline(time.Now().Add(socksRequestTimeout)) + if err != nil { + conn.Close() + goto retry + } + conn.Req, err = socks5Handshake(conn, false) + if err != nil { + conn.Close() + goto retry + } + err = conn.SetDeadline(time.Time{}) + if err != nil { + conn.Close() + goto retry + } + return conn, nil +} + +// Returns "socks5", suitable to be included in a call to Cmethod. +func (ln *SocksListener) Version() string { + return "socks5" +} + +// socks5handshake conducts the SOCKS5 handshake up to the point where the +// client command is read and the proxy must open the outgoing connection. +// Returns a SocksRequest. +func socks5Handshake(s io.ReadWriter, needOptions bool) (req SocksRequest, err error) { + rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) + + // Negotiate the authentication method. + var method byte + if method, err = socksNegotiateAuth(rw, needOptions); err != nil { + return + } + + // Authenticate the client. + if err = socksAuthenticate(rw, method, &req); err != nil { + return + } + + // Read the command. + err = socksReadCommand(rw, &req) + return +} + +// socksNegotiateAuth negotiates the authentication method and returns the +// selected method as a byte. On negotiation failures an error is returned. +func socksNegotiateAuth(rw *bufio.ReadWriter, needOptions bool) (method byte, err error) { + // Validate the version. + if err = socksReadByteVerify(rw, "version", socksVersion); err != nil { + return + } + + // Read the number of methods. + var nmethods byte + if nmethods, err = socksReadByte(rw); err != nil { + return + } + + // Read the methods. + var methods []byte + if methods, err = socksReadBytes(rw, int(nmethods)); err != nil { + return + } + + // Pick the most "suitable" method. + method = socksAuthNoAcceptableMethods + for _, m := range methods { + if needOptions { + switch m { + case socksAuthNoneRequired: + // Pick PT 2.0 private authentication method over None if the client happens to + // send both and SOCKS header options are needed. + if method == socksAuthNoAcceptableMethods { + method = m + } + + case socksAuthPrivateMethodPT2: + method = m + } + } else { + switch m { + case socksAuthPrivateMethodPT2: + // Pick None over PT 2.0 private authentication method if the client happens to + // send both and SOCKS header options are not needed. + if method == socksAuthNoAcceptableMethods { + method = m + } + + case socksAuthNoneRequired: + method = m + } + } + } + + // Send the negotiated method. + var msg [2]byte + msg[0] = socksVersion + msg[1] = method + if _, err = rw.Writer.Write(msg[:]); err != nil { + return + } + + if err = socksFlushBuffers(rw); err != nil { + return + } + return +} + +// socksAuthenticate authenticates the client via the chosen authentication +// mechanism. +func socksAuthenticate(rw *bufio.ReadWriter, method byte, req *SocksRequest) (err error) { + switch method { + case socksAuthNoneRequired: + // Straight into reading the connect. + + case socksAuthPrivateMethodPT2: + if err = socksAuthPT2(rw, req); err != nil { + return + } + + case socksAuthNoAcceptableMethods: + err = fmt.Errorf("SOCKS method select had no compatible methods") + return + + default: + err = fmt.Errorf("SOCKS method select picked a unsupported method 0x%02x", method) + return + } + + if err = socksFlushBuffers(rw); err != nil { + return + } + return +} + +// socksAuthRFC1929 authenticates the client via Pluggable Transports 2.0, draft 1 +// private auth method. +func socksAuthPT2(rw *bufio.ReadWriter, req *SocksRequest) (err error) { + // Read the authentication data. + var len uint32 + if len, err = socksReadUint32(rw); err != nil { + return + } + if len == 0 { + err = fmt.Errorf("PT 2.0 authentication data with 0 length") + return + } + var data []byte + if data, err = socksReadBytes(rw, int(len)); err != nil { + return + } + + var result string = string(data) + + // Parse the authentication data according to the PT 2.0 specification + if req.Args, err = ParsePT2ClientParameters(result); err != nil { + fmt.Println("Error parsing PT2 client parameters", err) + return + } + + return +} + +// socksReadCommand reads a SOCKS5 client command and parses out the relevant +// fields into a SocksRequest. Only CMD_CONNECT is supported. +func socksReadCommand(rw *bufio.ReadWriter, req *SocksRequest) (err error) { + sendErrResp := func(reason byte) { + // Swallow errors that occur when writing/flushing the response, + // connection will be closed anyway. + sendSocks5ResponseRejected(rw, reason) + socksFlushBuffers(rw) + } + + // Validate the fixed parts of the command message. + if err = socksReadByteVerify(rw, "version", socksVersion); err != nil { + sendErrResp(SocksRepGeneralFailure) + return + } + if err = socksReadByteVerify(rw, "command", socksCmdConnect); err != nil { + sendErrResp(SocksRepCommandNotSupported) + return + } + if err = socksReadByteVerify(rw, "reserved", socksRsv); err != nil { + sendErrResp(SocksRepGeneralFailure) + return + } + + // Read the destination address/port. + // XXX: This should probably eventually send socks 5 error messages instead + // of rudely closing connections on invalid addresses. + var atype byte + if atype, err = socksReadByte(rw); err != nil { + return + } + var host string + switch atype { + case socksAtypeV4: + var addr []byte + if addr, err = socksReadBytes(rw, net.IPv4len); err != nil { + return + } + host = net.IPv4(addr[0], addr[1], addr[2], addr[3]).String() + + case socksAtypeDomainName: + var alen byte + if alen, err = socksReadByte(rw); err != nil { + return + } + if alen == 0 { + err = fmt.Errorf("SOCKS request had domain name with 0 length") + return + } + var addr []byte + if addr, err = socksReadBytes(rw, int(alen)); err != nil { + return + } + host = string(addr) + + case socksAtypeV6: + var rawAddr []byte + if rawAddr, err = socksReadBytes(rw, net.IPv6len); err != nil { + return + } + addr := make(net.IP, net.IPv6len) + copy(addr[:], rawAddr[:]) + host = fmt.Sprintf("[%s]", addr.String()) + + default: + sendErrResp(SocksRepAddressNotSupported) + err = fmt.Errorf("SOCKS request had unsupported address type 0x%02x", atype) + return + } + var rawPort []byte + if rawPort, err = socksReadBytes(rw, 2); err != nil { + return + } + port := int(rawPort[0])<<8 | int(rawPort[1])<<0 + + if err = socksFlushBuffers(rw); err != nil { + return + } + + req.Target = fmt.Sprintf("%s:%d", host, port) + return +} + +// Send a SOCKS5 response with the given code. BND.ADDR/BND.PORT is always the +// IPv4 address/port "0.0.0.0:0". +func sendSocks5Response(w io.Writer, code byte) error { + resp := make([]byte, 4+4+2) + resp[0] = socksVersion + resp[1] = code + resp[2] = socksRsv + resp[3] = socksAtypeV4 + + // BND.ADDR/BND.PORT should be the address and port that the outgoing + // connection is bound to on the proxy, but Tor does not use this + // information, so all zeroes are sent. + + _, err := w.Write(resp[:]) + return err +} + +// Send a SOCKS5 response code 0x00. +func sendSocks5ResponseGranted(w io.Writer) error { + return sendSocks5Response(w, socksRepSucceeded) +} + +// Send a SOCKS5 response with the provided failure reason. +func sendSocks5ResponseRejected(w io.Writer, reason byte) error { + return sendSocks5Response(w, reason) +} + +func socksFlushBuffers(rw *bufio.ReadWriter) error { + if err := rw.Writer.Flush(); err != nil { + return err + } + if rw.Reader.Buffered() > 0 { + return fmt.Errorf("%d bytes left after SOCKS message", rw.Reader.Buffered()) + } + return nil +} + +func socksReadByte(rw *bufio.ReadWriter) (byte, error) { + return rw.Reader.ReadByte() +} + +func socksReadUint32(rw *bufio.ReadWriter) (uint32, error) { + buffer, err := socksReadBytes(rw, 4) + if err != nil { + return 0, err + } + + return binary.BigEndian.Uint32(buffer), nil +} + +func socksReadBytes(rw *bufio.ReadWriter, n int) ([]byte, error) { + ret := make([]byte, n) + if _, err := io.ReadFull(rw.Reader, ret); err != nil { + return nil, err + } + return ret, nil +} + +func socksReadByteVerify(rw *bufio.ReadWriter, descr string, expected byte) error { + val, err := socksReadByte(rw) + if err != nil { + return err + } + if val != expected { + return fmt.Errorf("SOCKS message field %s was 0x%02x, not 0x%02x", descr, val, expected) + } + return nil +} + +var _ net.Listener = (*SocksListener)(nil) diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-transports/LICENSE b/vendor/github.com/OperatorFoundation/shapeshifter-transports/LICENSE new file mode 100644 index 0000000..6b89f8c --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-transports/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. + diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/README.md b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/README.md new file mode 100644 index 0000000..6894167 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/README.md @@ -0,0 +1,31 @@ +# obfs4 + + +This is a look-like nothing obfuscation protocol that incorporates ideas and concepts from Philipp Winter's ScrambleSuit protocol. The obfs naming was chosen primarily because it was shorter, in terms of protocol ancestery obfs4 is much closer to ScrambleSuit than obfs2/obfs3. + +The notable differences between ScrambleSuit and obfs4: + +* The handshake always does a full key exchange (no such thing as a Session Ticket Handshake). +* The handshake uses the Tor Project's ntor handshake with public keys obfuscated via the Elligator 2 mapping. +* The link layer encryption uses NaCl secret boxes (Poly1305/XSalsa20). +* As an added bonus, obfs4proxy also supports acting as an obfs2/3 client and bridge to ease the transition to the new protocol. + +## Using obfs4 + + +### Go Version: + +obfs4 is one of the transports available in the [Shapeshifter-Transports library](https://github.com/OperatorFoundation/Shapeshifter-Transports). + +1. First, you need to create a dialer + `dialer := proxy.Direct` + +2. Create an instance of an obfs4 server + `obfs4Transport := obfs4.Transport{ + CertString: "InsertCertStringHere", + IatMode: 0 or 1, + Address: "InsertAddressHere", + Dialer: dialer,}` + +5. Call Dial on obfs4Transport: + `_, err := obfs4Transport.Dial()` diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/framing/framing.go b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/framing/framing.go new file mode 100644 index 0000000..3c9d05b --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/framing/framing.go @@ -0,0 +1,306 @@ +/* + * 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 ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + + "golang.org/x/crypto/nacl/secretbox" + + "github.com/OperatorFoundation/obfs4/common/csrand" + "github.com/OperatorFoundation/obfs4/common/drbg" +) + +const ( + // MaximumSegmentLength is the length of the largest possible segment + // including overhead. + MaximumSegmentLength = 1500 - (40 + 12) + + lengthLength = 2 + + // 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 +) + +// 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 +} diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/handshake_ntor.go b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/handshake_ntor.go new file mode 100644 index 0000000..b394ca6 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-transports/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" + + "github.com/OperatorFoundation/obfs4/common/csrand" + "github.com/OperatorFoundation/obfs4/common/ntor" + "github.com/OperatorFoundation/obfs4/common/replayfilter" + "github.com/OperatorFoundation/shapeshifter-transports/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 +} diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/obfs4.go b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/obfs4.go new file mode 100644 index 0000000..420c466 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/obfs4.go @@ -0,0 +1,714 @@ +/* + * 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 ( + "bytes" + "crypto/sha256" + "flag" + "fmt" + "math/rand" + "net" + "strconv" + "syscall" + "time" + + "golang.org/x/net/proxy" + + "github.com/OperatorFoundation/obfs4/common/drbg" + "github.com/OperatorFoundation/obfs4/common/ntor" + "github.com/OperatorFoundation/obfs4/common/probdist" + "github.com/OperatorFoundation/obfs4/common/replayfilter" + "github.com/OperatorFoundation/shapeshifter-ipc" + "github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/framing" +) + +const ( + 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 + +// Transport that uses the obfs4 protocol to shapeshift the application network traffic +type Obfs4Transport struct { + dialer proxy.Dialer + + serverFactory *Obfs4ServerFactory + clientArgs *Obfs4ClientArgs +} + +type Obfs4ServerFactory struct { + args *pt.Args + + nodeID *ntor.NodeID + identityKey *ntor.Keypair + lenSeed *drbg.Seed + iatSeed *drbg.Seed + iatMode int + replayFilter *replayfilter.ReplayFilter + + closeDelayBytes int + closeDelay int +} + +type Obfs4ClientArgs struct { + nodeID *ntor.NodeID + publicKey *ntor.PublicKey + sessionKey *ntor.Keypair + iatMode int +} + +func NewObfs4Server(stateDir string) (*Obfs4Transport, error) { + args := make(pt.Args) + 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{&ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, st.iatMode, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)} + + return &Obfs4Transport{dialer: nil, serverFactory: sf, clientArgs: nil}, nil +} + +func NewObfs4Client(certString string, iatMode int, dialer proxy.Dialer) (*Obfs4Transport, 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. + cert, err := serverCertFromString(certString) + if err != nil { + return nil, err + } + nodeID, publicKey = cert.unpack() + + // 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 &Obfs4Transport{dialer: dialer, serverFactory: nil, clientArgs: &Obfs4ClientArgs{nodeID, publicKey, sessionKey, iatMode}}, nil +} + +// Create outgoing transport connection +func (transport *Obfs4Transport) Dial(address string) (net.Conn, error) { + dialFn := transport.dialer.Dial + conn, dialErr := dialFn("tcp", address) + if dialErr != nil { + return nil, dialErr + } + + dialConn := conn + transportConn, err := newObfs4ClientConn(conn, transport.clientArgs) + if err != nil { + dialConn.Close() + return nil, err + } + + return transportConn, nil +} + +//begin code added from optimizer +type Transport struct { + CertString string + IatMode int + Address string + Dialer proxy.Dialer +} + +func (transport Transport) Dial() (net.Conn, error) { + Obfs4Transport, err := NewObfs4Client(transport.CertString, transport.IatMode, transport.Dialer) + if err != nil { + return nil, err + } + conn, err := Obfs4Transport.Dial(transport.Address) + if err != nil { + return nil, err + } + return conn, nil +} + +//end code added from optimizer +// Create listener for incoming transport connection +func (transport *Obfs4Transport) Listen(address string) net.Listener { + addr, resolveErr := pt.ResolveAddr(address) + if resolveErr != nil { + fmt.Println(resolveErr.Error()) + return nil + } + + ln, err := net.ListenTCP("tcp", addr) + if err != nil { + fmt.Println(err.Error()) + return nil + } + + return newObfs4TransportListener(transport.serverFactory, ln) +} + +func (transport *Obfs4Transport) Close() error { + return nil +} + +// End methods that implement the base.Transport interface + +// Listener that accepts connections using the obfs4 transport to communicate +type obfs4TransportListener struct { + serverFactory *Obfs4ServerFactory + + listener *net.TCPListener +} + +// Private initializer for the obfs4 listener. +// You get a new listener instance by calling the Listen method on the Transport. +func newObfs4TransportListener(sf *Obfs4ServerFactory, listener *net.TCPListener) *obfs4TransportListener { + return &obfs4TransportListener{serverFactory: sf, listener: listener} +} + +// Methods that implement the net.Listener interface + +// Listener for underlying network connection +func (listener *obfs4TransportListener) NetworkListener() net.Listener { + return listener.listener +} + +// Accept waits for and returns the next connection to the listener. +func (listener *obfs4TransportListener) Accept() (net.Conn, error) { + conn, err := listener.listener.Accept() + if err != nil { + return nil, err + } + + return newObfs4ServerConn(conn, listener.serverFactory) +} + +func (listener *obfs4TransportListener) Addr() net.Addr { + interfaces, _ := net.Interfaces() + addrs, _ := interfaces[0].Addrs() + return addrs[0] +} + +// Close closes the transport listener. +// Any blocked Accept operations will be unblocked and return errors. +func (listener *obfs4TransportListener) Close() error { + return listener.listener.Close() +} + +// End methods that implement the net.Listener interface + +// Implementation of net.Conn, which also requires implementing net.Conn +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 +} + +// Private initializer methods + +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 newObfs4ServerConn(conn net.Conn, sf *Obfs4ServerFactory) (*obfs4Conn, 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 +} + +// End initializer methods + +// Methods that implement the net.Conn interface +func (transportConn *obfs4Conn) NetworkConn() net.Conn { + return transportConn.Conn +} + +// End methods that implement the net.Conn interface + +// Methods implementing net.Conn +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) Close() error { + return conn.Conn.Close() +} + +func (conn *obfs4Conn) LocalAddr() net.Addr { + return conn.Conn.LocalAddr() +} + +func (conn *obfs4Conn) RemoteAddr() net.Addr { + return conn.Conn.RemoteAddr() +} + +func (conn *obfs4Conn) SetDeadline(t time.Time) error { + return syscall.ENOTSUP +} + +func (conn *obfs4Conn) SetReadDeadline(t time.Time) error { + return conn.Conn.SetReadDeadline(t) +} + +func (conn *obfs4Conn) SetWriteDeadline(t time.Time) error { + return syscall.ENOTSUP +} + +// End of methods implementing net.Conn + +// Private methods implementing the obfs4 protocol + +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) 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") +} + +// End private methods implementing the obfs4 protocol + +// Force type checks to make sure that instances conform to interfaces +var _ net.Listener = (*obfs4TransportListener)(nil) +var _ net.Conn = (*obfs4Conn)(nil) diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/packet.go b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/packet.go new file mode 100644 index 0000000..5ea4097 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-transports/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" + + "github.com/OperatorFoundation/obfs4/common/drbg" + "github.com/OperatorFoundation/shapeshifter-transports/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 +} diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/statefile.go b/vendor/github.com/OperatorFoundation/shapeshifter-transports/transports/obfs4/statefile.go new file mode 100644 index 0000000..b8b8fa0 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-transports/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" + + "github.com/OperatorFoundation/obfs4/common/csrand" + "github.com/OperatorFoundation/obfs4/common/drbg" + "github.com/OperatorFoundation/obfs4/common/ntor" + "github.com/OperatorFoundation/shapeshifter-ipc" +) + +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 +} |