mirror of https://github.com/v2ray/v2ray-core
Add ChaCha20 in Shadowsocks
parent
65819228c1
commit
87b15b2b20
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue