From 713ebfb203b25f10de8db292a925af3de5cf1f5c Mon Sep 17 00:00:00 2001 From: Darien Raymond Date: Sun, 26 Nov 2017 00:51:54 +0100 Subject: [PATCH] implementation for Shadowsocks AEAD --- common/crypto/auth.go | 20 +++++++ common/crypto/chunk.go | 23 ++++++++ proxy/shadowsocks/client.go | 11 +--- proxy/shadowsocks/config.go | 107 ++++++++++++++++++++++++++++++---- proxy/shadowsocks/protocol.go | 61 +++++++++---------- 5 files changed, 173 insertions(+), 49 deletions(-) diff --git a/common/crypto/auth.go b/common/crypto/auth.go index f6b966678..4e70fadde 100644 --- a/common/crypto/auth.go +++ b/common/crypto/auth.go @@ -29,6 +29,26 @@ func (v StaticBytesGenerator) Next() []byte { return v.Content } +type IncreasingAEADNonceGenerator struct { + nonce []byte +} + +func NewIncreasingAEADNonceGenerator() *IncreasingAEADNonceGenerator { + return &IncreasingAEADNonceGenerator{ + nonce: []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + } +} + +func (g *IncreasingAEADNonceGenerator) Next() []byte { + for i := range g.nonce { + g.nonce[i]++ + if g.nonce[i] != 0 { + break + } + } + return g.nonce +} + type Authenticator interface { NonceSize() int Overhead() int diff --git a/common/crypto/chunk.go b/common/crypto/chunk.go index c66a107bb..84eb438c1 100644 --- a/common/crypto/chunk.go +++ b/common/crypto/chunk.go @@ -34,6 +34,29 @@ func (PlainChunkSizeParser) Decode(b []byte) (uint16, error) { return serial.BytesToUint16(b), nil } +type AEADChunkSizeParser struct { + Auth *AEADAuthenticator +} + +func (p *AEADChunkSizeParser) SizeBytes() int { + return 2 + p.Auth.Overhead() +} + +func (p *AEADChunkSizeParser) Encode(size uint16, b []byte) []byte { + b = serial.Uint16ToBytes(size, b) + b, err := p.Auth.Seal(b[:0], b) + common.Must(err) + return b +} + +func (p *AEADChunkSizeParser) Decode(b []byte) (uint16, error) { + b, err := p.Auth.Open(b[:0], b) + if err != nil { + return 0, err + } + return serial.BytesToUint16(b), nil +} + type ChunkStreamReader struct { sizeDecoder ChunkSizeDecoder reader buf.Reader diff --git a/proxy/shadowsocks/client.go b/proxy/shadowsocks/client.go index a61b6afb7..e93ee370a 100644 --- a/proxy/shadowsocks/client.go +++ b/proxy/shadowsocks/client.go @@ -105,10 +105,7 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale } requestDone := signal.ExecuteAsync(func() error { - if err := buf.Copy(outboundRay.OutboundInput(), bodyWriter, buf.UpdateActivity(timer)); err != nil { - return err - } - return nil + return buf.Copy(outboundRay.OutboundInput(), bodyWriter, buf.UpdateActivity(timer)) }) responseDone := signal.ExecuteAsync(func() error { @@ -119,11 +116,7 @@ func (v *Client) Process(ctx context.Context, outboundRay ray.OutboundRay, diale return err } - if err := buf.Copy(responseReader, outboundRay.OutboundOutput(), buf.UpdateActivity(timer)); err != nil { - return err - } - - return nil + return buf.Copy(responseReader, outboundRay.OutboundOutput(), buf.UpdateActivity(timer)) }) if err := signal.ErrorOrFinish2(ctx, requestDone, responseDone); err != nil { diff --git a/proxy/shadowsocks/config.go b/proxy/shadowsocks/config.go index 15c353330..43e4b3c6a 100644 --- a/proxy/shadowsocks/config.go +++ b/proxy/shadowsocks/config.go @@ -2,9 +2,17 @@ package shadowsocks import ( "bytes" + "crypto/aes" "crypto/cipher" "crypto/md5" + "crypto/sha1" + "io" + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/hkdf" + + "v2ray.com/core/common" + "v2ray.com/core/common/buf" "v2ray.com/core/common/crypto" "v2ray.com/core/common/protocol" ) @@ -22,6 +30,20 @@ func (v *ShadowsocksAccount) Equals(another protocol.Account) bool { return false } +func createAesGcm(key []byte) cipher.AEAD { + block, err := aes.NewCipher(key) + common.Must(err) + gcm, err := cipher.NewGCM(block) + common.Must(err) + return gcm +} + +func createChacha20Poly1305(key []byte) cipher.AEAD { + chacha20, err := chacha20poly1305.New(key) + common.Must(err) + return chacha20 +} + func (v *Account) GetCipher() (Cipher, error) { switch v.CipherType { case CipherType_AES_128_CFB: @@ -32,6 +54,24 @@ func (v *Account) GetCipher() (Cipher, error) { return &ChaCha20{IVBytes: 8}, nil case CipherType_CHACHA20_IETF: return &ChaCha20{IVBytes: 12}, nil + case CipherType_AES_128_GCM: + return &AEADCipher{ + KeyBytes: 16, + IVBytes: 16, + AEADAuthCreator: createAesGcm, + }, nil + case CipherType_AES_256_GCM: + return &AEADCipher{ + KeyBytes: 32, + IVBytes: 32, + AEADAuthCreator: createAesGcm, + }, nil + case CipherType_CHACHA20_POLY1305: + return &AEADCipher{ + KeyBytes: 32, + IVBytes: 32, + AEADAuthCreator: createChacha20Poly1305, + }, nil default: return nil, newError("Unsupported cipher.") } @@ -60,8 +100,8 @@ func (v *Account) GetCipherKey() []byte { type Cipher interface { KeySize() int IVSize() int - NewEncodingStream(key []byte, iv []byte) (cipher.Stream, error) - NewDecodingStream(key []byte, iv []byte) (cipher.Stream, error) + NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) + NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) } type AesCfb struct { @@ -76,14 +116,54 @@ func (v *AesCfb) IVSize() int { return 16 } -func (v *AesCfb) NewEncodingStream(key []byte, iv []byte) (cipher.Stream, error) { +func (v *AesCfb) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) { stream := crypto.NewAesEncryptionStream(key, iv) - return stream, nil + return buf.NewWriter(crypto.NewCryptionWriter(stream, writer)), nil } -func (v *AesCfb) NewDecodingStream(key []byte, iv []byte) (cipher.Stream, error) { +func (v *AesCfb) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) { stream := crypto.NewAesDecryptionStream(key, iv) - return stream, nil + return buf.NewReader(crypto.NewCryptionReader(stream, reader)), nil +} + +type AEADCipher struct { + KeyBytes int + IVBytes int + AEADAuthCreator func(key []byte) cipher.AEAD +} + +func (c *AEADCipher) KeySize() int { + return c.KeyBytes +} + +func (c *AEADCipher) IVSize() int { + return c.IVBytes +} + +func (c *AEADCipher) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) { + nonce := crypto.NewIncreasingAEADNonceGenerator() + subkey := make([]byte, c.KeyBytes) + hkdfSHA1(key, iv, subkey) + auth := &crypto.AEADAuthenticator{ + AEAD: c.AEADAuthCreator(subkey), + NonceGenerator: nonce, + } + return crypto.NewAuthenticationWriter(auth, &crypto.AEADChunkSizeParser{ + Auth: auth, + }, writer, protocol.TransferTypeStream), nil +} + +func (c *AEADCipher) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) { + nonce := crypto.NewIncreasingAEADNonceGenerator() + subkey := make([]byte, c.KeyBytes) + hkdfSHA1(key, iv, subkey) + auth := &crypto.AEADAuthenticator{ + AEAD: c.AEADAuthCreator(subkey), + NonceGenerator: nonce, + } + return crypto.NewAuthenticationReader(auth, &crypto.AEADChunkSizeParser{ + Auth: auth, + }, reader, protocol.TransferTypeStream), nil } type ChaCha20 struct { @@ -98,12 +178,14 @@ func (v *ChaCha20) IVSize() int { return v.IVBytes } -func (v *ChaCha20) NewEncodingStream(key []byte, iv []byte) (cipher.Stream, error) { - return crypto.NewChaCha20Stream(key, iv), nil +func (v *ChaCha20) NewEncryptionWriter(key []byte, iv []byte, writer io.Writer) (buf.Writer, error) { + stream := crypto.NewChaCha20Stream(key, iv) + return buf.NewWriter(crypto.NewCryptionWriter(stream, writer)), nil } -func (v *ChaCha20) NewDecodingStream(key []byte, iv []byte) (cipher.Stream, error) { - return crypto.NewChaCha20Stream(key, iv), nil +func (v *ChaCha20) NewDecryptionReader(key []byte, iv []byte, reader io.Reader) (buf.Reader, error) { + stream := crypto.NewChaCha20Stream(key, iv) + return buf.NewReader(crypto.NewCryptionReader(stream, reader)), nil } func PasswordToCipherKey(password string, keySize int) []byte { @@ -123,3 +205,8 @@ func PasswordToCipherKey(password string, keySize int) []byte { } return key } + +func hkdfSHA1(secret, salt, outkey []byte) { + r := hkdf.New(sha1.New, secret, salt, []byte("ss-subkey")) + common.Must2(io.ReadFull(r, outkey)) +} diff --git a/proxy/shadowsocks/protocol.go b/proxy/shadowsocks/protocol.go index c44548957..5901d3d05 100644 --- a/proxy/shadowsocks/protocol.go +++ b/proxy/shadowsocks/protocol.go @@ -8,7 +8,6 @@ import ( "v2ray.com/core/common" "v2ray.com/core/common/bitmask" "v2ray.com/core/common/buf" - "v2ray.com/core/common/crypto" "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" "v2ray.com/core/common/serial" @@ -40,11 +39,11 @@ func ReadTCPSession(user *protocol.User, reader io.Reader) (*protocol.RequestHea iv := append([]byte(nil), buffer.BytesTo(ivLen)...) - stream, err := account.Cipher.NewDecodingStream(account.Key, iv) + r, err := account.Cipher.NewDecryptionReader(account.Key, iv, reader) if err != nil { return nil, nil, newError("failed to initialize decoding stream").Base(err).AtError() } - reader = crypto.NewCryptionReader(stream, reader) + reader = r.(io.Reader) authenticator := NewAuthenticator(HeaderKeyGenerator(account.Key, iv)) request := &protocol.RequestHeader{ @@ -144,12 +143,12 @@ func WriteTCPRequest(request *protocol.RequestHeader, writer io.Writer) (buf.Wri return nil, newError("failed to write IV") } - stream, err := account.Cipher.NewEncodingStream(account.Key, iv) + w, err := account.Cipher.NewEncryptionWriter(account.Key, iv, writer) if err != nil { return nil, newError("failed to create encoding stream").Base(err).AtError() } - writer = crypto.NewCryptionWriter(stream, writer) + writer = w.(io.Writer) header := buf.NewLocal(512) @@ -208,11 +207,7 @@ func ReadTCPResponse(user *protocol.User, reader io.Reader) (buf.Reader, error) return nil, newError("failed to read IV").Base(err) } - stream, err := account.Cipher.NewDecodingStream(account.Key, iv) - if err != nil { - return nil, newError("failed to initialize decoding stream").Base(err).AtError() - } - return buf.NewReader(crypto.NewCryptionReader(stream, reader)), nil + return account.Cipher.NewDecryptionReader(account.Key, iv, reader) } func WriteTCPResponse(request *protocol.RequestHeader, writer io.Writer) (buf.Writer, error) { @@ -230,12 +225,7 @@ func WriteTCPResponse(request *protocol.RequestHeader, writer io.Writer) (buf.Wr return nil, newError("failed to write IV.").Base(err) } - stream, err := account.Cipher.NewEncodingStream(account.Key, iv) - if err != nil { - return nil, newError("failed to create encoding stream.").Base(err).AtError() - } - - return buf.NewWriter(crypto.NewCryptionWriter(stream, writer)), nil + return account.Cipher.NewEncryptionWriter(account.Key, iv, writer) } func EncodeUDPPacket(request *protocol.RequestHeader, payload []byte) (*buf.Buffer, error) { @@ -251,36 +241,41 @@ func EncodeUDPPacket(request *protocol.RequestHeader, payload []byte) (*buf.Buff buffer.AppendSupplier(buf.ReadFullFrom(rand.Reader, ivLen)) iv := buffer.Bytes() + payloadBuffer := buf.NewLocal(512) + defer payloadBuffer.Release() + switch request.Address.Family() { case net.AddressFamilyIPv4: - buffer.AppendBytes(AddrTypeIPv4) - buffer.Append([]byte(request.Address.IP())) + payloadBuffer.AppendBytes(AddrTypeIPv4) + payloadBuffer.Append([]byte(request.Address.IP())) case net.AddressFamilyIPv6: - buffer.AppendBytes(AddrTypeIPv6) - buffer.Append([]byte(request.Address.IP())) + payloadBuffer.AppendBytes(AddrTypeIPv6) + payloadBuffer.Append([]byte(request.Address.IP())) case net.AddressFamilyDomain: - buffer.AppendBytes(AddrTypeDomain, byte(len(request.Address.Domain()))) - buffer.Append([]byte(request.Address.Domain())) + payloadBuffer.AppendBytes(AddrTypeDomain, byte(len(request.Address.Domain()))) + payloadBuffer.Append([]byte(request.Address.Domain())) default: return nil, newError("unsupported address type: ", request.Address.Family()).AtError() } - buffer.AppendSupplier(serial.WriteUint16(uint16(request.Port))) - buffer.Append(payload) + payloadBuffer.AppendSupplier(serial.WriteUint16(uint16(request.Port))) + payloadBuffer.Append(payload) if request.Option.Has(RequestOptionOneTimeAuth) { authenticator := NewAuthenticator(HeaderKeyGenerator(account.Key, iv)) - buffer.SetByte(ivLen, buffer.Byte(ivLen)|0x10) + payloadBuffer.SetByte(0, payloadBuffer.Byte(0)|0x10) - buffer.AppendSupplier(authenticator.Authenticate(buffer.BytesFrom(ivLen))) + payloadBuffer.AppendSupplier(authenticator.Authenticate(payloadBuffer.Bytes())) } - stream, err := account.Cipher.NewEncodingStream(account.Key, iv) + w, err := account.Cipher.NewEncryptionWriter(account.Key, iv, buffer) if err != nil { return nil, newError("failed to create encoding stream").Base(err).AtError() } + if err := w.WriteMultiBuffer(buf.NewMultiBufferValue(payloadBuffer)); err != nil { + return nil, newError("failed to encrypt UDP payload").Base(err).AtWarning() + } - stream.XORKeyStream(buffer.BytesFrom(ivLen), buffer.BytesFrom(ivLen)) return buffer, nil } @@ -295,11 +290,17 @@ func DecodeUDPPacket(user *protocol.User, payload *buf.Buffer) (*protocol.Reques iv := payload.BytesTo(ivLen) payload.SliceFrom(ivLen) - stream, err := account.Cipher.NewDecodingStream(account.Key, iv) + r, err := account.Cipher.NewDecryptionReader(account.Key, iv, payload) if err != nil { return nil, nil, newError("failed to initialize decoding stream").Base(err).AtError() } - stream.XORKeyStream(payload.Bytes(), payload.Bytes()) + mb, err := r.ReadMultiBuffer() + if err != nil { + return nil, nil, newError("failed to decrypt UDP payload").Base(err).AtWarning() + } + payload.Release() + payload = mb.SplitFirst() + mb.Release() authenticator := NewAuthenticator(HeaderKeyGenerator(account.Key, iv)) request := &protocol.RequestHeader{