From 5cb3369e200c72aa23c3f86816cb854c35cc95cb Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 2 Jun 2014 16:47:30 +0000 Subject: Move the SipHash DRBG off into it's own package. --- drbg/hash_drbg.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++ obfs4.go | 15 ++--- obfs4proxy/obfs4proxy.go | 3 +- packet.go | 9 +-- weighted_dist.go | 106 ++-------------------------------- 5 files changed, 165 insertions(+), 113 deletions(-) create mode 100644 drbg/hash_drbg.go diff --git a/drbg/hash_drbg.go b/drbg/hash_drbg.go new file mode 100644 index 0000000..13cc188 --- /dev/null +++ b/drbg/hash_drbg.go @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2014, Yawning Angel + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +// Package drbg implements a minimalistic DRBG based off SipHash-2-4 in OFB +// mode. +package drbg + +import ( + "encoding/base64" + "encoding/binary" + "fmt" + "hash" + + "github.com/dchest/siphash" + + "github.com/yawning/obfs4/csrand" +) + +// Size is the length of the HashDrbg output. +const Size = siphash.Size + +// SeedLength is the length of the HashDrbg seed. +const SeedLength = 32 + +// Seed is the initial state for a HashDrbg. It consists of a SipHash-2-4 +// key, and 16 bytes of initial data. +type Seed [SeedLength]byte + +// Bytes returns a pointer to the raw HashDrbg seed. +func (seed *Seed) Bytes() *[SeedLength]byte { + return (*[SeedLength]byte)(seed) +} + +// Base64 returns the Base64 representation of the seed. +func (seed *Seed) Base64() string { + return base64.StdEncoding.EncodeToString(seed.Bytes()[:]) +} + +// 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 { + return nil, err + } + + return +} + +// SeedFromBytes creates a Seed from the raw bytes. +func SeedFromBytes(src []byte) (seed *Seed, err error) { + if len(src) != SeedLength { + return nil, InvalidSeedLengthError(len(src)) + } + + seed = new(Seed) + copy(seed.Bytes()[:], src) + + return +} + +// SeedFromBase64 creates a Seed from the Base64 representation. +func SeedFromBase64(encoded string) (seed *Seed, err error) { + var raw []byte + raw, err = base64.StdEncoding.DecodeString(encoded) + if err != nil { + return nil, err + } + + return SeedFromBytes(raw) +} + +// InvalidSeedLengthError is the error returned when the seed provided to the +// DRBG is an invalid length. +type InvalidSeedLengthError int + +func (e InvalidSeedLengthError) Error() string { + return fmt.Sprintf("invalid seed length: %d", int(e)) +} + +// HashDrbg is a CSDRBG based off of SipHash-2-4 in OFB mode. +type HashDrbg struct { + sip hash.Hash64 + ofb [Size]byte +} + +// NewHashDrbg makes a HashDrbg instance based off an optional seed. The seed +// is truncated to SeedLength. +func NewHashDrbg(seed *Seed) *HashDrbg { + drbg := new(HashDrbg) + drbg.sip = siphash.New(seed.Bytes()[:16]) + copy(drbg.ofb[:], seed.Bytes()[16:]) + + return drbg +} + +// Int63 returns a uniformly distributed random integer [0, 1 << 63). +func (drbg *HashDrbg) Int63() int64 { + block := drbg.NextBlock() + ret := binary.BigEndian.Uint64(block) + ret &= (1<<63 - 1) + + return int64(ret) +} + +// Seed does nothing, call NewHashDrbg if you want to reseed. +func (drbg *HashDrbg) Seed(seed int64) { + // No-op. +} + +// NextBlock returns the next 8 byte DRBG block. +func (drbg *HashDrbg) NextBlock() []byte { + drbg.sip.Write(drbg.ofb[:]) + copy(drbg.ofb[:], drbg.sip.Sum(nil)) + + ret := make([]byte, Size) + copy(ret, drbg.ofb[:]) + return ret +} + +/* vim :set ts=4 sw=4 sts=4 noet : */ diff --git a/obfs4.go b/obfs4.go index b34eceb..ec33fb4 100644 --- a/obfs4.go +++ b/obfs4.go @@ -41,6 +41,7 @@ import ( "syscall" "time" + "github.com/yawning/obfs4/drbg" "github.com/yawning/obfs4/framing" "github.com/yawning/obfs4/ntor" ) @@ -561,7 +562,7 @@ func DialObfs4DialFn(dialFn DialFn, network, address, nodeID, publicKey string, } // Generate the initial length obfuscation distribution. - seed, err := NewDrbgSeed() + seed, err := drbg.NewSeed() if err != nil { return nil, err } @@ -571,7 +572,7 @@ func DialObfs4DialFn(dialFn DialFn, network, address, nodeID, publicKey string, c.lenProbDist = newWDist(seed, 0, framing.MaximumSegmentLength) if iatObfuscation { iatSeedSrc := sha256.Sum256(seed.Bytes()[:]) - iatSeed, err := DrbgSeedFromBytes(iatSeedSrc[:]) + iatSeed, err := drbg.SeedFromBytes(iatSeedSrc[:]) if err != nil { return nil, err } @@ -610,8 +611,8 @@ type Obfs4Listener struct { keyPair *ntor.Keypair nodeID *ntor.NodeID - seed *DrbgSeed - iatSeed *DrbgSeed + seed *drbg.Seed + iatSeed *drbg.Seed iatObfuscation bool closeDelayBytes int @@ -715,14 +716,14 @@ func ListenObfs4(network, laddr, nodeID, privateKey, seed string, iatObfuscation if err != nil { return nil, err } - l.seed, err = DrbgSeedFromBase64(seed) + l.seed, err = drbg.SeedFromBase64(seed) if err != nil { return nil, err } l.iatObfuscation = iatObfuscation if l.iatObfuscation { iatSeedSrc := sha256.Sum256(l.seed.Bytes()[:]) - l.iatSeed, err = DrbgSeedFromBytes(iatSeedSrc[:]) + l.iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:]) if err != nil { return nil, err } @@ -733,7 +734,7 @@ func ListenObfs4(network, laddr, nodeID, privateKey, seed string, iatObfuscation return nil, err } - rng := rand.New(newHashDrbg(l.seed)) + rng := rand.New(drbg.NewHashDrbg(l.seed)) l.closeDelayBytes = rng.Intn(maxCloseDelayBytes) l.closeDelay = rng.Intn(maxCloseDelay) diff --git a/obfs4proxy/obfs4proxy.go b/obfs4proxy/obfs4proxy.go index b0519ba..b8a3f00 100644 --- a/obfs4proxy/obfs4proxy.go +++ b/obfs4proxy/obfs4proxy.go @@ -62,6 +62,7 @@ import ( "git.torproject.org/pluggable-transports/goptlib.git" "github.com/yawning/obfs4" + "github.com/yawning/obfs4/drbg" "github.com/yawning/obfs4/ntor" ) @@ -389,7 +390,7 @@ func generateServerParams(id string) { return } - seed, err := obfs4.NewDrbgSeed() + seed, err := drbg.NewSeed() if err != nil { fmt.Println("Failed to generate DRBG seed:", err) return diff --git a/packet.go b/packet.go index 61ed981..3910604 100644 --- a/packet.go +++ b/packet.go @@ -34,6 +34,7 @@ import ( "io" "syscall" + "github.com/yawning/obfs4/drbg" "github.com/yawning/obfs4/framing" ) @@ -41,7 +42,7 @@ const ( packetOverhead = 2 + 1 maxPacketPayloadLength = framing.MaximumFramePayloadLength - packetOverhead maxPacketPaddingLength = maxPacketPayloadLength - seedPacketPayloadLength = DrbgSeedLength + seedPacketPayloadLength = drbg.SeedLength consumeReadSize = framing.MaximumSegmentLength * 16 ) @@ -176,15 +177,15 @@ 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) == seedPacketPayloadLength && !c.isServer { - var seed *DrbgSeed - seed, err = DrbgSeedFromBytes(payload) + var seed *drbg.Seed + seed, err = drbg.SeedFromBytes(payload) if err != nil { break } c.lenProbDist.reset(seed) if c.iatProbDist != nil { iatSeedSrc := sha256.Sum256(seed.Bytes()[:]) - iatSeed, err := DrbgSeedFromBytes(iatSeedSrc[:]) + iatSeed, err := drbg.SeedFromBytes(iatSeedSrc[:]) if err != nil { break } diff --git a/weighted_dist.go b/weighted_dist.go index 55432b2..02fb26d 100644 --- a/weighted_dist.go +++ b/weighted_dist.go @@ -28,15 +28,11 @@ package obfs4 import ( - "encoding/base64" - "encoding/binary" "fmt" - "hash" "math/rand" - "github.com/dchest/siphash" - "github.com/yawning/obfs4/csrand" + "github.com/yawning/obfs4/drbg" ) const ( @@ -44,98 +40,6 @@ const ( maxBuckets = 100 ) -// DrbgSeedLength is the length of the hashDrbg seed. -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()[:]) -} - -// NewDrbgSeed returns a DrbgSeed initialized with the runtime CSPRNG. -func NewDrbgSeed() (seed *DrbgSeed, err error) { - seed = new(DrbgSeed) - err = csrand.Bytes(seed.Bytes()[:]) - if err != nil { - return nil, err - } - - return -} - -// DrbgSeedFromBytes creates a DrbgSeed from the raw bytes. -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 -} - -// DrbgSeedFromBase64 creates a DrbgSeed from the Base64 representation. -func DrbgSeedFromBase64(encoded string) (seed *DrbgSeed, err error) { - var raw []byte - raw, err = base64.StdEncoding.DecodeString(encoded) - if err != nil { - return nil, err - } - - return DrbgSeedFromBytes(raw) -} - -// InvalidSeedLengthError is the error returned when the seed provided to the -// DRBG is an invalid length. -type InvalidSeedLengthError int - -func (e InvalidSeedLengthError) Error() string { - return fmt.Sprintf("hashDrbg: Invalid seed length: %d", int(e)) -} - -// hashDrbg is a CSDRBG based off of SipHash-2-4 in OFB mode. -type hashDrbg struct { - sip hash.Hash64 - ofb [siphash.Size]byte -} - -// newHashDrbg makes a hashDrbg instance based off an optional seed. The seed -// is truncated to DrbgSeedLength. -func newHashDrbg(seed *DrbgSeed) *hashDrbg { - drbg := new(hashDrbg) - drbg.sip = siphash.New(seed.Bytes()[:16]) - copy(drbg.ofb[:], seed.Bytes()[16:]) - - return drbg -} - -// Int63 returns a uniformly distributed random integer [0, 1 << 63). -func (drbg *hashDrbg) Int63() int64 { - // Use SipHash-2-4 in OFB mode to generate random numbers. - drbg.sip.Write(drbg.ofb[:]) - copy(drbg.ofb[:], drbg.sip.Sum(nil)) - - ret := binary.BigEndian.Uint64(drbg.ofb[:]) - ret &= (1<<63 - 1) - - return int64(ret) -} - -// Seed does nothing, call newHashDrbg if you want to reseed. -func (drbg *hashDrbg) Seed(seed int64) { - // No-op. -} - // wDist is a weighted distribution. type wDist struct { minValue int @@ -148,8 +52,8 @@ type wDist struct { } // newWDist creates a weighted distribution of values ranging from min to max -// based on a hashDrbg initialized with seed. -func newWDist(seed *DrbgSeed, min, max int) (w *wDist) { +// based on a HashDrbg initialized with seed. +func newWDist(seed *drbg.Seed, min, max int) (w *wDist) { w = new(wDist) w.minValue = min w.maxValue = max @@ -180,9 +84,9 @@ 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 *DrbgSeed) { +func (w *wDist) reset(seed *drbg.Seed) { // Initialize the deterministic random number generator. - drbg := newHashDrbg(seed) + drbg := drbg.NewHashDrbg(seed) w.rng = rand.New(drbg) nBuckets := (w.maxValue + 1) - w.minValue -- cgit v1.2.3