summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYawning Angel <yawning@schwanenlied.me>2014-05-24 05:06:34 +0000
committerYawning Angel <yawning@schwanenlied.me>2014-05-24 05:06:34 +0000
commit8cd2e1f0f9335221fbad892efdbb5e02e787a1e8 (patch)
tree47863b757a54e7030ab533e69868400e185ce4a3
parentb3f0f51775ae2e19c62c70e15f77ef991ad4bb49 (diff)
Randomize length when the decoder receives an out-of-bound value.
This makes the length error and MAC error indistinguishable to an external attacker.
-rw-r--r--framing/framing.go39
-rw-r--r--framing/framing_test.go2
2 files changed, 25 insertions, 16 deletions
diff --git a/framing/framing.go b/framing/framing.go
index bbb41cc..b51a7af 100644
--- a/framing/framing.go
+++ b/framing/framing.go
@@ -65,6 +65,8 @@ import (
"code.google.com/p/go.crypto/nacl/secretbox"
"github.com/dchest/siphash"
+
+ "github.com/yawning/obfs4/csrand"
)
const (
@@ -111,14 +113,6 @@ func (e InvalidPayloadLengthError) Error() string {
return fmt.Sprintf("framing: Invalid payload length: %d", int(e))
}
-// InvalidFrameLengthError is the error returned when Decoder.Decode()
-// rejects the payload length.
-type InvalidFrameLengthError int
-
-func (e InvalidFrameLengthError) Error() string {
- return fmt.Sprintf("framing: Invalid frame length: %d", int(e))
-}
-
type boxNonce struct {
prefix [noncePrefixLength]byte
counter uint64
@@ -179,7 +173,7 @@ func (encoder *Encoder) Encode(frame, payload []byte) (n int, err error) {
if MaximumFramePayloadLength < payloadLen {
return 0, InvalidPayloadLengthError(payloadLen)
}
- if len(frame) < payloadLen + FrameOverhead {
+ if len(frame) < payloadLen+FrameOverhead {
return 0, io.ErrShortBuffer
}
@@ -195,7 +189,7 @@ func (encoder *Encoder) Encode(frame, payload []byte) (n int, err error) {
box := secretbox.Seal(frame[:lengthLength], payload, &nonce, &encoder.key)
// Obfuscate the length.
- length := uint16(len(box)-lengthLength)
+ length := uint16(len(box) - lengthLength)
encoder.sip.Write(nonce[:])
lengthMask := encoder.sip.Sum(nil)
encoder.sip.Reset()
@@ -212,8 +206,9 @@ type Decoder struct {
nonce boxNonce
sip hash.Hash64
- nextNonce [nonceLength]byte
- nextLength uint16
+ nextNonce [nonceLength]byte
+ nextLength uint16
+ nextLengthInvalid bool
}
// NewDecoder creates a new Decoder instance. It must be supplied a slice
@@ -266,7 +261,19 @@ func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) {
decoder.sip.Reset()
length ^= binary.BigEndian.Uint16(lengthMask)
if maxFrameLength < length || minFrameLength > length {
- return 0, InvalidFrameLengthError(length)
+ // Per "Plaintext Recovery Attacks Against SSH" by
+ // Martin R. Albrecht, Kenneth G. Paterson and Gaven J. Watson,
+ // there are a class of attacks againt protocols that use similar
+ // sorts of framing schemes.
+ //
+ // While obfs4 should not allow plaintext recovery (CBC mode is
+ // not used), attempt to mitigate out of bound frame length errors
+ // by pretending that the length was a random valid range as per
+ // the countermeasure suggested by Denis Bider in section 6 of the
+ // paper.
+
+ decoder.nextLengthInvalid = true
+ length = uint16(csrand.IntRange(minFrameLength, maxFrameLength))
}
decoder.nextLength = length
}
@@ -283,10 +290,12 @@ func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) {
} else if n != int(decoder.nextLength) {
// Should *NEVER* happen, since the length is checked.
panic(fmt.Sprintf("BUG: Failed to read secretbox, got %d, should have %d",
- n, decoder.nextLength))
+ n, decoder.nextLength))
}
out, ok := secretbox.Open(data[:0], box[:n], &decoder.nextNonce, &decoder.key)
- if !ok {
+ if !ok || decoder.nextLengthInvalid {
+ // When a random lenght is used (on length error) the tag should always
+ // mismatch, but be paranoid.
return 0, ErrTagMismatch
}
diff --git a/framing/framing_test.go b/framing/framing_test.go
index 08f5f17..7df0e28 100644
--- a/framing/framing_test.go
+++ b/framing/framing_test.go
@@ -86,7 +86,7 @@ func TestEncoder_Encode_Oversize(t *testing.T) {
encoder := newEncoder(t)
var frame [MaximumSegmentLength]byte
- var buf [MaximumFramePayloadLength+1]byte
+ var buf [MaximumFramePayloadLength + 1]byte
_, _ = rand.Read(buf[:]) // YOLO
_, err := encoder.Encode(frame[:], buf[:])
if _, ok := err.(InvalidPayloadLengthError); !ok {