Skip to content

Commit

Permalink
Add ChaCha20 in Shadowsocks
Browse files Browse the repository at this point in the history
  • Loading branch information
DarienRaymond committed Feb 23, 2016
1 parent 6581922 commit 87b15b2
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 22 deletions.
36 changes: 27 additions & 9 deletions proxy/shadowsocks/config.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions proxy/shadowsocks/config_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 25 additions & 13 deletions proxy/shadowsocks/shadowsocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()))
Expand All @@ -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})
Expand All @@ -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)
Expand All @@ -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()))
Expand All @@ -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()
Expand Down
9 changes: 9 additions & 0 deletions testing/scenarios/data/test_6_server.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
"password": "v2ray-another",
"udp": true
}
},
{
"protocol": "shadowsocks",
"port": 50056,
"settings": {
"method": "chacha20",
"password": "new-password",
"udp": true
}
}
],
"outbound": {
Expand Down
19 changes: 19 additions & 0 deletions testing/scenarios/shadowsocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

0 comments on commit 87b15b2

Please sign in to comment.