diff --git a/common/crypto/auth.go b/common/crypto/auth.go index 4d20a8fe..5287ed0c 100644 --- a/common/crypto/auth.go +++ b/common/crypto/auth.go @@ -4,6 +4,7 @@ import ( "crypto/cipher" "io" + "golang.org/x/crypto/sha3" "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/errors" @@ -67,11 +68,56 @@ func (v *AEADAuthenticator) Seal(dst, plainText []byte) ([]byte, error) { return v.AEAD.Seal(dst, iv, plainText, additionalData), nil } +type Uint16Generator interface { + Next() uint16 +} + +type StaticUint16Generator uint16 + +func (g StaticUint16Generator) Next() uint16 { + return uint16(g) +} + +type MultiplyUint16Generator struct { + base uint16 + value uint16 +} + +func NewMultiplyUint16Generator(base uint16) *MultiplyUint16Generator { + return &MultiplyUint16Generator{ + base: base, + value: 1, + } +} + +func (g *MultiplyUint16Generator) Next() uint16 { + g.value *= g.base + return g.value +} + +type ShakeUint16Generator struct { + shake sha3.ShakeHash + buffer [2]byte +} + +func NewShakeUint16Generator(nonce []byte) *ShakeUint16Generator { + shake := sha3.NewShake128() + shake.Write(nonce) + return &ShakeUint16Generator{ + shake: shake, + } +} + +func (g *ShakeUint16Generator) Next() uint16 { + g.shake.Read(g.buffer[:]) + return serial.BytesToUint16(g.buffer[:]) +} + type AuthenticationReader struct { auth Authenticator buffer *buf.Buffer reader io.Reader - sizeMask uint16 + sizeMask Uint16Generator chunk []byte } @@ -80,7 +126,7 @@ const ( readerBufferSize = 32 * 1024 ) -func NewAuthenticationReader(auth Authenticator, reader io.Reader, sizeMask uint16) *AuthenticationReader { +func NewAuthenticationReader(auth Authenticator, reader io.Reader, sizeMask Uint16Generator) *AuthenticationReader { return &AuthenticationReader{ auth: auth, buffer: buf.NewLocal(readerBufferSize), @@ -89,11 +135,11 @@ func NewAuthenticationReader(auth Authenticator, reader io.Reader, sizeMask uint } } -func (v *AuthenticationReader) NextChunk() error { +func (v *AuthenticationReader) NextChunk(mask uint16) error { if v.buffer.Len() < 2 { return errInsufficientBuffer } - size := int(serial.BytesToUint16(v.buffer.BytesTo(2)) ^ v.sizeMask) + size := int(serial.BytesToUint16(v.buffer.BytesTo(2)) ^ mask) if size > v.buffer.Len()-2 { return errInsufficientBuffer } @@ -136,8 +182,9 @@ func (v *AuthenticationReader) EnsureChunk() error { atHead = true } + mask := v.sizeMask.Next() for { - err := v.NextChunk() + err := v.NextChunk(mask) if err != errInsufficientBuffer { return err } @@ -173,10 +220,10 @@ type AuthenticationWriter struct { auth Authenticator buffer []byte writer io.Writer - sizeMask uint16 + sizeMask Uint16Generator } -func NewAuthenticationWriter(auth Authenticator, writer io.Writer, sizeMask uint16) *AuthenticationWriter { +func NewAuthenticationWriter(auth Authenticator, writer io.Writer, sizeMask Uint16Generator) *AuthenticationWriter { return &AuthenticationWriter{ auth: auth, buffer: make([]byte, 32*1024), @@ -191,7 +238,7 @@ func (v *AuthenticationWriter) Write(b []byte) (int, error) { return 0, err } - size := uint16(len(cipherChunk)) ^ v.sizeMask + size := uint16(len(cipherChunk)) ^ v.sizeMask.Next() serial.Uint16ToBytes(size, v.buffer[:0]) _, err = v.writer.Write(v.buffer[:2+len(cipherChunk)]) return len(b), err diff --git a/common/crypto/auth_test.go b/common/crypto/auth_test.go index d9c71e65..f766a52c 100644 --- a/common/crypto/auth_test.go +++ b/common/crypto/auth_test.go @@ -10,15 +10,12 @@ import ( "v2ray.com/core/common/buf" . "v2ray.com/core/common/crypto" - "v2ray.com/core/common/dice" "v2ray.com/core/testing/assert" ) func TestAuthenticationReaderWriter(t *testing.T) { assert := assert.On(t) - sizeMask := uint16(dice.Roll(65536)) - key := make([]byte, 16) rand.Read(key) block, err := aes.NewCipher(key) @@ -40,7 +37,7 @@ func TestAuthenticationReaderWriter(t *testing.T) { Content: iv, }, AdditionalDataGenerator: &NoOpBytesGenerator{}, - }, cache, sizeMask) + }, cache, NewShakeUint16Generator([]byte{'a'})) nBytes, err := writer.Write(payload) assert.Error(err).IsNil() @@ -55,7 +52,7 @@ func TestAuthenticationReaderWriter(t *testing.T) { Content: iv, }, AdditionalDataGenerator: &NoOpBytesGenerator{}, - }, cache, sizeMask) + }, cache, NewShakeUint16Generator([]byte{'a'})) actualPayload := make([]byte, 16*1024) nBytes, err = reader.Read(actualPayload) @@ -70,8 +67,6 @@ func TestAuthenticationReaderWriter(t *testing.T) { func TestAuthenticationReaderWriterPartial(t *testing.T) { assert := assert.On(t) - sizeMask := uint16(dice.Roll(65536)) - key := make([]byte, 16) rand.Read(key) block, err := aes.NewCipher(key) @@ -93,7 +88,7 @@ func TestAuthenticationReaderWriterPartial(t *testing.T) { Content: iv, }, AdditionalDataGenerator: &NoOpBytesGenerator{}, - }, cache, sizeMask) + }, cache, NewShakeUint16Generator([]byte{'a', 'b'})) writer.Write([]byte{'a', 'b', 'c', 'd'}) @@ -123,7 +118,7 @@ func TestAuthenticationReaderWriterPartial(t *testing.T) { Content: iv, }, AdditionalDataGenerator: &NoOpBytesGenerator{}, - }, pr, sizeMask) + }, pr, NewShakeUint16Generator([]byte{'a', 'b'})) actualPayload := make([]byte, 7*1024) nBytes, err = reader.Read(actualPayload) diff --git a/common/protocol/headers.go b/common/protocol/headers.go index f4ad33ad..1e7832a1 100644 --- a/common/protocol/headers.go +++ b/common/protocol/headers.go @@ -24,8 +24,7 @@ const ( // RequestOptionConnectionReuse indicates client side expects to reuse the connection. RequestOptionConnectionReuse = RequestOption(0x02) - // RequestOptionCompressedStream indicates request payload is compressed. - RequestOptionCompressedStream = RequestOption(0x04) + RequestOptionChunkMasking = RequestOption(0x04) ) func (v RequestOption) Has(option RequestOption) bool { diff --git a/proxy/vmess/encoding/client.go b/proxy/vmess/encoding/client.go index 5d381fab..cb852ccf 100644 --- a/proxy/vmess/encoding/client.go +++ b/proxy/vmess/encoding/client.go @@ -119,6 +119,10 @@ func (v *ClientSession) EncodeRequestHeader(header *protocol.RequestHeader, writ func (v *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, writer io.Writer) buf.Writer { var authWriter io.Writer + var sizeMask crypto.Uint16Generator = crypto.StaticUint16Generator(0) + if request.Option.Has(protocol.RequestOptionChunkMasking) { + sizeMask = getSizeMask(v.requestBodyIV) + } if request.Security.Is(protocol.SecurityType_NONE) { if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ @@ -126,7 +130,7 @@ func (v *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write NonceGenerator: crypto.NoOpBytesGenerator{}, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authWriter = crypto.NewAuthenticationWriter(auth, writer, getSizeMask(v.requestBodyIV)) + authWriter = crypto.NewAuthenticationWriter(auth, writer, sizeMask) } else { authWriter = writer } @@ -139,7 +143,7 @@ func (v *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write NonceGenerator: crypto.NoOpBytesGenerator{}, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authWriter = crypto.NewAuthenticationWriter(auth, cryptionWriter, 0) + authWriter = crypto.NewAuthenticationWriter(auth, cryptionWriter, sizeMask) } else { authWriter = cryptionWriter } @@ -155,7 +159,7 @@ func (v *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write }, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authWriter = crypto.NewAuthenticationWriter(auth, writer, getSizeMask(v.requestBodyIV)) + authWriter = crypto.NewAuthenticationWriter(auth, writer, sizeMask) } else if request.Security.Is(protocol.SecurityType_CHACHA20_POLY1305) { aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(v.requestBodyKey)) @@ -167,7 +171,7 @@ func (v *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write }, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authWriter = crypto.NewAuthenticationWriter(auth, writer, getSizeMask(v.requestBodyIV)) + authWriter = crypto.NewAuthenticationWriter(auth, writer, sizeMask) } return buf.NewWriter(authWriter) @@ -214,14 +218,18 @@ func (v *ClientSession) DecodeResponseHeader(reader io.Reader) (*protocol.Respon func (v *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, reader io.Reader) buf.Reader { var authReader io.Reader + var sizeMask crypto.Uint16Generator = crypto.StaticUint16Generator(0) + if request.Option.Has(protocol.RequestOptionChunkMasking) { + sizeMask = getSizeMask(v.responseBodyIV) + } if request.Security.Is(protocol.SecurityType_NONE) { if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ - AEAD: new(FnvAuthenticator), + AEAD: NoOpAuthenticator{}, NonceGenerator: crypto.NoOpBytesGenerator{}, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authReader = crypto.NewAuthenticationReader(auth, reader, getSizeMask(v.responseBodyIV)) + authReader = crypto.NewAuthenticationReader(auth, reader, sizeMask) } else { authReader = reader } @@ -232,7 +240,7 @@ func (v *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read NonceGenerator: crypto.NoOpBytesGenerator{}, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authReader = crypto.NewAuthenticationReader(auth, v.responseReader, 0) + authReader = crypto.NewAuthenticationReader(auth, v.responseReader, sizeMask) } else { authReader = v.responseReader } @@ -248,7 +256,7 @@ func (v *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read }, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authReader = crypto.NewAuthenticationReader(auth, reader, getSizeMask(v.responseBodyIV)) + authReader = crypto.NewAuthenticationReader(auth, reader, sizeMask) } else if request.Security.Is(protocol.SecurityType_CHACHA20_POLY1305) { aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(v.responseBodyKey)) @@ -260,7 +268,7 @@ func (v *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read }, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authReader = crypto.NewAuthenticationReader(auth, reader, getSizeMask(v.responseBodyIV)) + authReader = crypto.NewAuthenticationReader(auth, reader, sizeMask) } return buf.NewReader(authReader) diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index e842ce19..cdfac9a8 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -94,12 +94,8 @@ func (h *SessionHistory) run() { } } -func getSizeMask(b []byte) uint16 { - mask := uint16(0) - for i := 0; i < len(b); i += 2 { - mask ^= serial.BytesToUint16(b[i : i+2]) - } - return mask +func getSizeMask(b []byte) crypto.Uint16Generator { + return crypto.NewShakeUint16Generator(b) } type ServerSession struct { @@ -245,6 +241,10 @@ func (v *ServerSession) DecodeRequestHeader(reader io.Reader) (*protocol.Request func (v *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reader io.Reader) buf.Reader { var authReader io.Reader + var sizeMask crypto.Uint16Generator = crypto.StaticUint16Generator(0) + if request.Option.Has(protocol.RequestOptionChunkMasking) { + sizeMask = getSizeMask(v.requestBodyIV) + } if request.Security.Is(protocol.SecurityType_NONE) { if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ @@ -252,7 +252,7 @@ func (v *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade NonceGenerator: crypto.NoOpBytesGenerator{}, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authReader = crypto.NewAuthenticationReader(auth, reader, getSizeMask(v.requestBodyIV)) + authReader = crypto.NewAuthenticationReader(auth, reader, sizeMask) } else { authReader = reader } @@ -265,7 +265,7 @@ func (v *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade NonceGenerator: crypto.NoOpBytesGenerator{}, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authReader = crypto.NewAuthenticationReader(auth, cryptionReader, 0) + authReader = crypto.NewAuthenticationReader(auth, cryptionReader, sizeMask) } else { authReader = cryptionReader } @@ -281,7 +281,7 @@ func (v *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade }, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authReader = crypto.NewAuthenticationReader(auth, reader, getSizeMask(v.requestBodyIV)) + authReader = crypto.NewAuthenticationReader(auth, reader, sizeMask) } else if request.Security.Is(protocol.SecurityType_CHACHA20_POLY1305) { aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(v.requestBodyKey)) @@ -293,7 +293,7 @@ func (v *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade }, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authReader = crypto.NewAuthenticationReader(auth, reader, getSizeMask(v.requestBodyIV)) + authReader = crypto.NewAuthenticationReader(auth, reader, sizeMask) } return buf.NewReader(authReader) @@ -318,14 +318,18 @@ func (v *ServerSession) EncodeResponseHeader(header *protocol.ResponseHeader, wr func (v *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writer io.Writer) buf.Writer { var authWriter io.Writer + var sizeMask crypto.Uint16Generator = crypto.StaticUint16Generator(0) + if request.Option.Has(protocol.RequestOptionChunkMasking) { + sizeMask = getSizeMask(v.responseBodyIV) + } if request.Security.Is(protocol.SecurityType_NONE) { if request.Option.Has(protocol.RequestOptionChunkStream) { auth := &crypto.AEADAuthenticator{ - AEAD: new(FnvAuthenticator), + AEAD: NoOpAuthenticator{}, NonceGenerator: crypto.NoOpBytesGenerator{}, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authWriter = crypto.NewAuthenticationWriter(auth, writer, getSizeMask(v.responseBodyIV)) + authWriter = crypto.NewAuthenticationWriter(auth, writer, sizeMask) } else { authWriter = writer } @@ -336,7 +340,7 @@ func (v *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ NonceGenerator: crypto.NoOpBytesGenerator{}, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authWriter = crypto.NewAuthenticationWriter(auth, v.responseWriter, 0) + authWriter = crypto.NewAuthenticationWriter(auth, v.responseWriter, sizeMask) } else { authWriter = v.responseWriter } @@ -352,7 +356,7 @@ func (v *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ }, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authWriter = crypto.NewAuthenticationWriter(auth, writer, getSizeMask(v.responseBodyIV)) + authWriter = crypto.NewAuthenticationWriter(auth, writer, sizeMask) } else if request.Security.Is(protocol.SecurityType_CHACHA20_POLY1305) { aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(v.responseBodyKey)) @@ -364,7 +368,7 @@ func (v *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ }, AdditionalDataGenerator: crypto.NoOpBytesGenerator{}, } - authWriter = crypto.NewAuthenticationWriter(auth, writer, getSizeMask(v.responseBodyIV)) + authWriter = crypto.NewAuthenticationWriter(auth, writer, sizeMask) } return buf.NewWriter(authWriter) diff --git a/proxy/vmess/outbound/outbound.go b/proxy/vmess/outbound/outbound.go index 188aec72..1d46f010 100644 --- a/proxy/vmess/outbound/outbound.go +++ b/proxy/vmess/outbound/outbound.go @@ -93,6 +93,10 @@ func (v *Handler) Process(ctx context.Context, outboundRay ray.OutboundRay, dial account := rawAccount.(*vmess.InternalAccount) request.Security = account.Security + if request.Security.Is(protocol.SecurityType_AES128_GCM) || request.Security.Is(protocol.SecurityType_NONE) || request.Security.Is(protocol.SecurityType_CHACHA20_POLY1305) { + request.Option.Set(protocol.RequestOptionChunkMasking) + } + conn.SetReusable(true) if conn.Reusable() { // Conn reuse may be disabled on transportation layer request.Option.Set(protocol.RequestOptionConnectionReuse)