Massive cleanup/code reorg.

* Changed obfs4proxy to be more like obfsproxy in terms of design,
   including being an easy framework for developing new TCP/IP style
   pluggable transports.
 * Added support for also acting as an obfs2/obfs3 client or bridge
   as a transition measure (and because the code itself is trivial).
 * Massively cleaned up the obfs4 and related code to be easier to
   read, and more idiomatic Go-like in style.
 * To ease deployment, obfs4proxy will now autogenerate the node-id,
   curve25519 keypair, and drbg seed if none are specified, and save
   them to a JSON file in the pt_state directory (Fixes Tor bug #12605).
merge-requests/3/head
Yawning Angel 10 years ago
parent 8a3eb4b309
commit 339c63f0c8

@ -1,12 +1,6 @@
## obfs4 - The obfourscator
#### Yawning Angel (yawning at torproject dot org)
### WARNING
This is pre-alpha. Don't expect any security or wire protocol stability yet.
If you want to use something like this, you should currently probably be looking
at ScrambleSuit.
### What?
This is a look-like nothing obfuscation protocol that incorporates ideas and
@ -22,6 +16,9 @@ The notable differences between ScrambleSuit and obfs4:
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.
### Why not extend ScrambleSuit?
It's my protocol and I'll obfuscate if I want to.
@ -43,20 +40,6 @@ listed for clarity.
* SipHash-2-4 (https://github.com/dchest/siphash)
* goptlib (https://git.torproject.org/pluggable-transports/goptlib.git)
### TODO
* Code cleanups.
* Write more unit tests.
* Optimize further.
### WON'T DO
* I do not care that much about standalone mode. Patches *MAY* be accepted,
especially if they are clean and are useful to Tor users.
* Yes, I use a bunch of code from the borg^w^wGoogle. If that bothers you
feel free to write your own implementation.
* I do not care about older versions of the go runtime.
### Thanks
* David Fifield for goptlib.

@ -29,7 +29,7 @@
// with some utility functions for common random number/byte related tasks.
//
// Not all of the convinience routines are replicated, only those that are
// useful for obfs4. The CsRand variable provides access to the full math/rand
// immediately useful. The Rand variable provides access to the full math/rand
// API.
package csrand
@ -44,8 +44,8 @@ import (
var (
csRandSourceInstance csRandSource
// CsRand is a math/rand instance backed by crypto/rand CSPRNG.
CsRand = rand.New(csRandSourceInstance)
// Rand is a math/rand instance backed by crypto/rand CSPRNG.
Rand = rand.New(csRandSourceInstance)
)
type csRandSource struct {
@ -54,8 +54,7 @@ type csRandSource struct {
func (r csRandSource) Int63() int64 {
var src [8]byte
err := Bytes(src[:])
if err != nil {
if err := Bytes(src[:]); err != nil {
panic(err)
}
val := binary.BigEndian.Uint64(src[:])
@ -70,12 +69,12 @@ func (r csRandSource) Seed(seed int64) {
// Intn returns, as a int, a pseudo random number in [0, n).
func Intn(n int) int {
return CsRand.Intn(n)
return Rand.Intn(n)
}
// Float64 returns, as a float64, a pesudo random number in [0.0,1.0).
func Float64() float64 {
return CsRand.Float64()
return Rand.Float64()
}
// IntRange returns a uniformly distributed int [min, max].
@ -85,18 +84,18 @@ func IntRange(min, max int) int {
}
r := (max + 1) - min
ret := CsRand.Intn(r)
ret := Rand.Intn(r)
return ret + min
}
// Bytes fills the slice with random data.
func Bytes(buf []byte) error {
_, err := io.ReadFull(cryptRand.Reader, buf)
if err != nil {
if _, err := io.ReadFull(cryptRand.Reader, buf); err != nil {
return err
}
return nil
}
/* vim :set ts=4 sw=4 sts=4 noet : */
// Reader is a alias of rand.Reader.
var Reader = cryptRand.Reader

@ -37,7 +37,7 @@ import (
"github.com/dchest/siphash"
"git.torproject.org/pluggable-transports/obfs4.git/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
)
// Size is the length of the HashDrbg output.
@ -63,8 +63,7 @@ func (seed *Seed) Base64() string {
// NewSeed returns a Seed initialized with the runtime CSPRNG.
func NewSeed() (seed *Seed, err error) {
seed = new(Seed)
err = csrand.Bytes(seed.Bytes()[:])
if err != nil {
if err = csrand.Bytes(seed.Bytes()[:]); err != nil {
return nil, err
}
@ -88,8 +87,7 @@ func SeedFromBytes(src []byte) (seed *Seed, err error) {
// SeedLength as appropriate.
func SeedFromBase64(encoded string) (seed *Seed, err error) {
var raw []byte
raw, err = base64.StdEncoding.DecodeString(encoded)
if err != nil {
if raw, err = base64.StdEncoding.DecodeString(encoded); err != nil {
return nil, err
}
@ -112,12 +110,18 @@ type HashDrbg struct {
// NewHashDrbg makes a HashDrbg instance based off an optional seed. The seed
// is truncated to SeedLength.
func NewHashDrbg(seed *Seed) *HashDrbg {
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
return drbg, nil
}
// Int63 returns a uniformly distributed random integer [0, 1 << 63).
@ -143,5 +147,3 @@ func (drbg *HashDrbg) NextBlock() []byte {
copy(ret, drbg.ofb[:])
return ret
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -25,7 +25,6 @@
* 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
@ -33,7 +32,6 @@
//
// Before using this package, it is strongly recommended that the specification
// is read and understood.
//
package ntor
import (
@ -50,7 +48,7 @@ import (
"github.com/agl/ed25519/extra25519"
"git.torproject.org/pluggable-transports/obfs4.git/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
)
const (
@ -267,8 +265,7 @@ func NewKeypair(elligator bool) (*Keypair, error) {
// Generate a Curve25519 private key. Like everyone who does this,
// run the CSPRNG output through SHA256 for extra tinfoil hattery.
priv := keypair.private.Bytes()[:]
err := csrand.Bytes(priv)
if err != nil {
if err := csrand.Bytes(priv); err != nil {
return nil, err
}
digest := sha256.Sum256(priv)
@ -433,5 +430,3 @@ func Kdf(keySeed []byte, okmLen int) []byte {
return okm
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -178,5 +178,3 @@ func BenchmarkHandshake(b *testing.B) {
}
}
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -25,15 +25,18 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
package obfs4
// 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 (
"container/list"
"fmt"
"math/rand"
"git.torproject.org/pluggable-transports/obfs4.git/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
)
const (
@ -41,10 +44,11 @@ const (
maxValues = 100
)
// wDist is a weighted distribution.
type wDist struct {
// WeightedDist is a weighted distribution.
type WeightedDist struct {
minValue int
maxValue int
biased bool
values []int
weights []float64
@ -52,23 +56,25 @@ type wDist struct {
prob []float64
}
// newWDist creates a weighted distribution of values ranging from min to max
// based on a HashDrbg initialized with seed.
func newWDist(seed *drbg.Seed, min, max int) (w *wDist) {
w = &wDist{minValue: min, maxValue: max}
// 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)
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 *wDist) genValues(rng *rand.Rand) {
func (w *WeightedDist) genValues(rng *rand.Rand) {
nValues := (w.maxValue + 1) - w.minValue
values := rng.Perm(nValues)
if nValues < minValues {
@ -83,11 +89,11 @@ func (w *wDist) genValues(rng *rand.Rand) {
// genBiasedWeights generates a non-uniform weight list, similar to the
// ScrambleSuit prob_dist module.
func (w *wDist) genBiasedWeights(rng *rand.Rand) {
func (w *WeightedDist) genBiasedWeights(rng *rand.Rand) {
w.weights = make([]float64, len(w.values))
culmProb := 0.0
for i := range w.values {
for i := range w.weights {
p := (1.0 - culmProb) * rng.Float64()
w.weights[i] = p
culmProb += p
@ -95,7 +101,7 @@ func (w *wDist) genBiasedWeights(rng *rand.Rand) {
}
// genUniformWeights generates a uniform weight list.
func (w *wDist) genUniformWeights(rng *rand.Rand) {
func (w *WeightedDist) genUniformWeights(rng *rand.Rand) {
w.weights = make([]float64, len(w.values))
for i := range w.weights {
w.weights[i] = rng.Float64()
@ -104,7 +110,7 @@ func (w *wDist) genUniformWeights(rng *rand.Rand) {
// 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 *wDist) genTables() {
func (w *WeightedDist) genTables() {
n := len(w.weights)
var sum float64
for _, weight := range w.weights {
@ -179,20 +185,24 @@ func (w *wDist) genTables() {
w.alias = alias
}
// reset generates a new distribution with the same min/max based on a new seed.
func (w *wDist) reset(seed *drbg.Seed) {
// 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)
drbg, _ := drbg.NewHashDrbg(seed)
rng := rand.New(drbg)
w.genValues(rng)
//w.genBiasedWeights(rng)
w.genUniformWeights(rng)
if w.biased {
w.genBiasedWeights(rng)
} else {
w.genUniformWeights(rng)
}
w.genTables()
}
// sample generates a random value according to the distribution.
func (w *wDist) sample() int {
// Sample generates a random value according to the distribution.
func (w *WeightedDist) Sample() int {
var idx int
// Generate a fair die roll from an $n$-sided die; call the side $i$.
@ -208,5 +218,3 @@ func (w *wDist) sample() int {
return w.minValue + w.values[idx]
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -25,13 +25,13 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
package obfs4
package probdist
import (
"fmt"
"testing"
"git.torproject.org/pluggable-transports/obfs4.git/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
)
const debug = false
@ -46,7 +46,7 @@ func TestWeightedDist(t *testing.T) {
hist := make([]int, 1000)
w := newWDist(seed, 0, 999)
w := New(seed, 0, 999, true)
if debug {
// Dump a string representation of the probability table.
fmt.Println("Table:")
@ -64,7 +64,7 @@ func TestWeightedDist(t *testing.T) {
}
for i := 0; i < nrTrials; i++ {
value := w.sample()
value := w.Sample()
hist[value]++
}
@ -78,5 +78,3 @@ func TestWeightedDist(t *testing.T) {
}
}
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -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"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
)
// maxFilterSize is the maximum capacity of a replay filter. This value is
// more as a safeguard to prevent runaway filter growth, and is sized to be
// serveral orders of magnitude greater than the number of connections a busy
// bridge sees in one day, so in practice should never be reached.
const maxFilterSize = 100 * 1024
type entry struct {
digest uint64
firstSeen time.Time
element *list.Element
}
// ReplayFilter is a simple filter designed only to detect if a given byte
// sequence has been seen before.
type ReplayFilter struct {
sync.Mutex
filter map[uint64]*entry
fifo *list.List
key [2]uint64
ttl time.Duration
}
// New creates a new ReplayFilter instance.
func New(ttl time.Duration) (filter *ReplayFilter, err error) {
// Initialize the SipHash-2-4 instance with a random key.
var key [16]byte
if err = csrand.Bytes(key[:]); err != nil {
return
}
filter = new(ReplayFilter)
filter.filter = make(map[uint64]*entry)
filter.fifo = list.New()
filter.key[0] = binary.BigEndian.Uint64(key[0:8])
filter.key[1] = binary.BigEndian.Uint64(key[8:16])
filter.ttl = ttl
return
}
// TestAndSet queries the filter for a given byte sequence, inserts the
// sequence, and returns if it was present before the insertion operation.
func (f *ReplayFilter) TestAndSet(now time.Time, buf []byte) bool {
digest := siphash.Hash(f.key[0], f.key[1], buf)
f.Lock()
defer f.Unlock()
f.compactFilter(now)
if e := f.filter[digest]; e != nil {
// Hit. Just return.
return true
}
// Miss. Add a new entry.
e := new(entry)
e.digest = digest
e.firstSeen = now
e.element = f.fifo.PushBack(e)
f.filter[digest] = e
return false
}
func (f *ReplayFilter) compactFilter(now time.Time) {
e := f.fifo.Front()
for e != nil {
ent, _ := e.Value.(*entry)
// If the filter is not full, only purge entries that exceed the TTL,
// otherwise purge at least one entry, then revert to TTL based
// compaction.
if f.fifo.Len() < maxFilterSize && f.ttl > 0 {
deltaT := now.Sub(ent.firstSeen)
if deltaT < 0 {
// Aeeeeeee, the system time jumped backwards, potentially by
// a lot. This will eventually self-correct, but "eventually"
// could be a long time. As much as this sucks, jettison the
// entire filter.
f.reset()
return
} else if deltaT < f.ttl {
return
}
}
// Remove the eldest entry.
eNext := e.Next()
delete(f.filter, ent.digest)
f.fifo.Remove(ent.element)
ent.element = nil
e = eNext
}
}
func (f *ReplayFilter) reset() {
f.filter = make(map[uint64]*entry)
f.fifo = list.New()
}

@ -25,55 +25,58 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
package obfs4
package replayfilter
import (
"testing"
"time"
)
func TestReplayFilter(t *testing.T) {
f, err := newReplayFilter()
ttl := 10 * time.Second
f, err := New(ttl)
if err != nil {
t.Fatal("newReplayFilter failed:", err)
}
buf := []byte("This is a test of the Emergency Broadcast System.")
var now int64 = 3600
now := time.Now()
// testAndSet into empty filter, returns false (not present).
set := f.testAndSet(now, buf)
set := f.TestAndSet(now, buf)
if set {
t.Fatal("testAndSet empty filter returned true")
t.Fatal("TestAndSet empty filter returned true")
}
// testAndSet into filter containing entry, should return true(present).
set = f.testAndSet(now, buf)
set = f.TestAndSet(now, buf)
if !set {
t.Fatal("testAndSet populated filter (replayed) returned false")
}
buf2 := []byte("This concludes this test of the Emergency Broadcast System.")
now += 3600 * 2
now = now.Add(ttl)
// testAndSet with time advanced.
set = f.testAndSet(now, buf2)
set = f.TestAndSet(now, buf2)
if set {
t.Fatal("testAndSet populated filter, 2nd entry returned true")
}
set = f.testAndSet(now, buf2)
set = f.TestAndSet(now, buf2)
if !set {
t.Fatal("testAndSet populated filter, 2nd entry (replayed) returned false")
}
// Ensure that the first entry has been removed by compact.
set = f.testAndSet(now, buf)
set = f.TestAndSet(now, buf)
if set {
t.Fatal("testAndSet populated filter, compact check returned true")
}
// Ensure that the filter gets reaped if the clock jumps backwards.
now = 0
set = f.testAndSet(now, buf)
now = time.Time{}
set = f.TestAndSet(now, buf)
if set {
t.Fatal("testAndSet populated filter, backward time jump returned true")
}
@ -85,7 +88,7 @@ func TestReplayFilter(t *testing.T) {
}
// Ensure that the entry is properly added after reaping.
set = f.testAndSet(now, buf)
set = f.TestAndSet(now, buf)
if !set {
t.Fatal("testAndSet populated filter, post-backward clock jump (replayed) returned false")
}

@ -0,0 +1,183 @@
/*
* 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 uniformdh implements the Tor Project's UniformDH key exchange
// mechanism as defined in the obfs3 protocol specification. This
// implementation is suitable for obfuscation but MUST NOT BE USED when strong
// security is required as it is not constant time.
package uniformdh
import (
"fmt"
"io"
"math/big"
)
const (
// Size is the size of a UniformDH key or shared secret in bytes.
Size = 1536 / 8
// modpStr is the RFC3526 1536-bit MODP Group (Group 5).
modpStr = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
"670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"
g = 2
)
var modpGroup *big.Int
var gen *big.Int
// A PrivateKey represents a UniformDH private key.
type PrivateKey struct {
PublicKey
privateKey *big.Int
}
// A PublicKey represents a UniformDH public key.
type PublicKey struct {
bytes []byte
publicKey *big.Int
}
// Bytes returns the byte representation of a PublicKey.
func (pub *PublicKey) Bytes() (pubBytes []byte, err error) {
if len(pub.bytes) != Size || pub.bytes == nil {
return nil, fmt.Errorf("public key is not initialized")
}
pubBytes = make([]byte, Size)
copy(pubBytes, pub.bytes)
return
}
// SetBytes sets the PublicKey from a byte slice.
func (pub *PublicKey) SetBytes(pubBytes []byte) error {
if len(pubBytes) != Size {
return fmt.Errorf("public key length %d is not %d", len(pubBytes), Size)
}
pub.bytes = make([]byte, Size)
copy(pub.bytes, pubBytes)
pub.publicKey = new(big.Int).SetBytes(pub.bytes)
return nil
}
// GenerateKey generates a UniformDH keypair using the random source random.
func GenerateKey(random io.Reader) (priv *PrivateKey, err error) {
privBytes := make([]byte, Size)
if _, err = io.ReadFull(random, privBytes); err != nil {
return
}
priv, err = generateKey(privBytes)
return
}
func generateKey(privBytes []byte) (priv *PrivateKey, err error) {
// This function does all of the actual heavy lifting of creating a public
// key from a raw 192 byte private key. It is split so that the KAT tests
// can be written easily, and not exposed since non-ephemeral keys are a
// terrible idea.
if len(privBytes) != Size {
return nil, fmt.Errorf("invalid private key size %d", len(privBytes))
}
// To pick a private UniformDH key, we pick a random 1536-bit number,
// and make it even by setting its low bit to 0
privBn := new(big.Int).SetBytes(privBytes)
wasEven := privBn.Bit(0) == 0
privBn = privBn.SetBit(privBn, 0, 0)
// Let x be that private key, and X = g^x (mod p).
pubBn := new(big.Int).Exp(gen, privBn, modpGroup)
pubAlt := new(big.Int).Sub(modpGroup, pubBn)
// When someone sends her public key to the other party, she randomly
// decides whether to send X or p-X. Use the lowest most bit of the
// private key here as the random coin flip since it is masked out and not
// used.
//
// Note: The spec doesn't explicitly specify it, but here we prepend zeros
// to the key so that it is always exactly Size bytes.
pubBytes := make([]byte, Size)
if wasEven {
err = prependZeroBytes(pubBytes, pubBn.Bytes())
} else {
err = prependZeroBytes(pubBytes, pubAlt.Bytes())
}
if err != nil {
return
}
priv = new(PrivateKey)
priv.PublicKey.bytes = pubBytes
priv.PublicKey.publicKey = pubBn
priv.privateKey = privBn
return
}
// Handshake generates a shared secret given a PrivateKey and PublicKey.
func Handshake(privateKey *PrivateKey, publicKey *PublicKey) (sharedSecret []byte, err error) {
// When a party wants to calculate the shared secret, she raises the
// foreign public key to her private key.
secretBn := new(big.Int).Exp(publicKey.publicKey, privateKey.privateKey, modpGroup)
sharedSecret = make([]byte, Size)
err = prependZeroBytes(sharedSecret, secretBn.Bytes())
return
}
func prependZeroBytes(dst, src []byte) error {
zeros := len(dst) - len(src)
if zeros < 0 {
return fmt.Errorf("src length is greater than destination: %d", zeros)
}
for i := 0; i < zeros; i++ {
dst[i] = 0
}
copy(dst[zeros:], src)
return nil
}
func init() {
// Load the MODP group and the generator.
var ok bool
modpGroup, ok = new(big.Int).SetString(modpStr, 16)
if !ok {
panic("Failed to load the RFC3526 MODP Group")
}
gen = big.NewInt(g)
}

@ -0,0 +1,220 @@
/*
* 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 uniformdh
import (
"bytes"
"crypto/rand"
"encoding/hex"
"testing"
)
const (
xPrivStr = "6f592d676f536874746f20686e6b776f" +
"20736874206561676574202e6f592d67" +
"6f536874746f20687369742065686720" +
"74612e655920676f532d746f6f686874" +
"6920207368742065656b20796e612064" +
"7567726169646e616f20206668742065" +
"61676574202e61507473202c72707365" +
"6e652c746620747572752c6561206c6c" +
"612065726f20656e6920206e6f592d67" +
"6f536874746f2e68482020656e6b776f" +
"2073687772652065687420656c4f2064" +
"6e4f736562206f72656b74207268756f"
xPubStr = "76a3d17d5c55b03e865fa3e8267990a7" +
"24baa24b0bdd0cc4af93be8de30be120" +
"d5533c91bf63ef923b02edcb84b74438" +
"3f7de232cca6eb46d07cad83dcaa317f" +
"becbc68ca13e2c4019e6a36531067450" +
"04aecc0be1dff0a78733fb0e7d5cb7c4" +
"97cab77b1331bf347e5f3a7847aa0bc0" +
"f4bc64146b48407fed7b931d16972d25" +
"fb4da5e6dc074ce2a58daa8de7624247" +
"cdf2ebe4e4dfec6d5989aac778c87559" +
"d3213d6040d4111ce3a2acae19f9ee15" +
"32509e037f69b252fdc30243cbbce9d0"
yPrivStr = "736562206f72656b74207268756f6867" +
"6f2020666c6f2c646120646e77206568" +
"657254206568207968736c61206c7262" +
"6165206b68746f726775206867616961" +
"2e6e482020656e6b776f207368777265" +
"2065685479656820766120657274646f" +
"652072616874732766206569646c2c73" +
"6120646e772065686572542065682079" +
"74736c69206c72746165206468746d65" +
"202c6e612064687720796f6e6f20656e" +
"63206e61622068656c6f206468546d65" +
"61202073685479657420657264610a2e"
yPubStr = "d04e156e554c37ffd7aba749df662350" +
"1e4ff4466cb12be055617c1a36872237" +
"36d2c3fdce9ee0f9b27774350849112a" +
"a5aeb1f126811c9c2f3a9cb13d2f0c3a" +
"7e6fa2d3bf71baf50d839171534f227e" +
"fbb2ce4227a38c25abdc5ba7fc430111" +
"3a2cb2069c9b305faac4b72bf21fec71" +
"578a9c369bcac84e1a7dcf0754e342f5" +
"bc8fe4917441b88254435e2abaf297e9" +
"3e1e57968672d45bd7d4c8ba1bc3d314" +
"889b5bc3d3e4ea33d4f2dfdd34e5e5a7" +
"2ff24ee46316d4757dad09366a0b66b3"
ssStr = "78afaf5f457f1fdb832bebc397644a33" +
"038be9dba10ca2ce4a076f327f3a0ce3" +
"151d477b869ee7ac467755292ad8a77d" +
"b9bd87ffbbc39955bcfb03b1583888c8" +
"fd037834ff3f401d463c10f899aa6378" +
"445140b7f8386a7d509e7b9db19b677f" +
"062a7a1a4e1509604d7a0839ccd5da61" +
"73e10afd9eab6dda74539d60493ca37f" +
"a5c98cd9640b409cd8bb3be2bc5136fd" +
"42e764fc3f3c0ddb8db3d87abcf2e659" +
"8d2b101bef7a56f50ebc658f9df1287d" +
"a81359543e77e4a4cfa7598a4152e4c0"
)
var xPriv, xPub, yPriv, yPub, ss []byte
// TestGenerateKeyOdd tests creating a UniformDH keypair with a odd private
// key.
func TestGenerateKeyOdd(t *testing.T) {
xX, err := generateKey(xPriv)
if err != nil {
t.Fatal("generateKey(xPriv) failed:", err)
}
xPubGen, err := xX.PublicKey.Bytes()
if err != nil {
t.Fatal("xX.PublicKey.Bytes() failed:", err)
}
if 0 != bytes.Compare(xPubGen, xPub) {
t.Fatal("Generated public key does not match known answer")
}
}
// TestGenerateKeyEven tests creating a UniformDH keypair with a even private
// key.
func TestGenerateKeyEven(t *testing.T) {
yY, err := generateKey(yPriv)
if err != nil {
t.Fatal("generateKey(yPriv) failed:", err)
}
yPubGen, err := yY.PublicKey.Bytes()
if err != nil {
t.Fatal("yY.PublicKey.Bytes() failed:", err)
}
if 0 != bytes.Compare(yPubGen, yPub) {
t.Fatal("Generated public key does not match known answer")
}
}
// TestHandshake tests conductiong a UniformDH handshake with know values.
func TestHandshake(t *testing.T) {
xX, err := generateKey(xPriv)
if err != nil {
t.Fatal("generateKey(xPriv) failed:", err)
}
yY, err := generateKey(yPriv)
if err != nil {
t.Fatal("generateKey(yPriv) failed:", err)
}
xY, err := Handshake(xX, &yY.PublicKey)
if err != nil {
t.Fatal("Handshake(xX, yY.PublicKey) failed:", err)
}
yX, err := Handshake(yY, &xX.PublicKey)
if err != nil {
t.Fatal("Handshake(yY, xX.PublicKey) failed:", err)
}
if 0 != bytes.Compare(xY, yX) {
t.Fatal("Generated shared secrets do not match between peers")
}
if 0 != bytes.Compare(xY, ss) {
t.Fatal("Generated shared secret does not match known value")
}
}
// Benchmark UniformDH key generation + exchange. THe actual time taken per
// peer is half of the reported time as this does 2 key generation an
// handshake operations.
func BenchmarkHandshake(b *testing.B) {
for i := 0; i < b.N; i++ {
xX, err := GenerateKey(rand.Reader)
if err != nil {
b.Fatal("Failed to generate xX keypair", err)
}
yY, err := GenerateKey(rand.Reader)
if err != nil {
b.Fatal("Failed to generate yY keypair", err)
}
xY, err := Handshake(xX, &yY.PublicKey)
if err != nil {
b.Fatal("Handshake(xX, yY.PublicKey) failed:", err)
}
yX, err := Handshake(yY, &xX.PublicKey)
if err != nil {
b.Fatal("Handshake(yY, xX.PublicKey) failed:", err)
}
_ = xY
_ = yX
}
}
func init() {
// Load the test vectors into byte slices.
var err error
xPriv, err = hex.DecodeString(xPrivStr)
if err != nil {
panic("hex.DecodeString(xPrivStr) failed")
}
xPub, err = hex.DecodeString(xPubStr)
if err != nil {
panic("hex.DecodeString(xPubStr) failed")
}
yPriv, err = hex.DecodeString(yPrivStr)
if err != nil {
panic("hex.DecodeString(yPrivStr) failed")
}
yPub, err = hex.DecodeString(yPubStr)
if err != nil {
panic("hex.DecodeString(yPubStr) failed")
}
ss, err = hex.DecodeString(ssStr)
if err != nil {
panic("hex.DecodeString(ssStr) failed")
}
}

@ -1,758 +0,0 @@
/*
* 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 implements the obfs4 protocol. For the most part, obfs4
// connections are exposed via the net.Conn and net.Listener interface, though
// accepting connections as a server requires calling ServerHandshake on the
// conn to finish connection establishment.
package obfs4
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"math/rand"
"net"
"syscall"
"time"
"git.torproject.org/pluggable-transports/obfs4.git/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/framing"
"git.torproject.org/pluggable-transports/obfs4.git/ntor"
)
const (
// SeedLength is the length of the obfs4 polymorphism seed.
SeedLength = 32
headerLength = framing.FrameOverhead + packetOverhead
connectionTimeout = time.Duration(30) * time.Second
maxCloseDelayBytes = maxHandshakeLength
maxCloseDelay = 60
maxIatDelay = 100
)
type connState int
const (
stateInit connState = iota
stateEstablished
stateBroken
stateClosed
)
// Obfs4Conn is the implementation of the net.Conn interface for obfs4
// connections.
type Obfs4Conn struct {
conn net.Conn
sessionKey *ntor.Keypair
lenProbDist *wDist
iatProbDist *wDist
encoder *framing.Encoder
decoder *framing.Decoder
receiveBuffer bytes.Buffer
receiveDecodedBuffer bytes.Buffer
state connState
isServer bool
// Server side state.
listener *Obfs4Listener
startTime time.Time
}
func (c *Obfs4Conn) padBurst(burst *bytes.Buffer) (err error) {
tailLen := burst.Len() % framing.MaximumSegmentLength
toPadTo := c.lenProbDist.sample()
padLen := 0
if toPadTo >= tailLen {
padLen = toPadTo - tailLen
} else {
padLen = (framing.MaximumSegmentLength - tailLen) + toPadTo
}
if padLen > headerLength {
err = c.producePacket(burst, packetTypePayload, []byte{},
uint16(padLen-headerLength))
if err != nil {
return
}
} else if padLen > 0 {
err = c.producePacket(burst, packetTypePayload, []byte{},
maxPacketPayloadLength)
if err != nil {
return
}
err = c.producePacket(burst, packetTypePayload, []byte{},
uint16(padLen))
if err != nil {
return
}
}
return
}
func (c *Obfs4Conn) closeAfterDelay() {
// I-it's not like I w-wanna handshake with you or anything. B-b-baka!
defer c.conn.Close()
delay := time.Duration(c.listener.closeDelay)*time.Second + connectionTimeout
deadline := c.startTime.Add(delay)
if time.Now().After(deadline) {
return
}
err := c.conn.SetReadDeadline(deadline)
if 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(c.listener.closeDelayBytes) {
n, err := c.conn.Read(buf[:])
if err != nil {
return
}
discarded += n
}
}
func (c *Obfs4Conn) setBroken() {
c.state = stateBroken
}
func (c *Obfs4Conn) clientHandshake(nodeID *ntor.NodeID, publicKey *ntor.PublicKey) (err error) {
if c.isServer {
panic(fmt.Sprintf("BUG: clientHandshake() called for server connection"))
}
defer func() {
c.sessionKey = nil
if err != nil {
c.setBroken()
}
}()
// Generate/send the client handshake.
var hs *clientHandshake
var blob []byte
hs = newClientHandshake(nodeID, publicKey, c.sessionKey)
blob, err = hs.generateHandshake()
if err != nil {
return
}
err = c.conn.SetDeadline(time.Now().Add(connectionTimeout * 2))
if err != nil {
return
}
_, err = c.conn.Write(blob)
if err != nil {
return
}
// Consume the server handshake.
var hsBuf [maxHandshakeLength]byte
for {
var n int
n, err = c.conn.Read(hsBuf[:])
if err != nil {
// Yes, just bail out of handshaking even if the Read could have
// returned data, no point in continuing on EOF/etc.
return
}
c.receiveBuffer.Write(hsBuf[:n])
var seed []byte
n, seed, err = hs.parseServerHandshake(c.receiveBuffer.Bytes())
if err == ErrMarkNotFoundYet {
continue
} else if err != nil {
return
}
_ = c.receiveBuffer.Next(n)
err = c.conn.SetDeadline(time.Time{})
if err != nil {
return
}
// Use the derived key material to intialize the link crypto.
okm := ntor.Kdf(seed, framing.KeyLength*2)
c.encoder = framing.NewEncoder(okm[:framing.KeyLength])
c.decoder = framing.NewDecoder(okm[framing.KeyLength:])
c.state = stateEstablished
return nil
}
}
func (c *Obfs4Conn) serverHandshake(nodeID *ntor.NodeID, keypair *ntor.Keypair) (err error) {
if !c.isServer {
panic(fmt.Sprintf("BUG: serverHandshake() called for client connection"))
}
defer func() {
c.sessionKey = nil
if err != nil {
c.setBroken()
}
}()
hs := newServerHandshake(nodeID, keypair, c.sessionKey)
err = c.conn.SetDeadline(time.Now().Add(connectionTimeout))
if err != nil {
return
}
// Consume the client handshake.
var hsBuf [maxHandshakeLength]byte
for {
var n int
n, err = c.conn.Read(hsBuf[:])
if err != nil {
// Yes, just bail out of handshaking even if the Read could have
// returned data, no point in continuing on EOF/etc.
return
}
c.receiveBuffer.Write(hsBuf[:n])
var seed []byte
seed, err = hs.parseClientHandshake(c.listener.filter, c.receiveBuffer.Bytes())
if err == ErrMarkNotFoundYet {
continue
} else if err != nil {
return
}
c.receiveBuffer.Reset()
err = c.conn.SetDeadline(time.Time{})
if err != nil {
return
}
// Use the derived key material to intialize the link crypto.
okm := ntor.Kdf(seed, framing.KeyLength*2)
c.encoder = framing.NewEncoder(okm[framing.KeyLength:])
c.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.
var blob []byte
blob, err = hs.generateHandshake()
if err != nil {
return
}
var frameBuf bytes.Buffer
_, err = frameBuf.Write(blob)
if err != nil {
return
}
c.state = stateEstablished
// Send the PRNG seed as the first packet.
err = c.producePacket(&frameBuf, packetTypePrngSeed, c.listener.rawSeed, 0)
if err != nil {
return
}
_, err = c.conn.Write(frameBuf.Bytes())
if err != nil {
return
}
return
}
// CanHandshake queries the connection state to see if it is appropriate to
// call ServerHandshake to complete connection establishment.
func (c *Obfs4Conn) CanHandshake() bool {
return c.state == stateInit
}
// CanReadWrite queries the connection state to see if it is possible to read
// and write data.
func (c *Obfs4Conn) CanReadWrite() bool {
return c.state == stateEstablished
}
// ServerHandshake completes the server side of the obfs4 handshake. Servers
// are required to call this after accepting a connection. ServerHandshake
// will treat errors encountered during the handshake as fatal and drop the
// connection before returning.
func (c *Obfs4Conn) ServerHandshake() error {
// Handshakes when already established are a no-op.
if c.CanReadWrite() {
return nil
} else if !c.CanHandshake() {
return syscall.EINVAL
}
if !c.isServer {
panic(fmt.Sprintf("BUG: ServerHandshake() called for client connection"))
}
// Complete the handshake.
err := c.serverHandshake(c.listener.nodeID, c.listener.keyPair)
if err != nil {
c.closeAfterDelay()
}
c.listener = nil
return err
}
// Read implements the net.Conn Read method.
func (c *Obfs4Conn) Read(b []byte) (n int, err error) {
if !c.CanReadWrite() {
return 0, syscall.EINVAL
}
for c.receiveDecodedBuffer.Len() == 0 {
_, err = c.consumeFramedPackets(nil)
if err == framing.ErrAgain {
continue
} else if err != nil {
return
}
}
n, err = c.receiveDecodedBuffer.Read(b)
return
}
// WriteTo implements the io.WriterTo WriteTo method.
func (c *Obfs4Conn) WriteTo(w io.Writer) (n int64, err error) {
if !c.CanReadWrite() {
return 0, syscall.EINVAL
}
// If there is buffered payload from earlier Read() calls, write.
wrLen := 0
if c.receiveDecodedBuffer.Len() > 0 {
wrLen, err = w.Write(c.receiveDecodedBuffer.Bytes())
if err != nil {
c.setBroken()
return int64(wrLen), err
} else if wrLen < int(c.receiveDecodedBuffer.Len()) {
c.setBroken()
return int64(wrLen), io.ErrShortWrite
}
c.receiveDecodedBuffer.Reset()
}
for {
wrLen, err = c.consumeFramedPackets(w)
n += int64(wrLen)
if err == framing.ErrAgain {
continue
} else if err != nil {
// io.EOF is treated as not an error.
if err == io.EOF {
err = nil
}
break
}
}
return
}
// Write implements the net.Conn Write method. The obfs4 lengt obfuscation is
// done based on the amount of data passed to Write (each call to Write results
// in up to 2 frames of padding). Passing excessively short buffers to Write
// will result in significant overhead.
func (c *Obfs4Conn) Write(b []byte) (n int, err error) {
if !c.CanReadWrite() {
return 0, syscall.EINVAL
}
defer func() {
if err != nil {
c.setBroken()
}
}()
// TODO: Change this to write directly to c.conn skipping frameBuf.
chopBuf := bytes.NewBuffer(b)
var payload [maxPacketPayloadLength]byte
var frameBuf bytes.Buffer
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 = c.producePacket(&frameBuf, packetTypePayload, payload[:rdLen], 0)
if err != nil {
return 0, err
}
}
// Insert random padding. In theory for some padding lengths, this can be
// inlined with the payload, but doing it this way simplifies the code
// significantly.
err = c.padBurst(&frameBuf)
if err != nil {
return 0, err
}
// Spit frame(s) onto the network.
//
// Partial writes are fatal because the frame encoder state is advanced
// at this point. It's possible to keep frameBuf around, but fuck it.
// Someone that wants write timeouts can change this.
if c.iatProbDist != nil {
var iatFrame [framing.MaximumSegmentLength]byte
for frameBuf.Len() > 0 {
iatWrLen := 0
iatWrLen, err = frameBuf.Read(iatFrame[:])
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(c.iatProbDist.sample() * 100)
// Write then sleep.
_, err = c.conn.Write(iatFrame[:iatWrLen])
if err != nil {
return 0, err
}
time.Sleep(iatDelta * time.Microsecond)
}
} else {
_, err = c.conn.Write(frameBuf.Bytes())
if err != nil {
return 0, err
}
}
return
}
// Close closes the connection.
func (c *Obfs4Conn) Close() error {
if c.conn == nil {
return syscall.EINVAL
}
c.state = stateClosed
return c.conn.Close()
}
// LocalAddr returns the local network address.
func (c *Obfs4Conn) LocalAddr() net.Addr {
if c.state == stateClosed {
return nil
}
return c.conn.LocalAddr()
}
// RemoteAddr returns the remote network address.
func (c *Obfs4Conn) RemoteAddr() net.Addr {
if c.state == stateClosed {
return nil
}
return c.conn.RemoteAddr()
}
// SetDeadline is a convoluted way to get syscall.ENOTSUP.
func (c *Obfs4Conn) SetDeadline(t time.Time) error {
return syscall.ENOTSUP
}
// SetReadDeadline implements the net.Conn SetReadDeadline method. Connections
// must be in the established state (CanReadWrite).
func (c *Obfs4Conn) SetReadDeadline(t time.Time) error {
if !c.CanReadWrite() {
return syscall.EINVAL
}
return c.conn.SetReadDeadline(t)
}
// SetWriteDeadline is a convoluted way to get syscall.ENOTSUP.
func (c *Obfs4Conn) SetWriteDeadline(t time.Time) error {
return syscall.ENOTSUP
}
// DialFn is a function pointer to a dial routine that matches the
// net.Dialer.Dial routine.
type DialFn func(string, string) (net.Conn, error)
// DialObfs4 connects to the remote address on the network, and handshakes with
// the peer's obfs4 Node ID and Identity Public Key. nodeID and publicKey are
// expected as strings containing the Base64 encoded values.
func DialObfs4(network, address, nodeID, publicKey string, iatObfuscation bool) (*Obfs4Conn, error) {
return DialObfs4DialFn(net.Dial, network, address, nodeID, publicKey, iatObfuscation)
}
// DialObfs4DialFn connects to the remote address on the network via DialFn,
// and handshakes with the peers' obfs4 Node ID and Identity Public Key.
func DialObfs4DialFn(dialFn DialFn, network, address, nodeID, publicKey string, iatObfuscation bool) (*Obfs4Conn, error) {
// Decode the node_id/public_key.
pub, err := ntor.PublicKeyFromBase64(publicKey)
if err != nil {
return nil, err
}
id, err := ntor.NodeIDFromBase64(nodeID)
if err != nil {
return nil, err
}
// Generate the initial length obfuscation distribution.
seed, err := drbg.NewSeed()
if err != nil {
return nil, err
}
// Generate the Obfs4Conn.
c := new(Obfs4Conn)
c.lenProbDist = newWDist(seed, 0, framing.MaximumSegmentLength)
if iatObfuscation {
iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
iatSeed, err := drbg.SeedFromBytes(iatSeedSrc[:])
if err != nil {
return nil, err
}
c.iatProbDist = newWDist(iatSeed, 0, maxIatDelay)
}
// Generate the session keypair *before* connecting to the remote peer.
c.sessionKey, err = ntor.NewKeypair(true)
if err != nil {
return nil, err
}
// Connect to the remote peer.
c.conn, err = dialFn(network, address)
if err != nil {
return nil, err
}
// Handshake.
err = c.clientHandshake(id, pub)
if err != nil {
c.conn.Close()
return nil, err
}
return c, nil
}
// Obfs4Listener is the implementation of the net.Listener interface for obfs4
// connections.
type Obfs4Listener struct {
listener net.Listener
filter *replayFilter
keyPair *ntor.Keypair
nodeID *ntor.NodeID
rawSeed []byte
seed *drbg.Seed
iatSeed *drbg.Seed
iatObfuscation bool
closeDelayBytes int
closeDelay int
}
// Accept implements the Accept method of the net.Listener interface; it waits
// for the next call and returns a generic net.Conn. Callers are responsible
// for completing the handshake by calling Obfs4Conn.ServerHandshake().
func (l *Obfs4Listener) Accept() (net.Conn, error) {
conn, err := l.AcceptObfs4()
if err != nil {
return nil, err
}
return conn, nil
}
// AcceptObfs4 accepts the next incoming call and returns a new connection.
// Callers are responsible for completing the handshake by calling
// Obfs4Conn.ServerHandshake().
func (l *Obfs4Listener) AcceptObfs4() (*Obfs4Conn, error) {
// Accept a connection.
c, err := l.listener.Accept()
if err != nil {
return nil, err
}
// Allocate the obfs4 connection state.
cObfs := new(Obfs4Conn)
// Generate the session keypair *before* consuming data from the peer, to
// add more noise to the keypair generation time. The idea is that jitter
// here is masked by network latency (the time it takes for a server to
// accept a socket out of the backlog should not be fixed, and the client
// needs to send the public key).
cObfs.sessionKey, err = ntor.NewKeypair(true)
if err != nil {
return nil, err
}
cObfs.conn = c
cObfs.isServer = true
cObfs.listener = l
cObfs.lenProbDist = newWDist(l.seed, 0, framing.MaximumSegmentLength)
if l.iatObfuscation {
cObfs.iatProbDist = newWDist(l.iatSeed, 0, maxIatDelay)
}
if err != nil {
c.Close()
return nil, err
}
cObfs.startTime = time.Now()
return cObfs, nil
}
// Close stops listening on the Obfs4 endpoint. Already Accepted connections
// are not closed.
func (l *Obfs4Listener) Close() error {
return l.listener.Close()
}
// Addr returns the listener's network address.
func (l *Obfs4Listener) Addr() net.Addr {
return l.listener.Addr()
}
// PublicKey returns the listener's Identity Public Key, a Base64 encoded
// obfs4.ntor.PublicKey.
func (l *Obfs4Listener) PublicKey() string {
if l.keyPair == nil {
return ""
}
return l.keyPair.Public().Base64()
}
// NodeID returns the listener's NodeID, a Base64 encoded obfs4.ntor.NodeID.
func (l *Obfs4Listener) NodeID() string {
if l.nodeID == nil {
return ""
}
return l.nodeID.Base64()
}
// ListenObfs4 annnounces on the network and address, and returns and
// Obfs4Listener. nodeId, privateKey and seed are expected as strings
// containing the Base64 encoded values.
func ListenObfs4(network, laddr, nodeID, privateKey, seed string, iatObfuscation bool) (*Obfs4Listener, error) {
var err error
// Decode node_id/private_key.
l := new(Obfs4Listener)
l.keyPair, err = ntor.KeypairFromBase64(privateKey)
if err != nil {
return nil, err
}
l.nodeID, err = ntor.NodeIDFromBase64(nodeID)
if err != nil {
return nil, err
}
l.rawSeed, err = base64.StdEncoding.DecodeString(seed)
if err != nil {
return nil, err
}
l.seed, err = drbg.SeedFromBytes(l.rawSeed)
if err != nil {
return nil, err
}
l.iatObfuscation = iatObfuscation
if l.iatObfuscation {
iatSeedSrc := sha256.Sum256(l.seed.Bytes()[:])
l.iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:])
if err != nil {
return nil, err
}
}
l.filter, err = newReplayFilter()
if err != nil {
return nil, err
}
rng := rand.New(drbg.NewHashDrbg(l.seed))
l.closeDelayBytes = rng.Intn(maxCloseDelayBytes)
l.closeDelay = rng.Intn(maxCloseDelay)
// Start up the listener.
l.listener, err = net.Listen(network, laddr)
if err != nil {
return nil, err
}
return l, nil
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -23,31 +23,13 @@
* 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.
*
* This file is based off goptlib's dummy-[client,server].go files.
*/
// obfs4 pluggable transport. Works only as a managed proxy.
//
// Client usage (in torrc):
// UseBridges 1
// Bridge obfs4 X.X.X.X:YYYY <Fingerprint> public-key=<Base64 Bridge Public Key> node-id=<Base64 Bridge Node ID>
// ClientTransportPlugin obfs4 exec obfs4proxy
//
// Server usage (in torrc):
// BridgeRelay 1
// ORPort 9001
// ExtORPort 6669
// ServerTransportPlugin obfs4 exec obfs4proxy
// ServerTransportOptions obfs4 private-key=<Base64 Bridge Private Key> node-id=<Base64 Node ID> drbg-seed=<Base64 DRBG Seed>
//
// Because the pluggable transport requires arguments, obfs4proxy requires
// tor-0.2.5.x to be useful.
// Go language Tor Pluggable Transport suite. Works only as a managed
// client/server.
package main
import (
"encoding/base64"
"encoding/hex"
"flag"
"fmt"
"io"
@ -61,292 +43,307 @@ import (
"sync"
"syscall"
"code.google.com/p/go.net/proxy"
"git.torproject.org/pluggable-transports/goptlib.git"
"git.torproject.org/pluggable-transports/obfs4.git"
"git.torproject.org/pluggable-transports/obfs4.git/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/ntor"
"git.torproject.org/pluggable-transports/obfs4.git/transports"
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
)
const (
obfs4Method = "obfs4"
obfs4LogFile = "obfs4proxy.log"
obfs4proxyLogFile = "obfs4proxy.log"
socksAddr = "127.0.0.1:0"
elidedAddr = "[scrubbed]"
)
var enableLogging bool
var unsafeLogging bool
var iatObfuscation bool
var ptListeners []net.Listener
var stateDir string
var handlerChan chan int
// When a connection handler starts, +1 is written to this channel; when it
// ends, -1 is written.
var handlerChan = make(chan int)
// DialFn is a function pointer to a function that matches the net.Dialer.Dial
// interface.
type DialFn func(string, string) (net.Conn, error)
func logAndRecover(conn *obfs4.Obfs4Conn) {
if err := recover(); err != nil {
log.Printf("[ERROR] %p: Panic: %s", conn, err)
func elideAddr(addrStr string) string {
if unsafeLogging {
return addrStr
}
if addr, err := resolveAddrStr(addrStr); err == nil {
// Only scrub off the address so that it's slightly easier to follow
// the logs by looking at the port.
return fmt.Sprintf("%s:%d", elidedAddr, addr.Port)
}
return elidedAddr
}
func copyLoop(a net.Conn, b *obfs4.Obfs4Conn) {
var wg sync.WaitGroup
wg.Add(2)
func clientSetup() (launched bool, listeners []net.Listener) {
ptClientInfo, err := pt.ClientSetup(transports.Transports())
if err != nil {
log.Fatal(err)
}
go func() {
defer logAndRecover(b)
defer wg.Done()
defer b.Close()
defer a.Close()
ptClientProxy, err := ptGetProxy()
if err != nil {
log.Fatal(err)
} else if ptClientProxy != nil {
ptProxyDone()
}
_, err := io.Copy(b, a)
if err != nil {
log.Printf("[WARN] copyLoop: %p: Connection closed: %s", b, err)
// Launch each of the client listeners.
for _, name := range ptClientInfo.MethodNames {
t := transports.Get(name)
if t == nil {
pt.CmethodError(name, "no such transport is supported")
continue
}
}()
go func() {
defer logAndRecover(b)
defer wg.Done()
defer a.Close()
defer b.Close()
_, err := io.Copy(a, b)
f, err := t.ClientFactory(stateDir)
if err != nil {
log.Printf("[WARN] copyLoop: %p: Connection closed: %s", b, err)
pt.CmethodError(name, "failed to get ClientFactory")
continue
}
}()
wg.Wait()
}
func serverHandler(conn *obfs4.Obfs4Conn, info *pt.ServerInfo) error {
defer conn.Close()
defer logAndRecover(conn)
handlerChan <- 1
defer func() {
handlerChan <- -1
}()
var addr string
if unsafeLogging {
addr = conn.RemoteAddr().String()
} else {
addr = "[scrubbed]"
}
ln, err := pt.ListenSocks("tcp", socksAddr)
if err != nil {
pt.CmethodError(name, err.Error())
continue
}
log.Printf("[INFO] server: %p: New connection from %s", conn, addr)
go clientAcceptLoop(f, ln, ptClientProxy)
pt.Cmethod(name, ln.Version(), ln.Addr())
// Handshake with the client.
err := conn.ServerHandshake()
if err != nil {
log.Printf("[WARN] server: %p: Handshake failed: %s", conn, err)
return err
}
log.Printf("[INFO]: %s - registered listener: %s", name, ln.Addr())
or, err := pt.DialOr(info, conn.RemoteAddr().String(), obfs4Method)
if err != nil {
log.Printf("[ERROR] server: %p: DialOr failed: %s", conn, err)
return err
listeners = append(listeners, ln)
launched = true
}
defer or.Close()
copyLoop(or, conn)
pt.CmethodsDone()
return nil
return
}
func serverAcceptLoop(ln *obfs4.Obfs4Listener, info *pt.ServerInfo) error {
func clientAcceptLoop(f base.ClientFactory, ln *pt.SocksListener, proxyURI *url.URL) error {
defer ln.Close()
for {
conn, err := ln.AcceptObfs4()
conn, err := ln.AcceptSocks()
if err != nil {
if e, ok := err.(net.Error); ok && !e.Temporary() {
return err
}
continue
}
go serverHandler(conn, info)
go clientHandler(f, conn, proxyURI)
}
}
func serverSetup() (launched bool) {
// Initialize pt logging.
err := ptInitializeLogging(enableLogging)
func clientHandler(f base.ClientFactory, conn *pt.SocksConn, proxyURI *url.URL) {
defer conn.Close()
handlerChan <- 1
defer func() {
handlerChan <- -1
}()
name := f.Transport().Name()
addrStr := elideAddr(conn.Req.Target)
log.Printf("[INFO]: %s(%s) - new connection", name, addrStr)
// Deal with arguments.
args, err := f.ParseArgs(&conn.Req.Args)
if err != nil {
log.Printf("[ERROR]: %s(%s) - invalid arguments: %s", name, addrStr, err)
conn.Reject()
return
}
ptServerInfo, err := pt.ServerSetup([]string{obfs4Method})
// Obtain the proxy dialer if any, and create the outgoing TCP connection.
var dialFn DialFn
if proxyURI == nil {
dialFn = proxy.Direct.Dial
} else {
// This is unlikely to happen as the proxy protocol is verified during
// the configuration phase.
dialer, err := proxy.FromURL(proxyURI, proxy.Direct)
if err != nil {
log.Printf("[ERROR]: %s(%s) - failed to obtain proxy dialer: %s", name, addrStr, err)
conn.Reject()
return
}
dialFn = dialer.Dial
}
remoteConn, err := dialFn("tcp", conn.Req.Target) // XXX: Allow UDP?
if err != nil {
// Note: The error message returned from the dialer can include the IP
// address/port of the remote peer.
if unsafeLogging {
log.Printf("[ERROR]: %s(%s) - outgoing connection failed: %s", name, addrStr, err)
} else {
log.Printf("[ERROR]: %s(%s) - outgoing connection failed", name, addrStr)
}
conn.Reject()
return
}
defer remoteConn.Close()
for _, bindaddr := range ptServerInfo.Bindaddrs {
switch bindaddr.MethodName {
case obfs4Method:
// Handle the mandetory arguments.
privateKey, ok := bindaddr.Options.Get("private-key")
if !ok {
pt.SmethodError(bindaddr.MethodName, "needs a private-key option")
break
}
nodeID, ok := bindaddr.Options.Get("node-id")
if !ok {
pt.SmethodError(bindaddr.MethodName, "needs a node-id option")
break
}
seed, ok := bindaddr.Options.Get("drbg-seed")
if !ok {
pt.SmethodError(bindaddr.MethodName, "needs a drbg-seed option")
break
}
// Initialize the listener.
ln, err := obfs4.ListenObfs4("tcp", bindaddr.Addr.String(), nodeID,
privateKey, seed, iatObfuscation)
if err != nil {
pt.SmethodError(bindaddr.MethodName, err.Error())
break
}
// Instantiate the client transport method, handshake, and start pushing
// bytes back and forth.
remote, err := f.WrapConn(remoteConn, args)
if err != nil {
log.Printf("[ERROR]: %s(%s) - handshake failed: %s", name, addrStr, err)
conn.Reject()
return
}
err = conn.Grant(remoteConn.RemoteAddr().(*net.TCPAddr))
if err != nil {
log.Printf("[ERROR]: %s(%s) - SOCKS grant failed: %s", name, addrStr, err)
return
}
// Report the SMETHOD including the parameters.
args := pt.Args{}
args.Add("node-id", nodeID)
args.Add("public-key", ln.PublicKey())
go serverAcceptLoop(ln, &ptServerInfo)
pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), args)
ptListeners = append(ptListeners, ln)
launched = true
default:
pt.SmethodError(bindaddr.MethodName, "no such method")
}
err = copyLoop(conn, remote)
if err != nil {
log.Printf("[INFO]: %s(%s) - closed connection: %s", name, addrStr, err)
} else {
log.Printf("[INFO]: %s(%s) - closed connection", name, addrStr)
}
pt.SmethodsDone()
return
}
func clientHandler(conn *pt.SocksConn, proxyURI *url.URL) error {
defer conn.Close()
var addr string
if unsafeLogging {
addr = conn.Req.Target
} else {
addr = "[scrubbed]"
func serverSetup() (launched bool, listeners []net.Listener) {
ptServerInfo, err := pt.ServerSetup(transports.Transports())
if err != nil {
log.Fatal(err)
}
log.Printf("[INFO] client: New connection to %s", addr)
for _, bindaddr := range ptServerInfo.Bindaddrs {
name := bindaddr.MethodName
t := transports.Get(name)
if t == nil {
pt.SmethodError(name, "no such transport is supported")
continue
}
// Extract the peer's node ID and public key.
nodeID, ok := conn.Req.Args.Get("node-id")
if !ok {
log.Printf("[ERROR] client: missing node-id argument")
conn.Reject()
return nil
}
publicKey, ok := conn.Req.Args.Get("public-key")
if !ok {
log.Printf("[ERROR] client: missing public-key argument")
conn.Reject()
return nil
}
f, err := t.ServerFactory(stateDir, &bindaddr.Options)
if err != nil {
pt.SmethodError(name, err.Error())
continue
}
handlerChan <- 1
defer func() {
handlerChan <- -1
}()
ln, err := net.ListenTCP("tcp", bindaddr.Addr)
if err != nil {
pt.SmethodError(name, err.Error())
}
defer logAndRecover(nil)
dialFn, err := getProxyDialer(proxyURI)
if err != nil {
log.Printf("[ERROR] client: failed to get proxy dialer: %s", err)
conn.Reject()
return err
}
remote, err := obfs4.DialObfs4DialFn(dialFn, "tcp", conn.Req.Target, nodeID, publicKey, iatObfuscation)
if err != nil {
log.Printf("[ERROR] client: %p: Handshake failed: %s", remote, err)
conn.Reject()
return err
}
defer remote.Close()
err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr))
if err != nil {
return err
}
go serverAcceptLoop(f, ln, &ptServerInfo)
if args := f.Args(); args != nil {
pt.SmethodArgs(name, ln.Addr(), *args)
} else {
pt.SmethodArgs(name, ln.Addr(), nil)
}
copyLoop(conn, remote)
log.Printf("[INFO]: %s - registered listener: %s", name, elideAddr(ln.Addr().String()))
return nil
listeners = append(listeners, ln)
launched = true
}
pt.SmethodsDone()
return
}
func clientAcceptLoop(ln *pt.SocksListener, proxyURI *url.URL) error {
func serverAcceptLoop(f base.ServerFactory, ln net.Listener, info *pt.ServerInfo) error {
defer ln.Close()
for {
conn, err := ln.AcceptSocks()
conn, err := ln.Accept()
if err != nil {
if e, ok := err.(net.Error); ok && !e.Temporary() {
return err
}
continue
}
go clientHandler(conn, proxyURI)
go serverHandler(f, conn, info)
}
}
func clientSetup() (launched bool) {
// Initialize pt logging.
err := ptInitializeLogging(enableLogging)
func serverHandler(f base.ServerFactory, conn net.Conn, info *pt.ServerInfo) {
defer conn.Close()
handlerChan <- 1
defer func() {
handlerChan <- -1
}()
name := f.Transport().Name()
addrStr := elideAddr(conn.RemoteAddr().String())
log.Printf("[INFO]: %s(%s) - new connection", name, addrStr)
// Instantiate the server transport method and handshake.
remote, err := f.WrapConn(conn)
if err != nil {
log.Printf("[ERROR]: %s(%s) - handshake failed: %s", name, addrStr, err)
return
}
ptClientInfo, err := pt.ClientSetup([]string{obfs4Method})
// Connect to the orport.
orConn, err := pt.DialOr(info, conn.RemoteAddr().String(), name)
if err != nil {
log.Fatal(err)
log.Printf("[ERROR]: %s(%s) - failed to connect to ORPort: %s", name, addrStr, err)
return
}
defer orConn.Close()
ptClientProxy, err := ptGetProxy()
err = copyLoop(orConn, remote)
if err != nil {
log.Fatal(err)
} else if ptClientProxy != nil {
ptProxyDone()
log.Printf("[INFO]: %s(%s) - closed connection: %s", name, addrStr, err)
} else {
log.Printf("[INFO]: %s(%s) - closed connection", name, addrStr)
}
for _, methodName := range ptClientInfo.MethodNames {
switch methodName {
case obfs4Method:
ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
if err != nil {
pt.CmethodError(methodName, err.Error())
break
}
go clientAcceptLoop(ln, ptClientProxy)
pt.Cmethod(methodName, ln.Version(), ln.Addr())
ptListeners = append(ptListeners, ln)
launched = true
default:
pt.CmethodError(methodName, "no such method")
}
return
}
func copyLoop(a net.Conn, b net.Conn) error {
// Note: b is always the pt connection. a is the SOCKS/ORPort connection.
errChan := make(chan error, 2)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
defer b.Close()
defer a.Close()
_, err := io.Copy(b, a)
errChan <- err
}()
go func() {
defer wg.Done()
defer a.Close()
defer b.Close()
_, err := io.Copy(a, b)
errChan <- err
}()
// Wait for both upstream and downstream to close. Since one side
// terminating closes the other, the second error in the channel will be
// something like EINVAL (though io.Copy() will swallow EOF), so only the
// first error is returned.
wg.Wait()
if len(errChan) > 0 {
return <-errChan
}
pt.CmethodsDone()
return
return nil
}
func ptInitializeLogging(enable bool) error {
if enable {
// pt.MakeStateDir will ENV-ERROR for us.
dir, err := pt.MakeStateDir()
if err != nil {
return err
}
// While we could just exit, log an ENV-ERROR so it will propagate to
// the tor log.
f, err := os.OpenFile(path.Join(dir, obfs4LogFile), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
f, err := os.OpenFile(path.Join(stateDir, obfs4proxyLogFile), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return ptEnvError(fmt.Sprintf("Failed to open log file: %s\n", err))
return ptEnvError(fmt.Sprintf("failed to open log file: %s\n", err))
}
log.SetOutput(f)
} else {
@ -356,122 +353,74 @@ func ptInitializeLogging(enable bool) error {
return nil
}
func generateServerParams(id string) {
idIsFP := id != ""
var rawID []byte
if idIsFP {
var err error
rawID, err = hex.DecodeString(id)
if err != nil {
fmt.Println("Failed to hex decode id:", err)
return
}
} else {
rawID = make([]byte, ntor.NodeIDLength)
err := csrand.Bytes(rawID)
if err != nil {
fmt.Println("Failed to generate random node-id:", err)
return
}
}
parsedID, err := ntor.NewNodeID(rawID)
if err != nil {
fmt.Println("Failed to parse id:", err)
return
}
fmt.Println("Generated node-id:", parsedID.Base64())
keypair, err := ntor.NewKeypair(false)
if err != nil {
fmt.Println("Failed to generate keypair:", err)
return
}
seed := make([]byte, obfs4.SeedLength)
err = csrand.Bytes(seed)
if err != nil {
fmt.Println("Failed to generate DRBG seed:", err)
return
}
seedBase64 := base64.StdEncoding.EncodeToString(seed)
fmt.Println("Generated private-key:", keypair.Private().Base64())
fmt.Println("Generated public-key:", keypair.Public().Base64())
fmt.Println("Generated drbg-seed:", seedBase64)
fmt.Println()
fmt.Println("Client config: ")
if idIsFP {
fmt.Printf(" Bridge obfs4 <IP Address:Port> %s node-id=%s public-key=%s\n",
id, parsedID.Base64(), keypair.Public().Base64())
} else {
fmt.Printf(" Bridge obfs4 <IP Address:Port> <Fingerprint> node-id=%s public-key=%s\n",
parsedID.Base64(), keypair.Public().Base64())
}
fmt.Println()
fmt.Println("Server config:")
fmt.Printf(" ServerTransportOptions obfs4 node-id=%s private-key=%s drbg-seed=%s\n",
parsedID.Base64(), keypair.Private().Base64(), seedBase64)
}
func main() {
// Some command line args.
genParams := flag.Bool("genServerParams", false, "Generate Bridge operator torrc parameters")
genParamsFP := flag.String("genServerParamsFP", "", "Optional bridge fingerprint for genServerParams")
flag.BoolVar(&enableLogging, "enableLogging", false, "Log to TOR_PT_STATE_LOCATION/obfs4proxy.log")
flag.BoolVar(&iatObfuscation, "iatObfuscation", false, "Enable IAT obufscation (EXPENSIVE)")
// Handle the command line arguments.
_, execName := path.Split(os.Args[0])
flag.BoolVar(&enableLogging, "enableLogging", false, "Log to TOR_PT_STATE_LOCATION/"+obfs4proxyLogFile)
flag.BoolVar(&unsafeLogging, "unsafeLogging", false, "Disable the address scrubber")
flag.Parse()
if *genParams {
generateServerParams(*genParamsFP)
return
}
// Go through the pt protocol and initialize client or server mode.
// Determine if this is a client or server, initialize logging, and finish
// the pt configuration.
var ptListeners []net.Listener
handlerChan = make(chan int)
launched := false
isClient, err := ptIsClient()
if err != nil {
log.Fatal("[ERROR] obfs4proxy must be run as a managed transport or server")
} else if isClient {
launched = clientSetup()
log.Fatalf("[ERROR]: %s - must be run as a managed transport", execName)
}
if stateDir, err = pt.MakeStateDir(); err != nil {
log.Fatalf("[ERROR]: %s - No state directory: %s", execName, err)
}
if err = ptInitializeLogging(enableLogging); err != nil {
log.Fatalf("[ERROR]: %s - failed to initialize logging", execName)
}
if isClient {
log.Printf("[INFO]: %s - initializing client transport listeners", execName)
launched, ptListeners = clientSetup()
} else {
launched = serverSetup()
log.Printf("[INFO]: %s - initializing server transport listeners", execName)
launched, ptListeners = serverSetup()
}
if !launched {
// Something must have failed in client/server setup, just bail.
// Initialization failed, the client or server setup routines should
// have logged, so just exit here.
os.Exit(-1)
}
log.Println("[INFO] obfs4proxy - Launched and listening")
log.Printf("[INFO]: %s - launched and accepting connections", execName)
defer func() {
log.Println("[INFO] obfs4proxy - Terminated")
log.Printf("[INFO]: %s - terminated", execName)
}()
// Handle termination notification.
numHandlers := 0
var sig os.Signal
// At this point, the pt config protocol is finished, and incoming
// connections will be processed. Per the pt spec, on sane platforms
// termination is signaled via SIGINT (or SIGTERM), so wait on tor to
// request a shutdown of some sort.
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// wait for first signal
sig = nil
// Wait for the first SIGINT (close listeners).
var sig os.Signal
numHandlers := 0
for sig == nil {
select {
case n := <-handlerChan:
numHandlers += n
case sig = <-sigChan:
if sig == syscall.SIGTERM {
// SIGTERM causes immediate termination.
return
}
}
}
for _, ln := range ptListeners {
ln.Close()
}
if sig == syscall.SIGTERM {
return
}
// wait for second signal or no more handlers
// Wait for the 2nd SIGINT (or a SIGTERM), or for all current sessions to
// finish.
sig = nil
for sig == nil && numHandlers != 0 {
select {
@ -481,5 +430,3 @@ func main() {
}
}
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -1,51 +0,0 @@
/*
* 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 main
import (
"net/url"
"code.google.com/p/go.net/proxy"
"git.torproject.org/pluggable-transports/obfs4.git"
)
// getProxyDialer is a trival wrapper around the go.net/proxy package to avoid
// having it as a dependency for anything else.
func getProxyDialer(uri *url.URL) (obfs4.DialFn, error) {
if uri == nil {
return proxy.Direct.Dial, nil
}
dialer, err := proxy.FromURL(uri, proxy.Direct)
if err != nil {
return nil, err
}
return dialer.Dial, nil
}

@ -108,7 +108,6 @@ func (s *httpProxy) Dial(network, addr string) (net.Conn, error) {
return conn, nil
}
// httpConn is the mountain of bullshit we need to do just for staleReader.
type httpConn struct {
remoteAddr *net.TCPAddr
httpConn *httputil.ClientConn
@ -157,5 +156,3 @@ func (c *httpConn) SetWriteDeadline(t time.Time) error {
func init() {
proxy.RegisterDialerType("http", newHTTP)
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -162,5 +162,3 @@ func init() {
// Despite the scheme name, this really is SOCKS4.
proxy.RegisterDialerType("socks4a", newSOCKS4)
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -124,7 +124,7 @@ func ptGetProxy() (*url.URL, error) {
return nil, ptProxyError(fmt.Sprintf("proxy URI has invalid scheme: %s", spec.Scheme))
}
err = validateAddrStr(spec.Host)
_, err = resolveAddrStr(spec.Host)
if err != nil {
return nil, ptProxyError(fmt.Sprintf("proxy URI has invalid host: %s", err))
}
@ -135,27 +135,26 @@ func ptGetProxy() (*url.URL, error) {
// Sigh, pt.resolveAddr() isn't exported. Include our own getto version that
// doesn't work around #7011, because we don't work with pre-0.2.5.x tor, and
// all we care about is validation anyway.
func validateAddrStr(addrStr string) error {
func resolveAddrStr(addrStr string) (*net.TCPAddr, error) {
ipStr, portStr, err := net.SplitHostPort(addrStr)
if err != nil {
return err
return nil, err
}
if ipStr == "" {
return net.InvalidAddrError(fmt.Sprintf("address string %q lacks a host part", addrStr))
return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a host part", addrStr))
}
if portStr == "" {
return net.InvalidAddrError(fmt.Sprintf("address string %q lacks a port part", addrStr))
return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a port part", addrStr))
}
if net.ParseIP(ipStr) == nil {
return net.InvalidAddrError(fmt.Sprintf("not an IP string: %q", ipStr))
ip := net.ParseIP(ipStr)
if ip == nil {
return nil, net.InvalidAddrError(fmt.Sprintf("not an IP string: %q", ipStr))
}
_, err = strconv.ParseUint(portStr, 10, 16)
port, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
return net.InvalidAddrError(fmt.Sprintf("not a Port string: %q", portStr))
return nil, net.InvalidAddrError(fmt.Sprintf("not a Port string: %q", portStr))
}
return nil
return &net.TCPAddr{IP: ip, Port: int(port), Zone: ""}, nil
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -1,145 +0,0 @@
/*
* 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 (
"container/list"
"encoding/binary"
"sync"
"github.com/dchest/siphash"
"git.torproject.org/pluggable-transports/obfs4.git/csrand"
)
// maxFilterSize is the maximum capacity of the replay filter. The busiest
// bridge I know about processes something along the order of 3000 connections
// per day. The maximum timespan any entry can live in the filter is 2 hours,
// so this value should be sufficient.
const maxFilterSize = 100 * 1024
// replayFilter is a simple filter designed only to answer if it has seen a
// given byte sequence before. It is based around comparing the SipHash-2-4
// digest of data to match against. Collisions are treated as positive matches
// however, the probability of such occurences is negligible.
type replayFilter struct {
lock sync.Mutex
key [2]uint64
filter map[uint64]*filterEntry
fifo *list.List
}
type filterEntry struct {
firstSeen int64
hash uint64
element *list.Element
}
// newReplayFilter creates a new replayFilter instance.
func newReplayFilter() (filter *replayFilter, err error) {
// Initialize the SipHash-2-4 instance with a random key.
var key [16]byte
err = csrand.Bytes(key[:])
if err != nil {
return
}
filter = new(replayFilter)
filter.key[0] = binary.BigEndian.Uint64(key[0:8])
filter.key[1] = binary.BigEndian.Uint64(key[8:16])
filter.filter = make(map[uint64]*filterEntry)
filter.fifo = list.New()
return
}
// testAndSet queries the filter for buf, adds it if it was not present and
// returns if it has added the entry or not. This method is threadsafe.
func (f *replayFilter) testAndSet(now int64, buf []byte) bool {
hash := siphash.Hash(f.key[0], f.key[1], buf)
f.lock.Lock()
defer f.lock.Unlock()
f.compactFilter(now)
entry := f.filter[hash]
if entry != nil {
return true
}
entry = new(filterEntry)
entry.hash = hash
entry.firstSeen = now
entry.element = f.fifo.PushBack(entry)
f.filter[hash] = entry
return false
}
// compactFilter purges entries that are too old to be relevant. If the filter
// is filled to maxFilterCapacity, it will force purge a single entry. This
// method is NOT threadsafe.
func (f *replayFilter) compactFilter(now int64) {
e := f.fifo.Front()
for e != nil {
entry, _ := e.Value.(*filterEntry)
// If the filter is at max capacity, force purge at least one entry.
if f.fifo.Len() < maxFilterSize {
deltaT := now - entry.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
}
if deltaT < 3600*2 {
// Why yes, this is 2 hours. The MAC code includes a hour
// resolution timestamp, but to deal with clock skew, it
// accepts time +- 1 hour.
break
}
}
eNext := e.Next()
delete(f.filter, entry.hash)
f.fifo.Remove(entry.element)
entry.element = nil
e = eNext
}
}
// reset purges the entire filter. This methoid is NOT threadsafe.
func (f *replayFilter) reset() {
f.filter = make(map[uint64]*filterEntry)
f.fifo = list.New()
}
/* vim :set ts=4 sw=4 sts=4 noet : */

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

@ -0,0 +1,367 @@
/*
* 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 obfs2 provides an implementation of the Tor Project's obfs2
// obfuscation protocol. This protocol is considered trivially broken by most
// sophisticated adversaries.
package obfs2
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
"net"
"time"
"git.torproject.org/pluggable-transports/goptlib.git"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
)
const (
transportName = "obfs2"
sharedSecretArg = "shared-secret"
clientHandshakeTimeout = time.Duration(30) * time.Second
serverHandshakeTimeout = time.Duration(30) * time.Second
magicValue = 0x2bf5ca7e
initiatorPadString = "Initiator obfuscation padding"
responderPadString = "Responder obfuscation padding"
initiatorKdfString = "Initiator obfuscated data"
responderKdfString = "Responder obfuscated data"
maxPadding = 8192
keyLen = 16
seedLen = 16
hsLen = 4 + 4
)
func validateArgs(args *pt.Args) error {
if _, ok := args.Get(sharedSecretArg); ok {
// "shared-secret" is something no bridges use in practice and is thus
// unimplemented.
return fmt.Errorf("unsupported argument '%s'", sharedSecretArg)
}
return nil
}
// Transport is the obfs2 implementation of the base.Transport interface.
type Transport struct{}
// Name returns the name of the obfs2 transport protocol.
func (t *Transport) Name() string {
return transportName
}
// ClientFactory returns a new obfs2ClientFactory instance.
func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
cf := &obfs2ClientFactory{transport: t}
return cf, nil
}
// ServerFactory returns a new obfs2ServerFactory instance.
func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
if err := validateArgs(args); err != nil {
return nil, err
}
sf := &obfs2ServerFactory{t}
return sf, nil
}
type obfs2ClientFactory struct {
transport base.Transport
}
func (cf *obfs2ClientFactory) Transport() base.Transport {
return cf.transport
}
func (cf *obfs2ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
return nil, validateArgs(args)
}
func (cf *obfs2ClientFactory) WrapConn(conn net.Conn, args interface{}) (net.Conn, error) {
return newObfs2ClientConn(conn)
}
type obfs2ServerFactory struct {
transport base.Transport
}
func (sf *obfs2ServerFactory) Transport() base.Transport {
return sf.transport
}
func (sf *obfs2ServerFactory) Args() *pt.Args {
return nil
}
func (sf *obfs2ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
return newObfs2ServerConn(conn)
}
type obfs2Conn struct {
net.Conn
isInitiator bool
rx *cipher.StreamReader
tx *cipher.StreamWriter
}
func (conn *obfs2Conn) Read(b []byte) (int, error) {
return conn.rx.Read(b)
}
func (conn *obfs2Conn) Write(b []byte) (int, error) {
return conn.tx.Write(b)
}
func newObfs2ClientConn(conn net.Conn) (c *obfs2Conn, err error) {
// Initialize a client connection, and start the handshake timeout.
c = &obfs2Conn{conn, true, nil, nil}
deadline := time.Now().Add(clientHandshakeTimeout)
if err = c.SetDeadline(deadline); err != nil {
return nil, err
}
// Handshake.
if err = c.handshake(); err != nil {
return nil, err
}
// Disarm the handshake timer.
if err = c.SetDeadline(time.Time{}); err != nil {
return nil, err
}
return
}
func newObfs2ServerConn(conn net.Conn) (c *obfs2Conn, err error) {
// Initialize a server connection, and start the handshake timeout.
c = &obfs2Conn{conn, false, nil, nil}
deadline := time.Now().Add(serverHandshakeTimeout)
if err = c.SetDeadline(deadline); err != nil {
return nil, err
}
// Handshake.
if err = c.handshake(); err != nil {
return nil, err
}
// Disarm the handshake timer.
if err = c.SetDeadline(time.Time{}); err != nil {
return nil, err
}
return
}
func (conn *obfs2Conn) handshake() (err error) {
// Each begins by generating a seed and a padding key as follows.
// The initiator generates:
//
// INIT_SEED = SR(SEED_LENGTH)
// INIT_PAD_KEY = MAC("Initiator obfuscation padding", INIT_SEED)[:KEYLEN]
//
// And the responder generates:
//
// RESP_SEED = SR(SEED_LENGTH)
// RESP_PAD_KEY = MAC("Responder obfuscation padding", INIT_SEED)[:KEYLEN]
//
// Each then generates a random number PADLEN in range from 0 through
// MAX_PADDING (inclusive).
var seed [seedLen]byte
if err = csrand.Bytes(seed[:]); err != nil {
return
}
var padMagic []byte
if conn.isInitiator {
padMagic = []byte(initiatorPadString)
} else {
padMagic = []byte(responderPadString)
}
padKey, padIV := hsKdf(padMagic, seed[:], conn.isInitiator)
padLen := uint32(csrand.IntRange(0, maxPadding))
hsBlob := make([]byte, hsLen+padLen)
binary.BigEndian.PutUint32(hsBlob[0:4], magicValue)
binary.BigEndian.PutUint32(hsBlob[4:8], padLen)
if padLen > 0 {
if err = csrand.Bytes(hsBlob[8:]); err != nil {
return
}
}
// The initiator then sends:
//
// INIT_SEED | E(INIT_PAD_KEY, UINT32(MAGIC_VALUE) | UINT32(PADLEN) | WR(PADLEN))
//
// and the responder sends:
//
// RESP_SEED | E(RESP_PAD_KEY, UINT32(MAGIC_VALUE) | UINT32(PADLEN) | WR(PADLEN))
var txBlock cipher.Block
if txBlock, err = aes.NewCipher(padKey); err != nil {
return
}
txStream := cipher.NewCTR(txBlock, padIV)
conn.tx = &cipher.StreamWriter{txStream, conn.Conn, nil}
if _, err = conn.Conn.Write(seed[:]); err != nil {
return
}
if _, err = conn.Write(hsBlob); err != nil {
return
}
// Upon receiving the SEED from the other party, each party derives
// the other party's padding key value as above, and decrypts the next
// 8 bytes of the key establishment message.
var peerSeed [seedLen]byte
if _, err = io.ReadFull(conn.Conn, peerSeed[:]); err != nil {
return
}
var peerPadMagic []byte
if conn.isInitiator {
peerPadMagic = []byte(responderPadString)
} else {
peerPadMagic = []byte(initiatorPadString)
}
peerKey, peerIV := hsKdf(peerPadMagic, peerSeed[:], !conn.isInitiator)
var rxBlock cipher.Block
if rxBlock, err = aes.NewCipher(peerKey); err != nil {
return
}
rxStream := cipher.NewCTR(rxBlock, peerIV)
conn.rx = &cipher.StreamReader{rxStream, conn.Conn}
hsHdr := make([]byte, hsLen)
if _, err = io.ReadFull(conn, hsHdr[:]); err != nil {
return
}
// If the MAGIC_VALUE does not match, or the PADLEN value is greater than
// MAX_PADDING, the party receiving it should close the connection
// immediately.
if peerMagic := binary.BigEndian.Uint32(hsHdr[0:4]); peerMagic != magicValue {
err = fmt.Errorf("invalid magic value: %x", peerMagic)
return
}
padLen = binary.BigEndian.Uint32(hsHdr[4:8])
if padLen > maxPadding {
err = fmt.Errorf("padlen too long: %d", padLen)
return
}
// Otherwise, it should read the remaining PADLEN bytes of padding data
// and discard them.
tmp := make([]byte, padLen)
if _, err = io.ReadFull(conn.Conn, tmp); err != nil { // Note: Skips AES.
return
}
// Derive the actual keys.
if err = conn.kdf(seed[:], peerSeed[:]); err != nil {
return
}
return
}
func (conn *obfs2Conn) kdf(seed, peerSeed []byte) (err error) {
// Additional keys are then derived as:
//
// INIT_SECRET = MAC("Initiator obfuscated data", INIT_SEED|RESP_SEED)
// RESP_SECRET = MAC("Responder obfuscated data", INIT_SEED|RESP_SEED)
// INIT_KEY = INIT_SECRET[:KEYLEN]
// INIT_IV = INIT_SECRET[KEYLEN:]
// RESP_KEY = RESP_SECRET[:KEYLEN]
// RESP_IV = RESP_SECRET[KEYLEN:]
combSeed := make([]byte, 0, seedLen*2)
if conn.isInitiator {
combSeed = append(combSeed, seed...)
combSeed = append(combSeed, peerSeed...)
} else {
combSeed = append(combSeed, peerSeed...)
combSeed = append(combSeed, seed...)
}
initKey, initIV := hsKdf([]byte(initiatorKdfString), combSeed, true)
var initBlock cipher.Block
if initBlock, err = aes.NewCipher(initKey); err != nil {
return
}
initStream := cipher.NewCTR(initBlock, initIV)
respKey, respIV := hsKdf([]byte(responderKdfString), combSeed, false)
var respBlock cipher.Block
if respBlock, err = aes.NewCipher(respKey); err != nil {
return
}
respStream := cipher.NewCTR(respBlock, respIV)
if conn.isInitiator {
conn.tx.S = initStream
conn.rx.S = respStream
} else {
conn.tx.S = respStream
conn.rx.S = initStream
}
return
}
func hsKdf(magic, seed []byte, isInitiator bool) (padKey, padIV []byte) {
// The actual key/IV is derived in the form of:
// m = MAC(magic, seed)
// KEY = m[:KEYLEN]
// IV = m[KEYLEN:]
m := mac(magic, seed)
padKey = m[:keyLen]
padIV = m[keyLen:]
return
}
func mac(s, x []byte) []byte {
// H(x) is SHA256 of x.
// MAC(s, x) = H(s | x | s)
h := sha256.New()
h.Write(s)
h.Write(x)
h.Write(s)
return h.Sum(nil)
}
var _ base.ClientFactory = (*obfs2ClientFactory)(nil)
var _ base.ServerFactory = (*obfs2ServerFactory)(nil)
var _ base.Transport = (*Transport)(nil)
var _ net.Conn = (*obfs2Conn)(nil)

@ -0,0 +1,358 @@
/*
* 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 obfs3 provides an implementation of the Tor Project's obfs3
// obfuscation protocol.
package obfs3
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"errors"
"io"
"net"
"time"
"git.torproject.org/pluggable-transports/goptlib.git"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/uniformdh"
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
)
const (
transportName = "obfs3"
clientHandshakeTimeout = time.Duration(30) * time.Second
serverHandshakeTimeout = time.Duration(30) * time.Second
initiatorKdfString = "Initiator obfuscated data"
responderKdfString = "Responder obfuscated data"
initiatorMagicString = "Initiator magic"
responderMagicString = "Responder magic"
maxPadding = 8194
keyLen = 16
)
// Transport is the obfs3 implementation of the base.Transport interface.
type Transport struct{}
// Name returns the name of the obfs3 transport protocol.
func (t *Transport) Name() string {
return transportName
}
// ClientFactory returns a new obfs3ClientFactory instance.
func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
cf := &obfs3ClientFactory{transport: t}
return cf, nil
}
// ServerFactory returns a new obfs3ServerFactory instance.
func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
sf := &obfs3ServerFactory{transport: t}
return sf, nil
}
type obfs3ClientFactory struct {
transport base.Transport
}
func (cf *obfs3ClientFactory) Transport() base.Transport {
return cf.transport
}
func (cf *obfs3ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
return nil, nil
}
func (cf *obfs3ClientFactory) WrapConn(conn net.Conn, args interface{}) (net.Conn, error) {
return newObfs3ClientConn(conn)
}
type obfs3ServerFactory struct {
transport base.Transport
}
func (sf *obfs3ServerFactory) Transport() base.Transport {
return sf.transport
}
func (sf *obfs3ServerFactory) Args() *pt.Args {
return nil
}
func (sf *obfs3ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
return newObfs3ServerConn(conn)
}
type obfs3Conn struct {
net.Conn
isInitiator bool
rxMagic []byte
txMagic []byte
rxBuf *bytes.Buffer
rx *cipher.StreamReader
tx *cipher.StreamWriter
}
func newObfs3ClientConn(conn net.Conn) (c *obfs3Conn, err error) {
// Initialize a client connection, and start the handshake timeout.
c = &obfs3Conn{conn, true, nil, nil, new(bytes.Buffer), nil, nil}
deadline := time.Now().Add(clientHandshakeTimeout)
if err = c.SetDeadline(deadline); err != nil {
return nil, err
}
// Handshake.
if err = c.handshake(); err != nil {
return nil, err
}
// Disarm the handshake timer.
if err = c.SetDeadline(time.Time{}); err != nil {
return nil, err
}
return
}
func newObfs3ServerConn(conn net.Conn) (c *obfs3Conn, err error) {
// Initialize a server connection, and start the handshake timeout.
c = &obfs3Conn{conn, false, nil, nil, new(bytes.Buffer), nil, nil}
deadline := time.Now().Add(serverHandshakeTimeout)
if err = c.SetDeadline(deadline); err != nil {
return nil, err
}
// Handshake.
if err = c.handshake(); err != nil {
return nil, err
}
// Disarm the handshake timer.
if err = c.SetDeadline(time.Time{}); err != nil {
return nil, err
}
return
}
func (conn *obfs3Conn) handshake() (err error) {
// The party who opens the connection is the 'initiator'; the one who
// accepts it is the 'responder'. Each begins by generating a
// UniformDH keypair, and a random number PADLEN in [0, MAX_PADDING/2].
// Both parties then send:
//
// PUB_KEY | WR(PADLEN)
var privateKey *uniformdh.PrivateKey
if privateKey, err = uniformdh.GenerateKey(csrand.Reader); err != nil {
return
}
padLen := csrand.IntRange(0, maxPadding/2)
blob := make([]byte, uniformdh.Size+padLen)
var publicKey []byte
if publicKey, err = privateKey.PublicKey.Bytes(); err != nil {
return
}
copy(blob[0:], publicKey)
if err = csrand.Bytes(blob[uniformdh.Size:]); err != nil {
return
}
if _, err = conn.Conn.Write(blob); err != nil {
return
}
// Read the public key from the peer.
rawPeerPublicKey := make([]byte, uniformdh.Size)
if _, err = io.ReadFull(conn.Conn, rawPeerPublicKey); err != nil {
return
}
var peerPublicKey uniformdh.PublicKey
if err = peerPublicKey.SetBytes(rawPeerPublicKey); err != nil {
return
}
// After retrieving the public key of the other end, each party
// completes the DH key exchange and generates a shared-secret for the
// session (named SHARED_SECRET).
var sharedSecret []byte
if sharedSecret, err = uniformdh.Handshake(privateKey, &peerPublicKey); err != nil {
return
}
if err = conn.kdf(sharedSecret); err != nil {
return
}
return
}
func (conn *obfs3Conn) kdf(sharedSecret []byte) (err error) {
// Using that shared-secret each party derives its encryption keys as
// follows:
//
// INIT_SECRET = HMAC(SHARED_SECRET, "Initiator obfuscated data")
// RESP_SECRET = HMAC(SHARED_SECRET, "Responder obfuscated data")
// INIT_KEY = INIT_SECRET[:KEYLEN]
// INIT_COUNTER = INIT_SECRET[KEYLEN:]
// RESP_KEY = RESP_SECRET[:KEYLEN]
// RESP_COUNTER = RESP_SECRET[KEYLEN:]
initHmac := hmac.New(sha256.New, sharedSecret)
initHmac.Write([]byte(initiatorKdfString))
initSecret := initHmac.Sum(nil)
initHmac.Reset()
initHmac.Write([]byte(initiatorMagicString))
initMagic := initHmac.Sum(nil)
respHmac := hmac.New(sha256.New, sharedSecret)
respHmac.Write([]byte(responderKdfString))
respSecret := respHmac.Sum(nil)
respHmac.Reset()
respHmac.Write([]byte(responderMagicString))
respMagic := respHmac.Sum(nil)
// The INIT_KEY value keys a block cipher (in CTR mode) used to
// encrypt values from initiator to responder thereafter. The counter
// mode's initial counter value is INIT_COUNTER. The RESP_KEY value
// keys a block cipher (in CTR mode) used to encrypt values from
// responder to initiator thereafter. That counter mode's initial
// counter value is RESP_COUNTER.
//
// Note: To have this be the last place where the shared secret is used,
// also generate the magic value to send/scan for here.
var initBlock cipher.Block
if initBlock, err = aes.NewCipher(initSecret[:keyLen]); err != nil {
return err
}
initStream := cipher.NewCTR(initBlock, initSecret[keyLen:])
var respBlock cipher.Block
if respBlock, err = aes.NewCipher(respSecret[:keyLen]); err != nil {
return err
}
respStream := cipher.NewCTR(respBlock, respSecret[keyLen:])
if conn.isInitiator {
conn.tx = &cipher.StreamWriter{initStream, conn.Conn, nil}
conn.rx = &cipher.StreamReader{respStream, conn.rxBuf}
conn.txMagic = initMagic
conn.rxMagic = respMagic
} else {
conn.tx = &cipher.StreamWriter{respStream, conn.Conn, nil}
conn.rx = &cipher.StreamReader{initStream, conn.rxBuf}
conn.txMagic = respMagic
conn.rxMagic = initMagic
}
return
}
func (conn *obfs3Conn) findPeerMagic() error {
var hsBuf [maxPadding + sha256.Size]byte
for {
n, err := conn.Conn.Read(hsBuf[:])
if err != nil {
// Yes, Read can return partial data and an error, but continuing
// past that is nonsensical.
return err
}
conn.rxBuf.Write(hsBuf[:n])
pos := bytes.Index(conn.rxBuf.Bytes(), conn.rxMagic)
if pos == -1 {
if conn.rxBuf.Len() >= maxPadding+sha256.Size {
return errors.New("failed to find peer magic value")
}
continue
} else if pos > maxPadding {
return errors.New("peer sent too much pre-magic-padding")
}
// Discard the padding/MAC.
pos += len(conn.rxMagic)
_ = conn.rxBuf.Next(pos)
return nil
}
}
func (conn *obfs3Conn) Read(b []byte) (n int, err error) {
// If this is the first time we read data post handshake, scan for the
// magic value.
if conn.rxMagic != nil {
if err = conn.findPeerMagic(); err != nil {
conn.Close()
return
}
conn.rxMagic = nil
}
// If the handshake receive buffer is still present...
if conn.rxBuf != nil {
// And it is empty...
if conn.rxBuf.Len() == 0 {
// There is no more trailing data left from the handshake process,
// so rewire the cipher.StreamReader to pull data from the network
// instead of the temporary receive buffer.
conn.rx.R = conn.Conn
conn.rxBuf = nil
}
}
return conn.rx.Read(b)
}
func (conn *obfs3Conn) Write(b []byte) (n int, err error) {
// If this is the first time we write data post handshake, send the
// padding/magic value.
if conn.txMagic != nil {
padLen := csrand.IntRange(0, maxPadding/2)
blob := make([]byte, padLen+len(conn.txMagic))
if err = csrand.Bytes(blob[:padLen]); err != nil {
conn.Close()
return
}
copy(blob[padLen:], conn.txMagic)
if _, err = conn.Conn.Write(blob); err != nil {
conn.Close()
return
}
conn.txMagic = nil
}
return conn.tx.Write(b)
}
var _ base.ClientFactory = (*obfs3ClientFactory)(nil)
var _ base.ServerFactory = (*obfs3ServerFactory)(nil)
var _ base.Transport = (*Transport)(nil)
var _ net.Conn = (*obfs3Conn)(nil)

@ -69,8 +69,8 @@ import (
"code.google.com/p/go.crypto/nacl/secretbox"
"git.torproject.org/pluggable-transports/obfs4.git/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
)
const (
@ -168,7 +168,7 @@ func NewEncoder(key []byte) *Encoder {
if err != nil {
panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err))
}
encoder.drbg = drbg.NewHashDrbg(seed)
encoder.drbg, _ = drbg.NewHashDrbg(seed)
return encoder
}
@ -231,7 +231,7 @@ func NewDecoder(key []byte) *Decoder {
if err != nil {
panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err))
}
decoder.drbg = drbg.NewHashDrbg(seed)
decoder.drbg, _ = drbg.NewHashDrbg(seed)
return decoder
}
@ -306,5 +306,3 @@ func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) {
return len(out), nil
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -167,5 +167,3 @@ func BenchmarkEncoder_Encode(b *testing.B) {
}
}
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -38,9 +38,10 @@ import (
"strconv"
"time"
"git.torproject.org/pluggable-transports/obfs4.git/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/framing"
"git.torproject.org/pluggable-transports/obfs4.git/ntor"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/ntor"
"git.torproject.org/pluggable-transports/obfs4.git/common/replayfilter"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4/framing"
)
const (
@ -247,7 +248,7 @@ func newServerHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.Keypair, sessi
return hs
}
func (hs *serverHandshake) parseClientHandshake(filter *replayFilter, resp []byte) ([]byte, error) {
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) {
@ -287,7 +288,7 @@ func (hs *serverHandshake) parseClientHandshake(filter *replayFilter, resp []byt
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().Unix(), macRx) {
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.
@ -423,5 +424,3 @@ func makePad(padLen int) ([]byte, error) {
return pad, err
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -31,14 +31,15 @@ import (
"bytes"
"testing"
"git.torproject.org/pluggable-transports/obfs4.git/ntor"
"git.torproject.org/pluggable-transports/obfs4.git/common/ntor"
"git.torproject.org/pluggable-transports/obfs4.git/common/replayfilter"
)
func TestHandshakeNtor(t *testing.T) {
// Generate the server node id and id keypair.
nodeID, _ := ntor.NewNodeID([]byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13"))
idKeypair, _ := ntor.NewKeypair(false)
serverFilter, _ := newReplayFilter()
serverFilter, _ := replayfilter.New(replayTTL)
// Test client handshake padding.
for l := clientMinPadLength; l <= clientMaxPadLength; l++ {

@ -0,0 +1,579 @@
/*
* 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"
"fmt"
"math/rand"
"net"
"syscall"
"time"
"git.torproject.org/pluggable-transports/goptlib.git"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/common/ntor"
"git.torproject.org/pluggable-transports/obfs4.git/common/probdist"
"git.torproject.org/pluggable-transports/obfs4.git/common/replayfilter"
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4/framing"
)
const (
transportName = "obfs4"
nodeIDArg = "node-id"
publicKeyArg = "public-key"
privateKeyArg = "private-key"
seedArg = "drbg-seed"
seedLength = 32
headerLength = framing.FrameOverhead + packetOverhead
clientHandshakeTimeout = time.Duration(60) * time.Second
serverHandshakeTimeout = time.Duration(30) * time.Second
replayTTL = time.Duration(3) * time.Hour
// Use a ScrambleSuit style biased probability table.
biasedDist = false
// Use IAT obfuscation.
iatObfuscation = false
// Maximum IAT delay (100 usec increments).
maxIATDelay = 100
maxCloseDelayBytes = maxHandshakeLength
maxCloseDelay = 60
)
type obfs4ClientArgs struct {
nodeID *ntor.NodeID
publicKey *ntor.PublicKey
sessionKey *ntor.Keypair
}
// Transport is the obfs4 implementation of the base.Transport interface.
type Transport struct{}
// Name returns the name of the obfs4 transport protocol.
func (t *Transport) Name() string {
return transportName
}
// ClientFactory returns a new obfs4ClientFactory instance.
func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
cf := &obfs4ClientFactory{transport: t}
return cf, nil
}
// ServerFactory returns a new obfs4ServerFactory instance.
func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
var err error
var st *obfs4ServerState
if st, err = serverStateFromArgs(stateDir, args); err != nil {
return nil, err
}
var iatSeed *drbg.Seed
if iatObfuscation {
iatSeedSrc := sha256.Sum256(st.drbgSeed.Bytes()[:])
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(nodeIDArg, st.nodeID.Base64())
ptArgs.Add(publicKeyArg, st.identityKey.Public().Base64())
// Initialize the replay filter.
filter, err := replayfilter.New(replayTTL)
if err != nil {
return nil, err
}
// Initialize the close thresholds for failed connections.
drbg, err := drbg.NewHashDrbg(st.drbgSeed)
if err != nil {
return nil, err
}
rng := rand.New(drbg)
sf := &obfs4ServerFactory{t, &ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)}
return sf, nil
}
type obfs4ClientFactory struct {
transport base.Transport
}
func (cf *obfs4ClientFactory) Transport() base.Transport {
return cf.transport
}
func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
var err error
// Handle the arguments.
nodeIDStr, ok := args.Get(nodeIDArg)
if !ok {
return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
}
var nodeID *ntor.NodeID
if nodeID, err = ntor.NodeIDFromBase64(nodeIDStr); err != nil {
return nil, err
}
publicKeyStr, ok := args.Get(publicKeyArg)
if !ok {
return nil, fmt.Errorf("missing argument '%s'", publicKeyArg)
}
var publicKey *ntor.PublicKey
if publicKey, err = ntor.PublicKeyFromBase64(publicKeyStr); err != nil {
return nil, err
}
// Generate the session key pair before connectiong to hide the Elligator2
// rejection sampling from network observers.
sessionKey, err := ntor.NewKeypair(true)
if err != nil {
return nil, err
}
return &obfs4ClientArgs{nodeID, publicKey, sessionKey}, nil
}
func (cf *obfs4ClientFactory) WrapConn(conn net.Conn, args interface{}) (net.Conn, error) {
ca, ok := args.(*obfs4ClientArgs)
if !ok {
return nil, fmt.Errorf("invalid argument type for args")
}
return newObfs4ClientConn(conn, ca)
}
type obfs4ServerFactory struct {
transport base.Transport
args *pt.Args
nodeID *ntor.NodeID
identityKey *ntor.Keypair
lenSeed *drbg.Seed
iatSeed *drbg.Seed
replayFilter *replayfilter.ReplayFilter
closeDelayBytes int
closeDelay int
}
func (sf *obfs4ServerFactory) Transport() base.Transport {
return sf.transport
}
func (sf *obfs4ServerFactory) Args() *pt.Args {
return sf.args
}
func (sf *obfs4ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
// Not much point in having a separate newObfs4ServerConn routine when
// wrapping requires using values from the factory instance.
// Generate the session keypair *before* consuming data from the peer, to
// attempt to mask the rejection sampling due to use of Elligator2. This
// might be futile, but the timing differential isn't very large on modern
// hardware, and there are far easier statistical attacks that can be
// mounted as a distinguisher.
sessionKey, err := ntor.NewKeypair(true)
if err != nil {
return nil, err
}
lenDist := probdist.New(sf.lenSeed, 0, framing.MaximumSegmentLength, biasedDist)
var iatDist *probdist.WeightedDist
if sf.iatSeed != nil {
iatDist = probdist.New(sf.iatSeed, 0, maxIATDelay, biasedDist)
}
c := &obfs4Conn{conn, true, lenDist, iatDist, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil}
startTime := time.Now()
if err = c.serverHandshake(sf, sessionKey); err != nil {
c.closeAfterDelay(sf, startTime)
return nil, err
}
return c, nil
}
type obfs4Conn struct {
net.Conn
isServer bool
lenDist *probdist.WeightedDist
iatDist *probdist.WeightedDist
receiveBuffer *bytes.Buffer
receiveDecodedBuffer *bytes.Buffer
encoder *framing.Encoder
decoder *framing.Decoder
}
func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err error) {
// Generate the initial protocol polymorphism distribution(s).
var seed *drbg.Seed
if seed, err = drbg.NewSeed(); err != nil {
return
}
lenDist := probdist.New(seed, 0, framing.MaximumSegmentLength, biasedDist)
var iatDist *probdist.WeightedDist
if iatObfuscation {
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, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil}
// Start the handshake timeout.
deadline := time.Now().Add(clientHandshakeTimeout)
if err = conn.SetDeadline(deadline); err != nil {
return nil, err
}
if err = c.clientHandshake(args.nodeID, args.publicKey, args.sessionKey); err != nil {
return nil, err
}
// Stop the handshake timeout.
if err = conn.SetDeadline(time.Time{}); err != nil {
return nil, err
}
return
}
func (conn *obfs4Conn) clientHandshake(nodeID *ntor.NodeID, peerIdentityKey *ntor.PublicKey, sessionKey *ntor.Keypair) error {
if conn.isServer {
return fmt.Errorf("clientHandshake called on server connection")
}
// Generate and send the client handshake.
hs := newClientHandshake(nodeID, peerIdentityKey, sessionKey)
blob, err := hs.generateHandshake()
if err != nil {
return err
}
if _, err = conn.Conn.Write(blob); err != nil {
return err
}
// Consume the server handshake.
var hsBuf [maxHandshakeLength]byte
for {
var n int
if n, err = conn.Conn.Read(hsBuf[:]); 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])
var seed []byte
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) (err 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
}
// Consume the client handshake.
var hsBuf [maxHandshakeLength]byte
for {
var n int
if n, err = conn.Conn.Read(hsBuf[:]); err != nil {
// The Read() could have returned data and an error, but there is
// no point in continuing on an EOF or whatever.
return
}
conn.receiveBuffer.Write(hsBuf[:n])
var seed []byte
seed, err = hs.parseClientHandshake(sf.replayFilter, conn.receiveBuffer.Bytes())
if err == ErrMarkNotFoundYet {
continue
} else if err != nil {
return
}
conn.receiveBuffer.Reset()
if err = conn.Conn.SetDeadline(time.Time{}); err != nil {
return
}
// 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.
var blob []byte
blob, err = hs.generateHandshake()
if err != nil {
return
}
var frameBuf bytes.Buffer
_, err = frameBuf.Write(blob)
if err != nil {
return
}
// Send the PRNG seed as the first packet.
if err = conn.makePacket(&frameBuf, packetTypePrngSeed, sf.lenSeed.Bytes()[:], 0); err != nil {
return
}
if _, err = conn.Conn.Write(frameBuf.Bytes()); err != nil {
return
}
return
}
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
}
}
// Add the length obfuscation padding. In theory, this could be inlined
// with the last chopped packet for certain (most?) payload lenghts, but
// this is simpler.
if err = conn.padBurst(&frameBuf); 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.iatDist != nil {
var iatFrame [framing.MaximumSegmentLength]byte
for frameBuf.Len() > 0 {
iatWrLen := 0
iatWrLen, err = frameBuf.Read(iatFrame[:])
if err != nil {
return 0, err
} else if iatWrLen == 0 {
panic(fmt.Sprintf("BUG: Write(), iat length was 0"))
}
// Calculate the delay. The delay resolution is 100 usec, leading
// to a maximum delay of 10 msec.
iatDelta := time.Duration(conn.iatDist.Sample() * 100)
// Write then sleep.
_, err = conn.Conn.Write(iatFrame[:iatWrLen])
if err != nil {
return 0, err
}
time.Sleep(iatDelta * time.Microsecond)
}
} else {
_, err = conn.Conn.Write(frameBuf.Bytes())
}
return
}
func (conn *obfs4Conn) SetDeadline(t time.Time) error {
return syscall.ENOTSUP
}
func (conn *obfs4Conn) SetWriteDeadline(t time.Time) error {
return syscall.ENOTSUP
}
func (conn *obfs4Conn) closeAfterDelay(sf *obfs4ServerFactory, startTime time.Time) {
// I-it's not like I w-wanna handshake with you or anything. B-b-baka!
defer conn.Conn.Close()
delay := time.Duration(sf.closeDelay)*time.Second + serverHandshakeTimeout
deadline := startTime.Add(delay)
if time.Now().After(deadline) {
return
}
if err := conn.Conn.SetReadDeadline(deadline); err != nil {
return
}
// Consume and discard data on this connection until either the specified
// interval passes or a certain size has been reached.
discarded := 0
var buf [framing.MaximumSegmentLength]byte
for discarded < int(sf.closeDelayBytes) {
n, err := conn.Conn.Read(buf[:])
if err != nil {
return
}
discarded += n
}
}
func (conn *obfs4Conn) padBurst(burst *bytes.Buffer) (err error) {
tailLen := burst.Len() % framing.MaximumSegmentLength
toPadTo := conn.lenDist.Sample()
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
}
var _ base.ClientFactory = (*obfs4ClientFactory)(nil)
var _ base.ServerFactory = (*obfs4ServerFactory)(nil)
var _ base.Transport = (*Transport)(nil)
var _ net.Conn = (*obfs4Conn)(nil)

@ -32,17 +32,16 @@ import (
"encoding/binary"
"fmt"
"io"
"syscall"
"git.torproject.org/pluggable-transports/obfs4.git/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/framing"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4/framing"
)
const (
packetOverhead = 2 + 1
maxPacketPayloadLength = framing.MaximumFramePayloadLength - packetOverhead
maxPacketPaddingLength = maxPacketPayloadLength
seedPacketPayloadLength = SeedLength
seedPacketPayloadLength = seedLength
consumeReadSize = framing.MaximumSegmentLength * 16
)
@ -70,24 +69,14 @@ func (e InvalidPayloadLengthError) Error() string {
var zeroPadBytes [maxPacketPaddingLength]byte
func (c *Obfs4Conn) producePacket(w io.Writer, pktType uint8, data []byte, padLen uint16) (err error) {
func (conn *obfs4Conn) makePacket(w io.Writer, pktType uint8, data []byte, padLen uint16) (err error) {
var pkt [framing.MaximumFramePayloadLength]byte
if !c.CanReadWrite() {
return syscall.EINVAL
}
if len(data)+int(padLen) > maxPacketPayloadLength {
panic(fmt.Sprintf("BUG: makePacket() len(data) + padLen > maxPacketPayloadLength: %d + %d > %d",
len(data), padLen, maxPacketPayloadLength))
}
defer func() {
if err != nil {
c.setBroken()
}
}()
// Packets are:
// uint8_t type packetTypePayload (0x00)
// uint16_t length Length of the payload (Big Endian).
@ -105,7 +94,7 @@ func (c *Obfs4Conn) producePacket(w io.Writer, pktType uint8, data []byte, padLe
// Encode the packet in an AEAD frame.
var frame [framing.MaximumSegmentLength]byte
frameLen := 0
frameLen, err = c.encoder.Encode(frame[:], pkt[:pktLen])
frameLen, err = conn.encoder.Encode(frame[:], pkt[:pktLen])
if err != nil {
// All encoder errors are fatal.
return
@ -122,19 +111,17 @@ func (c *Obfs4Conn) producePacket(w io.Writer, pktType uint8, data []byte, padLe
return
}
func (c *Obfs4Conn) consumeFramedPackets(w io.Writer) (n int, err error) {
if !c.CanReadWrite() {
return n, syscall.EINVAL
}
func (conn *obfs4Conn) readPackets() (err error) {
// Attempt to read off the network.
var buf [consumeReadSize]byte
rdLen, rdErr := c.conn.Read(buf[:])
c.receiveBuffer.Write(buf[:rdLen])
rdLen, rdErr := conn.Conn.Read(buf[:])
conn.receiveBuffer.Write(buf[:rdLen])
var decoded [framing.MaximumFramePayloadLength]byte
for c.receiveBuffer.Len() > 0 {
for conn.receiveBuffer.Len() > 0 {
// Decrypt an AEAD frame.
decLen := 0
decLen, err = c.decoder.Decode(decoded[:], &c.receiveBuffer)
decLen, err = conn.decoder.Decode(decoded[:], conn.receiveBuffer)
if err == framing.ErrAgain {
break
} else if err != nil {
@ -157,56 +144,36 @@ func (c *Obfs4Conn) consumeFramedPackets(w io.Writer) (n int, err error) {
switch pktType {
case packetTypePayload:
if payloadLen > 0 {
if w != nil {
// c.WriteTo() skips buffering in c.receiveDecodedBuffer
var wrLen int
wrLen, err = w.Write(payload)
n += wrLen
if err != nil {
break
} else if wrLen < int(payloadLen) {
err = io.ErrShortWrite
break
}
} else {
// c.Read() stashes decoded payload in receiveDecodedBuffer
c.receiveDecodedBuffer.Write(payload)
n += int(payloadLen)
}
conn.receiveDecodedBuffer.Write(payload)
}
case packetTypePrngSeed:
// Only regenerate the distribution if we are the client.
if len(payload) == seedPacketPayloadLength && !c.isServer {
if len(payload) == seedPacketPayloadLength && !conn.isServer {
var seed *drbg.Seed
seed, err = drbg.SeedFromBytes(payload)
if err != nil {
break
}
c.lenProbDist.reset(seed)
if c.iatProbDist != nil {
conn.lenDist.Reset(seed)
if conn.iatDist != nil {
iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
iatSeed, err := drbg.SeedFromBytes(iatSeedSrc[:])
if err != nil {
break
}
c.iatProbDist.reset(iatSeed)
conn.iatDist.Reset(iatSeed)
}
}
default:
// Ignore unrecognised packet types.
// Ignore unknown packet types.
}
}
// Read errors and non-framing.ErrAgain errors are all fatal.
if (err != nil && err != framing.ErrAgain) || rdErr != nil {
// Propagate read errors correctly.
if err == nil && rdErr != nil {
err = rdErr
}
c.setBroken()
// Read errors (all fatal) take priority over various frame processing
// errors.
if rdErr != nil {
return rdErr
}
return
}
/* vim :set ts=4 sw=4 sts=4 noet : */

@ -0,0 +1,156 @@
/*
* 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"
"git.torproject.org/pluggable-transports/goptlib.git"
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
"git.torproject.org/pluggable-transports/obfs4.git/common/ntor"
)
const (
stateFile = "obfs4_state.json"
)
type jsonServerState struct {
NodeID string `json:"node-id"`
PrivateKey string `json:"private-key"`
PublicKey string `json:"public-key"`
DrbgSeed string `json:"drbgSeed"`
}
type obfs4ServerState struct {
nodeID *ntor.NodeID
identityKey *ntor.Keypair
drbgSeed *drbg.Seed
}
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)
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)
}
return serverStateFromJSONServerState(&js)
}
func serverStateFromJSONServerState(js *jsonServerState) (*obfs4ServerState, error) {
var err error
st := new(obfs4ServerState)
if st.nodeID, err = ntor.NodeIDFromBase64(js.NodeID); err != nil {
return nil, err
}
if st.identityKey, err = ntor.KeypairFromBase64(js.PrivateKey); err != nil {
return nil, err
}
var rawSeed []byte
if rawSeed, err = base64.StdEncoding.DecodeString(js.DrbgSeed); err != nil {
return nil, err
}
if st.drbgSeed, err = drbg.SeedFromBytes(rawSeed); err != nil {
return nil, err
}
return st, nil
}
func jsonServerStateFromFile(stateDir string, js *jsonServerState) error {
f, err := ioutil.ReadFile(path.Join(stateDir, stateFile))
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 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
}
// Encode it into JSON format and write the state file.
js.NodeID = st.nodeID.Base64()
js.PrivateKey = st.identityKey.Private().Base64()
js.PublicKey = st.identityKey.Public().Base64()
js.DrbgSeed = st.drbgSeed.Base64()
var encoded []byte
if encoded, err = json.Marshal(js); err != nil {
return
}
if err = ioutil.WriteFile(path.Join(stateDir, stateFile), encoded, 0600); err != nil {
return err
}
return nil
}

@ -0,0 +1,91 @@
/*
* 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 transports provides a interface to query supported pluggable
// transports.
package transports
import (
"fmt"
"sync"
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs2"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs3"
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4"
)
var transportMapLock sync.Mutex
var transportMap map[string]base.Transport
// Register registers a transport protocol.
func Register(transport base.Transport) error {
transportMapLock.Lock()
defer transportMapLock.Unlock()
name := transport.Name()
_, registered := transportMap[name]
if registered {
return fmt.Errorf("transport '%s' already registered", name)
}
transportMap[name] = transport
return nil
}
// Transports returns the list of registered transport protocols.
func Transports() []string {
transportMapLock.Lock()
defer transportMapLock.Unlock()
var ret []string
for name := range transportMap {
ret = append(ret, name)
}
return ret
}
// Get returns a transport protocol implementation by name.
func Get(name string) base.Transport {
transportMapLock.Lock()
defer transportMapLock.Unlock()
t := transportMap[name]
return t
}
func init() {
// Initialize the transport list.
transportMap = make(map[string]base.Transport)
// Register all the currently supported transports.
Register(new(obfs2.Transport))
Register(new(obfs3.Transport))
Register(new(obfs4.Transport))
}
Loading…
Cancel
Save