From 2c709f92dfddaddb957e6f0a2cc09b7aab15e6d0 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Mon, 28 Dec 2020 12:04:32 +0000 Subject: [PATCH 1/2] Correctly assign payloadCipher to Obfuscator field, and add test for this issue --- internal/multiplex/obfs.go | 29 ++++--- internal/multiplex/obfs_test.go | 140 ++++++++++++++++++++++---------- 2 files changed, 109 insertions(+), 60 deletions(-) diff --git a/internal/multiplex/obfs.go b/internal/multiplex/obfs.go index 91c9a76..8fdf9ce 100644 --- a/internal/multiplex/obfs.go +++ b/internal/multiplex/obfs.go @@ -158,50 +158,49 @@ func (o *Obfuscator) deobfuscate(f *Frame, in []byte) error { return nil } -func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte) (obfuscator Obfuscator, err error) { - obfuscator = Obfuscator{ +func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte) (o Obfuscator, err error) { + o = Obfuscator{ SessionKey: sessionKey, } - var payloadCipher cipher.AEAD switch encryptionMethod { case EncryptionMethodPlain: - payloadCipher = nil - obfuscator.maxOverhead = salsa20NonceSize + o.payloadCipher = nil + o.maxOverhead = salsa20NonceSize case EncryptionMethodAES256GCM: var c cipher.Block c, err = aes.NewCipher(sessionKey[:]) if err != nil { return } - payloadCipher, err = cipher.NewGCM(c) + o.payloadCipher, err = cipher.NewGCM(c) if err != nil { return } - obfuscator.maxOverhead = payloadCipher.Overhead() + o.maxOverhead = o.payloadCipher.Overhead() case EncryptionMethodAES128GCM: var c cipher.Block c, err = aes.NewCipher(sessionKey[:16]) if err != nil { return } - payloadCipher, err = cipher.NewGCM(c) + o.payloadCipher, err = cipher.NewGCM(c) if err != nil { return } - obfuscator.maxOverhead = payloadCipher.Overhead() + o.maxOverhead = o.payloadCipher.Overhead() case EncryptionMethodChaha20Poly1305: - payloadCipher, err = chacha20poly1305.New(sessionKey[:]) + o.payloadCipher, err = chacha20poly1305.New(sessionKey[:]) if err != nil { return } - obfuscator.maxOverhead = payloadCipher.Overhead() + o.maxOverhead = o.payloadCipher.Overhead() default: - return obfuscator, fmt.Errorf("unknown encryption method valued %v", encryptionMethod) + return o, fmt.Errorf("unknown encryption method valued %v", encryptionMethod) } - if payloadCipher != nil { - if payloadCipher.NonceSize() > frameHeaderLength { - return obfuscator, errors.New("payload AEAD's nonce size cannot be greater than size of frame header") + if o.payloadCipher != nil { + if o.payloadCipher.NonceSize() > frameHeaderLength { + return o, errors.New("payload AEAD's nonce size cannot be greater than size of frame header") } } diff --git a/internal/multiplex/obfs_test.go b/internal/multiplex/obfs_test.go index 78a760d..21c29fd 100644 --- a/internal/multiplex/obfs_test.go +++ b/internal/multiplex/obfs_test.go @@ -1,9 +1,9 @@ package multiplex import ( - "bytes" "crypto/aes" "crypto/cipher" + "github.com/stretchr/testify/assert" "golang.org/x/crypto/chacha20poly1305" "math/rand" "reflect" @@ -15,69 +15,119 @@ func TestGenerateObfs(t *testing.T) { var sessionKey [32]byte rand.Read(sessionKey[:]) - run := func(obfuscator Obfuscator, ct *testing.T) { + run := func(o Obfuscator, t *testing.T) { obfsBuf := make([]byte, 512) _testFrame, _ := quick.Value(reflect.TypeOf(&Frame{}), rand.New(rand.NewSource(42))) testFrame := _testFrame.Interface().(*Frame) - i, err := obfuscator.obfuscate(testFrame, obfsBuf, 0) - if err != nil { - ct.Error("failed to obfs ", err) - return - } - + i, err := o.obfuscate(testFrame, obfsBuf, 0) + assert.NoError(t, err) var resultFrame Frame - err = obfuscator.deobfuscate(&resultFrame, obfsBuf[:i]) - if err != nil { - ct.Error("failed to deobfs ", err) - return - } - if !bytes.Equal(testFrame.Payload, resultFrame.Payload) || testFrame.StreamID != resultFrame.StreamID { - ct.Error("expecting", testFrame, - "got", resultFrame) - return - } + + err = o.deobfuscate(&resultFrame, obfsBuf[:i]) + assert.NoError(t, err) + assert.EqualValues(t, testFrame, resultFrame) } t.Run("plain", func(t *testing.T) { - obfuscator, err := MakeObfuscator(EncryptionMethodPlain, sessionKey) - if err != nil { - t.Errorf("failed to generate obfuscator %v", err) - } else { - run(obfuscator, t) - } + o, err := MakeObfuscator(EncryptionMethodPlain, sessionKey) + assert.NoError(t, err) + run(o, t) }) t.Run("aes-256-gcm", func(t *testing.T) { - obfuscator, err := MakeObfuscator(EncryptionMethodAES256GCM, sessionKey) - if err != nil { - t.Errorf("failed to generate obfuscator %v", err) - } else { - run(obfuscator, t) - } + o, err := MakeObfuscator(EncryptionMethodAES256GCM, sessionKey) + assert.NoError(t, err) + run(o, t) }) t.Run("aes-128-gcm", func(t *testing.T) { - obfuscator, err := MakeObfuscator(EncryptionMethodAES128GCM, sessionKey) - if err != nil { - t.Errorf("failed to generate obfuscator %v", err) - } else { - run(obfuscator, t) - } + o, err := MakeObfuscator(EncryptionMethodAES128GCM, sessionKey) + assert.NoError(t, err) + run(o, t) }) t.Run("chacha20-poly1305", func(t *testing.T) { - obfuscator, err := MakeObfuscator(EncryptionMethodChaha20Poly1305, sessionKey) - if err != nil { - t.Errorf("failed to generate obfuscator %v", err) - } else { - run(obfuscator, t) - } + o, err := MakeObfuscator(EncryptionMethodChaha20Poly1305, sessionKey) + assert.NoError(t, err) + run(o, t) }) t.Run("unknown encryption method", func(t *testing.T) { _, err := MakeObfuscator(0xff, sessionKey) - if err == nil { - t.Errorf("unknown encryption mehtod error expected") - } + assert.Error(t, err) }) } +func TestObfuscate(t *testing.T) { + var sessionKey [32]byte + rand.Read(sessionKey[:]) + + const testPayloadLen = 1024 + testPayload := make([]byte, testPayloadLen) + rand.Read(testPayload) + f := Frame{ + StreamID: 0, + Seq: 0, + Closing: 0, + Payload: testPayload, + } + + runTest := func(t *testing.T, o Obfuscator) { + obfsBuf := make([]byte, testPayloadLen*2) + n, err := o.obfuscate(&f, obfsBuf, 0) + assert.NoError(t, err) + + resultFrame := Frame{} + err = o.deobfuscate(&resultFrame, obfsBuf[:n]) + assert.NoError(t, err) + + assert.EqualValues(t, f, resultFrame) + } + + t.Run("plain", func(t *testing.T) { + o := Obfuscator{ + payloadCipher: nil, + SessionKey: sessionKey, + maxOverhead: salsa20NonceSize, + } + runTest(t, o) + }) + + t.Run("aes-128-gcm", func(t *testing.T) { + c, err := aes.NewCipher(sessionKey[:16]) + assert.NoError(t, err) + payloadCipher, err := cipher.NewGCM(c) + assert.NoError(t, err) + o := Obfuscator{ + payloadCipher: payloadCipher, + SessionKey: sessionKey, + maxOverhead: payloadCipher.Overhead(), + } + runTest(t, o) + }) + + t.Run("aes-256-gcm", func(t *testing.T) { + c, err := aes.NewCipher(sessionKey[:]) + assert.NoError(t, err) + payloadCipher, err := cipher.NewGCM(c) + assert.NoError(t, err) + o := Obfuscator{ + payloadCipher: payloadCipher, + SessionKey: sessionKey, + maxOverhead: payloadCipher.Overhead(), + } + runTest(t, o) + }) + + t.Run("chacha20-poly1305", func(t *testing.T) { + payloadCipher, err := chacha20poly1305.New(sessionKey[:]) + assert.NoError(t, err) + o := Obfuscator{ + payloadCipher: payloadCipher, + SessionKey: sessionKey, + maxOverhead: payloadCipher.Overhead(), + } + runTest(t, o) + }) + +} + func BenchmarkObfs(b *testing.B) { testPayload := make([]byte, 1024) rand.Read(testPayload) From 439b7f0eb3dcd8d2ef0328f51097b5b056c50574 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Mon, 28 Dec 2020 12:15:01 +0000 Subject: [PATCH 2/2] Improve encapsulation --- internal/multiplex/obfs.go | 10 +++++----- internal/multiplex/obfs_test.go | 30 +++++++++++++++--------------- internal/multiplex/session.go | 4 ++++ internal/server/dispatcher.go | 2 +- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/internal/multiplex/obfs.go b/internal/multiplex/obfs.go index 8fdf9ce..9fa614d 100644 --- a/internal/multiplex/obfs.go +++ b/internal/multiplex/obfs.go @@ -25,7 +25,7 @@ const ( type Obfuscator struct { payloadCipher cipher.AEAD - SessionKey [32]byte + sessionKey [32]byte maxOverhead int } @@ -35,7 +35,7 @@ func (o *Obfuscator) obfuscate(f *Frame, buf []byte, payloadOffsetInBuf int) (in // The method here is to use the first payloadCipher.NonceSize() bytes of the serialised frame header // as iv/nonce for the AEAD cipher to encrypt the frame payload. Then we use // the authentication tag produced appended to the end of the ciphertext (of size payloadCipher.Overhead()) - // as nonce for Salsa20 to encrypt the frame header. Both with SessionKey as keys. + // as nonce for Salsa20 to encrypt the frame header. Both with sessionKey as keys. // // Several cryptographic guarantees we have made here: that payloadCipher, as an AEAD, is given a unique // iv/nonce each time, relative to its key; that the frame header encryptor Salsa20 is given a unique @@ -108,7 +108,7 @@ func (o *Obfuscator) obfuscate(f *Frame, buf []byte, payloadOffsetInBuf int) (in } nonce := buf[usefulLen-salsa20NonceSize : usefulLen] - salsa20.XORKeyStream(header, header, nonce, &o.SessionKey) + salsa20.XORKeyStream(header, header, nonce, &o.sessionKey) return usefulLen, nil } @@ -123,7 +123,7 @@ func (o *Obfuscator) deobfuscate(f *Frame, in []byte) error { pldWithOverHead := in[frameHeaderLength:] // payload + potential overhead nonce := in[len(in)-salsa20NonceSize:] - salsa20.XORKeyStream(header, header, nonce, &o.SessionKey) + salsa20.XORKeyStream(header, header, nonce, &o.sessionKey) streamID := binary.BigEndian.Uint32(header[0:4]) seq := binary.BigEndian.Uint64(header[4:12]) @@ -160,7 +160,7 @@ func (o *Obfuscator) deobfuscate(f *Frame, in []byte) error { func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte) (o Obfuscator, err error) { o = Obfuscator{ - SessionKey: sessionKey, + sessionKey: sessionKey, } switch encryptionMethod { case EncryptionMethodPlain: diff --git a/internal/multiplex/obfs_test.go b/internal/multiplex/obfs_test.go index 21c29fd..2dad728 100644 --- a/internal/multiplex/obfs_test.go +++ b/internal/multiplex/obfs_test.go @@ -17,9 +17,9 @@ func TestGenerateObfs(t *testing.T) { run := func(o Obfuscator, t *testing.T) { obfsBuf := make([]byte, 512) - _testFrame, _ := quick.Value(reflect.TypeOf(&Frame{}), rand.New(rand.NewSource(42))) - testFrame := _testFrame.Interface().(*Frame) - i, err := o.obfuscate(testFrame, obfsBuf, 0) + _testFrame, _ := quick.Value(reflect.TypeOf(Frame{}), rand.New(rand.NewSource(42))) + testFrame := _testFrame.Interface().(Frame) + i, err := o.obfuscate(&testFrame, obfsBuf, 0) assert.NoError(t, err) var resultFrame Frame @@ -83,7 +83,7 @@ func TestObfuscate(t *testing.T) { t.Run("plain", func(t *testing.T) { o := Obfuscator{ payloadCipher: nil, - SessionKey: sessionKey, + sessionKey: sessionKey, maxOverhead: salsa20NonceSize, } runTest(t, o) @@ -96,7 +96,7 @@ func TestObfuscate(t *testing.T) { assert.NoError(t, err) o := Obfuscator{ payloadCipher: payloadCipher, - SessionKey: sessionKey, + sessionKey: sessionKey, maxOverhead: payloadCipher.Overhead(), } runTest(t, o) @@ -109,7 +109,7 @@ func TestObfuscate(t *testing.T) { assert.NoError(t, err) o := Obfuscator{ payloadCipher: payloadCipher, - SessionKey: sessionKey, + sessionKey: sessionKey, maxOverhead: payloadCipher.Overhead(), } runTest(t, o) @@ -120,7 +120,7 @@ func TestObfuscate(t *testing.T) { assert.NoError(t, err) o := Obfuscator{ payloadCipher: payloadCipher, - SessionKey: sessionKey, + sessionKey: sessionKey, maxOverhead: payloadCipher.Overhead(), } runTest(t, o) @@ -148,7 +148,7 @@ func BenchmarkObfs(b *testing.B) { obfuscator := Obfuscator{ payloadCipher: payloadCipher, - SessionKey: key, + sessionKey: key, maxOverhead: payloadCipher.Overhead(), } @@ -164,7 +164,7 @@ func BenchmarkObfs(b *testing.B) { obfuscator := Obfuscator{ payloadCipher: payloadCipher, - SessionKey: key, + sessionKey: key, maxOverhead: payloadCipher.Overhead(), } b.SetBytes(int64(len(testFrame.Payload))) @@ -176,7 +176,7 @@ func BenchmarkObfs(b *testing.B) { b.Run("plain", func(b *testing.B) { obfuscator := Obfuscator{ payloadCipher: nil, - SessionKey: key, + sessionKey: key, maxOverhead: salsa20NonceSize, } b.SetBytes(int64(len(testFrame.Payload))) @@ -190,7 +190,7 @@ func BenchmarkObfs(b *testing.B) { obfuscator := Obfuscator{ payloadCipher: payloadCipher, - SessionKey: key, + sessionKey: key, maxOverhead: payloadCipher.Overhead(), } b.SetBytes(int64(len(testFrame.Payload))) @@ -220,7 +220,7 @@ func BenchmarkDeobfs(b *testing.B) { payloadCipher, _ := cipher.NewGCM(c) obfuscator := Obfuscator{ payloadCipher: payloadCipher, - SessionKey: key, + sessionKey: key, maxOverhead: payloadCipher.Overhead(), } @@ -239,7 +239,7 @@ func BenchmarkDeobfs(b *testing.B) { obfuscator := Obfuscator{ payloadCipher: payloadCipher, - SessionKey: key, + sessionKey: key, maxOverhead: payloadCipher.Overhead(), } n, _ := obfuscator.obfuscate(testFrame, obfsBuf, 0) @@ -254,7 +254,7 @@ func BenchmarkDeobfs(b *testing.B) { b.Run("plain", func(b *testing.B) { obfuscator := Obfuscator{ payloadCipher: nil, - SessionKey: key, + sessionKey: key, maxOverhead: salsa20NonceSize, } n, _ := obfuscator.obfuscate(testFrame, obfsBuf, 0) @@ -271,7 +271,7 @@ func BenchmarkDeobfs(b *testing.B) { obfuscator := Obfuscator{ payloadCipher: nil, - SessionKey: key, + sessionKey: key, maxOverhead: payloadCipher.Overhead(), } diff --git a/internal/multiplex/session.go b/internal/multiplex/session.go index e05e399..6abc90e 100644 --- a/internal/multiplex/session.go +++ b/internal/multiplex/session.go @@ -128,6 +128,10 @@ func MakeSession(id uint32, config SessionConfig) *Session { return sesh } +func (sesh *Session) GetSessionKey() [32]byte { + return sesh.sessionKey +} + func (sesh *Session) streamCountIncr() uint32 { return atomic.AddUint32(&sesh.activeStreamCount, 1) } diff --git a/internal/server/dispatcher.go b/internal/server/dispatcher.go index 9daa772..287e363 100644 --- a/internal/server/dispatcher.go +++ b/internal/server/dispatcher.go @@ -236,7 +236,7 @@ func dispatchConnection(conn net.Conn, sta *State) { return } - preparedConn, err := finishHandshake(conn, sesh.SessionKey, sta.WorldState.Rand) + preparedConn, err := finishHandshake(conn, sesh.GetSessionKey(), sta.WorldState.Rand) if err != nil { log.Error(err) return