diff --git a/client/flyclient.go b/client/flyclient.go index 5331f58..11b7df2 100644 --- a/client/flyclient.go +++ b/client/flyclient.go @@ -6,6 +6,8 @@ type FlyClient struct { Mode int localhost string Ports []string // ports[0] stands for the listening port; others are for reserve + Method string + Password string protocol string // tcp or udp protocol ServerAddr string @@ -19,8 +21,8 @@ func (client *FlyClient) LocalHttpProxy(port string) { fly.StartHttp(port) } -func (client *FlyClient) Socks5ProxyForTCP(localPort, serverAddr string) { - fly.Socks5ForClientByTCP(localPort, serverAddr) +func (client *FlyClient) Socks5ProxyForTCP(localPort, serverAddr, method, key string) { + fly.Socks5ForClientByTCP(localPort, serverAddr, method, key) } func (client *FlyClient) Socks5ProxyForUDP(localPort, serverAddr string) { diff --git a/cmd/client/client.go b/cmd/client/client.go index 831b5aa..79cb18d 100644 --- a/cmd/client/client.go +++ b/cmd/client/client.go @@ -30,7 +30,7 @@ func main() { case 2: flyClient.LocalSocks5Proxy(flyClient.Ports[0]) case 3: - flyClient.Socks5ProxyForTCP(flyClient.Ports[0], flyClient.ServerAddr) + flyClient.Socks5ProxyForTCP(flyClient.Ports[0], flyClient.ServerAddr, flyClient.Method, flyClient.Password) case 4: flyClient.Socks5ProxyForUDP(flyClient.Ports[0], flyClient.ServerAddr) case 5: @@ -48,8 +48,12 @@ func printHelp() { 'socks5-tcp', 'socks5-udp', 'forward'] -L, --listen choose which port(s) to listen or forward -S, --server the server address client connect to + -m, --method choose a encrypt method, which must be one of ['aes-128-cfb','aes-192-cfb', + 'aes-256-cfb', 'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr', 'rc4-md5', + 'rc4-md5-6', 'chacha20', 'chacha20-ietf'], default is 'aes-256-cfb' + -P, --password password of server -V, --verbose output detail info - -l, --logs output detail info to logs file + -l, --logs output detail info to logs file -H, --help show detail usage Mail bug reports and suggestions to @@ -110,6 +114,24 @@ func parseArgs(args []string) { printHelp() os.Exit(1) } + case "-m", "--method": + if len(args) > 1 && !strings.HasPrefix(args[1], "-") { + flyClient.Method = args[1] + parseArgs(args[2:]) + } else { + fmt.Println("fly: no password found!") + printHelp() + os.Exit(1) + } + case "--password", "-P": + if len(args) > 1 && !strings.HasPrefix(args[1], "-") { + flyClient.Password = args[1] + parseArgs(args[2:]) + } else { + fmt.Println("fly: no password found!") + printHelp() + os.Exit(1) + } case "--verbose", "-V": logs.EnableDebug(true) parseArgs(args[1:]) diff --git a/cmd/server/server.go b/cmd/server/server.go index cd09acb..88b3215 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -31,7 +31,7 @@ func main() { case 2: flyServer.LocalSocks5Proxy(flyServer.Ports[0]) case 3: - flyServer.Socks5ProxyForTCP(flyServer.Ports[0]) + flyServer.Socks5ProxyForTCP(flyServer.Ports[0], flyServer.Method, flyServer.Password) case 4: flyServer.Socks5ProxyForUDP(flyServer.Ports[0]) case 5: @@ -48,8 +48,12 @@ func printHelp() { -M, --mode choose which mode to run. the mode must be one of['http', 'socks5', 'socks5-tcp', 'socks5-udp', 'forward'] -L, --listen choose which port(s) to listen or forward + -m, --method choose a encrypt method, which must be one of ['aes-128-cfb','aes-192-cfb', + 'aes-256-cfb', 'aes-128-ctr', 'aes-192-ctr', 'aes-256-ctr', 'rc4-md5', + 'rc4-md5-6', 'chacha20', 'chacha20-ietf'], default is 'aes-256-cfb' + -P, --password password for client connecting -V, --verbose output detail info - -l, --logs output detail info to logs file + -l, --logs output detail info to logs file -H, --help show detail usage Mail bug reports and suggestions to @@ -101,6 +105,24 @@ func parseArgs(args []string) { printHelp() os.Exit(1) } + case "-m", "--method": + if len(args) > 1 && !strings.HasPrefix(args[1], "-") { + flyServer.Method = args[1] + parseArgs(args[2:]) + } else { + fmt.Println("fly: no password found!") + printHelp() + os.Exit(1) + } + case "--password", "-P": + if len(args) > 1 && !strings.HasPrefix(args[1], "-") { + flyServer.Password = args[1] + parseArgs(args[2:]) + } else { + fmt.Println("fly: no password found!") + printHelp() + os.Exit(1) + } case "--verbose", "-V": logs.EnableDebug(true) parseArgs(args[1:]) diff --git a/fly/cipher.go b/fly/cipher.go index fb5829f..369c90c 100644 --- a/fly/cipher.go +++ b/fly/cipher.go @@ -3,14 +3,51 @@ package fly import ( "crypto/aes" "crypto/cipher" + "crypto/md5" + "crypto/rc4" + "fmt" + + "github.com/aead/chacha20" ) -const key = "asche910-flynet-" +var key = "asche910-flynet-" + +type EncOrDec int + +const ( + ENC EncOrDec = iota + DEC +) var ( - commonIV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f} + IV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f} ) +var cipherMap = map[string]*cipherEntity{ + "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}, + "rc4-md5": {16, 16, newRC4MD5Stream}, + "rc4-md5-6": {16, 6, newRC4MD5Stream}, + "chacha20": {32, 8, newChaCha20Stream}, + "chacha20-ietf": {32, 12, newChaCha20Stream}, +} + +// generate a fixed length key +func genKey(rawKey string, len int) []byte { + key := make([]byte, 256) + cur := 0 + for cur < len { + sum := md5.Sum([]byte(fmt.Sprintf("%s%d", rawKey, cur))) + copy(key[cur:cur+16], sum[:]) + cur += 16 + } + return key[:len] +} + type Cipher struct { encoder cipher.Stream decoder cipher.Stream @@ -18,20 +55,78 @@ type Cipher struct { method string } -func NewCipherInstance() *Cipher{ - c, err := aes.NewCipher([]byte(key)) +func (cipher *Cipher) Encrypt(dst, src []byte) { + cipher.encoder.XORKeyStream(dst, src) +} + +func (cipher *Cipher) Decrypt(dst, src []byte) { + cipher.decoder.XORKeyStream(dst, src) +} + +// a detail encrypt or decrypt method +type cipherEntity struct { + keyLen int + ivLen int + newStream func(key, iv []byte, eod EncOrDec) cipher.Stream +} + +func NewCipherInstance(secretKey, method string) *Cipher { + if secretKey == "" { + secretKey = key + } + entity := cipherMap[method] + if entity == nil { + entity = cipherMap["aes-256-cfb"] + } + key := genKey(secretKey, entity.keyLen) + newIV := IV[:entity.ivLen] + enc := entity.newStream(key, newIV, ENC) + dec := entity.newStream(key, newIV, DEC) + return &Cipher{encoder: enc, decoder: dec} +} + +func newAESCFBStream(key, iv []byte, eod EncOrDec) cipher.Stream { + block, err := aes.NewCipher(key) if err != nil { logger.Println("aes.NewCipher failed!", err) + return nil + } + if eod == ENC { + enc := cipher.NewCFBEncrypter(block, IV) + return enc + } else { + dec := cipher.NewCFBDecrypter(block, IV) + return dec } - enc := cipher.NewCFBEncrypter(c, commonIV) - dec := cipher.NewCFBDecrypter(c, commonIV) - return &Cipher{encoder:enc, decoder:dec} } -func (cipher *Cipher) Encrypt(dst, src []byte) { - cipher.encoder.XORKeyStream(dst, src) +func newAESCTRStream(key, iv []byte, eod EncOrDec) cipher.Stream { + block, err := aes.NewCipher(key) + if err != nil { + logger.Println("aes.NewCipher failed!", err) + return nil + } + return cipher.NewCTR(block, iv) } -func (cipher *Cipher) Decrypt(dst, src []byte) { - cipher.decoder.XORKeyStream(dst, src) +func newChaCha20Stream(key, iv []byte, eod EncOrDec) cipher.Stream { + stream, err := chacha20.NewCipher(iv, key) + if err != nil { + logger.Println("chacha20.NewCipher failed!", err) + return nil + } + return stream +} + +func newRC4MD5Stream(key, iv []byte, eod EncOrDec) cipher.Stream { + hs := md5.New() + hs.Write(key) + hs.Write(iv) + rc4key := hs.Sum(nil) + stream, err := rc4.NewCipher(rc4key) + if err != nil { + logger.Println("rc4.NewCipher failed!", err) + return nil + } + return stream } diff --git a/fly/cipher_test.go b/fly/cipher_test.go new file mode 100644 index 0000000..055ae92 --- /dev/null +++ b/fly/cipher_test.go @@ -0,0 +1,48 @@ +package fly + +import ( + "fmt" + "testing" +) + +const text = "hello,world!" + +func testCipher(t *testing.T, method string) { + textLen := len(text) + cipherInstance := NewCipherInstance(key, method) + + textBuff := make([]byte, textLen) + encryptBuff := make([]byte, textLen) + + cipherInstance.Encrypt(encryptBuff, []byte(text)) + fmt.Println("after", encryptBuff) + cipherInstance.Decrypt(textBuff, encryptBuff) + if string(textBuff) != text { + t.Error(method, " test failed!") + } +} + +func TestCipher(t *testing.T) { + key := "123456" + keyLen := len(key) + buff := make([]byte, 1024) + cipherInstance := NewCipherInstance(key, "aes-128-cfb") + cipherInstance.encoder.XORKeyStream(buff, []byte(key)) + fmt.Println("before", string(buff[:keyLen])) + cipherInstance.decoder.XORKeyStream(buff, buff[:keyLen]) + fmt.Println("after", string(buff[:keyLen])) +} + +func TestAESCTR(t *testing.T) { + testCipher(t, "aes-128-ctr") +} + +func TestChacha20(t *testing.T) { + testCipher(t, "chacha20") + //testCipher(t, "chacha20-ietf") +} + +func TestRC4MD5(t *testing.T) { + testCipher(t, "rc4-md5") + //testCipher(t, "rc4-md5-6") +} \ No newline at end of file diff --git a/fly/conn.go b/fly/conn.go index 490d21e..47f873d 100644 --- a/fly/conn.go +++ b/fly/conn.go @@ -17,13 +17,13 @@ func NewConn(con net.Conn, cipher *Cipher) *Conn { } // dial server and send request addr of client -func DialWithAddr(server string, addr []byte) *Conn { +func DialWithAddr(server, method, key string, addr []byte) *Conn { conn, err := net.Dial("tcp", server) if err != nil { logger.Println("Dial server failed!", err) return nil } - newConn := NewConn(conn, NewCipherInstance()) + newConn := NewConn(conn, NewCipherInstance(key, method)) if _, err := newConn.Write(addr); err != nil { logger.Println("write addr to server failed!", err) return nil diff --git a/fly/error_utils.go b/fly/error_utils.go index b803ba4..b6fcdeb 100644 --- a/fly/error_utils.go +++ b/fly/error_utils.go @@ -10,7 +10,6 @@ var logger *log.Logger func InitLog() { logger = logs.GetLogger() - } // just check error and print if err is not nil diff --git a/fly/relay.go b/fly/relay.go index 94d7c94..4c8ac47 100644 --- a/fly/relay.go +++ b/fly/relay.go @@ -48,10 +48,10 @@ func RelayTraffic(dst, src net.Conn) { fmt.Println(err) break } - fmt.Println("Read", n) + //logger.Println("Read", n) if n > 0 { if n, err = dst.Write(buff[:n]); err != nil { - fmt.Println(err) + logger.Println(err) break } } diff --git a/fly/socks5.go b/fly/socks5.go index 451b716..270801c 100644 --- a/fly/socks5.go +++ b/fly/socks5.go @@ -2,7 +2,6 @@ package fly import ( "crypto/sha1" - "fmt" "github.com/xtaci/kcp-go" "golang.org/x/crypto/pbkdf2" "io" @@ -67,7 +66,7 @@ func handleClient(client net.Conn) { } } -func Socks5ForClientByTCP(localPort, serverAddr string) { +func Socks5ForClientByTCP(localPort, serverAddr, method, key string) { listener := ListenTCP(localPort) for { client, err := listener.Accept() @@ -104,7 +103,7 @@ func Socks5ForClientByTCP(localPort, serverAddr string) { // | 1 | 1 | X'00' | 1 | Variable | 2 | // +----+-----+-------+------+----------+----------+ // - server := DialWithAddr(serverAddr, buff[:n]) + server := DialWithAddr(serverAddr, method, key, buff[:n]) if server == nil { return } @@ -116,22 +115,26 @@ func Socks5ForClientByTCP(localPort, serverAddr string) { } } -func Socks5ForServerByTCP(localPort string) { +func Socks5ForServerByTCP(localPort, method, key string) { listener := ListenTCP(localPort) for { logger.Println("waiting...") client, err := listener.Accept() if err != nil { - fmt.Println("server accept failed!", err) + logger.Println("server accept failed!", err) continue } go func() { buff := make([]byte, 1024) - conn := NewConn(client, NewCipherInstance()) + conn := NewConn(client, NewCipherInstance(key, method)) n, err := conn.Read(buff) + if err != nil { + logger.Println("read target address failed!", err) + return + } host, port := parseSocksRequest(buff[:n], n) - fmt.Printf("target server %s:%s\n", host, port) + logger.Printf("target server %s:%s\n", host, port) // dial the target server server, err := net.Dial("tcp", net.JoinHostPort(host, port)) diff --git a/server/flyserver.go b/server/flyserver.go index fa24575..5d34d0d 100644 --- a/server/flyserver.go +++ b/server/flyserver.go @@ -9,6 +9,8 @@ type FlyServer struct { Mode int // which function to use, [1:'http', 2:'socks5', 3:'socks5-tcp', 4:'socks5-udp', 5:'forward'] localHost string Ports []string // ports[0] stands for the listening port; others are for reserve + Method string + Password string protocol string // tcp or udp protocol clients map[string]net.Conn @@ -22,8 +24,8 @@ func (server *FlyServer) LocalHttpProxy(port string) { fly.StartHttp(port) } -func (server *FlyServer) Socks5ProxyForTCP(localPort string) { - fly.Socks5ForServerByTCP(localPort) +func (server *FlyServer) Socks5ProxyForTCP(localPort, method, key string) { + fly.Socks5ForServerByTCP(localPort, method, key) } func (server *FlyServer) Socks5ProxyForUDP(localPort string) {