diff --git a/.gitignore b/.gitignore index 5966b0f5..b4278f4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.deb script/http +bin diff --git a/.travis.yml b/.travis.yml index 2cc770b2..dea91125 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go go: - - 1.4.2 + - 1.4.3 install: - go get golang.org/x/crypto/blowfish - go get golang.org/x/crypto/cast5 @@ -10,3 +10,4 @@ install: - go install ./cmd/shadowsocks-server script: - PATH=$PATH:$HOME/gopath/bin bash -x ./script/test.sh +sudo: false \ No newline at end of file diff --git a/README.md b/README.md index 7cfc02e3..21128964 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # shadowsocks-go -Current version: 1.1.4 [![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-go.png?branch=master)](https://travis-ci.org/shadowsocks/shadowsocks-go) +Current version: 1.1.5 [![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-go.png?branch=master)](https://travis-ci.org/shadowsocks/shadowsocks-go) shadowsocks-go is a lightweight tunnel proxy which can help you get through firewalls. It is a port of [shadowsocks](https://github.com/clowwindy/shadowsocks). @@ -12,7 +12,7 @@ The protocol is compatible with the origin shadowsocks (if both have been upgrad # Install -Compiled client binaries can be download [here](http://dl.chenyufei.info/shadowsocks/). (All compiled with cgo disabled, except the mac version.) +Download precompiled binarys from the [release page](https://github.com/shadowsocks/shadowsocks-go/releases). (All compiled with cgo disabled, except the mac version.) You can also install from source (assume you have go installed): @@ -55,6 +55,13 @@ AES is recommended for shadowsocks-go. [Intel AES Instruction Set](http://en.wik **rc4 and table encryption methods are deprecated because they are not secure.** +### One Time Auth + +Append `-auth` to the encryption method to enable [One Time Auth (OTA)](https://shadowsocks.org/en/spec/one-time-auth.html). + +- For server: this will **force client use OTA**, non-OTA connection will be dropped. Otherwise, both OTA and non-OTA clients can connect +- For client: the `-A` command line option can also enable OTA + ## Command line options Command line options can override settings from configuration files. Use `-h` option to see all available options. diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 03bd8efa..1de4262f 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -5,7 +5,6 @@ import ( "errors" "flag" "fmt" - ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" "io" "log" "math/rand" @@ -13,7 +12,10 @@ import ( "os" "path" "strconv" + "strings" "time" + + ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" ) var debug ss.DebugLog @@ -170,8 +172,12 @@ func parseServerConfig(config *ss.Config) { } if len(config.ServerPassword) == 0 { + method := config.Method + if config.Auth { + method += "-auth" + } // only one encryption table - cipher, err := ss.NewCipher(config.Method, config.Password) + cipher, err := ss.NewCipher(method, config.Password) if err != nil { log.Fatal("Failed generating ciphers:", err) } @@ -208,14 +214,17 @@ func parseServerConfig(config *ss.Config) { if !hasPort(server) { log.Fatalf("no port for server %s\n", server) } - cipher, ok := cipherCache[passwd] + // Using "|" as delimiter is safe here, since no encryption + // method contains it in the name. + cacheKey := encmethod + "|" + passwd + cipher, ok := cipherCache[cacheKey] if !ok { var err error cipher, err = ss.NewCipher(encmethod, passwd) if err != nil { log.Fatal("Failed generating ciphers:", err) } - cipherCache[passwd] = cipher + cipherCache[cacheKey] = cipher } servers.srvCipher[i] = &ServerCipher{server, cipher} i++ @@ -360,6 +369,7 @@ func main() { flag.IntVar(&cmdConfig.LocalPort, "l", 0, "local socks5 proxy port") flag.StringVar(&cmdConfig.Method, "m", "", "encryption method, default: aes-256-cfb") flag.BoolVar((*bool)(&debug), "d", false, "print debug message") + flag.BoolVar(&cmdConfig.Auth, "A", false, "one time auth") flag.Parse() @@ -371,6 +381,11 @@ func main() { cmdConfig.Server = cmdServer ss.SetDebug(debug) + if strings.HasSuffix(cmdConfig.Method, "-auth") { + cmdConfig.Method = cmdConfig.Method[:len(cmdConfig.Method)-4] + cmdConfig.Auth = true + } + exists, err := ss.IsFileExists(configFile) // If no config file in current directory, try search it in the binary directory // Note there's no portable way to detect the binary directory. diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index 982fcaf3..d2969e90 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -1,11 +1,11 @@ package main import ( + "bytes" "encoding/binary" "errors" "flag" "fmt" - ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" "io" "log" "net" @@ -13,65 +13,68 @@ import ( "os/signal" "runtime" "strconv" + "strings" "sync" "syscall" + + ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" ) -var debug ss.DebugLog +const ( + idType = 0 // address type index + idIP0 = 1 // ip addres start index + idDmLen = 1 // domain address length index + idDm0 = 2 // domain address start index -func getRequest(conn *ss.Conn) (host string, extra []byte, err error) { - const ( - idType = 0 // address type index - idIP0 = 1 // ip addres start index - idDmLen = 1 // domain address length index - idDm0 = 2 // domain address start index + typeIPv4 = 1 // type is ipv4 address + typeDm = 3 // type is domain address + typeIPv6 = 4 // type is ipv6 address - typeIPv4 = 1 // type is ipv4 address - typeDm = 3 // type is domain address - typeIPv6 = 4 // type is ipv6 address + lenIPv4 = net.IPv4len + 2 // ipv4 + 2port + lenIPv6 = net.IPv6len + 2 // ipv6 + 2port + lenDmBase = 2 // 1addrLen + 2port, plus addrLen + lenHmacSha1 = 10 +) - lenIPv4 = 1 + net.IPv4len + 2 // 1addrType + ipv4 + 2port - lenIPv6 = 1 + net.IPv6len + 2 // 1addrType + ipv6 + 2port - lenDmBase = 1 + 1 + 2 // 1addrType + 1addrLen + 2port, plus addrLen - ) +var debug ss.DebugLog + +func getRequest(conn *ss.Conn, auth bool) (host string, ota bool, err error) { + ss.SetReadTimeout(conn) // buf size should at least have the same size with the largest possible // request size (when addrType is 3, domain name has at most 256 bytes) - // 1(addrType) + 1(lenByte) + 256(max length address) + 2(port) - buf := make([]byte, 260) - var n int + // 1(addrType) + 1(lenByte) + 256(max length address) + 2(port) + 10(hmac-sha1) + buf := make([]byte, 270) // read till we get possible domain length field - ss.SetReadTimeout(conn) - if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil { + if _, err = io.ReadFull(conn, buf[:idType+1]); err != nil { return } - reqLen := -1 - switch buf[idType] { + var reqStart, reqEnd int + addrType := buf[idType] + switch addrType & ss.AddrMask { case typeIPv4: - reqLen = lenIPv4 + reqStart, reqEnd = idIP0, idIP0+lenIPv4 case typeIPv6: - reqLen = lenIPv6 + reqStart, reqEnd = idIP0, idIP0+lenIPv6 case typeDm: - reqLen = int(buf[idDmLen]) + lenDmBase + if _, err = io.ReadFull(conn, buf[idType+1:idDmLen+1]); err != nil { + return + } + reqStart, reqEnd = idDm0, int(idDm0+buf[idDmLen]+lenDmBase) default: - err = fmt.Errorf("addr type %d not supported", buf[idType]) + err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask) return } - if n < reqLen { // rare case - if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil { - return - } - } else if n > reqLen { - // it's possible to read more than just the request head - extra = buf[reqLen:n] + if _, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil { + return } // Return string for typeIP is not most efficient, but browsers (Chrome, // Safari, Firefox) all seems using typeDm exclusively. So this is not a // big problem. - switch buf[idType] { + switch addrType & ss.AddrMask { case typeIPv4: host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String() case typeIPv6: @@ -80,8 +83,22 @@ func getRequest(conn *ss.Conn) (host string, extra []byte, err error) { host = string(buf[idDm0 : idDm0+buf[idDmLen]]) } // parse port - port := binary.BigEndian.Uint16(buf[reqLen-2 : reqLen]) + port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd]) host = net.JoinHostPort(host, strconv.Itoa(int(port))) + // if specified one time auth enabled, we should verify this + if auth || addrType&ss.OneTimeAuthMask > 0 { + ota = true + if _, err = io.ReadFull(conn, buf[reqEnd:reqEnd+lenHmacSha1]); err != nil { + return + } + iv := conn.GetIv() + key := conn.GetKey() + actualHmacSha1Buf := ss.HmacSha1(append(iv, key...), buf[:reqEnd]) + if !bytes.Equal(buf[reqEnd:reqEnd+lenHmacSha1], actualHmacSha1Buf) { + err = fmt.Errorf("verify one time auth failed, iv=%v key=%v data=%v", iv, key, buf[:reqEnd]) + return + } + } return } @@ -90,7 +107,7 @@ const logCntDelta = 100 var connCnt int var nextLogConnCnt int = logCntDelta -func handleConnection(conn *ss.Conn) { +func handleConnection(conn *ss.Conn, auth bool) { var host string connCnt++ // this maybe not accurate, but should be enough @@ -118,7 +135,7 @@ func handleConnection(conn *ss.Conn) { } }() - host, extra, err := getRequest(conn) + host, ota, err := getRequest(conn, auth) if err != nil { log.Println("error getting request", conn.RemoteAddr(), conn.LocalAddr(), err) return @@ -140,18 +157,14 @@ func handleConnection(conn *ss.Conn) { remote.Close() } }() - // write extra bytes read from - if extra != nil { - // debug.Println("getRequest read extra data, writing to remote, len", len(extra)) - if _, err = remote.Write(extra); err != nil { - debug.Println("write request extra error:", err) - return - } - } if debug { - debug.Printf("piping %s<->%s", conn.RemoteAddr(), host) + debug.Printf("piping %s<->%s ota=%v connOta=%v", conn.RemoteAddr(), host, ota, conn.IsOta()) + } + if ota { + go ss.PipeThenCloseOta(conn, remote) + } else { + go ss.PipeThenClose(conn, remote) } - go ss.PipeThenClose(conn, remote) ss.PipeThenClose(remote, conn) closed = true return @@ -195,7 +208,7 @@ func (pm *PasswdManager) del(port string) { // port. A different approach would be directly change the password used by // that port, but that requires **sharing** password between the port listener // and password manager. -func (pm *PasswdManager) updatePortPasswd(port, password string) { +func (pm *PasswdManager) updatePortPasswd(port, password string, auth bool) { pl, ok := pm.get(port) if !ok { log.Printf("new port %s added\n", port) @@ -208,7 +221,7 @@ func (pm *PasswdManager) updatePortPasswd(port, password string) { } // run will add the new port listener to passwdManager. // So there maybe concurrent access to passwdManager and we need lock to protect it. - go run(port, password) + go run(port, password, auth) } var passwdManager = PasswdManager{portListener: map[string]*PortListener{}} @@ -227,7 +240,7 @@ func updatePasswd() { return } for port, passwd := range config.PortPassword { - passwdManager.updatePortPasswd(port, passwd) + passwdManager.updatePortPasswd(port, passwd, config.Auth) if oldconfig.PortPassword != nil { delete(oldconfig.PortPassword, port) } @@ -254,7 +267,7 @@ func waitSignal() { } } -func run(port, password string) { +func run(port, password string, auth bool) { ln, err := net.Listen("tcp", ":"+port) if err != nil { log.Printf("error listening port %v: %v\n", port, err) @@ -280,7 +293,7 @@ func run(port, password string) { continue } } - go handleConnection(ss.NewConn(conn, cipher.Copy())) + go handleConnection(ss.NewConn(conn, cipher.Copy()), auth) } } @@ -332,6 +345,11 @@ func main() { ss.SetDebug(debug) + if strings.HasSuffix(cmdConfig.Method, "-auth") { + cmdConfig.Method = cmdConfig.Method[:len(cmdConfig.Method)-4] + cmdConfig.Auth = true + } + var err error config, err = ss.ParseConfig(configFile) if err != nil { @@ -357,7 +375,7 @@ func main() { runtime.GOMAXPROCS(core) } for port, password := range config.PortPassword { - go run(port, password) + go run(port, password, config.Auth) } waitSignal() diff --git a/config.json b/config.json index 8475f08d..35d08271 100644 --- a/config.json +++ b/config.json @@ -3,6 +3,6 @@ "server_port":8388, "local_port":1080, "password":"barfoo!", - "method": "aes-128-cfb", + "method": "aes-128-cfb-auth", "timeout":600 } diff --git a/script/build.sh b/script/build.sh index 207b4cfa..14a4cd27 100755 --- a/script/build.sh +++ b/script/build.sh @@ -49,13 +49,13 @@ build linux 386 linux32 local build windows amd64 win64 local build windows 386 win32 local +#build darwin amd64 mac64 server build linux amd64 linux64 server build linux 386 linux32 server -build darwin amd64 mac64 server build windows amd64 win64 server build windows 386 win32 server #script/createdeb.sh amd64 -#script/createdeb.sh i386 +#script/createdeb.sh 386 #mv shadowsocks-go_$version-1-*.deb bin/ #rm -rf shadowsocks-go_$version-1* diff --git a/script/test.sh b/script/test.sh index f9b46169..1f99481b 100755 --- a/script/test.sh +++ b/script/test.sh @@ -47,7 +47,7 @@ test_get() { # -s silent to disable progress meter, but enable --show-error # -i to include http header # -L to follow redirect so we should always get HTTP 200 - cont=`curl --socks5 $SOCKS -s --show-error -i -L $url 2>&1` + cont=`curl -m 5 --socks5 $SOCKS -s --show-error -i -L $url 2>&1` ok=`echo $cont | grep -E -o "HTTP/1\.1 +$code"` html=`echo $cont | grep -E -o -i "$target"` if [[ -z $ok || -z $html ]] ; then @@ -74,7 +74,7 @@ test_shadowsocks() { server_pid=$! wait_server $SERVER_PORT - $LOCAL $OPTION -s 127.0.0.1 -l $LOCAL_PORT -m "$method" & + $LOCAL $OPTION -s 127.0.0.1 -l $LOCAL_PORT -m "$method" -A & local_pid=$! wait_server $LOCAL_PORT @@ -101,12 +101,10 @@ test_server_local_pair() { local url url=http://127.0.0.1:$HTTP_PORT/README.md - test_shadowsocks $url table - test_shadowsocks $url rc4 test_shadowsocks $url rc4-md5 test_shadowsocks $url aes-128-cfb - test_shadowsocks $url aes-192-cfb - test_shadowsocks $url aes-256-cfb + #test_shadowsocks $url aes-192-cfb + #test_shadowsocks $url aes-256-cfb test_shadowsocks $url bf-cfb test_shadowsocks $url des-cfb test_shadowsocks $url cast5-cfb diff --git a/shadowsocks/config.go b/shadowsocks/config.go index 9d3696b4..4db50ac2 100644 --- a/shadowsocks/config.go +++ b/shadowsocks/config.go @@ -14,6 +14,7 @@ import ( // "log" "os" "reflect" + "strings" "time" ) @@ -23,6 +24,7 @@ type Config struct { LocalPort int `json:"local_port"` Password string `json:"password"` Method string `json:"method"` // encryption method + Auth bool `json:"auth"` // one time auth // following options are only used by server PortPassword map[string]string `json:"port_password"` @@ -85,6 +87,10 @@ func ParseConfig(path string) (config *Config, err error) { return nil, err } readTimeout = time.Duration(config.Timeout) * time.Second + if strings.HasSuffix(strings.ToLower(config.Method), "-auth") { + config.Method = config.Method[:len(config.Method)-5] + config.Auth = true + } return } @@ -124,9 +130,6 @@ func UpdateConfig(old, new *Config) { } } } - if old.Method == "table" { - old.Method = "" - } old.Timeout = new.Timeout readTimeout = time.Duration(old.Timeout) * time.Second diff --git a/shadowsocks/conn.go b/shadowsocks/conn.go index eacb41fb..2069f9f8 100644 --- a/shadowsocks/conn.go +++ b/shadowsocks/conn.go @@ -8,11 +8,17 @@ import ( "strconv" ) +const ( + OneTimeAuthMask byte = 0x10 + AddrMask byte = 0xf +) + type Conn struct { net.Conn *Cipher - readBuf []byte - writeBuf []byte + readBuf []byte + writeBuf []byte + chunkId uint32 } func NewConn(c net.Conn, cipher *Cipher) *Conn { @@ -58,7 +64,18 @@ func DialWithRawAddr(rawaddr []byte, server string, cipher *Cipher) (c *Conn, er return } c = NewConn(conn, cipher) - if _, err = c.Write(rawaddr); err != nil { + if cipher.ota { + if c.enc == nil { + if _, err = c.initEncrypt(); err != nil { + return + } + } + // since we have initEncrypt, we must send iv manually + conn.Write(cipher.iv) + rawaddr[0] |= OneTimeAuthMask + rawaddr = otaConnectAuth(cipher.iv, cipher.key, rawaddr) + } + if _, err = c.write(rawaddr); err != nil { c.Close() return nil, err } @@ -74,6 +91,28 @@ func Dial(addr, server string, cipher *Cipher) (c *Conn, err error) { return DialWithRawAddr(ra, server, cipher) } +func (c *Conn) GetIv() (iv []byte) { + iv = make([]byte, len(c.iv)) + copy(iv, c.iv) + return +} + +func (c *Conn) GetKey() (key []byte) { + key = make([]byte, len(c.key)) + copy(key, c.key) + return +} + +func (c *Conn) IsOta() bool { + return c.ota +} + +func (c *Conn) GetAndIncrChunkId() (chunkId uint32) { + chunkId = c.chunkId + c.chunkId += 1 + return +} + func (c *Conn) Read(b []byte) (n int, err error) { if c.dec == nil { iv := make([]byte, c.info.ivLen) @@ -83,6 +122,9 @@ func (c *Conn) Read(b []byte) (n int, err error) { if err = c.initDecrypt(iv); err != nil { return } + if len(c.iv) == 0 { + c.iv = iv + } } cipherData := c.readBuf @@ -100,6 +142,14 @@ func (c *Conn) Read(b []byte) (n int, err error) { } func (c *Conn) Write(b []byte) (n int, err error) { + if c.ota { + chunkId := c.GetAndIncrChunkId() + b = otaReqChunkAuth(c.iv, chunkId, b) + } + return c.write(b) +} + +func (c *Conn) write(b []byte) (n int, err error) { var iv []byte if c.enc == nil { iv, err = c.initEncrypt() diff --git a/shadowsocks/encrypt.go b/shadowsocks/encrypt.go index 5a79a25f..146947d3 100644 --- a/shadowsocks/encrypt.go +++ b/shadowsocks/encrypt.go @@ -1,7 +1,6 @@ package shadowsocks import ( - "bytes" "crypto/aes" "crypto/cipher" "crypto/des" @@ -10,17 +9,17 @@ import ( "crypto/rc4" "encoding/binary" "errors" + "io" + "strings" + "github.com/codahale/chacha20" "golang.org/x/crypto/blowfish" "golang.org/x/crypto/cast5" "golang.org/x/crypto/salsa20/salsa" - "io" ) var errEmptyPassword = errors.New("empty key") -type tableCipher []byte - func md5sum(d []byte) []byte { h := md5.New() h.Write(d) @@ -47,50 +46,6 @@ func evpBytesToKey(password string, keyLen int) (key []byte) { return m[:keyLen] } -func (tbl tableCipher) XORKeyStream(dst, src []byte) { - for i := 0; i < len(src); i++ { - dst[i] = tbl[src[i]] - } -} - -// NewTableCipher creates a new table based cipher. -func newTableCipher(s []byte) (enc, dec tableCipher) { - const tbl_size = 256 - enc = make([]byte, tbl_size) - dec = make([]byte, tbl_size) - table := make([]uint64, tbl_size) - - var a uint64 - buf := bytes.NewBuffer(s) - binary.Read(buf, binary.LittleEndian, &a) - var i uint64 - for i = 0; i < tbl_size; i++ { - table[i] = i - } - for i = 1; i < 1024; i++ { - table = Sort(table, func(x, y uint64) int64 { - return int64(a%uint64(x+i) - a%uint64(y+i)) - }) - } - for i = 0; i < tbl_size; i++ { - enc[i] = byte(table[i]) - } - for i = 0; i < tbl_size; i++ { - dec[enc[i]] = byte(i) - } - return enc, dec -} - -func newRC4Cipher(key []byte) (enc, dec cipher.Stream, err error) { - rc4Enc, err := rc4.NewCipher(key) - if err != nil { - return - } - // create a copy, as RC4 encrypt and decrypt uses the same keystream - rc4Dec := *rc4Enc - return rc4Enc, &rc4Dec, nil -} - type DecOrEnc int const ( @@ -190,8 +145,6 @@ type cipherInfo struct { } var cipherMethod = map[string]*cipherInfo{ - "rc4": {16, 0, nil}, - "table": {16, 0, nil}, "aes-128-cfb": {16, 16, newAESStream}, "aes-192-cfb": {24, 16, newAESStream}, "aes-256-cfb": {32, 16, newAESStream}, @@ -205,7 +158,7 @@ var cipherMethod = map[string]*cipherInfo{ func CheckCipherMethod(method string) error { if method == "" { - method = "table" + method = "aes-256-cfb" } _, ok := cipherMethod[method] if !ok { @@ -219,6 +172,8 @@ type Cipher struct { dec cipher.Stream key []byte info *cipherInfo + ota bool // one-time auth + iv []byte } // NewCipher creates a cipher that can be used in Dial() etc. @@ -228,8 +183,12 @@ func NewCipher(method, password string) (c *Cipher, err error) { if password == "" { return nil, errEmptyPassword } - if method == "" { - method = "table" + var ota bool + if strings.HasSuffix(strings.ToLower(method), "-auth") { + method = method[:len(method)-5] // len("-auth") = 5 + ota = true + } else { + ota = false } mi, ok := cipherMethod[method] if !ok { @@ -240,29 +199,25 @@ func NewCipher(method, password string) (c *Cipher, err error) { c = &Cipher{key: key, info: mi} - if mi.newStream == nil { - if method == "table" { - c.enc, c.dec = newTableCipher(key) - } else if method == "rc4" { - c.enc, c.dec, err = newRC4Cipher(key) - } - } if err != nil { return nil, err } + c.ota = ota return c, nil } // Initializes the block cipher with CFB mode, returns IV. func (c *Cipher) initEncrypt() (iv []byte, err error) { - iv = make([]byte, c.info.ivLen) - if _, err := io.ReadFull(rand.Reader, iv); err != nil { - return nil, err + if c.iv == nil { + iv = make([]byte, c.info.ivLen) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + c.iv = iv + } else { + iv = c.iv } c.enc, err = c.info.newStream(c.key, iv, Encrypt) - if err != nil { - return nil, err - } return } @@ -294,18 +249,9 @@ func (c *Cipher) Copy() *Cipher { // because the current implementation is not highly optimized, or this is // the nature of the algorithm.) - switch c.enc.(type) { - case tableCipher: - return c - case *rc4.Cipher: - enc, _ := c.enc.(*rc4.Cipher) - encCpy := *enc - decCpy := *enc - return &Cipher{enc: &encCpy, dec: &decCpy} - default: - nc := *c - nc.enc = nil - nc.dec = nil - return &nc - } + nc := *c + nc.enc = nil + nc.dec = nil + nc.ota = c.ota + return &nc } diff --git a/shadowsocks/encrypt_test.go b/shadowsocks/encrypt_test.go index 24da305e..9ff24286 100644 --- a/shadowsocks/encrypt_test.go +++ b/shadowsocks/encrypt_test.go @@ -2,38 +2,11 @@ package shadowsocks import ( "crypto/rand" - "crypto/rc4" "io" "reflect" "testing" ) -func TestEncrypTable1(t *testing.T) { - encTarget := []byte{60, 53, 84, 138, 217, 94, 88, 23, 39, 242, 219, 35, 12, 157, 165, 181, 255, 143, 83, 247, 162, 16, 31, 209, 190, 171, 115, 65, 38, 41, 21, 245, 236, 46, 121, 62, 166, 233, 44, 154, 153, 145, 230, 49, 128, 216, 173, 29, 241, 119, 64, 229, 194, 103, 131, 110, 26, 197, 218, 59, 204, 56, 27, 34, 141, 221, 149, 239, 192, 195, 24, 155, 170, 183, 11, 254, 213, 37, 137, 226, 75, 203, 55, 19, 72, 248, 22, 129, 33, 175, 178, 10, 198, 71, 77, 36, 113, 167, 48, 2, 117, 140, 142, 66, 199, 232, 243, 32, 123, 54, 51, 82, 57, 177, 87, 251, 150, 196, 133, 5, 253, 130, 8, 184, 14, 152, 231, 3, 186, 159, 76, 89, 228, 205, 156, 96, 163, 146, 18, 91, 132, 85, 80, 109, 172, 176, 105, 13, 50, 235, 127, 0, 189, 95, 98, 136, 250, 200, 108, 179, 211, 214, 106, 168, 78, 79, 74, 210, 30, 73, 201, 151, 208, 114, 101, 174, 92, 52, 120, 240, 15, 169, 220, 182, 81, 224, 43, 185, 40, 99, 180, 17, 212, 158, 42, 90, 9, 191, 45, 6, 25, 4, 222, 67, 126, 1, 116, 124, 206, 69, 61, 7, 68, 97, 202, 63, 244, 20, 28, 58, 93, 134, 104, 144, 227, 147, 102, 118, 135, 148, 47, 238, 86, 112, 122, 70, 107, 215, 100, 139, 223, 225, 164, 237, 111, 125, 207, 160, 187, 246, 234, 161, 188, 193, 249, 252} - decTarget := []byte{151, 205, 99, 127, 201, 119, 199, 211, 122, 196, 91, 74, 12, 147, 124, 180, 21, 191, 138, 83, 217, 30, 86, 7, 70, 200, 56, 62, 218, 47, 168, 22, 107, 88, 63, 11, 95, 77, 28, 8, 188, 29, 194, 186, 38, 198, 33, 230, 98, 43, 148, 110, 177, 1, 109, 82, 61, 112, 219, 59, 0, 210, 35, 215, 50, 27, 103, 203, 212, 209, 235, 93, 84, 169, 166, 80, 130, 94, 164, 165, 142, 184, 111, 18, 2, 141, 232, 114, 6, 131, 195, 139, 176, 220, 5, 153, 135, 213, 154, 189, 238, 174, 226, 53, 222, 146, 162, 236, 158, 143, 55, 244, 233, 96, 173, 26, 206, 100, 227, 49, 178, 34, 234, 108, 207, 245, 204, 150, 44, 87, 121, 54, 140, 118, 221, 228, 155, 78, 3, 239, 101, 64, 102, 17, 223, 41, 137, 225, 229, 66, 116, 171, 125, 40, 39, 71, 134, 13, 193, 129, 247, 251, 20, 136, 242, 14, 36, 97, 163, 181, 72, 25, 144, 46, 175, 89, 145, 113, 90, 159, 190, 15, 183, 73, 123, 187, 128, 248, 252, 152, 24, 197, 68, 253, 52, 69, 117, 57, 92, 104, 157, 170, 214, 81, 60, 133, 208, 246, 172, 23, 167, 160, 192, 76, 161, 237, 45, 4, 58, 10, 182, 65, 202, 240, 185, 241, 79, 224, 132, 51, 42, 126, 105, 37, 250, 149, 32, 243, 231, 67, 179, 48, 9, 106, 216, 31, 249, 19, 85, 254, 156, 115, 255, 120, 75, 16} - key := evpBytesToKey("foobar!", 16) - enc, dec := newTableCipher(key) - if !reflect.DeepEqual([]byte(enc), encTarget) { - t.Error("Password foobar encrypt table wrong") - } - if !reflect.DeepEqual([]byte(dec), decTarget) { - t.Error("Password foobar decrypt table wrong") - } -} - -func TestEncryptTable2(t *testing.T) { - encTarget := []byte{124, 30, 170, 247, 27, 127, 224, 59, 13, 22, 196, 76, 72, 154, 32, 209, 4, 2, 131, 62, 101, 51, 230, 9, 166, 11, 99, 80, 208, 112, 36, 248, 81, 102, 130, 88, 218, 38, 168, 15, 241, 228, 167, 117, 158, 41, 10, 180, 194, 50, 204, 243, 246, 251, 29, 198, 219, 210, 195, 21, 54, 91, 203, 221, 70, 57, 183, 17, 147, 49, 133, 65, 77, 55, 202, 122, 162, 169, 188, 200, 190, 125, 63, 244, 96, 31, 107, 106, 74, 143, 116, 148, 78, 46, 1, 137, 150, 110, 181, 56, 95, 139, 58, 3, 231, 66, 165, 142, 242, 43, 192, 157, 89, 175, 109, 220, 128, 0, 178, 42, 255, 20, 214, 185, 83, 160, 253, 7, 23, 92, 111, 153, 26, 226, 33, 176, 144, 18, 216, 212, 28, 151, 71, 206, 222, 182, 8, 174, 205, 201, 152, 240, 155, 108, 223, 104, 239, 98, 164, 211, 184, 34, 193, 14, 114, 187, 40, 254, 12, 67, 93, 217, 6, 94, 16, 19, 82, 86, 245, 24, 197, 134, 132, 138, 229, 121, 5, 235, 238, 85, 47, 103, 113, 179, 69, 250, 45, 135, 156, 25, 61, 75, 44, 146, 189, 84, 207, 172, 119, 53, 123, 186, 120, 171, 68, 227, 145, 136, 100, 90, 48, 79, 159, 149, 39, 213, 236, 126, 52, 60, 225, 199, 105, 73, 233, 252, 118, 215, 35, 115, 64, 37, 97, 129, 161, 177, 87, 237, 141, 173, 191, 163, 140, 234, 232, 249} - decTarget := []byte{117, 94, 17, 103, 16, 186, 172, 127, 146, 23, 46, 25, 168, 8, 163, 39, 174, 67, 137, 175, 121, 59, 9, 128, 179, 199, 132, 4, 140, 54, 1, 85, 14, 134, 161, 238, 30, 241, 37, 224, 166, 45, 119, 109, 202, 196, 93, 190, 220, 69, 49, 21, 228, 209, 60, 73, 99, 65, 102, 7, 229, 200, 19, 82, 240, 71, 105, 169, 214, 194, 64, 142, 12, 233, 88, 201, 11, 72, 92, 221, 27, 32, 176, 124, 205, 189, 177, 246, 35, 112, 219, 61, 129, 170, 173, 100, 84, 242, 157, 26, 218, 20, 33, 191, 155, 232, 87, 86, 153, 114, 97, 130, 29, 192, 164, 239, 90, 43, 236, 208, 212, 185, 75, 210, 0, 81, 227, 5, 116, 243, 34, 18, 182, 70, 181, 197, 217, 95, 183, 101, 252, 248, 107, 89, 136, 216, 203, 68, 91, 223, 96, 141, 150, 131, 13, 152, 198, 111, 44, 222, 125, 244, 76, 251, 158, 106, 24, 42, 38, 77, 2, 213, 207, 249, 147, 113, 135, 245, 118, 193, 47, 98, 145, 66, 160, 123, 211, 165, 78, 204, 80, 250, 110, 162, 48, 58, 10, 180, 55, 231, 79, 149, 74, 62, 50, 148, 143, 206, 28, 15, 57, 159, 139, 225, 122, 237, 138, 171, 36, 56, 115, 63, 144, 154, 6, 230, 133, 215, 41, 184, 22, 104, 254, 234, 253, 187, 226, 247, 188, 156, 151, 40, 108, 51, 83, 178, 52, 3, 31, 255, 195, 53, 235, 126, 167, 120} - key := evpBytesToKey("barfoo!", 16) - enc, dec := newTableCipher(key) - if !reflect.DeepEqual([]byte(enc), encTarget) { - t.Error("Password barfoo! encrypt table wrong") - } - if !reflect.DeepEqual([]byte(dec), decTarget) { - t.Error("Password barfoo! decrypt table wrong") - } -} - const text = "Don't tell me the moon is shining; show me the glint of light on broken glass." func testCiphter(t *testing.T, c *Cipher, msg string) { @@ -49,41 +22,6 @@ func testCiphter(t *testing.T, c *Cipher, msg string) { } } -func TestTableCipher(t *testing.T) { - cipher, err := NewCipher("", "OpenSesame!") - if err != nil { - t.Fatal("Should not get error generating table cipher") - } - if _, ok := cipher.enc.(tableCipher); !ok { - t.Error("Should get table cipher") - } else { - testCiphter(t, cipher, "TableCipher") - } -} - -func TestRC4Cipher(t *testing.T) { - cipher, err := NewCipher("no-such-method", "foobar") - if err == nil { - t.Error("Should return error for unsupported encryption method") - } - - cipher, err = NewCipher("rc4", "") - if err == nil { - t.Error("Should get error for empty key creating rc4 cipher") - } - cipher, err = NewCipher("rc4", "Alibaba") - ciphercopy := cipher.Copy() - if err != nil { - t.Error("Should not error creating rc4 cipher with key Alibaba") - } - if _, ok := cipher.enc.(*rc4.Cipher); !ok { - t.Error("Should get rc4 cipher") - } else { - testCiphter(t, cipher, "RC4Cipher") - testCiphter(t, ciphercopy, "RC4Cipher copy") - } -} - func TestEvpBytesToKey(t *testing.T) { // key, iv := evpBytesToKey("foobar", 32, 16) key := evpBytesToKey("foobar", 32) @@ -161,13 +99,6 @@ func init() { io.ReadFull(rand.Reader, cipherIv) } -func BenchmarkRC4Init(b *testing.B) { - key := cipherKey[:16] - for i := 0; i < b.N; i++ { - rc4.NewCipher(key) - } -} - func benchmarkCipherInit(b *testing.B, method string) { ci := cipherMethod[method] key := cipherKey[:ci.keyLen] @@ -246,7 +177,7 @@ func BenchmarkBlowFishEncrypt(b *testing.B) { } func BenchmarkCast5Encrypt(b *testing.B) { - benchmarkCipherEncrypt(b, "bf-cfb") + benchmarkCipherEncrypt(b, "cast5-cfb") } func BenchmarkDESEncrypt(b *testing.B) { @@ -303,7 +234,7 @@ func BenchmarkBlowFishDecrypt(b *testing.B) { } func BenchmarkCast5Decrypt(b *testing.B) { - benchmarkCipherDecrypt(b, "bf-cfb") + benchmarkCipherDecrypt(b, "cast5-cfb") } func BenchmarkDESDecrypt(b *testing.B) { diff --git a/shadowsocks/leakybuf.go b/shadowsocks/leakybuf.go index 3b55832d..b6922eb9 100644 --- a/shadowsocks/leakybuf.go +++ b/shadowsocks/leakybuf.go @@ -6,7 +6,7 @@ type LeakyBuf struct { freeList chan []byte } -const leakyBufSize = 4096 +const leakyBufSize = 4108 // data.len(2) + hmacsha1(10) + data(4096) const maxNBuf = 2048 var leakyBuf = NewLeakyBuf(maxNBuf, leakyBufSize) diff --git a/shadowsocks/pipe.go b/shadowsocks/pipe.go index 515c99f8..ca17c002 100644 --- a/shadowsocks/pipe.go +++ b/shadowsocks/pipe.go @@ -1,7 +1,9 @@ package shadowsocks import ( - // "io" + "bytes" + "encoding/binary" + "io" "net" "time" ) @@ -42,3 +44,57 @@ func PipeThenClose(src, dst net.Conn) { } } } + +// PipeThenClose copies data from src to dst, closes dst when done, with ota verification. +func PipeThenCloseOta(src *Conn, dst net.Conn) { + const ( + dataLenLen = 2 + hmacSha1Len = 10 + idxData0 = dataLenLen + hmacSha1Len + ) + + defer func() { + dst.Close() + }() + // sometimes it have to fill large block + buf := leakyBuf.Get() + defer leakyBuf.Put(buf) + for i := 1; ; i += 1 { + SetReadTimeout(src) + if n, err := io.ReadFull(src, buf[:dataLenLen+hmacSha1Len]); err != nil { + if err == io.EOF { + break + } + Debug.Printf("conn=%p #%v read header error n=%v: %v", src, i, n, err) + break + } + dataLen := binary.BigEndian.Uint16(buf[:dataLenLen]) + expectedHmacSha1 := buf[dataLenLen:idxData0] + + var dataBuf []byte + if len(buf) < int(idxData0+dataLen) { + dataBuf = make([]byte, dataLen) + } else { + dataBuf = buf[idxData0 : idxData0+dataLen] + } + if n, err := io.ReadFull(src, dataBuf); err != nil { + if err == io.EOF { + break + } + Debug.Printf("conn=%p #%v read data error n=%v: %v", src, i, n, err) + break + } + chunkIdBytes := make([]byte, 4) + chunkId := src.GetAndIncrChunkId() + binary.BigEndian.PutUint32(chunkIdBytes, chunkId) + actualHmacSha1 := HmacSha1(append(src.GetIv(), chunkIdBytes...), dataBuf) + if !bytes.Equal(expectedHmacSha1, actualHmacSha1) { + Debug.Printf("conn=%p #%v read data hmac-sha1 mismatch, iv=%v chunkId=%v src=%v dst=%v len=%v expeced=%v actual=%v", src, i, src.GetIv(), chunkId, src.RemoteAddr(), dst.RemoteAddr(), dataLen, expectedHmacSha1, actualHmacSha1) + break + } + if n, err := dst.Write(dataBuf); err != nil { + Debug.Printf("conn=%p #%v write data error n=%v: %v", dst, i, n, err) + break + } + } +} diff --git a/shadowsocks/proxy.go b/shadowsocks/proxy.go new file mode 100644 index 00000000..0b6145bc --- /dev/null +++ b/shadowsocks/proxy.go @@ -0,0 +1,84 @@ +package shadowsocks + +import ( + "errors" + "strings" + "fmt" + "net" + "time" +) + +type Dialer struct { + cipher *Cipher + server string + support_udp bool +} + +type ProxyConn struct { + *Conn + raddr *ProxyAddr +} + +type ProxyAddr struct { + network string + address string +} + +var ErrNilCipher = errors.New("cipher can't be nil.") + +func NewDialer(server string, cipher *Cipher) (dialer *Dialer, err error) { + // Currently shadowsocks-go do not support UDP + if cipher == nil { + return nil, ErrNilCipher + } + return &Dialer { + cipher: cipher, + server: server, + support_udp: false, + }, nil +} + +func (d *Dialer) Dial(network, addr string) (c net.Conn, err error) { + if strings.HasPrefix(network, "tcp") { + conn, err := Dial(addr, d.server, d.cipher.Copy()) + if err != nil { + return nil, err + } + return &ProxyConn { + Conn: conn, + raddr: &ProxyAddr { + network: network, + address: addr, + }, + }, nil + } + return nil, fmt.Errorf("unsupported connection type: %s", network) +} + +func (c *ProxyConn) LocalAddr() net.Addr { + return c.Conn.LocalAddr() +} + +func (c *ProxyConn) RemoteAddr() net.Addr { + return c.raddr +} + +func (c *ProxyConn) SetDeadline(t time.Time) error { + return c.Conn.SetDeadline(t) +} + +func (c *ProxyConn) SetReadDeadline(t time.Time) error { + return c.Conn.SetReadDeadline(t) +} + +func (c *ProxyConn) SetWriteDeadline(t time.Time) error { + return c.Conn.SetWriteDeadline(t) +} + +func (a *ProxyAddr) Network() string { + return a.network +} + +func (a *ProxyAddr) String() string { + return a.address +} diff --git a/shadowsocks/util.go b/shadowsocks/util.go index 2c5198d2..21c01c6a 100644 --- a/shadowsocks/util.go +++ b/shadowsocks/util.go @@ -4,10 +4,13 @@ import ( "errors" "fmt" "os" + "crypto/hmac" + "crypto/sha1" + "encoding/binary" ) func PrintVersion() { - const version = "1.1.4" + const version = "1.1.5" fmt.Println("shadowsocks-go version", version) } @@ -24,3 +27,34 @@ func IsFileExists(path string) (bool, error) { } return false, err } + +func HmacSha1(key []byte, data []byte) []byte { + hmacSha1 := hmac.New(sha1.New, key) + hmacSha1.Write(data) + return hmacSha1.Sum(nil)[:10] +} + +func otaConnectAuth(iv, key, data []byte) []byte { + return append(data, HmacSha1(append(iv, key...), data)...) +} + +func otaReqChunkAuth(iv []byte, chunkId uint32, data []byte) []byte { + nb := make([]byte, 2) + binary.BigEndian.PutUint16(nb, uint16(len(data))) + chunkIdBytes := make([]byte, 4) + binary.BigEndian.PutUint32(chunkIdBytes, chunkId) + header := append(nb, HmacSha1(append(iv, chunkIdBytes...), data)...) + return append(header, data...) +} + +type ClosedFlag struct { + flag bool +} + +func (flag *ClosedFlag) SetClosed() { + flag.flag = true +} + +func (flag *ClosedFlag) IsClosed() bool { + return flag.flag +} \ No newline at end of file