Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>
This commit is contained in:
@ -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.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.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.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.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.DisableSocks5 = sps.Flag("disable-socks", "disable socks proxy").Default("false").Bool()
|
||||||
spsArgs.DisableSS = sps.Flag("disable-ss", "").Hidden().Default("false").Bool()
|
|
||||||
|
|
||||||
//parse args
|
//parse args
|
||||||
serviceName := kingpin.MustParse(app.Parse(os.Args[1:]))
|
serviceName := kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||||
|
|||||||
@ -232,13 +232,8 @@ type SPSArgs struct {
|
|||||||
ParentKey *string
|
ParentKey *string
|
||||||
LocalCompress *bool
|
LocalCompress *bool
|
||||||
ParentCompress *bool
|
ParentCompress *bool
|
||||||
SSMethod *string
|
|
||||||
SSKey *string
|
|
||||||
ParentSSMethod *string
|
|
||||||
ParentSSKey *string
|
|
||||||
DisableHTTP *bool
|
DisableHTTP *bool
|
||||||
DisableSocks5 *bool
|
DisableSocks5 *bool
|
||||||
DisableSS *bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SPSArgs) Protocol() string {
|
func (a *SPSArgs) Protocol() string {
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/snail007/goproxy/utils"
|
"github.com/snail007/goproxy/utils"
|
||||||
"github.com/snail007/goproxy/utils/conncrypt"
|
"github.com/snail007/goproxy/utils/conncrypt"
|
||||||
"github.com/snail007/goproxy/utils/socks"
|
"github.com/snail007/goproxy/utils/socks"
|
||||||
"github.com/snail007/goproxy/utils/ss"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SPS struct {
|
type SPS struct {
|
||||||
@ -27,8 +26,6 @@ type SPS struct {
|
|||||||
serverChannels []*utils.ServerChannel
|
serverChannels []*utils.ServerChannel
|
||||||
userConns utils.ConcurrentMap
|
userConns utils.ConcurrentMap
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
localCipher *ss.Cipher
|
|
||||||
parentCipher *ss.Cipher
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSPS() Service {
|
func NewSPS() Service {
|
||||||
@ -49,10 +46,6 @@ func (s *SPS) CheckArgs() (err error) {
|
|||||||
err = fmt.Errorf("parent type unkown,use -T <tls|tcp|kcp>")
|
err = fmt.Errorf("parent type unkown,use -T <tls|tcp|kcp>")
|
||||||
return
|
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 <sskey>")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS {
|
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)
|
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -69,28 +62,11 @@ func (s *SPS) CheckArgs() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
func (s *SPS) InitService() (err error) {
|
func (s *SPS) InitService() (err error) {
|
||||||
|
|
||||||
if *s.cfg.DNSAddress != "" {
|
if *s.cfg.DNSAddress != "" {
|
||||||
(*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL, s.log)
|
(*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL, s.log)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.InitOutConnPool()
|
s.InitOutConnPool()
|
||||||
|
|
||||||
err = s.InitBasicAuth()
|
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
|
return
|
||||||
}
|
}
|
||||||
func (s *SPS) InitOutConnPool() {
|
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]}
|
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 == "" {
|
if err != nil || address == "" {
|
||||||
s.log.Printf("unknown request from: %s,%s", (*inConn).RemoteAddr(), string(h))
|
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)
|
forwardBytes = utils.RemoveProxyHeaders(forwardBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,18 +368,6 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) {
|
|||||||
if err = clientConn.Handshake(); err != nil {
|
if err = clientConn.Handshake(); err != nil {
|
||||||
return
|
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.
|
//forward client data to target,if necessary.
|
||||||
|
|||||||
193
utils/ss/conn.go
193
utils/ss/conn.go
@ -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
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
131
utils/ss/util.go
131
utils/ss/util.go
@ -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
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user