summaryrefslogtreecommitdiff
path: root/framing/framing.go
diff options
context:
space:
mode:
authorYawning Angel <yawning@schwanenlied.me>2014-06-02 17:50:01 +0000
committerYawning Angel <yawning@schwanenlied.me>2014-06-02 17:50:01 +0000
commit5bdc376e2abaf5ac87816b763f5b26e314ee9536 (patch)
tree8746291873e187d7783116a2c9758bab23da5eb1 /framing/framing.go
parent5cb3369e200c72aa23c3f86816cb854c35cc95cb (diff)
Change how the length obfsucation mask is derived.
Instead of using the nonce for the secret box, just use SipHash-2-4 in OFB mode instead. The IV is generated as part of the KDF. This simplifies the code a decent amount and also is better on the off chance that SipHash-2-4 does not avalanche as well as it is currently assumed. While here, also decouple the fact that *this implementation* of obfs4 uses a PRNG with 24 bytes of internal state for protocol polymorphism instead of 32 bytes (that the spec requires). THIS CHANGE BREAKS WIRE PROTCOL COMPATIBILITY.
Diffstat (limited to 'framing/framing.go')
-rw-r--r--framing/framing.go38
1 files changed, 23 insertions, 15 deletions
diff --git a/framing/framing.go b/framing/framing.go
index 48d12c3..c053c4c 100644
--- a/framing/framing.go
+++ b/framing/framing.go
@@ -32,6 +32,7 @@
// uint8_t[32] NaCl secretbox key
// uint8_t[16] NaCl Nonce prefix
// uint8_t[16] SipHash-2-4 key (used to obfsucate length)
+// uint8_t[8] SipHash-2-4 IV
//
// The frame format is:
// uint16_t length (obfsucated, big endian)
@@ -40,7 +41,12 @@
// uint8_t[] payload
//
// The length field is length of the NaCl secretbox XORed with the truncated
-// SipHash-2-4 digest of the nonce used to seal/unseal the current secretbox.
+// SipHash-2-4 digest ran in OFB mode.
+//
+// Initialize K, IV[0] with values from the shared secret.
+// On each packet, IV[n] = H(K, IV[n - 1])
+// mask[n] = IV[n][0:2]
+// obfsLen = length ^ mask[n]
//
// The NaCl secretbox (Poly1305/XSalsa20) nonce format is:
// uint8_t[24] prefix (Fixed)
@@ -59,14 +65,12 @@ import (
"encoding/binary"
"errors"
"fmt"
- "hash"
"io"
"code.google.com/p/go.crypto/nacl/secretbox"
- "github.com/dchest/siphash"
-
"github.com/yawning/obfs4/csrand"
+ "github.com/yawning/obfs4/drbg"
)
const (
@@ -82,7 +86,7 @@ const (
MaximumFramePayloadLength = MaximumSegmentLength - FrameOverhead
// KeyLength is the length of the Encoder/Decoder secret key.
- KeyLength = keyLength + noncePrefixLength + 16
+ KeyLength = keyLength + noncePrefixLength + drbg.SeedLength
maxFrameLength = MaximumSegmentLength - lengthLength
minFrameLength = FrameOverhead - lengthLength
@@ -146,8 +150,8 @@ func (nonce boxNonce) bytes(out *[nonceLength]byte) error {
// Encoder is a frame encoder instance.
type Encoder struct {
key [keyLength]byte
- sip hash.Hash64
nonce boxNonce
+ drbg *drbg.HashDrbg
}
// NewEncoder creates a new Encoder instance. It must be supplied a slice
@@ -160,7 +164,11 @@ func NewEncoder(key []byte) *Encoder {
encoder := new(Encoder)
copy(encoder.key[:], key[0:keyLength])
encoder.nonce.init(key[keyLength : keyLength+noncePrefixLength])
- encoder.sip = siphash.New(key[keyLength+noncePrefixLength:])
+ seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:])
+ if err != nil {
+ panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err))
+ }
+ encoder.drbg = drbg.NewHashDrbg(seed)
return encoder
}
@@ -190,9 +198,7 @@ func (encoder *Encoder) Encode(frame, payload []byte) (n int, err error) {
// Obfuscate the length.
length := uint16(len(box) - lengthLength)
- encoder.sip.Write(nonce[:])
- lengthMask := encoder.sip.Sum(nil)
- encoder.sip.Reset()
+ lengthMask := encoder.drbg.NextBlock()
length ^= binary.BigEndian.Uint16(lengthMask)
binary.BigEndian.PutUint16(frame[:2], length)
@@ -204,7 +210,7 @@ func (encoder *Encoder) Encode(frame, payload []byte) (n int, err error) {
type Decoder struct {
key [keyLength]byte
nonce boxNonce
- sip hash.Hash64
+ drbg *drbg.HashDrbg
nextNonce [nonceLength]byte
nextLength uint16
@@ -221,7 +227,11 @@ func NewDecoder(key []byte) *Decoder {
decoder := new(Decoder)
copy(decoder.key[:], key[0:keyLength])
decoder.nonce.init(key[keyLength : keyLength+noncePrefixLength])
- decoder.sip = siphash.New(key[keyLength+noncePrefixLength:])
+ seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:])
+ if err != nil {
+ panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err))
+ }
+ decoder.drbg = drbg.NewHashDrbg(seed)
return decoder
}
@@ -253,9 +263,7 @@ func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) {
// Deobfuscate the length field.
length := binary.BigEndian.Uint16(obfsLen[:])
- decoder.sip.Write(decoder.nextNonce[:])
- lengthMask := decoder.sip.Sum(nil)
- decoder.sip.Reset()
+ lengthMask := decoder.drbg.NextBlock()
length ^= binary.BigEndian.Uint16(lengthMask)
if maxFrameLength < length || minFrameLength > length {
// Per "Plaintext Recovery Attacks Against SSH" by