Browse Source

update frame header masking strategy

Darien Raymond 8 năm trước cách đây
mục cha
commit
3c032f0d53

+ 55 - 8
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

+ 4 - 9
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)

+ 1 - 2
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 {

+ 17 - 9
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)

+ 19 - 15
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)

+ 4 - 0
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)