From 81ff3dadd5d30b16169d3244d6c49d99d4e4c9ac Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 22 May 2018 11:26:34 +0800 Subject: [PATCH] Signed-off-by: arraykeys@gmail.com --- config.go | 5 - services/args.go | 5 - services/sps.go | 57 +-------- utils/ss/conn.go | 193 ------------------------------- utils/ss/encrypt.go | 274 -------------------------------------------- utils/ss/util.go | 131 --------------------- 6 files changed, 1 insertion(+), 664 deletions(-) delete mode 100644 utils/ss/conn.go delete mode 100644 utils/ss/encrypt.go delete mode 100644 utils/ss/util.go diff --git a/config.go b/config.go index 80e5a63..f72e69e 100755 --- a/config.go +++ b/config.go @@ -245,13 +245,8 @@ func initConfig() (err error) { spsArgs.ParentKey = sps.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String() spsArgs.LocalCompress = sps.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool() spsArgs.ParentCompress = sps.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool() - spsArgs.SSMethod = sps.Flag("ss-method", "").Hidden().Short('h').Default("aes-256-cfb").String() - spsArgs.SSKey = sps.Flag("ss-key", "").Hidden().Short('j').Default("sspassword").String() - spsArgs.ParentSSMethod = sps.Flag("parent-ss-method", "").Hidden().Short('H').Default("aes-256-cfb").String() - spsArgs.ParentSSKey = sps.Flag("parent-ss-key", "").Hidden().Short('J').Default("sspassword").String() spsArgs.DisableHTTP = sps.Flag("disable-http", "disable http(s) proxy").Default("false").Bool() spsArgs.DisableSocks5 = sps.Flag("disable-socks", "disable socks proxy").Default("false").Bool() - spsArgs.DisableSS = sps.Flag("disable-ss", "").Hidden().Default("false").Bool() //parse args serviceName := kingpin.MustParse(app.Parse(os.Args[1:])) diff --git a/services/args.go b/services/args.go index e2b5c7b..e0ddf4d 100644 --- a/services/args.go +++ b/services/args.go @@ -232,13 +232,8 @@ type SPSArgs struct { ParentKey *string LocalCompress *bool ParentCompress *bool - SSMethod *string - SSKey *string - ParentSSMethod *string - ParentSSKey *string DisableHTTP *bool DisableSocks5 *bool - DisableSS *bool } func (a *SPSArgs) Protocol() string { diff --git a/services/sps.go b/services/sps.go index a034004..44c606e 100644 --- a/services/sps.go +++ b/services/sps.go @@ -16,7 +16,6 @@ import ( "github.com/snail007/goproxy/utils" "github.com/snail007/goproxy/utils/conncrypt" "github.com/snail007/goproxy/utils/socks" - "github.com/snail007/goproxy/utils/ss" ) type SPS struct { @@ -27,8 +26,6 @@ type SPS struct { serverChannels []*utils.ServerChannel userConns utils.ConcurrentMap log *logger.Logger - localCipher *ss.Cipher - parentCipher *ss.Cipher } func NewSPS() Service { @@ -49,10 +46,6 @@ func (s *SPS) CheckArgs() (err error) { err = fmt.Errorf("parent type unkown,use -T ") return } - if *s.cfg.ParentType == "ss" && (*s.cfg.ParentSSKey == "" || *s.cfg.ParentSSMethod == "") { - err = fmt.Errorf("ss parent need a ss key, set it by : -J ") - return - } if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS { s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) if err != nil { @@ -69,28 +62,11 @@ func (s *SPS) CheckArgs() (err error) { return } func (s *SPS) InitService() (err error) { - if *s.cfg.DNSAddress != "" { (*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL, s.log) } - s.InitOutConnPool() - err = s.InitBasicAuth() - if *s.cfg.SSMethod != "" && *s.cfg.SSKey != "" { - s.localCipher, err = ss.NewCipher(*s.cfg.SSMethod, *s.cfg.SSKey) - if err != nil { - s.log.Printf("error generating cipher : %s", err) - return - } - } - if *s.cfg.ParentServiceType == "ss" { - s.parentCipher, err = ss.NewCipher(*s.cfg.ParentSSMethod, *s.cfg.ParentSSKey) - if err != nil { - s.log.Printf("error generating cipher : %s", err) - return - } - } return } func (s *SPS) InitOutConnPool() { @@ -281,25 +257,6 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { auth = socks.Auth{User: userpassA[0], Password: userpassA[1]} } } - } else { - //ss - if *s.cfg.DisableSS { - (*inConn).Close() - return - } - (*inConn).SetDeadline(time.Now().Add(time.Second * 5)) - ssConn := ss.NewConn(*inConn, s.localCipher.Copy()) - address, err = ss.GetRequest(ssConn) - (*inConn).SetDeadline(time.Time{}) - if err != nil { - return - } - // ensure the host does not contain some illegal characters, NUL may panic on Win32 - if strings.ContainsRune(address, 0x00) { - err = errors.New("invalid domain name") - return - } - *inConn = ssConn } if err != nil || address == "" { s.log.Printf("unknown request from: %s,%s", (*inConn).RemoteAddr(), string(h)) @@ -325,7 +282,7 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { }) } - if *s.cfg.ParentAuth != "" || *s.cfg.ParentSSKey != "" || s.IsBasicAuth() { + if *s.cfg.ParentAuth != "" || s.IsBasicAuth() { forwardBytes = utils.RemoveProxyHeaders(forwardBytes) } @@ -411,18 +368,6 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { if err = clientConn.Handshake(); err != nil { return } - } else if *s.cfg.ParentServiceType == "ss" { - ra, e := ss.RawAddr(address) - if e != nil { - err = fmt.Errorf("build ss raw addr fail, err: %s", e) - return - } - - outConn, err = ss.DialWithRawAddr(&outConn, ra, "", s.parentCipher.Copy()) - if err != nil { - err = fmt.Errorf("dial ss parent fail, err : %s", err) - return - } } //forward client data to target,if necessary. diff --git a/utils/ss/conn.go b/utils/ss/conn.go deleted file mode 100644 index 83f0681..0000000 --- a/utils/ss/conn.go +++ /dev/null @@ -1,193 +0,0 @@ -package ss - -import ( - "encoding/binary" - "fmt" - "io" - "net" - "strconv" -) - -const ( - OneTimeAuthMask byte = 0x10 - AddrMask byte = 0xf -) - -type Conn struct { - net.Conn - *Cipher - readBuf []byte - writeBuf []byte - chunkId uint32 -} - -func NewConn(c net.Conn, cipher *Cipher) *Conn { - return &Conn{ - Conn: c, - Cipher: cipher, - readBuf: leakyBuf.Get(), - writeBuf: leakyBuf.Get()} -} - -func (c *Conn) Close() error { - leakyBuf.Put(c.readBuf) - leakyBuf.Put(c.writeBuf) - return c.Conn.Close() -} - -func RawAddr(addr string) (buf []byte, err error) { - host, portStr, err := net.SplitHostPort(addr) - if err != nil { - return nil, fmt.Errorf("ss: address error %s %v", addr, err) - } - port, err := strconv.Atoi(portStr) - if err != nil { - return nil, fmt.Errorf("ss: invalid port %s", addr) - } - - hostLen := len(host) - l := 1 + 1 + hostLen + 2 // addrType + lenByte + address + port - buf = make([]byte, l) - buf[0] = 3 // 3 means the address is domain name - buf[1] = byte(hostLen) // host address length followed by host address - copy(buf[2:], host) - binary.BigEndian.PutUint16(buf[2+hostLen:2+hostLen+2], uint16(port)) - return -} - -// This is intended for use by users implementing a local socks proxy. -// rawaddr shoud contain part of the data in socks request, starting from the -// ATYP field. (Refer to rfc1928 for more information.) -func DialWithRawAddr(rawConn *net.Conn, rawaddr []byte, server string, cipher *Cipher) (c *Conn, err error) { - var conn net.Conn - if rawConn == nil { - conn, err = net.Dial("tcp", server) - } - if err != nil { - return - } - if rawConn != nil { - c = NewConn(*rawConn, cipher) - } else { - c = NewConn(conn, cipher) - } - 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 - } - return -} - -// addr should be in the form of host:port -func Dial(addr, server string, cipher *Cipher) (c *Conn, err error) { - ra, err := RawAddr(addr) - if err != nil { - return - } - return DialWithRawAddr(nil, 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) - if _, err = io.ReadFull(c.Conn, iv); err != nil { - return - } - if err = c.initDecrypt(iv); err != nil { - return - } - if len(c.iv) == 0 { - c.iv = iv - } - } - - cipherData := c.readBuf - if len(b) > len(cipherData) { - cipherData = make([]byte, len(b)) - } else { - cipherData = cipherData[:len(b)] - } - - n, err = c.Conn.Read(cipherData) - if n > 0 { - c.decrypt(b[0:n], cipherData[0:n]) - } - return -} - -func (c *Conn) Write(b []byte) (n int, err error) { - nn := len(b) - if c.ota { - chunkId := c.GetAndIncrChunkId() - b = otaReqChunkAuth(c.iv, chunkId, b) - } - headerLen := len(b) - nn - - n, err = c.write(b) - // Make sure <= 0 <= len(b), where b is the slice passed in. - if n >= headerLen { - n -= headerLen - } - return -} - -func (c *Conn) write(b []byte) (n int, err error) { - var iv []byte - if c.enc == nil { - iv, err = c.initEncrypt() - if err != nil { - return - } - } - - cipherData := c.writeBuf - dataSize := len(b) + len(iv) - if dataSize > len(cipherData) { - cipherData = make([]byte, dataSize) - } else { - cipherData = cipherData[:dataSize] - } - - if iv != nil { - // Put initialization vector in buffer, do a single write to send both - // iv and data. - copy(cipherData, iv) - } - - c.encrypt(cipherData[len(iv):], b) - n, err = c.Conn.Write(cipherData) - return -} diff --git a/utils/ss/encrypt.go b/utils/ss/encrypt.go deleted file mode 100644 index 6553aa0..0000000 --- a/utils/ss/encrypt.go +++ /dev/null @@ -1,274 +0,0 @@ -package ss - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/des" - "crypto/md5" - "crypto/rand" - "crypto/rc4" - "encoding/binary" - "errors" - "io" - "strings" - - "github.com/Yawning/chacha20" - "golang.org/x/crypto/blowfish" - "golang.org/x/crypto/cast5" - "golang.org/x/crypto/salsa20/salsa" -) - -var errEmptyPassword = errors.New("empty key") - -func md5sum(d []byte) []byte { - h := md5.New() - h.Write(d) - return h.Sum(nil) -} - -func evpBytesToKey(password string, keyLen int) (key []byte) { - const md5Len = 16 - - cnt := (keyLen-1)/md5Len + 1 - m := make([]byte, cnt*md5Len) - copy(m, md5sum([]byte(password))) - - // Repeatedly call md5 until bytes generated is enough. - // Each call to md5 uses data: prev md5 sum + password. - d := make([]byte, md5Len+len(password)) - start := 0 - for i := 1; i < cnt; i++ { - start += md5Len - copy(d, m[start-md5Len:start]) - copy(d[md5Len:], password) - copy(m[start:], md5sum(d)) - } - return m[:keyLen] -} - -type DecOrEnc int - -const ( - Decrypt DecOrEnc = iota - Encrypt -) - -func newStream(block cipher.Block, err error, key, iv []byte, - doe DecOrEnc) (cipher.Stream, error) { - if err != nil { - return nil, err - } - if doe == Encrypt { - return cipher.NewCFBEncrypter(block, iv), nil - } else { - return cipher.NewCFBDecrypter(block, iv), nil - } -} - -func newAESCFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := aes.NewCipher(key) - return newStream(block, err, key, iv, doe) -} - -func newAESCTRStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - return cipher.NewCTR(block, iv), nil -} - -func newDESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := des.NewCipher(key) - return newStream(block, err, key, iv, doe) -} - -func newBlowFishStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := blowfish.NewCipher(key) - return newStream(block, err, key, iv, doe) -} - -func newCast5Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { - block, err := cast5.NewCipher(key) - return newStream(block, err, key, iv, doe) -} - -func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { - h := md5.New() - h.Write(key) - h.Write(iv) - rc4key := h.Sum(nil) - - return rc4.NewCipher(rc4key) -} - -func newChaCha20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { - return chacha20.NewCipher(key, iv) -} - -func newChaCha20IETFStream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { - return chacha20.NewCipher(key, iv) -} - -type salsaStreamCipher struct { - nonce [8]byte - key [32]byte - counter int -} - -func (c *salsaStreamCipher) XORKeyStream(dst, src []byte) { - var buf []byte - padLen := c.counter % 64 - dataSize := len(src) + padLen - if cap(dst) >= dataSize { - buf = dst[:dataSize] - } else if leakyBufSize >= dataSize { - buf = leakyBuf.Get() - defer leakyBuf.Put(buf) - buf = buf[:dataSize] - } else { - buf = make([]byte, dataSize) - } - - var subNonce [16]byte - copy(subNonce[:], c.nonce[:]) - binary.LittleEndian.PutUint64(subNonce[len(c.nonce):], uint64(c.counter/64)) - - // It's difficult to avoid data copy here. src or dst maybe slice from - // Conn.Read/Write, which can't have padding. - copy(buf[padLen:], src[:]) - salsa.XORKeyStream(buf, buf, &subNonce, &c.key) - copy(dst, buf[padLen:]) - - c.counter += len(src) -} - -func newSalsa20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { - var c salsaStreamCipher - copy(c.nonce[:], iv[:8]) - copy(c.key[:], key[:32]) - return &c, nil -} - -type cipherInfo struct { - keyLen int - ivLen int - newStream func(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) -} - -var cipherMethod = map[string]*cipherInfo{ - "aes-128-cfb": {16, 16, newAESCFBStream}, - "aes-192-cfb": {24, 16, newAESCFBStream}, - "aes-256-cfb": {32, 16, newAESCFBStream}, - "aes-128-ctr": {16, 16, newAESCTRStream}, - "aes-192-ctr": {24, 16, newAESCTRStream}, - "aes-256-ctr": {32, 16, newAESCTRStream}, - "des-cfb": {8, 8, newDESStream}, - "bf-cfb": {16, 8, newBlowFishStream}, - "cast5-cfb": {16, 8, newCast5Stream}, - "rc4-md5": {16, 16, newRC4MD5Stream}, - "rc4-md5-6": {16, 6, newRC4MD5Stream}, - "chacha20": {32, 8, newChaCha20Stream}, - "chacha20-ietf": {32, 12, newChaCha20IETFStream}, - "salsa20": {32, 8, newSalsa20Stream}, -} - -func CheckCipherMethod(method string) error { - if method == "" { - method = "aes-256-cfb" - } - _, ok := cipherMethod[method] - if !ok { - return errors.New("Unsupported encryption method: " + method) - } - return nil -} - -type Cipher struct { - enc cipher.Stream - 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. -// Use cipher.Copy() to create a new cipher with the same method and password -// to avoid the cost of repeated cipher initialization. -func NewCipher(method, password string) (c *Cipher, err error) { - if password == "" { - return nil, errEmptyPassword - } - 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 { - return nil, errors.New("Unsupported encryption method: " + method) - } - - key := evpBytesToKey(password, mi.keyLen) - - c = &Cipher{key: key, info: mi} - - 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) { - 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) - return -} - -func (c *Cipher) initDecrypt(iv []byte) (err error) { - c.dec, err = c.info.newStream(c.key, iv, Decrypt) - return -} - -func (c *Cipher) encrypt(dst, src []byte) { - c.enc.XORKeyStream(dst, src) -} - -func (c *Cipher) decrypt(dst, src []byte) { - c.dec.XORKeyStream(dst, src) -} - -// Copy creates a new cipher at it's initial state. -func (c *Cipher) Copy() *Cipher { - // This optimization maybe not necessary. But without this function, we - // need to maintain a table cache for newTableCipher and use lock to - // protect concurrent access to that cache. - - // AES and DES ciphers does not return specific types, so it's difficult - // to create copy. But their initizliation time is less than 4000ns on my - // 2.26 GHz Intel Core 2 Duo processor. So no need to worry. - - // Currently, blow-fish and cast5 initialization cost is an order of - // maganitude slower than other ciphers. (I'm not sure whether this is - // because the current implementation is not highly optimized, or this is - // the nature of the algorithm.) - - nc := *c - nc.enc = nil - nc.dec = nil - nc.ota = c.ota - return &nc -} diff --git a/utils/ss/util.go b/utils/ss/util.go deleted file mode 100644 index dcc56db..0000000 --- a/utils/ss/util.go +++ /dev/null @@ -1,131 +0,0 @@ -package ss - -import ( - "crypto/hmac" - "crypto/sha1" - "encoding/binary" - "errors" - "fmt" - "io" - "net" - "os" - "strconv" - - "github.com/snail007/goproxy/utils" -) - -const leakyBufSize = 4108 // data.len(2) + hmacsha1(10) + data(4096) -const maxNBuf = 2048 - -var leakyBuf = utils.NewLeakyBuf(maxNBuf, leakyBufSize) - -func IsFileExists(path string) (bool, error) { - stat, err := os.Stat(path) - if err == nil { - if stat.Mode()&os.ModeType == 0 { - return true, nil - } - return false, errors.New(path + " exists but is not regular file") - } - if os.IsNotExist(err) { - return false, nil - } - 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...) -} - -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 - - lenIPv4 = net.IPv4len + 2 // ipv4 + 2port - lenIPv6 = net.IPv6len + 2 // ipv6 + 2port - lenDmBase = 2 // 1addrLen + 2port, plus addrLen - lenHmacSha1 = 10 -) - -func GetRequest(conn *Conn) (host string, err error) { - - // 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) + 255(max length address) + 2(port) + 10(hmac-sha1) - buf := make([]byte, 269) - // read till we get possible domain length field - if _, err = io.ReadFull(conn, buf[:idType+1]); err != nil { - return - } - - var reqStart, reqEnd int - addrType := buf[idType] - switch addrType & AddrMask { - case typeIPv4: - reqStart, reqEnd = idIP0, idIP0+lenIPv4 - case typeIPv6: - reqStart, reqEnd = idIP0, idIP0+lenIPv6 - case typeDm: - if _, err = io.ReadFull(conn, buf[idType+1:idDmLen+1]); err != nil { - return - } - reqStart, reqEnd = idDm0, idDm0+int(buf[idDmLen])+lenDmBase - default: - err = fmt.Errorf("addr type %d not supported", addrType&AddrMask) - return - } - - 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 addrType & AddrMask { - case typeIPv4: - host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String() - case typeIPv6: - host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String() - case typeDm: - host = string(buf[idDm0 : idDm0+int(buf[idDmLen])]) - } - // parse port - port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd]) - host = net.JoinHostPort(host, strconv.Itoa(int(port))) - - return -} - -type ClosedFlag struct { - flag bool -} - -func (flag *ClosedFlag) SetClosed() { - flag.flag = true -} - -func (flag *ClosedFlag) IsClosed() bool { - return flag.flag -}