From f54a8735abe469888f188996fbc179530d1dad5f Mon Sep 17 00:00:00 2001 From: Darien Raymond Date: Sat, 7 Jul 2018 15:42:24 +0200 Subject: [PATCH] global padding in vmess protocol --- common/crypto/auth.go | 47 ++++++++++++++++++++++++++-------- common/crypto/auth_test.go | 10 ++++---- common/crypto/chunk.go | 4 +++ common/protocol/headers.go | 2 ++ proxy/shadowsocks/config.go | 4 +-- proxy/vmess/encoding/auth.go | 4 +++ proxy/vmess/encoding/client.go | 26 +++++++++++++------ proxy/vmess/encoding/server.go | 26 +++++++++++++------ 8 files changed, 90 insertions(+), 33 deletions(-) diff --git a/common/crypto/auth.go b/common/crypto/auth.go index 5121ff34a..0a8140b05 100644 --- a/common/crypto/auth.go +++ b/common/crypto/auth.go @@ -2,6 +2,7 @@ package crypto import ( "crypto/cipher" + "crypto/rand" "io" "v2ray.com/core/common" @@ -85,31 +86,39 @@ type AuthenticationReader struct { reader *buf.BufferedReader sizeParser ChunkSizeDecoder transferType protocol.TransferType + padding PaddingLengthGenerator size int32 + paddingLen int32 } -func NewAuthenticationReader(auth Authenticator, sizeParser ChunkSizeDecoder, reader io.Reader, transferType protocol.TransferType) *AuthenticationReader { +func NewAuthenticationReader(auth Authenticator, sizeParser ChunkSizeDecoder, reader io.Reader, transferType protocol.TransferType, paddingLen PaddingLengthGenerator) *AuthenticationReader { return &AuthenticationReader{ auth: auth, reader: &buf.BufferedReader{Reader: buf.NewReader(reader)}, sizeParser: sizeParser, transferType: transferType, + padding: paddingLen, size: -1, + paddingLen: -1, } } -func (r *AuthenticationReader) readSize() (int32, error) { +func (r *AuthenticationReader) readSize() (int32, int32, error) { if r.size != -1 { s := r.size r.size = -1 - return s, nil + return s, r.paddingLen, nil } sizeBytes := make([]byte, r.sizeParser.SizeBytes()) if _, err := io.ReadFull(r.reader, sizeBytes); err != nil { - return 0, err + return 0, 0, err + } + var padding int32 + if r.padding != nil { + padding = int32(r.padding.NextPaddingLen()) } size, err := r.sizeParser.Decode(sizeBytes) - return int32(size), err + return int32(size), padding, err } var errSoft = newError("waiting for more data") @@ -119,18 +128,19 @@ func (r *AuthenticationReader) readInternal(soft bool) (*buf.Buffer, error) { return nil, errSoft } - size, err := r.readSize() + size, padding, err := r.readSize() if err != nil { return nil, err } - if size == -2 || size == int32(r.auth.Overhead()) { + if size == -2 || size == int32(r.auth.Overhead())+padding { r.size = -2 return nil, io.EOF } if soft && size > r.reader.BufferedBytes() { r.size = size + r.paddingLen = padding return nil, errSoft } @@ -140,6 +150,8 @@ func (r *AuthenticationReader) readInternal(soft bool) (*buf.Buffer, error) { return nil, err } + size -= padding + rb, err := r.auth.Open(b.BytesTo(0), b.BytesTo(size)) if err != nil { b.Release() @@ -179,23 +191,29 @@ type AuthenticationWriter struct { writer buf.Writer sizeParser ChunkSizeEncoder transferType protocol.TransferType + padding PaddingLengthGenerator } -func NewAuthenticationWriter(auth Authenticator, sizeParser ChunkSizeEncoder, writer io.Writer, transferType protocol.TransferType) *AuthenticationWriter { +func NewAuthenticationWriter(auth Authenticator, sizeParser ChunkSizeEncoder, writer io.Writer, transferType protocol.TransferType, padding PaddingLengthGenerator) *AuthenticationWriter { return &AuthenticationWriter{ auth: auth, writer: buf.NewWriter(writer), sizeParser: sizeParser, transferType: transferType, + padding: padding, } } func (w *AuthenticationWriter) seal(b *buf.Buffer) (*buf.Buffer, error) { encryptedSize := int(b.Len()) + w.auth.Overhead() + paddingSize := 0 + if w.padding != nil { + paddingSize = int(w.padding.NextPaddingLen()) + } eb := buf.New() common.Must(eb.Reset(func(bb []byte) (int, error) { - w.sizeParser.Encode(uint16(encryptedSize), bb[:0]) + w.sizeParser.Encode(uint16(encryptedSize+paddingSize), bb[:0]) return int(w.sizeParser.SizeBytes()), nil })) if err := eb.AppendSupplier(func(bb []byte) (int, error) { @@ -205,6 +223,15 @@ func (w *AuthenticationWriter) seal(b *buf.Buffer) (*buf.Buffer, error) { eb.Release() return nil, err } + if paddingSize > 0 { + if err := eb.AppendSupplier(func(bb []byte) (int, error) { + common.Must2(rand.Read(bb[:paddingSize])) + return paddingSize, nil + }); err != nil { + eb.Release() + return nil, err + } + } return eb, nil } @@ -212,7 +239,7 @@ func (w *AuthenticationWriter) seal(b *buf.Buffer) (*buf.Buffer, error) { func (w *AuthenticationWriter) writeStream(mb buf.MultiBuffer) error { defer mb.Release() - payloadSize := buf.Size - int32(w.auth.Overhead()) - w.sizeParser.SizeBytes() + payloadSize := buf.Size - int32(w.auth.Overhead()) - w.sizeParser.SizeBytes() - 64 /* padding buffer */ mb2Write := buf.NewMultiBufferCap(int32(len(mb) + 10)) for { diff --git a/common/crypto/auth_test.go b/common/crypto/auth_test.go index f905b150a..0d421c9a3 100644 --- a/common/crypto/auth_test.go +++ b/common/crypto/auth_test.go @@ -40,17 +40,17 @@ func TestAuthenticationReaderWriter(t *testing.T) { AEAD: aead, NonceGenerator: GenerateStaticBytes(iv), AdditionalDataGenerator: GenerateEmptyBytes(), - }, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream) + }, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream, nil) assert(writer.WriteMultiBuffer(buf.NewMultiBufferValue(payload)), IsNil) - assert(cache.Len(), Equals, int32(82658)) + assert(cache.Len(), Equals, int32(82676)) assert(writer.WriteMultiBuffer(buf.MultiBuffer{}), IsNil) reader := NewAuthenticationReader(&AEADAuthenticator{ AEAD: aead, NonceGenerator: GenerateStaticBytes(iv), AdditionalDataGenerator: GenerateEmptyBytes(), - }, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream) + }, PlainChunkSizeParser{}, cache, protocol.TransferTypeStream, nil) var mb buf.MultiBuffer @@ -90,7 +90,7 @@ func TestAuthenticationReaderWriterPacket(t *testing.T) { AEAD: aead, NonceGenerator: GenerateStaticBytes(iv), AdditionalDataGenerator: GenerateEmptyBytes(), - }, PlainChunkSizeParser{}, cache, protocol.TransferTypePacket) + }, PlainChunkSizeParser{}, cache, protocol.TransferTypePacket, nil) var payload buf.MultiBuffer pb1 := buf.New() @@ -110,7 +110,7 @@ func TestAuthenticationReaderWriterPacket(t *testing.T) { AEAD: aead, NonceGenerator: GenerateStaticBytes(iv), AdditionalDataGenerator: GenerateEmptyBytes(), - }, PlainChunkSizeParser{}, cache, protocol.TransferTypePacket) + }, PlainChunkSizeParser{}, cache, protocol.TransferTypePacket, nil) mb, err := reader.ReadMultiBuffer() assert(err, IsNil) diff --git a/common/crypto/chunk.go b/common/crypto/chunk.go index 7960aa971..f7e7ba957 100755 --- a/common/crypto/chunk.go +++ b/common/crypto/chunk.go @@ -20,6 +20,10 @@ type ChunkSizeEncoder interface { Encode(uint16, []byte) []byte } +type PaddingLengthGenerator interface { + NextPaddingLen() uint16 +} + type PlainChunkSizeParser struct{} func (PlainChunkSizeParser) SizeBytes() int32 { diff --git a/common/protocol/headers.go b/common/protocol/headers.go index 5a3a16267..9bd332f45 100644 --- a/common/protocol/headers.go +++ b/common/protocol/headers.go @@ -36,6 +36,8 @@ const ( RequestOptionConnectionReuse bitmask.Byte = 0x02 RequestOptionChunkMasking bitmask.Byte = 0x04 + + RequestOptionGlobalPadding bitmask.Byte = 0x08 ) type RequestHeader struct { diff --git a/proxy/shadowsocks/config.go b/proxy/shadowsocks/config.go index 39ad222ca..9735ee874 100644 --- a/proxy/shadowsocks/config.go +++ b/proxy/shadowsocks/config.go @@ -182,14 +182,14 @@ func (c *AEADCipher) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer auth := c.createAuthenticator(key, iv) return crypto.NewAuthenticationWriter(auth, &crypto.AEADChunkSizeParser{ Auth: auth, - }, writer, protocol.TransferTypeStream), nil + }, writer, protocol.TransferTypeStream, nil), nil } func (c *AEADCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) { auth := c.createAuthenticator(key, iv) return crypto.NewAuthenticationReader(auth, &crypto.AEADChunkSizeParser{ Auth: auth, - }, reader, protocol.TransferTypeStream), nil + }, reader, protocol.TransferTypeStream, nil), nil } func (c *AEADCipher) EncodePacket(key []byte, b *buf.Buffer) error { diff --git a/proxy/vmess/encoding/auth.go b/proxy/vmess/encoding/auth.go index 2f23fa673..3212b800b 100644 --- a/proxy/vmess/encoding/auth.go +++ b/proxy/vmess/encoding/auth.go @@ -107,3 +107,7 @@ func (s *ShakeSizeParser) Encode(size uint16, b []byte) []byte { mask := s.next() return serial.Uint16ToBytes(mask^size, b[:0]) } + +func (s *ShakeSizeParser) NextPaddingLen() uint16 { + return s.next() % 64 +} diff --git a/proxy/vmess/encoding/client.go b/proxy/vmess/encoding/client.go index 407aeed33..5b0fc5fc5 100644 --- a/proxy/vmess/encoding/client.go +++ b/proxy/vmess/encoding/client.go @@ -110,6 +110,11 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(c.requestBodyIV[:]) } + var padding crypto.PaddingLengthGenerator = nil + if request.Option.Has(protocol.RequestOptionGlobalPadding) { + padding = sizeParser.(crypto.PaddingLengthGenerator) + } + switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { @@ -121,7 +126,7 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding) } return buf.NewWriter(writer) @@ -134,7 +139,7 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, cryptionWriter, request.Command.TransferType()) + return crypto.NewAuthenticationWriter(auth, sizeParser, cryptionWriter, request.Command.TransferType(), padding) } return buf.NewWriter(cryptionWriter) @@ -147,7 +152,7 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write NonceGenerator: GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType()) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.requestBodyKey[:])) @@ -156,7 +161,7 @@ func (c *ClientSession) EncodeRequestBody(request *protocol.RequestHeader, write NonceGenerator: GenerateChunkNonce(c.requestBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType()) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) default: panic("Unknown security type.") } @@ -202,6 +207,11 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(c.responseBodyIV[:]) } + var padding crypto.PaddingLengthGenerator = nil + if request.Option.Has(protocol.RequestOptionGlobalPadding) { + padding = sizeParser.(crypto.PaddingLengthGenerator) + } + switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { @@ -215,7 +225,7 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding) } return buf.NewReader(reader) @@ -226,7 +236,7 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, c.responseReader, request.Command.TransferType()) + return crypto.NewAuthenticationReader(auth, sizeParser, c.responseReader, request.Command.TransferType(), padding) } return buf.NewReader(c.responseReader) @@ -239,7 +249,7 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read NonceGenerator: GenerateChunkNonce(c.responseBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType()) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(c.responseBodyKey[:])) @@ -248,7 +258,7 @@ func (c *ClientSession) DecodeResponseBody(request *protocol.RequestHeader, read NonceGenerator: GenerateChunkNonce(c.responseBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType()) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) default: panic("Unknown security type.") } diff --git a/proxy/vmess/encoding/server.go b/proxy/vmess/encoding/server.go index db5c4cade..4c5c76dc9 100644 --- a/proxy/vmess/encoding/server.go +++ b/proxy/vmess/encoding/server.go @@ -238,6 +238,11 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(s.requestBodyIV[:]) } + var padding crypto.PaddingLengthGenerator = nil + if request.Option.Has(protocol.RequestOptionGlobalPadding) { + padding = sizeParser.(crypto.PaddingLengthGenerator) + } + switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { @@ -250,7 +255,7 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, protocol.TransferTypePacket, padding) } return buf.NewReader(reader) @@ -263,7 +268,7 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, cryptionReader, request.Command.TransferType()) + return crypto.NewAuthenticationReader(auth, sizeParser, cryptionReader, request.Command.TransferType(), padding) } return buf.NewReader(cryptionReader) @@ -276,7 +281,7 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade NonceGenerator: GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType()) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.requestBodyKey[:])) @@ -285,7 +290,7 @@ func (s *ServerSession) DecodeRequestBody(request *protocol.RequestHeader, reade NonceGenerator: GenerateChunkNonce(s.requestBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType()) + return crypto.NewAuthenticationReader(auth, sizeParser, reader, request.Command.TransferType(), padding) default: panic("Unknown security type.") } @@ -313,6 +318,11 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ if request.Option.Has(protocol.RequestOptionChunkMasking) { sizeParser = NewShakeSizeParser(s.responseBodyIV[:]) } + var padding crypto.PaddingLengthGenerator = nil + if request.Option.Has(protocol.RequestOptionGlobalPadding) { + padding = sizeParser.(crypto.PaddingLengthGenerator) + } + switch request.Security { case protocol.SecurityType_NONE: if request.Option.Has(protocol.RequestOptionChunkStream) { @@ -325,7 +335,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, protocol.TransferTypePacket, padding) } return buf.NewWriter(writer) @@ -336,7 +346,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ NonceGenerator: crypto.GenerateEmptyBytes(), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, s.responseWriter, request.Command.TransferType()) + return crypto.NewAuthenticationWriter(auth, sizeParser, s.responseWriter, request.Command.TransferType(), padding) } return buf.NewWriter(s.responseWriter) @@ -349,7 +359,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ NonceGenerator: GenerateChunkNonce(s.responseBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType()) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) case protocol.SecurityType_CHACHA20_POLY1305: aead, _ := chacha20poly1305.New(GenerateChacha20Poly1305Key(s.responseBodyKey[:])) @@ -358,7 +368,7 @@ func (s *ServerSession) EncodeResponseBody(request *protocol.RequestHeader, writ NonceGenerator: GenerateChunkNonce(s.responseBodyIV[:], uint32(aead.NonceSize())), AdditionalDataGenerator: crypto.GenerateEmptyBytes(), } - return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType()) + return crypto.NewAuthenticationWriter(auth, sizeParser, writer, request.Command.TransferType(), padding) default: panic("Unknown security type.") }