Change the weighted distribution algorithm be uniform.

The old way was biasted towards the earlier values.  Thanks to asn for
pointing this out and suggesting an alternative.

As an additional tweak, do not reuse the drbg seed when calculating the
IAT distribution, but instead run the seed through SHA256 first, for
extra tinfoil goodness.
This commit is contained in:
Yawning Angel 2014-05-28 04:22:36 +00:00
parent bd2bef2ead
commit 9fe9959c76
5 changed files with 61 additions and 23 deletions

View File

@ -68,7 +68,12 @@ func (r csRandSource) Seed(seed int64) {
// No-op.
}
// Float64 returns, as a float 64, a pesudo random number in [0.0,1.0).
// Int63n returns, as a int64, a pseudo random number in [0, n).
func Int63n(n int64) int64 {
return CsRand.Int63n(n)
}
// Float64 returns, as a float64, a pesudo random number in [0.0,1.0).
func Float64() float64 {
return CsRand.Float64()
}

View File

@ -287,7 +287,7 @@ func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) {
}
out, ok := secretbox.Open(data[:0], box[:n], &decoder.nextNonce, &decoder.key)
if !ok || decoder.nextLengthInvalid {
// When a random lenght is used (on length error) the tag should always
// When a random length is used (on length error) the tag should always
// mismatch, but be paranoid.
return 0, ErrTagMismatch
}

View File

@ -33,6 +33,7 @@ package obfs4
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
"math/rand"
@ -568,7 +569,12 @@ func DialObfs4DialFn(dialFn DialFn, network, address, nodeID, publicKey string,
c := new(Obfs4Conn)
c.lenProbDist = newWDist(seed, 0, framing.MaximumSegmentLength)
if iatObfuscation {
c.iatProbDist = newWDist(seed, 0, maxIatDelay)
iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
iatSeed, err := DrbgSeedFromBytes(iatSeedSrc[:])
if err != nil {
return nil, err
}
c.iatProbDist = newWDist(iatSeed, 0, maxIatDelay)
}
c.conn, err = dialFn(network, address)
if err != nil {
@ -596,6 +602,7 @@ type Obfs4Listener struct {
nodeID *ntor.NodeID
seed *DrbgSeed
iatSeed *DrbgSeed
iatObfuscation bool
closeDelayBytes int
@ -631,7 +638,7 @@ func (l *Obfs4Listener) AcceptObfs4() (*Obfs4Conn, error) {
cObfs.listener = l
cObfs.lenProbDist = newWDist(l.seed, 0, framing.MaximumSegmentLength)
if l.iatObfuscation {
cObfs.iatProbDist = newWDist(l.seed, 0, maxIatDelay)
cObfs.iatProbDist = newWDist(l.iatSeed, 0, maxIatDelay)
}
if err != nil {
c.Close()
@ -692,6 +699,14 @@ func ListenObfs4(network, laddr, nodeID, privateKey, seed string, iatObfuscation
if err != nil {
return nil, err
}
l.iatObfuscation = iatObfuscation
if l.iatObfuscation {
iatSeedSrc := sha256.Sum256(l.seed.Bytes()[:])
l.iatSeed, err = DrbgSeedFromBytes(iatSeedSrc[:])
if err != nil {
return nil, err
}
}
l.filter, err = newReplayFilter()
if err != nil {
@ -701,7 +716,6 @@ func ListenObfs4(network, laddr, nodeID, privateKey, seed string, iatObfuscation
rng := rand.New(newHashDrbg(l.seed))
l.closeDelayBytes = rng.Intn(maxCloseDelayBytes)
l.closeDelay = rng.Intn(maxCloseDelay)
l.iatObfuscation = iatObfuscation
// Start up the listener.
l.listener, err = net.Listen(network, laddr)

View File

@ -28,6 +28,7 @@
package obfs4
import (
"crypto/sha256"
"encoding/binary"
"fmt"
"io"
@ -182,7 +183,12 @@ func (c *Obfs4Conn) consumeFramedPackets(w io.Writer) (n int, err error) {
}
c.lenProbDist.reset(seed)
if c.iatProbDist != nil {
c.iatProbDist.reset(seed)
iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
iatSeed, err := DrbgSeedFromBytes(iatSeedSrc[:])
if err != nil {
break
}
c.iatProbDist.reset(iatSeed)
}
}
default:

View File

@ -39,6 +39,11 @@ import (
"github.com/yawning/obfs4/csrand"
)
const (
minBuckets = 1
maxBuckets = 100
)
// DrbgSeedLength is the length of the hashDrbg seed.
const DrbgSeedLength = 32
@ -133,10 +138,11 @@ func (drbg *hashDrbg) Seed(seed int64) {
// wDist is a weighted distribution.
type wDist struct {
minValue int
maxValue int
values []int
buckets []float64
minValue int
maxValue int
values []int
buckets []int64
totalWeight int64
rng *rand.Rand
}
@ -160,11 +166,11 @@ func newWDist(seed *DrbgSeed, min, max int) (w *wDist) {
// sample generates a random value according to the distribution.
func (w *wDist) sample() int {
retIdx := 0
totalProb := 0.0
prob := csrand.Float64()
for i, bucketProb := range w.buckets {
totalProb += bucketProb
if prob <= totalProb {
var totalWeight int64
weight := csrand.Int63n(w.totalWeight)
for i, bucketWeight := range w.buckets {
totalWeight += bucketWeight
if weight <= totalWeight {
retIdx = i
break
}
@ -181,15 +187,22 @@ func (w *wDist) reset(seed *DrbgSeed) {
nBuckets := (w.maxValue + 1) - w.minValue
w.values = w.rng.Perm(nBuckets)
w.buckets = make([]float64, nBuckets)
var totalProb float64
for i, _ := range w.buckets {
prob := w.rng.Float64() * (1.0 - totalProb)
w.buckets[i] = prob
totalProb += prob
if nBuckets < minBuckets {
nBuckets = minBuckets
}
w.buckets[len(w.buckets)-1] = 1.0
if nBuckets > maxBuckets {
nBuckets = maxBuckets
}
nBuckets = w.rng.Intn(nBuckets) + 1
w.totalWeight = 0
w.buckets = make([]int64, nBuckets)
for i, _ := range w.buckets {
prob := w.rng.Int63n(1000)
w.buckets[i] = prob
w.totalWeight += prob
}
w.buckets[len(w.buckets)-1] = w.totalWeight
}
/* vim :set ts=4 sw=4 sts=4 noet : */