Explorar o código

Add ChaCha20 in Shadowsocks

v2ray %!s(int64=9) %!d(string=hai) anos
pai
achega
87b15b2b20

+ 27 - 9
proxy/shadowsocks/config.go

@@ -1,8 +1,8 @@
 package shadowsocks
 
 import (
+	"crypto/cipher"
 	"crypto/md5"
-	"io"
 
 	"github.com/v2ray/v2ray-core/common/crypto"
 	"github.com/v2ray/v2ray-core/common/protocol"
@@ -11,8 +11,8 @@ import (
 type Cipher interface {
 	KeySize() int
 	IVSize() int
-	NewEncodingStream(key []byte, iv []byte, writer io.Writer) (io.Writer, error)
-	NewDecodingStream(key []byte, iv []byte, reader io.Reader) (io.Reader, error)
+	NewEncodingStream(key []byte, iv []byte) (cipher.Stream, error)
+	NewDecodingStream(key []byte, iv []byte) (cipher.Stream, error)
 }
 
 type AesCfb struct {
@@ -27,22 +27,40 @@ func (this *AesCfb) IVSize() int {
 	return 16
 }
 
-func (this *AesCfb) NewEncodingStream(key []byte, iv []byte, writer io.Writer) (io.Writer, error) {
+func (this *AesCfb) NewEncodingStream(key []byte, iv []byte) (cipher.Stream, error) {
 	stream, err := crypto.NewAesEncryptionStream(key, iv)
 	if err != nil {
 		return nil, err
 	}
-	aesWriter := crypto.NewCryptionWriter(stream, writer)
-	return aesWriter, nil
+	return stream, nil
 }
 
-func (this *AesCfb) NewDecodingStream(key []byte, iv []byte, reader io.Reader) (io.Reader, error) {
+func (this *AesCfb) NewDecodingStream(key []byte, iv []byte) (cipher.Stream, error) {
 	stream, err := crypto.NewAesDecryptionStream(key, iv)
 	if err != nil {
 		return nil, err
 	}
-	aesReader := crypto.NewCryptionReader(stream, reader)
-	return aesReader, nil
+	return stream, nil
+}
+
+type ChaCha20 struct {
+	IVBytes int
+}
+
+func (this *ChaCha20) KeySize() int {
+	return 32
+}
+
+func (this *ChaCha20) IVSize() int {
+	return this.IVBytes
+}
+
+func (this *ChaCha20) NewEncodingStream(key []byte, iv []byte) (cipher.Stream, error) {
+	return crypto.NewChaCha20Stream(key, iv), nil
+}
+
+func (this *ChaCha20) NewDecodingStream(key []byte, iv []byte) (cipher.Stream, error) {
+	return crypto.NewChaCha20Stream(key, iv), nil
 }
 
 type Config struct {

+ 8 - 0
proxy/shadowsocks/config_json.go

@@ -35,6 +35,14 @@ func (this *Config) UnmarshalJSON(data []byte) error {
 		this.Cipher = &AesCfb{
 			KeyBytes: 16,
 		}
+	case "chacha20":
+		this.Cipher = &ChaCha20{
+			IVBytes: 8,
+		}
+	case "chacha20-ietf":
+		this.Cipher = &ChaCha20{
+			IVBytes: 12,
+		}
 	default:
 		log.Error("Shadowsocks: Unknown cipher method: ", jsonConfig.Cipher)
 		return internal.ErrorBadConfiguration

+ 25 - 13
proxy/shadowsocks/shadowsocks.go

@@ -9,6 +9,7 @@ import (
 	"github.com/v2ray/v2ray-core/app"
 	"github.com/v2ray/v2ray-core/app/dispatcher"
 	"github.com/v2ray/v2ray-core/common/alloc"
+	"github.com/v2ray/v2ray-core/common/crypto"
 	v2io "github.com/v2ray/v2ray-core/common/io"
 	"github.com/v2ray/v2ray-core/common/log"
 	v2net "github.com/v2ray/v2ray-core/common/net"
@@ -90,16 +91,19 @@ func (this *Shadowsocks) Listen(port v2net.Port) error {
 func (this *Shadowsocks) handlerUDPPayload(payload *alloc.Buffer, source v2net.Destination) {
 	defer payload.Release()
 
-	iv := payload.Value[:this.config.Cipher.IVSize()]
+	ivLen := this.config.Cipher.IVSize()
+	iv := payload.Value[:ivLen]
 	key := this.config.Key
-	payload.SliceFrom(this.config.Cipher.IVSize())
+	payload.SliceFrom(ivLen)
 
-	reader, err := this.config.Cipher.NewDecodingStream(key, iv, payload)
+	stream, err := this.config.Cipher.NewDecodingStream(key, iv)
 	if err != nil {
 		log.Error("Shadowsocks: Failed to create decoding stream: ", err)
 		return
 	}
 
+	reader := crypto.NewCryptionReader(stream, payload)
+
 	request, err := ReadRequest(reader, NewAuthenticator(HeaderKeyGenerator(key, iv)), true)
 	if err != nil {
 		log.Access(source, serial.StringLiteral(""), log.AccessRejected, serial.StringLiteral(err.Error()))
@@ -115,18 +119,20 @@ func (this *Shadowsocks) handlerUDPPayload(payload *alloc.Buffer, source v2net.D
 	this.udpServer.Dispatch(source, packet, func(packet v2net.Packet) {
 		defer packet.Chunk().Release()
 
-		response := alloc.NewBuffer().Slice(0, this.config.Cipher.IVSize())
+		response := alloc.NewBuffer().Slice(0, ivLen)
 		defer response.Release()
 
 		rand.Read(response.Value)
 		respIv := response.Value
 
-		writer, err := this.config.Cipher.NewEncodingStream(key, respIv, response)
+		stream, err := this.config.Cipher.NewEncodingStream(key, respIv)
 		if err != nil {
 			log.Error("Shadowsocks: Failed to create encoding stream: ", err)
 			return
 		}
 
+		writer := crypto.NewCryptionWriter(stream, response)
+
 		switch {
 		case request.Address.IsIPv4():
 			writer.Write([]byte{AddrTypeIPv4})
@@ -144,7 +150,7 @@ func (this *Shadowsocks) handlerUDPPayload(payload *alloc.Buffer, source v2net.D
 
 		if request.OTA {
 			respAuth := NewAuthenticator(HeaderKeyGenerator(key, respIv))
-			respAuth.Authenticate(response.Value, response.Value[this.config.Cipher.IVSize():])
+			respAuth.Authenticate(response.Value, response.Value[ivLen:])
 		}
 
 		this.udpHub.WriteTo(response.Value, source)
@@ -159,22 +165,25 @@ func (this *Shadowsocks) handleConnection(conn *hub.TCPConn) {
 
 	timedReader := v2net.NewTimeOutReader(16, conn)
 
-	_, err := io.ReadFull(timedReader, buffer.Value[:this.config.Cipher.IVSize()])
+	ivLen := this.config.Cipher.IVSize()
+	_, err := io.ReadFull(timedReader, buffer.Value[:ivLen])
 	if err != nil {
 		log.Access(conn.RemoteAddr(), serial.StringLiteral(""), log.AccessRejected, serial.StringLiteral(err.Error()))
 		log.Error("Shadowsocks: Failed to read IV: ", err)
 		return
 	}
 
-	iv := buffer.Value[:this.config.Cipher.IVSize()]
+	iv := buffer.Value[:ivLen]
 	key := this.config.Key
 
-	reader, err := this.config.Cipher.NewDecodingStream(key, iv, timedReader)
+	stream, err := this.config.Cipher.NewDecodingStream(key, iv)
 	if err != nil {
 		log.Error("Shadowsocks: Failed to create decoding stream: ", err)
 		return
 	}
 
+	reader := crypto.NewCryptionReader(stream, timedReader)
+
 	request, err := ReadRequest(reader, NewAuthenticator(HeaderKeyGenerator(iv, key)), false)
 	if err != nil {
 		log.Access(conn.RemoteAddr(), serial.StringLiteral(""), log.AccessRejected, serial.StringLiteral(err.Error()))
@@ -196,17 +205,20 @@ func (this *Shadowsocks) handleConnection(conn *hub.TCPConn) {
 	writeFinish.Lock()
 	go func() {
 		if payload, ok := <-ray.InboundOutput(); ok {
-			payload.SliceBack(16)
-			rand.Read(payload.Value[:16])
+			payload.SliceBack(ivLen)
+			rand.Read(payload.Value[:ivLen])
 
-			writer, err := this.config.Cipher.NewEncodingStream(key, payload.Value[:16], conn)
+			stream, err := this.config.Cipher.NewEncodingStream(key, payload.Value[:ivLen])
 			if err != nil {
 				log.Error("Shadowsocks: Failed to create encoding stream: ", err)
 				return
 			}
+			stream.XORKeyStream(payload.Value[ivLen:], payload.Value[ivLen:])
 
-			writer.Write(payload.Value)
+			conn.Write(payload.Value)
 			payload.Release()
+
+			writer := crypto.NewCryptionWriter(stream, conn)
 			v2io.ChanToRawWriter(writer, ray.InboundOutput())
 		}
 		writeFinish.Unlock()

+ 9 - 0
testing/scenarios/data/test_6_server.json

@@ -16,6 +16,15 @@
         "password": "v2ray-another",
         "udp": true
       }
+    },
+    {
+      "protocol": "shadowsocks",
+      "port": 50056,
+      "settings": {
+        "method": "chacha20",
+        "password": "new-password",
+        "udp": true
+      }
     }
   ],
   "outbound": {

+ 19 - 0
testing/scenarios/shadowsocks_test.go

@@ -69,5 +69,24 @@ func TestShadowsocksTCP(t *testing.T) {
 	assert.StringLiteral("Processed: " + payload).Equals(string(response[:nBytes]))
 	conn.Close()
 
+	cipher, err = ssclient.NewCipher("chacha20", "new-password")
+	assert.Error(err).IsNil()
+
+	conn, err = ssclient.DialWithRawAddr(rawAddr, "127.0.0.1:50056", cipher)
+	assert.Error(err).IsNil()
+
+	payload = "shadowsocks request 3."
+	nBytes, err = conn.Write([]byte(payload))
+	assert.Error(err).IsNil()
+	assert.Int(nBytes).Equals(len(payload))
+
+	conn.Conn.(*net.TCPConn).CloseWrite()
+
+	response = make([]byte, 1024)
+	nBytes, err = conn.Read(response)
+	assert.Error(err).IsNil()
+	assert.StringLiteral("Processed: " + payload).Equals(string(response[:nBytes]))
+	conn.Close()
+
 	CloseAllServers()
 }