From f1b1dcdb106c00fef1acffe04caaeabb3a34239b Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Thu, 15 May 2014 18:33:24 +0000 Subject: Change hashDrbg to take a drbgSeed for initialization. This paves the way for having servers use the same seed for all incoming connections, across multiple startup/shutdown cycles. As opposed to the current situation where each Obfs4Listener will randomly generate it's seed at creation time. Additionally, use 256 bit seeds (128 bit SipHash-2-4 key + 16 bytes of initial material). --- obfs4.go | 19 ++++++++++--- packet.go | 9 +++++-- weighted_dist.go | 82 ++++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 78 insertions(+), 32 deletions(-) diff --git a/obfs4.go b/obfs4.go index 429ba95..d3dfc38 100644 --- a/obfs4.go +++ b/obfs4.go @@ -467,12 +467,15 @@ func Dial(network, address, nodeID, publicKey string) (net.Conn, error) { return nil, err } - // Connect to the peer. - c := new(Obfs4Conn) - c.lenProbDist, err = newWDist(nil, 0, framing.MaximumSegmentLength) + // Generate the initial length obfuscation distribution. + seed, err := newRandomDrbgSeed() if err != nil { return nil, err } + + // Connect to the peer. + c := new(Obfs4Conn) + c.lenProbDist = newWDist(seed, 0, framing.MaximumSegmentLength) c.conn, err = net.Dial(network, address) if err != nil { return nil, err @@ -495,6 +498,7 @@ type Obfs4Listener struct { keyPair *ntor.Keypair nodeID *ntor.NodeID + seed *drbgSeed } func (l *Obfs4Listener) Accept() (net.Conn, error) { @@ -509,7 +513,7 @@ func (l *Obfs4Listener) Accept() (net.Conn, error) { cObfs.conn = c cObfs.isServer = true cObfs.listener = l - cObfs.lenProbDist, err = newWDist(nil, 0, framing.MaximumSegmentLength) + cObfs.lenProbDist = newWDist(l.seed, 0, framing.MaximumSegmentLength) if err != nil { c.Close() return nil, err @@ -548,6 +552,13 @@ func Listen(network, laddr, nodeID, privateKey string) (net.Listener, error) { return nil, err } + // Generate the initial length obfuscation distribution. + // XXX: Load this from args. + l.seed, err = newRandomDrbgSeed() + if err != nil { + return nil, err + } + // Start up the listener. l.listener, err = net.Listen(network, laddr) if err != nil { diff --git a/packet.go b/packet.go index 75179cb..2528a53 100644 --- a/packet.go +++ b/packet.go @@ -173,8 +173,13 @@ func (c *Obfs4Conn) consumeFramedPackets(w io.Writer) (n int, err error) { } case packetTypePrngSeed: // Only regenerate the distribution if we are the client. - if len(payload) >= distSeedLength && !c.isServer { - c.lenProbDist.reset(payload[:distSeedLength]) + if len(payload) >= drbgSeedLength && !c.isServer { + var seed *drbgSeed + seed, err = drbgSeedFromBytes(payload[:drbgSeedLength]) + if err != nil { + break + } + c.lenProbDist.reset(seed) } default: // Ignore unrecognised packet types. diff --git a/weighted_dist.go b/weighted_dist.go index b165869..56128ae 100644 --- a/weighted_dist.go +++ b/weighted_dist.go @@ -29,6 +29,7 @@ package obfs4 import ( csrand "crypto/rand" + "encoding/base64" "encoding/binary" "fmt" "hash" @@ -37,7 +38,51 @@ import ( "github.com/dchest/siphash" ) -const distSeedLength = 16 +const drbgSeedLength = 32 + +// drbgSeed is the initial state for a hashDrbg. It consists of a SipHash-2-4 +// key, and 16 bytes of initial data. +type drbgSeed [drbgSeedLength]byte + +// bytes returns a pointer to the raw hashDrbg seed. +func (seed *drbgSeed) bytes() *[drbgSeedLength]byte { + return (*[drbgSeedLength]byte)(seed) +} + +// base64 returns the Base64 representation of the seed. +func (seed *drbgSeed) base64() string { + return base64.StdEncoding.EncodeToString(seed.bytes()[:]) +} + +// newRandomDrbgSeed returns a drbgSeed initialized with the runtime CSPRNG. +func newRandomDrbgSeed() (seed *drbgSeed, err error) { + seed = new(drbgSeed) + _, err = csrand.Read(seed.bytes()[:]) + if err != nil { + return nil, err + } + + return +} + +// drbgSeedFromBytes returns a drbg seed initialized with the caller provided +// slice. +func drbgSeedFromBytes(src []byte) (seed *drbgSeed, err error) { + if len(src) != drbgSeedLength { + return nil, InvalidSeedLengthError(len(src)) + } + + seed = new(drbgSeed) + copy(seed.bytes()[:], src) + + return +} + +/* +func drbgSeedFromBse64(encoded string) (seed *drbgSeed, err error) { + return +} +*/ // InvalidSeedLengthError is the error returned when the seed provided to the // DRBG is an invalid length. @@ -54,10 +99,11 @@ type hashDrbg struct { } // newHashDrbg makes a hashDrbg instance based off an optional seed. The seed -// is truncated to distSeedLength. -func newHashDrbg(seed []byte) *hashDrbg { +// is truncated to drbgSeedLength. +func newHashDrbg(seed *drbgSeed) *hashDrbg { drbg := new(hashDrbg) - drbg.sip = siphash.New(seed) + drbg.sip = siphash.New(seed.bytes()[:16]) + copy(drbg.ofb[:], seed.bytes()[16:]) return drbg } @@ -88,9 +134,9 @@ type wDist struct { } // newWDist creates a weighted distribution of values ranging from min to max -// based on a CSDRBG initialized with the optional 128 bit seed. -func newWDist(seed []byte, min, max int) (*wDist, error) { - w := new(wDist) +// based on a hashDrbg initialized with seed. +func newWDist(seed *drbgSeed, min, max int) (w *wDist) { + w = new(wDist) w.minValue = min w.maxValue = max @@ -98,12 +144,9 @@ func newWDist(seed []byte, min, max int) (*wDist, error) { panic(fmt.Sprintf("wDist.Reset(): min >= max (%d, %d)", min, max)) } - err := w.reset(seed) - if err != nil { - return nil, err - } + w.reset(seed) - return w, nil + return } // sample generates a random value according to the distribution. @@ -123,18 +166,7 @@ func (w *wDist) sample() int { } // reset generates a new distribution with the same min/max based on a new seed. -func (w *wDist) reset(seed []byte) error { - if seed == nil { - seed = make([]byte, distSeedLength) - _, err := csrand.Read(seed) - if err != nil { - return err - } - } - if len(seed) != distSeedLength { - return InvalidSeedLengthError(len(seed)) - } - +func (w *wDist) reset(seed *drbgSeed) { // Initialize the deterministic random number generator. drbg := newHashDrbg(seed) dRng := rand.New(drbg) @@ -150,8 +182,6 @@ func (w *wDist) reset(seed []byte) error { totalProb += prob } w.buckets[len(w.buckets)-1] = 1.0 - - return nil } /* vim :set ts=4 sw=4 sts=4 noet : */ -- cgit v1.2.3