Add ChaCha20 in Shadowsocks

pull/97/head
v2ray 2016-02-23 18:16:13 +01:00
parent 65819228c1
commit 87b15b2b20
5 changed files with 88 additions and 22 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -69,5 +69,24 @@ func TestShadowsocksTCP(t *testing.T) {
assert.StringLiteral("Processed: " + payload).Equals(string(response[:nBytes])) assert.StringLiteral("Processed: " + payload).Equals(string(response[:nBytes]))
conn.Close() 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() CloseAllServers()
} }