From fef07e2ae11405560c1f043d01545ccbd581b254 Mon Sep 17 00:00:00 2001 From: Yifu Yu Date: Fri, 7 Aug 2015 01:36:51 +0800 Subject: [PATCH 01/15] Add proxy api specified in golang.org/x/net/proxy Other golang projects can now integrate with shadowsocks-go much easier. --- shadowsocks/proxy.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 shadowsocks/proxy.go diff --git a/shadowsocks/proxy.go b/shadowsocks/proxy.go new file mode 100644 index 00000000..25f6040d --- /dev/null +++ b/shadowsocks/proxy.go @@ -0,0 +1,34 @@ +package shadowsocks + +import ( + "errors" + "strings" + "fmt" +) + +type Dialer struct { + cipher *Cipher + server string + support_udp bool +} + +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 *Conn, err error) { + if strings.HasPrefix(network, "tcp") { + return Dial(addr, d.server, d.cipher) + } + return nil, fmt.Errorf("unsupported connection type: %s", network) +} From 9f6e7123423e8fc4e976628f8c67eb7169dcd297 Mon Sep 17 00:00:00 2001 From: Yifu Yu Date: Fri, 7 Aug 2015 02:32:24 +0800 Subject: [PATCH 02/15] Fix net.Conn interface. --- shadowsocks/proxy.go | 54 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/shadowsocks/proxy.go b/shadowsocks/proxy.go index 25f6040d..9758a6df 100644 --- a/shadowsocks/proxy.go +++ b/shadowsocks/proxy.go @@ -4,6 +4,8 @@ import ( "errors" "strings" "fmt" + "net" + "time" ) type Dialer struct { @@ -12,6 +14,16 @@ type Dialer struct { 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) { @@ -26,9 +38,47 @@ func NewDialer(server string, cipher *Cipher) (dialer *Dialer, err error) { }, nil } -func (d *Dialer) Dial(network, addr string) (c *Conn, err error) { +func (d *Dialer) Dial(network, addr string) (c *ProxyConn, err error) { if strings.HasPrefix(network, "tcp") { - return Dial(addr, d.server, d.cipher) + conn, err := Dial(addr, d.server, d.cipher) + 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 +} From a119059c5850ac25c8061a9ee816b067065e66d1 Mon Sep 17 00:00:00 2001 From: Yifu Yu Date: Fri, 7 Aug 2015 02:36:19 +0800 Subject: [PATCH 03/15] Fix return type. --- shadowsocks/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/proxy.go b/shadowsocks/proxy.go index 9758a6df..83049a8a 100644 --- a/shadowsocks/proxy.go +++ b/shadowsocks/proxy.go @@ -38,7 +38,7 @@ func NewDialer(server string, cipher *Cipher) (dialer *Dialer, err error) { }, nil } -func (d *Dialer) Dial(network, addr string) (c *ProxyConn, err error) { +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) if err != nil { From ad930372543d473907ab4f46771691c239a9af13 Mon Sep 17 00:00:00 2001 From: Yifu Yu Date: Fri, 7 Aug 2015 04:43:44 +0800 Subject: [PATCH 04/15] Fix cipher reuse problem. --- shadowsocks/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowsocks/proxy.go b/shadowsocks/proxy.go index 83049a8a..0b6145bc 100644 --- a/shadowsocks/proxy.go +++ b/shadowsocks/proxy.go @@ -40,7 +40,7 @@ func NewDialer(server string, cipher *Cipher) (dialer *Dialer, err error) { 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) + conn, err := Dial(addr, d.server, d.cipher.Copy()) if err != nil { return nil, err } From 97d83ee63c6fade19398697b236172540c3b5b03 Mon Sep 17 00:00:00 2001 From: Xiaodu Date: Fri, 14 Aug 2015 22:54:26 +0800 Subject: [PATCH 05/15] Cipher cache should include encryption method in key ... otherwise two servers with different encryption method but same password will share the same cipher, and client will fail to authenticate. Instead of using map[string]map[string]*ss.Cipher, just prepend password with encryption method name and a delimiter "|" as cache key. Since there are no encryption methods with "|" in their names (and unlikely any further ones), it's safe to use this delimiter. Another problem is that, no method provided ("") is equivalent to "table", but will have two entries in cipher cache. But it's hard to know the default encryption method from package ss, and "table" is rarely used, this won't be a big problem. --- cmd/shadowsocks-local/local.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 03bd8efa..cfcdbf81 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -208,14 +208,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++ From 8bd4f179bb6a5c83c801b901cc0b1db2ab59899b Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Tue, 25 Aug 2015 22:49:20 +0800 Subject: [PATCH 06/15] Add apache license. --- LICENSE | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 2390b7f5e6a59962a2b690937884e644b4734893 Mon Sep 17 00:00:00 2001 From: amyangfei Date: Wed, 16 Sep 2015 20:26:20 +0800 Subject: [PATCH 07/15] Fix encrypt test code --- shadowsocks/encrypt_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowsocks/encrypt_test.go b/shadowsocks/encrypt_test.go index 24da305e..680e73c2 100644 --- a/shadowsocks/encrypt_test.go +++ b/shadowsocks/encrypt_test.go @@ -246,7 +246,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 +303,7 @@ func BenchmarkBlowFishDecrypt(b *testing.B) { } func BenchmarkCast5Decrypt(b *testing.B) { - benchmarkCipherDecrypt(b, "bf-cfb") + benchmarkCipherDecrypt(b, "cast5-cfb") } func BenchmarkDESDecrypt(b *testing.B) { From dbe517874c00920e61772524f14a04e857cf2aaa Mon Sep 17 00:00:00 2001 From: ayanamist Date: Wed, 2 Dec 2015 21:11:33 +0800 Subject: [PATCH 08/15] use travis ci new infrastructure use go 1.4.3 in travis ci, add timeout for curl in test.sh --- .travis.yml | 3 ++- script/test.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) 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/script/test.sh b/script/test.sh index f9b46169..2342952d 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 From 07f4a06d619f331c117cc05e2fff351c2a822795 Mon Sep 17 00:00:00 2001 From: ayanamist Date: Sat, 5 Dec 2015 00:18:32 +0800 Subject: [PATCH 09/15] deprecate table and rc4 --- script/test.sh | 2 - shadowsocks/config.go | 3 -- shadowsocks/encrypt.go | 80 +++---------------------------------- shadowsocks/encrypt_test.go | 69 -------------------------------- 4 files changed, 6 insertions(+), 148 deletions(-) diff --git a/script/test.sh b/script/test.sh index 2342952d..99608030 100755 --- a/script/test.sh +++ b/script/test.sh @@ -101,8 +101,6 @@ 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 diff --git a/shadowsocks/config.go b/shadowsocks/config.go index 9d3696b4..ec5d2f22 100644 --- a/shadowsocks/config.go +++ b/shadowsocks/config.go @@ -124,9 +124,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/encrypt.go b/shadowsocks/encrypt.go index 5a79a25f..0e06bd9f 100644 --- a/shadowsocks/encrypt.go +++ b/shadowsocks/encrypt.go @@ -1,7 +1,6 @@ package shadowsocks import ( - "bytes" "crypto/aes" "crypto/cipher" "crypto/des" @@ -19,8 +18,6 @@ import ( var errEmptyPassword = errors.New("empty key") -type tableCipher []byte - func md5sum(d []byte) []byte { h := md5.New() h.Write(d) @@ -47,50 +44,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 +143,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 +156,7 @@ var cipherMethod = map[string]*cipherInfo{ func CheckCipherMethod(method string) error { if method == "" { - method = "table" + method = "aes-256-cfb" } _, ok := cipherMethod[method] if !ok { @@ -228,9 +179,6 @@ func NewCipher(method, password string) (c *Cipher, err error) { if password == "" { return nil, errEmptyPassword } - if method == "" { - method = "table" - } mi, ok := cipherMethod[method] if !ok { return nil, errors.New("Unsupported encryption method: " + method) @@ -240,13 +188,6 @@ 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 } @@ -294,18 +235,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 680e73c2..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] From 89460d2e476940f0b702e4bdff4991152d9cdbbe Mon Sep 17 00:00:00 2001 From: ayanamist Date: Sat, 5 Dec 2015 17:12:14 +0800 Subject: [PATCH 10/15] support one time auth in client & server append "-ota" suffix in method name to enable one time auth --- cmd/shadowsocks-local/local.go | 6 +- cmd/shadowsocks-server/server.go | 119 +++++++++++++++++-------------- shadowsocks/config.go | 6 ++ shadowsocks/conn.go | 56 ++++++++++++++- shadowsocks/encrypt.go | 30 ++++++-- shadowsocks/leakybuf.go | 2 +- shadowsocks/pipe.go | 60 +++++++++++++++- shadowsocks/util.go | 34 +++++++++ 8 files changed, 249 insertions(+), 64 deletions(-) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index cfcdbf81..72514b60 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -170,8 +170,12 @@ func parseServerConfig(config *ss.Config) { } if len(config.ServerPassword) == 0 { + method := config.Method + if config.Auth { + method += "-ota" + } // 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) } diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index 982fcaf3..77155f83 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "encoding/binary" "errors" "flag" @@ -17,61 +18,61 @@ import ( "syscall" ) -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 + + typeIPv4 = 1 // type is ipv4 address + typeDm = 3 // type is domain address + typeIPv6 = 4 // type is ipv6 address -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 + lenIPv4 = net.IPv4len + 2 // ipv4 + 2port + lenIPv6 = net.IPv6len + 2 // ipv6 + 2port + lenDmBase = 2 // 1addrLen + 2port, plus addrLen + lenHmacSha1 = 10 +) - typeIPv4 = 1 // type is ipv4 address - typeDm = 3 // type is domain address - typeIPv6 = 4 // type is ipv6 address +var debug ss.DebugLog - 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 - ) +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 +81,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 +105,11 @@ const logCntDelta = 100 var connCnt int var nextLogConnCnt int = logCntDelta -func handleConnection(conn *ss.Conn) { +type isClosed struct { + isClosed bool +} + +func handleConnection(conn *ss.Conn, auth bool) { var host string connCnt++ // this maybe not accurate, but should be enough @@ -118,7 +137,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 +159,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 +210,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 +223,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 +242,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 +269,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 +295,7 @@ func run(port, password string) { continue } } - go handleConnection(ss.NewConn(conn, cipher.Copy())) + go handleConnection(ss.NewConn(conn, cipher.Copy()), auth) } } @@ -357,7 +372,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/shadowsocks/config.go b/shadowsocks/config.go index ec5d2f22..7794d9fa 100644 --- a/shadowsocks/config.go +++ b/shadowsocks/config.go @@ -15,6 +15,7 @@ import ( "os" "reflect" "time" + "strings" ) type Config struct { @@ -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), "-ota") { + config.Method = config.Method[:len(config.Method) - 4] + config.Auth = true + } return } 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 0e06bd9f..ea23e759 100644 --- a/shadowsocks/encrypt.go +++ b/shadowsocks/encrypt.go @@ -14,6 +14,7 @@ import ( "golang.org/x/crypto/cast5" "golang.org/x/crypto/salsa20/salsa" "io" + "strings" ) var errEmptyPassword = errors.New("empty key") @@ -170,6 +171,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. @@ -179,6 +182,13 @@ func NewCipher(method, password string) (c *Cipher, err error) { if password == "" { return nil, errEmptyPassword } + var ota bool + if strings.HasSuffix(strings.ToLower(method), "-ota") { + method = method[:len(method) - 4] // len("-ota") = 4 + ota = true + } else { + ota = false + } mi, ok := cipherMethod[method] if !ok { return nil, errors.New("Unsupported encryption method: " + method) @@ -191,18 +201,26 @@ func NewCipher(method, password string) (c *Cipher, err error) { 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 + if c.enc == nil { + c.enc, err = c.info.newStream(c.key, iv, Encrypt) + if err != nil { + return nil, err + } } return } 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..e0fa9756 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,59 @@ 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) + i := 0 + for { + 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/util.go b/shadowsocks/util.go index 2c5198d2..378f24da 100644 --- a/shadowsocks/util.go +++ b/shadowsocks/util.go @@ -4,6 +4,9 @@ import ( "errors" "fmt" "os" + "crypto/hmac" + "crypto/sha1" + "encoding/binary" ) func PrintVersion() { @@ -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 From cb20fe4563719cb0c670f2c1ca5a9af8f4dbc9a1 Mon Sep 17 00:00:00 2001 From: ayanamist Date: Wed, 16 Dec 2015 18:17:43 +0800 Subject: [PATCH 11/15] remove some useless code introduced since ota support --- cmd/shadowsocks-server/server.go | 4 ---- shadowsocks/encrypt.go | 7 +------ shadowsocks/pipe.go | 6 ++---- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index 77155f83..e8b27c80 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -105,10 +105,6 @@ const logCntDelta = 100 var connCnt int var nextLogConnCnt int = logCntDelta -type isClosed struct { - isClosed bool -} - func handleConnection(conn *ss.Conn, auth bool) { var host string diff --git a/shadowsocks/encrypt.go b/shadowsocks/encrypt.go index ea23e759..28ea0ed3 100644 --- a/shadowsocks/encrypt.go +++ b/shadowsocks/encrypt.go @@ -216,12 +216,7 @@ func (c *Cipher) initEncrypt() (iv []byte, err error) { } else { iv = c.iv } - if c.enc == nil { - c.enc, err = c.info.newStream(c.key, iv, Encrypt) - if err != nil { - return nil, err - } - } + c.enc, err = c.info.newStream(c.key, iv, Encrypt) return } diff --git a/shadowsocks/pipe.go b/shadowsocks/pipe.go index e0fa9756..ca17c002 100644 --- a/shadowsocks/pipe.go +++ b/shadowsocks/pipe.go @@ -59,9 +59,7 @@ func PipeThenCloseOta(src *Conn, dst net.Conn) { // sometimes it have to fill large block buf := leakyBuf.Get() defer leakyBuf.Put(buf) - i := 0 - for { - i += 1 + for i := 1; ; i += 1 { SetReadTimeout(src) if n, err := io.ReadFull(src, buf[:dataLenLen+hmacSha1Len]); err != nil { if err == io.EOF { @@ -77,7 +75,7 @@ func PipeThenCloseOta(src *Conn, dst net.Conn) { if len(buf) < int(idxData0+dataLen) { dataBuf = make([]byte, dataLen) } else { - dataBuf = buf[idxData0:idxData0+dataLen] + dataBuf = buf[idxData0 : idxData0+dataLen] } if n, err := io.ReadFull(src, dataBuf); err != nil { if err == io.EOF { From b651734b28ccf49e6fd218105fc10b08f847d222 Mon Sep 17 00:00:00 2001 From: Shuai Lin Date: Sun, 3 Apr 2016 22:58:56 +0800 Subject: [PATCH 12/15] Add flags for ota. --- cmd/shadowsocks-local/local.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 72514b60..05316999 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -17,6 +17,7 @@ import ( ) var debug ss.DebugLog +var oneTimeAuth bool var ( errAddrType = errors.New("socks addr type not supported") @@ -367,6 +368,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() From 94e33e7d22d76f900bef25ed0e06101404459f43 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Wed, 4 May 2016 18:54:39 +0800 Subject: [PATCH 13/15] Allow specify -m -ota on command line. --- cmd/shadowsocks-local/local.go | 10 ++++++++-- cmd/shadowsocks-server/server.go | 9 ++++++++- shadowsocks/config.go | 4 ++-- shadowsocks/encrypt.go | 7 ++++--- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 05316999..17b7762b 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,11 +12,13 @@ import ( "os" "path" "strconv" + "strings" "time" + + ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" ) var debug ss.DebugLog -var oneTimeAuth bool var ( errAddrType = errors.New("socks addr type not supported") @@ -380,6 +381,11 @@ func main() { cmdConfig.Server = cmdServer ss.SetDebug(debug) + if strings.HasSuffix(cmdConfig.Method, "-ota") { + 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 e8b27c80..19dfc0b2 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -6,7 +6,6 @@ import ( "errors" "flag" "fmt" - ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" "io" "log" "net" @@ -14,8 +13,11 @@ import ( "os/signal" "runtime" "strconv" + "strings" "sync" "syscall" + + ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" ) const ( @@ -343,6 +345,11 @@ func main() { ss.SetDebug(debug) + if strings.HasSuffix(cmdConfig.Method, "-ota") { + cmdConfig.Method = cmdConfig.Method[:len(cmdConfig.Method)-4] + cmdConfig.Auth = true + } + var err error config, err = ss.ParseConfig(configFile) if err != nil { diff --git a/shadowsocks/config.go b/shadowsocks/config.go index 7794d9fa..88f7c16f 100644 --- a/shadowsocks/config.go +++ b/shadowsocks/config.go @@ -14,8 +14,8 @@ import ( // "log" "os" "reflect" - "time" "strings" + "time" ) type Config struct { @@ -88,7 +88,7 @@ func ParseConfig(path string) (config *Config, err error) { } readTimeout = time.Duration(config.Timeout) * time.Second if strings.HasSuffix(strings.ToLower(config.Method), "-ota") { - config.Method = config.Method[:len(config.Method) - 4] + config.Method = config.Method[:len(config.Method)-4] config.Auth = true } return diff --git a/shadowsocks/encrypt.go b/shadowsocks/encrypt.go index 28ea0ed3..45b2eb95 100644 --- a/shadowsocks/encrypt.go +++ b/shadowsocks/encrypt.go @@ -9,12 +9,13 @@ 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" - "strings" ) var errEmptyPassword = errors.New("empty key") @@ -184,7 +185,7 @@ func NewCipher(method, password string) (c *Cipher, err error) { } var ota bool if strings.HasSuffix(strings.ToLower(method), "-ota") { - method = method[:len(method) - 4] // len("-ota") = 4 + method = method[:len(method)-4] // len("-ota") = 4 ota = true } else { ota = false From 0206d55920e7ef3e376efc10b41c8e609c253d91 Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Wed, 4 May 2016 20:08:52 +0800 Subject: [PATCH 14/15] Bump version to 1.1.5 --- .gitignore | 1 + README.md | 11 +++++++++-- config.json | 2 +- script/build.sh | 4 ++-- script/test.sh | 6 +++--- shadowsocks/util.go | 2 +- 6 files changed, 17 insertions(+), 9 deletions(-) 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/README.md b/README.md index f3736a38..37f3811a 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=develop)](https://travis-ci.org/shadowsocks/shadowsocks-go) +Current version: 1.1.5 [![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-go.png?branch=develop)](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 `-ota` 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/config.json b/config.json index 8475f08d..7b783612 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-ota", "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 99608030..1f99481b 100755 --- a/script/test.sh +++ b/script/test.sh @@ -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 @@ -103,8 +103,8 @@ test_server_local_pair() { url=http://127.0.0.1:$HTTP_PORT/README.md 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/util.go b/shadowsocks/util.go index 378f24da..21c01c6a 100644 --- a/shadowsocks/util.go +++ b/shadowsocks/util.go @@ -10,7 +10,7 @@ import ( ) func PrintVersion() { - const version = "1.1.4" + const version = "1.1.5" fmt.Println("shadowsocks-go version", version) } From 0fd1c39d569b4a378df4293f41d0810ecff07b4c Mon Sep 17 00:00:00 2001 From: Chen Yufei Date: Wed, 4 May 2016 20:31:19 +0800 Subject: [PATCH 15/15] Use method-auth to enable OTA. --- README.md | 2 +- cmd/shadowsocks-local/local.go | 4 ++-- cmd/shadowsocks-server/server.go | 2 +- config.json | 2 +- shadowsocks/config.go | 4 ++-- shadowsocks/encrypt.go | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 37f3811a..5b3a3182 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ AES is recommended for shadowsocks-go. [Intel AES Instruction Set](http://en.wik ### One Time Auth -Append `-ota` to the encryption method to enable [One Time Auth (OTA)](https://shadowsocks.org/en/spec/one-time-auth.html). +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 diff --git a/cmd/shadowsocks-local/local.go b/cmd/shadowsocks-local/local.go index 17b7762b..1de4262f 100644 --- a/cmd/shadowsocks-local/local.go +++ b/cmd/shadowsocks-local/local.go @@ -174,7 +174,7 @@ func parseServerConfig(config *ss.Config) { if len(config.ServerPassword) == 0 { method := config.Method if config.Auth { - method += "-ota" + method += "-auth" } // only one encryption table cipher, err := ss.NewCipher(method, config.Password) @@ -381,7 +381,7 @@ func main() { cmdConfig.Server = cmdServer ss.SetDebug(debug) - if strings.HasSuffix(cmdConfig.Method, "-ota") { + if strings.HasSuffix(cmdConfig.Method, "-auth") { cmdConfig.Method = cmdConfig.Method[:len(cmdConfig.Method)-4] cmdConfig.Auth = true } diff --git a/cmd/shadowsocks-server/server.go b/cmd/shadowsocks-server/server.go index 19dfc0b2..d2969e90 100644 --- a/cmd/shadowsocks-server/server.go +++ b/cmd/shadowsocks-server/server.go @@ -345,7 +345,7 @@ func main() { ss.SetDebug(debug) - if strings.HasSuffix(cmdConfig.Method, "-ota") { + if strings.HasSuffix(cmdConfig.Method, "-auth") { cmdConfig.Method = cmdConfig.Method[:len(cmdConfig.Method)-4] cmdConfig.Auth = true } diff --git a/config.json b/config.json index 7b783612..35d08271 100644 --- a/config.json +++ b/config.json @@ -3,6 +3,6 @@ "server_port":8388, "local_port":1080, "password":"barfoo!", - "method": "aes-128-cfb-ota", + "method": "aes-128-cfb-auth", "timeout":600 } diff --git a/shadowsocks/config.go b/shadowsocks/config.go index 88f7c16f..4db50ac2 100644 --- a/shadowsocks/config.go +++ b/shadowsocks/config.go @@ -87,8 +87,8 @@ 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), "-ota") { - config.Method = config.Method[:len(config.Method)-4] + if strings.HasSuffix(strings.ToLower(config.Method), "-auth") { + config.Method = config.Method[:len(config.Method)-5] config.Auth = true } return diff --git a/shadowsocks/encrypt.go b/shadowsocks/encrypt.go index 45b2eb95..146947d3 100644 --- a/shadowsocks/encrypt.go +++ b/shadowsocks/encrypt.go @@ -184,8 +184,8 @@ func NewCipher(method, password string) (c *Cipher, err error) { return nil, errEmptyPassword } var ota bool - if strings.HasSuffix(strings.ToLower(method), "-ota") { - method = method[:len(method)-4] // len("-ota") = 4 + if strings.HasSuffix(strings.ToLower(method), "-auth") { + method = method[:len(method)-5] // len("-auth") = 5 ota = true } else { ota = false