mirror of https://github.com/cbeuw/Cloak
Cloak 2: generalising cloak as a universal pluggable transport for arbitary proxies
This commit is contained in:
parent
ebd7e6b1bd
commit
3e9855191b
|
|
@ -48,12 +48,12 @@ func makeRemoteConn(sta *client.State) (net.Conn, error) {
|
||||||
d := net.Dialer{Control: protector}
|
d := net.Dialer{Control: protector}
|
||||||
|
|
||||||
clientHello := TLS.ComposeInitHandshake(sta)
|
clientHello := TLS.ComposeInitHandshake(sta)
|
||||||
connectingIP := sta.SS_REMOTE_HOST
|
connectingIP := sta.RemoteHost
|
||||||
if net.ParseIP(connectingIP).To4() == nil {
|
if net.ParseIP(connectingIP).To4() == nil {
|
||||||
// IPv6 needs square brackets
|
// IPv6 needs square brackets
|
||||||
connectingIP = "[" + connectingIP + "]"
|
connectingIP = "[" + connectingIP + "]"
|
||||||
}
|
}
|
||||||
remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.SS_REMOTE_PORT)
|
remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.RemotePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Connecting to remote: %v\n", err)
|
log.Printf("Connecting to remote: %v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -86,15 +86,15 @@ func makeRemoteConn(sta *client.State) (net.Conn, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Should be 127.0.0.1 to listen to ss-local on this machine
|
// Should be 127.0.0.1 to listen to a proxy client on this machine
|
||||||
var localHost string
|
var localHost string
|
||||||
// server_port in ss config, ss sends data on loopback using this port
|
// port used by proxy clients to communicate with cloak client
|
||||||
var localPort string
|
var localPort string
|
||||||
// The ip of the proxy server
|
// The ip of the proxy server
|
||||||
var remoteHost string
|
var remoteHost string
|
||||||
// The proxy port,should be 443
|
// The proxy port,should be 443
|
||||||
var remotePort string
|
var remotePort string
|
||||||
var pluginOpts string
|
var config string
|
||||||
isAdmin := new(bool)
|
isAdmin := new(bool)
|
||||||
|
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
|
@ -106,13 +106,13 @@ func main() {
|
||||||
localPort = os.Getenv("SS_LOCAL_PORT")
|
localPort = os.Getenv("SS_LOCAL_PORT")
|
||||||
remoteHost = os.Getenv("SS_REMOTE_HOST")
|
remoteHost = os.Getenv("SS_REMOTE_HOST")
|
||||||
remotePort = os.Getenv("SS_REMOTE_PORT")
|
remotePort = os.Getenv("SS_REMOTE_PORT")
|
||||||
pluginOpts = os.Getenv("SS_PLUGIN_OPTIONS")
|
config = os.Getenv("SS_PLUGIN_OPTIONS")
|
||||||
} else {
|
} else {
|
||||||
localHost = "127.0.0.1"
|
localHost = "127.0.0.1"
|
||||||
flag.StringVar(&localPort, "l", "", "localPort: same as server_port in ss config, the plugin listens to SS using this")
|
flag.StringVar(&localPort, "l", "", "localPort: Cloak listens to proxy clients on this port")
|
||||||
flag.StringVar(&remoteHost, "s", "", "remoteHost: IP of your proxy server")
|
flag.StringVar(&remoteHost, "s", "", "remoteHost: IP of your proxy server")
|
||||||
flag.StringVar(&remotePort, "p", "443", "remotePort: proxy port, should be 443")
|
flag.StringVar(&remotePort, "p", "443", "remotePort: proxy port, should be 443")
|
||||||
flag.StringVar(&pluginOpts, "c", "ckclient.json", "pluginOpts: path to ckclient.json or options seperated with semicolons")
|
flag.StringVar(&config, "c", "ckclient.json", "config: path to the configuration file or options seperated with semicolons")
|
||||||
askVersion := flag.Bool("v", false, "Print the version number")
|
askVersion := flag.Bool("v", false, "Print the version number")
|
||||||
isAdmin = flag.Bool("a", false, "Admin mode")
|
isAdmin = flag.Bool("a", false, "Admin mode")
|
||||||
printUsage := flag.Bool("h", false, "Print this message")
|
printUsage := flag.Bool("h", false, "Print this message")
|
||||||
|
|
@ -133,7 +133,7 @@ func main() {
|
||||||
|
|
||||||
if *isAdmin {
|
if *isAdmin {
|
||||||
sta := client.InitState("", "", "", "", time.Now)
|
sta := client.InitState("", "", "", "", time.Now)
|
||||||
err := sta.ParseConfig(pluginOpts)
|
err := sta.ParseConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -145,27 +145,27 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
sta := client.InitState(localHost, localPort, remoteHost, remotePort, time.Now)
|
sta := client.InitState(localHost, localPort, remoteHost, remotePort, time.Now)
|
||||||
err := sta.ParseConfig(pluginOpts)
|
err := sta.ParseConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sta.SS_LOCAL_PORT == "" {
|
if sta.LocalPort == "" {
|
||||||
log.Fatal("Must specify localPort")
|
log.Fatal("Must specify localPort")
|
||||||
}
|
}
|
||||||
if sta.SS_REMOTE_HOST == "" {
|
if sta.RemoteHost == "" {
|
||||||
log.Fatal("Must specify remoteHost")
|
log.Fatal("Must specify remoteHost")
|
||||||
}
|
}
|
||||||
if sta.TicketTimeHint == 0 {
|
if sta.TicketTimeHint == 0 {
|
||||||
log.Fatal("TicketTimeHint cannot be empty or 0")
|
log.Fatal("TicketTimeHint cannot be empty or 0")
|
||||||
}
|
}
|
||||||
listeningIP := sta.SS_LOCAL_HOST
|
listeningIP := sta.LocalHost
|
||||||
if net.ParseIP(listeningIP).To4() == nil {
|
if net.ParseIP(listeningIP).To4() == nil {
|
||||||
// IPv6 needs square brackets
|
// IPv6 needs square brackets
|
||||||
listeningIP = "[" + listeningIP + "]"
|
listeningIP = "[" + listeningIP + "]"
|
||||||
}
|
}
|
||||||
listener, err := net.Listen("tcp", listeningIP+":"+sta.SS_LOCAL_PORT)
|
listener, err := net.Listen("tcp", listeningIP+":"+sta.LocalPort)
|
||||||
log.Println("Listening for ss on " + listeningIP + ":" + sta.SS_LOCAL_PORT)
|
log.Println("Listening for proxy clients on " + listeningIP + ":" + sta.LocalPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -207,34 +207,34 @@ start:
|
||||||
if sesh.IsBroken() {
|
if sesh.IsBroken() {
|
||||||
goto start
|
goto start
|
||||||
}
|
}
|
||||||
ssConn, err := listener.Accept()
|
localConn, err := listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
data := make([]byte, 10240)
|
data := make([]byte, 10240)
|
||||||
i, err := io.ReadAtLeast(ssConn, data, 1)
|
i, err := io.ReadAtLeast(localConn, data, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
ssConn.Close()
|
localConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
stream, err := sesh.OpenStream()
|
stream, err := sesh.OpenStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
ssConn.Close()
|
localConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = stream.Write(data[:i])
|
_, err = stream.Write(data[:i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
ssConn.Close()
|
localConn.Close()
|
||||||
stream.Close()
|
stream.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go pipe(ssConn, stream)
|
go pipe(localConn, stream)
|
||||||
pipe(stream, ssConn)
|
pipe(stream, localConn)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ func pipe(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
|
||||||
|
|
||||||
func dispatchConnection(conn net.Conn, sta *server.State) {
|
func dispatchConnection(conn net.Conn, sta *server.State) {
|
||||||
goWeb := func(data []byte) {
|
goWeb := func(data []byte) {
|
||||||
webConn, err := net.Dial("tcp", sta.WebServerAddr)
|
webConn, err := net.Dial("tcp", sta.RedirAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Making connection to redirection server: %v\n", err)
|
log.Printf("Making connection to redirection server: %v\n", err)
|
||||||
return
|
return
|
||||||
|
|
@ -64,14 +64,19 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
||||||
data := buf[:i]
|
data := buf[:i]
|
||||||
ch, err := server.ParseClientHello(data)
|
ch, err := server.ParseClientHello(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("+1 non SS non (or malformed) TLS traffic from %v\n", conn.RemoteAddr())
|
log.Printf("+1 non Cloak non (or malformed) TLS traffic from %v\n", conn.RemoteAddr())
|
||||||
goWeb(data)
|
goWeb(data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isSS, UID, sessionID := server.TouchStone(ch, sta)
|
isCloak, UID, sessionID, proxyMethod := server.TouchStone(ch, sta)
|
||||||
if !isSS {
|
if !isCloak {
|
||||||
log.Printf("+1 non SS TLS traffic from %v\n", conn.RemoteAddr())
|
log.Printf("+1 non Cloak TLS traffic from %v\n", conn.RemoteAddr())
|
||||||
|
goWeb(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := sta.ProxyBook[proxyMethod]; !ok {
|
||||||
|
log.Printf("+1 Cloak TLS traffic with invalid proxy method `%v` from %v\n", proxyMethod, conn.RemoteAddr())
|
||||||
goWeb(data)
|
goWeb(data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -167,47 +172,35 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ssIP := sta.SS_LOCAL_HOST
|
localConn, err := net.Dial("tcp", sta.ProxyBook[proxyMethod])
|
||||||
if net.ParseIP(ssIP).To4() == nil {
|
|
||||||
// IPv6 needs square brackets
|
|
||||||
ssIP = "[" + ssIP + "]"
|
|
||||||
}
|
|
||||||
ssConn, err := net.Dial("tcp", ssIP+":"+sta.SS_LOCAL_PORT)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to connect to ssserver: %v\n", err)
|
log.Printf("Failed to connect to %v: %v\n", proxyMethod, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go pipe(ssConn, newStream)
|
go pipe(localConn, newStream)
|
||||||
go pipe(newStream, ssConn)
|
go pipe(newStream, localConn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Should be 127.0.0.1 to listen to ss-server on this machine
|
|
||||||
var localHost string
|
|
||||||
// server_port in ss config, same as remotePort in plugin mode
|
|
||||||
var localPort string
|
|
||||||
// server in ss config, the outbound listening ip
|
// server in ss config, the outbound listening ip
|
||||||
var remoteHost string
|
var bindHost string
|
||||||
// Outbound listening ip, should be 443
|
// Outbound listening ip, should be 443
|
||||||
var remotePort string
|
var bindPort string
|
||||||
var pluginOpts string
|
var config string
|
||||||
|
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
|
||||||
if os.Getenv("SS_LOCAL_HOST") != "" {
|
if os.Getenv("SS_LOCAL_HOST") != "" {
|
||||||
localHost = os.Getenv("SS_LOCAL_HOST")
|
bindHost = os.Getenv("SS_REMOTE_HOST")
|
||||||
localPort = os.Getenv("SS_LOCAL_PORT")
|
bindPort = os.Getenv("SS_REMOTE_PORT")
|
||||||
remoteHost = os.Getenv("SS_REMOTE_HOST")
|
config = os.Getenv("SS_PLUGIN_OPTIONS")
|
||||||
remotePort = os.Getenv("SS_REMOTE_PORT")
|
|
||||||
pluginOpts = os.Getenv("SS_PLUGIN_OPTIONS")
|
|
||||||
} else {
|
} else {
|
||||||
localAddr := flag.String("r", "", "localAddr: the ip:port ss-server is listening on, set in Shadowsocks' configuration. If ss-server is running locally, it should be 127.0.0.1:some port")
|
flag.StringVar(&bindHost, "s", "0.0.0.0", "bindHost: ip to bind to, set to 0.0.0.0 to listen to everything")
|
||||||
flag.StringVar(&remoteHost, "s", "0.0.0.0", "remoteHost: outbound listing ip, set to 0.0.0.0 to listen to everything")
|
flag.StringVar(&bindPort, "p", "443", "bindPort: port to bind to, should be 443")
|
||||||
flag.StringVar(&remotePort, "p", "443", "remotePort: outbound listing port, should be 443")
|
flag.StringVar(&config, "c", "server.json", "config: path to the configuration file or its content")
|
||||||
flag.StringVar(&pluginOpts, "c", "server.json", "pluginOpts: path to server.json or options seperated by semicolons")
|
|
||||||
askVersion := flag.Bool("v", false, "Print the version number")
|
askVersion := flag.Bool("v", false, "Print the version number")
|
||||||
printUsage := flag.Bool("h", false, "Print this message")
|
printUsage := flag.Bool("h", false, "Print this message")
|
||||||
|
|
||||||
|
|
@ -240,20 +233,25 @@ func main() {
|
||||||
startPprof(*pprofAddr)
|
startPprof(*pprofAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *localAddr == "" {
|
log.Printf("Starting standalone mode, listening on %v:%v", bindHost, bindPort)
|
||||||
log.Fatal("Must specify localAddr")
|
|
||||||
}
|
|
||||||
localHost = strings.Split(*localAddr, ":")[0]
|
|
||||||
localPort = strings.Split(*localAddr, ":")[1]
|
|
||||||
log.Printf("Starting standalone mode, listening on %v:%v to ss at %v:%v\n", remoteHost, remotePort, localHost, localPort)
|
|
||||||
}
|
}
|
||||||
sta, _ := server.InitState(localHost, localPort, remoteHost, remotePort, time.Now)
|
sta, _ := server.InitState(bindHost, bindPort, time.Now)
|
||||||
|
|
||||||
err := sta.ParseConfig(pluginOpts)
|
err := sta.ParseConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Configuration file error: %v", err)
|
log.Fatalf("Configuration file error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when cloak is started as a shadowsocks plugin
|
||||||
|
if os.Getenv("SS_LOCAL_HOST") != "" && os.Getenv("SS_LOCAL_PORT") != "" {
|
||||||
|
ssLocalHost := os.Getenv("SS_LOCAL_HOST")
|
||||||
|
ssLocalPort := os.Getenv("SS_LOCAL_PORT")
|
||||||
|
if net.ParseIP(ssLocalHost).To4() == nil {
|
||||||
|
ssLocalHost = "[" + ssLocalHost + "]"
|
||||||
|
}
|
||||||
|
sta.ProxyBook["shadowsocks"] = ssLocalHost + ":" + ssLocalPort
|
||||||
|
}
|
||||||
|
|
||||||
if sta.AdminUID == nil {
|
if sta.AdminUID == nil {
|
||||||
log.Fatalln("AdminUID cannot be empty!")
|
log.Fatalln("AdminUID cannot be empty!")
|
||||||
}
|
}
|
||||||
|
|
@ -277,7 +275,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// When listening on an IPv6 and IPv4, SS gives REMOTE_HOST as e.g. ::|0.0.0.0
|
// When listening on an IPv6 and IPv4, SS gives REMOTE_HOST as e.g. ::|0.0.0.0
|
||||||
listeningIP := strings.Split(sta.SS_REMOTE_HOST, "|")
|
listeningIP := strings.Split(sta.BindHost, "|")
|
||||||
for i, ip := range listeningIP {
|
for i, ip := range listeningIP {
|
||||||
if net.ParseIP(ip).To4() == nil {
|
if net.ParseIP(ip).To4() == nil {
|
||||||
// IPv6 needs square brackets
|
// IPv6 needs square brackets
|
||||||
|
|
@ -286,9 +284,9 @@ func main() {
|
||||||
|
|
||||||
// The last listener must block main() because the program exits on main return.
|
// The last listener must block main() because the program exits on main return.
|
||||||
if i == len(listeningIP)-1 {
|
if i == len(listeningIP)-1 {
|
||||||
listen(ip, sta.SS_REMOTE_PORT)
|
listen(ip, sta.BindPort)
|
||||||
} else {
|
} else {
|
||||||
go listen(ip, sta.SS_REMOTE_PORT)
|
go listen(ip, sta.BindPort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
{
|
{
|
||||||
|
"ProxyMethod":"shadowsocks",
|
||||||
"UID":"iGAO85zysIyR4c09CyZSLdNhtP/ckcYu7nIPI082AHA=",
|
"UID":"iGAO85zysIyR4c09CyZSLdNhtP/ckcYu7nIPI082AHA=",
|
||||||
"PublicKey":"IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=",
|
"PublicKey":"IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=",
|
||||||
"ServerName":"www.bing.com",
|
"ServerName":"www.bing.com",
|
||||||
"TicketTimeHint":3600,
|
"TicketTimeHint":3600,
|
||||||
"NumConn":4,
|
"NumConn":4,
|
||||||
"MaskBrowser":"chrome"
|
"BrowserSig":"chrome"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
{
|
{
|
||||||
"WebServerAddr":"204.79.197.200:443",
|
"ProxyBook":{
|
||||||
|
"shadowsocks":"127.0.0.1:8388",
|
||||||
|
"openvpn":"127.0.0.1:8389",
|
||||||
|
"tor":"127.0.0.1:9001"
|
||||||
|
},
|
||||||
|
"RedirAddr":"204.79.197.200:443",
|
||||||
"PrivateKey":"EN5aPEpNBO+vw+BtFQY2OnK9bQU7rvEj5qmnmgwEtUc=",
|
"PrivateKey":"EN5aPEpNBO+vw+BtFQY2OnK9bQU7rvEj5qmnmgwEtUc=",
|
||||||
"AdminUID":"ugDmcEmxWf0pKxfkZ/8EoP35Ht+wQnqf3L0xYgyQFlQ=",
|
"AdminUID":"ugDmcEmxWf0pKxfkZ/8EoP35Ht+wQnqf3L0xYgyQFlQ=",
|
||||||
"DatabasePath":"userinfo.db",
|
"DatabasePath":"userinfo.db",
|
||||||
|
|
|
||||||
|
|
@ -49,13 +49,13 @@ func addExtRec(typ []byte, data []byte) []byte {
|
||||||
// ComposeInitHandshake composes ClientHello with record layer
|
// ComposeInitHandshake composes ClientHello with record layer
|
||||||
func ComposeInitHandshake(sta *client.State) []byte {
|
func ComposeInitHandshake(sta *client.State) []byte {
|
||||||
var ch []byte
|
var ch []byte
|
||||||
switch sta.MaskBrowser {
|
switch sta.BrowserSig {
|
||||||
case "chrome":
|
case "chrome":
|
||||||
ch = (&chrome{}).composeClientHello(sta)
|
ch = (&chrome{}).composeClientHello(sta)
|
||||||
case "firefox":
|
case "firefox":
|
||||||
ch = (&firefox{}).composeClientHello(sta)
|
ch = (&firefox{}).composeClientHello(sta)
|
||||||
default:
|
default:
|
||||||
panic("Unsupported browser:" + sta.MaskBrowser)
|
panic("Unsupported browser:" + sta.BrowserSig)
|
||||||
}
|
}
|
||||||
return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01})
|
return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ func MakeRandomField(sta *State) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeSessionTicket(sta *State) []byte {
|
func MakeSessionTicket(sta *State) []byte {
|
||||||
// sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID+sessionID 36 bytes][padding 124 bytes]
|
// sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID+sessionID 36 bytes and proxy method 16 bytes][padding 108 bytes]
|
||||||
// The first 16 bytes of the marshalled ephemeral public key is used as the IV
|
// The first 16 bytes of the marshalled ephemeral public key is used as the IV
|
||||||
// for encrypting the UID
|
// for encrypting the UID
|
||||||
tthInterval := sta.Now().Unix() / int64(sta.TicketTimeHint)
|
tthInterval := sta.Now().Unix() / int64(sta.TicketTimeHint)
|
||||||
|
|
@ -52,11 +52,12 @@ func MakeSessionTicket(sta *State) []byte {
|
||||||
ticket := make([]byte, 192)
|
ticket := make([]byte, 192)
|
||||||
copy(ticket[0:32], ecdh.Marshal(ephKP.PublicKey))
|
copy(ticket[0:32], ecdh.Marshal(ephKP.PublicKey))
|
||||||
key := ecdh.GenerateSharedSecret(ephKP.PrivateKey, sta.staticPub)
|
key := ecdh.GenerateSharedSecret(ephKP.PrivateKey, sta.staticPub)
|
||||||
plainUIDsID := make([]byte, 36)
|
plain := make([]byte, 52)
|
||||||
copy(plainUIDsID, sta.UID)
|
copy(plain, sta.UID)
|
||||||
binary.BigEndian.PutUint32(plainUIDsID[32:36], sta.sessionID)
|
binary.BigEndian.PutUint32(plain[32:36], sta.sessionID)
|
||||||
cipherUIDsID := util.AESEncrypt(ticket[0:16], key, plainUIDsID)
|
copy(plain[36:52], []byte(sta.ProxyMethod))
|
||||||
copy(ticket[32:68], cipherUIDsID)
|
cipher := util.AESEncrypt(ticket[0:16], key, plain)
|
||||||
|
copy(ticket[32:84], cipher)
|
||||||
// The purpose of adding sessionID is that, the generated padding of sessionTicket needs to be unpredictable.
|
// The purpose of adding sessionID is that, the generated padding of sessionTicket needs to be unpredictable.
|
||||||
// As shown in auth.go, the padding is generated by a psudo random generator. The seed
|
// As shown in auth.go, the padding is generated by a psudo random generator. The seed
|
||||||
// needs to be the same for each TicketTimeHint interval. However the value of epoch/TicketTimeHint
|
// needs to be the same for each TicketTimeHint interval. However the value of epoch/TicketTimeHint
|
||||||
|
|
@ -67,6 +68,6 @@ func MakeSessionTicket(sta *State) []byte {
|
||||||
// With the sessionID value generated at startup of ckclient and used as a part of the seed, the
|
// With the sessionID value generated at startup of ckclient and used as a part of the seed, the
|
||||||
// sessionTicket is still identical for each TicketTimeHint interval, but others won't be able to know
|
// sessionTicket is still identical for each TicketTimeHint interval, but others won't be able to know
|
||||||
// how it was generated. It will also be different for each client.
|
// how it was generated. It will also be different for each client.
|
||||||
copy(ticket[68:192], util.PsudoRandBytes(124, tthInterval+int64(sta.sessionID)))
|
copy(ticket[84:192], util.PsudoRandBytes(108, tthInterval+int64(sta.sessionID)))
|
||||||
return ticket
|
return ticket
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,20 @@ import (
|
||||||
|
|
||||||
type rawConfig struct {
|
type rawConfig struct {
|
||||||
ServerName string
|
ServerName string
|
||||||
|
ProxyMethod string
|
||||||
UID string
|
UID string
|
||||||
PublicKey string
|
PublicKey string
|
||||||
TicketTimeHint int
|
TicketTimeHint int
|
||||||
MaskBrowser string
|
BrowserSig string
|
||||||
NumConn int
|
NumConn int
|
||||||
}
|
}
|
||||||
|
|
||||||
// State stores global variables
|
// State stores global variables
|
||||||
type State struct {
|
type State struct {
|
||||||
SS_LOCAL_HOST string
|
LocalHost string
|
||||||
SS_LOCAL_PORT string
|
LocalPort string
|
||||||
SS_REMOTE_HOST string
|
RemoteHost string
|
||||||
SS_REMOTE_PORT string
|
RemotePort string
|
||||||
|
|
||||||
Now func() time.Time
|
Now func() time.Time
|
||||||
sessionID uint32
|
sessionID uint32
|
||||||
|
|
@ -36,19 +37,20 @@ type State struct {
|
||||||
keyPairsM sync.RWMutex
|
keyPairsM sync.RWMutex
|
||||||
keyPairs map[int64]*keyPair
|
keyPairs map[int64]*keyPair
|
||||||
|
|
||||||
|
ProxyMethod string
|
||||||
TicketTimeHint int
|
TicketTimeHint int
|
||||||
ServerName string
|
ServerName string
|
||||||
MaskBrowser string
|
BrowserSig string
|
||||||
NumConn int
|
NumConn int
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State {
|
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State {
|
||||||
ret := &State{
|
ret := &State{
|
||||||
SS_LOCAL_HOST: localHost,
|
LocalHost: localHost,
|
||||||
SS_LOCAL_PORT: localPort,
|
LocalPort: localPort,
|
||||||
SS_REMOTE_HOST: remoteHost,
|
RemoteHost: remoteHost,
|
||||||
SS_REMOTE_PORT: remotePort,
|
RemotePort: remotePort,
|
||||||
Now: nowFunc,
|
Now: nowFunc,
|
||||||
}
|
}
|
||||||
ret.keyPairs = make(map[int64]*keyPair)
|
ret.keyPairs = make(map[int64]*keyPair)
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -102,9 +104,10 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
sta.ProxyMethod = preParse.ProxyMethod
|
||||||
sta.ServerName = preParse.ServerName
|
sta.ServerName = preParse.ServerName
|
||||||
sta.TicketTimeHint = preParse.TicketTimeHint
|
sta.TicketTimeHint = preParse.TicketTimeHint
|
||||||
sta.MaskBrowser = preParse.MaskBrowser
|
sta.BrowserSig = preParse.BrowserSig
|
||||||
sta.NumConn = preParse.NumConn
|
sta.NumConn = preParse.NumConn
|
||||||
uid, err := base64.StdEncoding.DecodeString(preParse.UID)
|
uid, err := base64.StdEncoding.DecodeString(preParse.UID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,11 @@ import (
|
||||||
// order of arrival is not guaranteed. A stream's first packet may be sent through
|
// order of arrival is not guaranteed. A stream's first packet may be sent through
|
||||||
// connection0 and its second packet may be sent through connection1. Although both
|
// connection0 and its second packet may be sent through connection1. Although both
|
||||||
// packets are transmitted reliably (as TCP is reliable), packet1 may arrive to the
|
// packets are transmitted reliably (as TCP is reliable), packet1 may arrive to the
|
||||||
// remote side before packet0.
|
// remote side before packet0. Cloak have to therefore sequence the packets so that they
|
||||||
//
|
// arrive in order as they were sent by the proxy software
|
||||||
// However, shadowsocks' protocol does not provide sequence control. We must therefore
|
|
||||||
// make sure packets arrive in order.
|
|
||||||
//
|
//
|
||||||
// Cloak packets will have a 32-bit sequence number on them, so we know in which order
|
// Cloak packets will have a 32-bit sequence number on them, so we know in which order
|
||||||
// they should be sent to shadowsocks. The code in this file provides buffering and sorting.
|
// they should be sent to the proxy software. The code in this file provides buffering and sorting.
|
||||||
//
|
//
|
||||||
// Similar to TCP, the next seq number after 2^32-1 is 0. This is called wrap around.
|
// Similar to TCP, the next seq number after 2^32-1 is 0. This is called wrap around.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// input ticket, return UID
|
// input ticket, return UID
|
||||||
func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) ([]byte, uint32) {
|
func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) ([]byte, uint32, string) {
|
||||||
ephPub, _ := ecdh.Unmarshal(ticket[0:32])
|
ephPub, _ := ecdh.Unmarshal(ticket[0:32])
|
||||||
key := ecdh.GenerateSharedSecret(staticPv, ephPub)
|
key := ecdh.GenerateSharedSecret(staticPv, ephPub)
|
||||||
UIDsID := util.AESDecrypt(ticket[0:16], key, ticket[32:68])
|
plain := util.AESDecrypt(ticket[0:16], key, ticket[32:84])
|
||||||
sessionID := binary.BigEndian.Uint32(UIDsID[32:36])
|
sessionID := binary.BigEndian.Uint32(plain[32:36])
|
||||||
return UIDsID[0:32], sessionID
|
return plain[0:32], sessionID, string(bytes.Trim(plain[36:52], "\x00"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRandom(random []byte, UID []byte, time int64) bool {
|
func validateRandom(random []byte, UID []byte, time int64) bool {
|
||||||
|
|
@ -32,7 +32,7 @@ func validateRandom(random []byte, UID []byte, time int64) bool {
|
||||||
h.Write(preHash)
|
h.Write(preHash)
|
||||||
return bytes.Equal(h.Sum(nil)[0:16], random[16:32])
|
return bytes.Equal(h.Sum(nil)[0:16], random[16:32])
|
||||||
}
|
}
|
||||||
func TouchStone(ch *ClientHello, sta *State) (isSS bool, UID []byte, sessionID uint32) {
|
func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID uint32, proxyMethod string) {
|
||||||
var random [32]byte
|
var random [32]byte
|
||||||
copy(random[:], ch.random)
|
copy(random[:], ch.random)
|
||||||
|
|
||||||
|
|
@ -43,17 +43,17 @@ func TouchStone(ch *ClientHello, sta *State) (isSS bool, UID []byte, sessionID u
|
||||||
|
|
||||||
if used != 0 {
|
if used != 0 {
|
||||||
log.Println("Replay! Duplicate random")
|
log.Println("Replay! Duplicate random")
|
||||||
return false, nil, 0
|
return false, nil, 0, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
ticket := ch.extensions[[2]byte{0x00, 0x23}]
|
ticket := ch.extensions[[2]byte{0x00, 0x23}]
|
||||||
if len(ticket) < 68 {
|
if len(ticket) < 68 {
|
||||||
return false, nil, 0
|
return false, nil, 0, ""
|
||||||
}
|
}
|
||||||
UID, sessionID = decryptSessionTicket(sta.staticPv, ticket)
|
UID, sessionID, proxyMethod = decryptSessionTicket(sta.staticPv, ticket)
|
||||||
isSS = validateRandom(ch.random, UID, sta.Now().Unix())
|
isCK = validateRandom(ch.random, UID, sta.Now().Unix())
|
||||||
if !isSS {
|
if !isCK {
|
||||||
return false, nil, 0
|
return false, nil, 0, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ func TestDecryptSessionTicket(t *testing.T) {
|
||||||
staticPv, _ := ecdh.Unmarshal(pvb)
|
staticPv, _ := ecdh.Unmarshal(pvb)
|
||||||
sessionTicket, _ := hex.DecodeString("f586223b50cada583d61dc9bf3d01cc3a45aab4b062ed6a31ead0badb87f7761aab4f9f737a1d8ff2a2aa4d50ceb808844588ee3c8fdf36c33a35ef5003e287337659c8164a7949e9e63623090763fc24d0386c8904e47bdd740e09dd9b395c72de669629c2a865ed581452d23306adf26de0c8a46ee05e3dac876f2bcd9a2de946d319498f579383d06b3e66b3aca05f533fdc5f017eeba45b42080aabd4f71151fa0dfc1b0e23be4ed3abdb47adc0d5740ca7b7689ad34426309fb6984a086")
|
sessionTicket, _ := hex.DecodeString("f586223b50cada583d61dc9bf3d01cc3a45aab4b062ed6a31ead0badb87f7761aab4f9f737a1d8ff2a2aa4d50ceb808844588ee3c8fdf36c33a35ef5003e287337659c8164a7949e9e63623090763fc24d0386c8904e47bdd740e09dd9b395c72de669629c2a865ed581452d23306adf26de0c8a46ee05e3dac876f2bcd9a2de946d319498f579383d06b3e66b3aca05f533fdc5f017eeba45b42080aabd4f71151fa0dfc1b0e23be4ed3abdb47adc0d5740ca7b7689ad34426309fb6984a086")
|
||||||
|
|
||||||
decryUID, decrySessionID := decryptSessionTicket(staticPv, sessionTicket)
|
decryUID, decrySessionID, _ := decryptSessionTicket(staticPv, sessionTicket)
|
||||||
if !bytes.Equal(decryUID, UID) {
|
if !bytes.Equal(decryUID, UID) {
|
||||||
t.Error(
|
t.Error(
|
||||||
"For", "UID",
|
"For", "UID",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -14,7 +13,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type rawConfig struct {
|
type rawConfig struct {
|
||||||
WebServerAddr string
|
ProxyBook map[string]string
|
||||||
|
RedirAddr string
|
||||||
PrivateKey string
|
PrivateKey string
|
||||||
AdminUID string
|
AdminUID string
|
||||||
DatabasePath string
|
DatabasePath string
|
||||||
|
|
@ -23,10 +23,10 @@ type rawConfig struct {
|
||||||
|
|
||||||
// State type stores the global state of the program
|
// State type stores the global state of the program
|
||||||
type State struct {
|
type State struct {
|
||||||
SS_LOCAL_HOST string
|
ProxyBook map[string]string
|
||||||
SS_LOCAL_PORT string
|
|
||||||
SS_REMOTE_HOST string
|
BindHost string
|
||||||
SS_REMOTE_PORT string
|
BindPort string
|
||||||
|
|
||||||
Now func() time.Time
|
Now func() time.Time
|
||||||
AdminUID []byte
|
AdminUID []byte
|
||||||
|
|
@ -35,62 +35,35 @@ type State struct {
|
||||||
usedRandomM sync.RWMutex
|
usedRandomM sync.RWMutex
|
||||||
usedRandom map[[32]byte]int
|
usedRandom map[[32]byte]int
|
||||||
|
|
||||||
WebServerAddr string
|
RedirAddr string
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) (*State, error) {
|
func InitState(bindHost, bindPort string, nowFunc func() time.Time) (*State, error) {
|
||||||
ret := &State{
|
ret := &State{
|
||||||
SS_LOCAL_HOST: localHost,
|
BindHost: bindHost,
|
||||||
SS_LOCAL_PORT: localPort,
|
BindPort: bindPort,
|
||||||
SS_REMOTE_HOST: remoteHost,
|
Now: nowFunc,
|
||||||
SS_REMOTE_PORT: remotePort,
|
|
||||||
Now: nowFunc,
|
|
||||||
}
|
}
|
||||||
ret.usedRandom = make(map[[32]byte]int)
|
ret.usedRandom = make(map[[32]byte]int)
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// semi-colon separated value.
|
|
||||||
func ssvToJson(ssv string) (ret []byte) {
|
|
||||||
unescape := func(s string) string {
|
|
||||||
r := strings.Replace(s, `\\`, `\`, -1)
|
|
||||||
r = strings.Replace(r, `\=`, `=`, -1)
|
|
||||||
r = strings.Replace(r, `\;`, `;`, -1)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
lines := strings.Split(unescape(ssv), ";")
|
|
||||||
ret = []byte("{")
|
|
||||||
for _, ln := range lines {
|
|
||||||
if ln == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
sp := strings.SplitN(ln, "=", 2)
|
|
||||||
key := sp[0]
|
|
||||||
value := sp[1]
|
|
||||||
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
|
|
||||||
|
|
||||||
}
|
|
||||||
ret = ret[:len(ret)-1] // remove the last comma
|
|
||||||
ret = append(ret, '}')
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseConfig parses the config (either a path to json or in-line ssv config) into a State variable
|
// ParseConfig parses the config (either a path to json or in-line ssv config) into a State variable
|
||||||
func (sta *State) ParseConfig(conf string) (err error) {
|
func (sta *State) ParseConfig(conf string) (err error) {
|
||||||
var content []byte
|
var content []byte
|
||||||
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
|
|
||||||
content = ssvToJson(conf)
|
|
||||||
} else {
|
|
||||||
content, err = ioutil.ReadFile(conf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var preParse rawConfig
|
var preParse rawConfig
|
||||||
err = json.Unmarshal(content, &preParse)
|
|
||||||
if err != nil {
|
content, errPath := ioutil.ReadFile(conf)
|
||||||
return errors.New("Failed to unmarshal: " + err.Error())
|
if errPath != nil {
|
||||||
|
errJson := json.Unmarshal(content, &preParse)
|
||||||
|
if errJson != nil {
|
||||||
|
return errors.New("Failed to read/unmarshal configuration, path is invalid or " + errJson.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errJson := json.Unmarshal(content, &preParse)
|
||||||
|
if errJson != nil {
|
||||||
|
return errors.New("Failed to read configuration file: " + errJson.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
up, err := usermanager.MakeUserpanel(preParse.DatabasePath, preParse.BackupDirPath)
|
up, err := usermanager.MakeUserpanel(preParse.DatabasePath, preParse.BackupDirPath)
|
||||||
|
|
@ -99,7 +72,8 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
||||||
}
|
}
|
||||||
sta.Userpanel = up
|
sta.Userpanel = up
|
||||||
|
|
||||||
sta.WebServerAddr = preParse.WebServerAddr
|
sta.RedirAddr = preParse.RedirAddr
|
||||||
|
sta.ProxyBook = preParse.ProxyBook
|
||||||
|
|
||||||
pvBytes, err := base64.StdEncoding.DecodeString(preParse.PrivateKey)
|
pvBytes, err := base64.StdEncoding.DecodeString(preParse.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSSVtoJson(t *testing.T) {
|
|
||||||
ssv := "WebServerAddr=204.79.197.200:443;PrivateKey=EN5aPEpNBO+vw+BtFQY2OnK9bQU7rvEj5qmnmgwEtUc=;AdminUID=ugDmcEmxWf0pKxfkZ/8EoP35Ht+wQnqf3L0xYgyQFlQ=;DatabasePath=userinfo.db;BackupDirPath=;"
|
|
||||||
json := ssvToJson(ssv)
|
|
||||||
expected := []byte(`{"WebServerAddr":"204.79.197.200:443","PrivateKey":"EN5aPEpNBO+vw+BtFQY2OnK9bQU7rvEj5qmnmgwEtUc=","AdminUID":"ugDmcEmxWf0pKxfkZ/8EoP35Ht+wQnqf3L0xYgyQFlQ=","DatabasePath":"userinfo.db","BackupDirPath":""}`)
|
|
||||||
if !bytes.Equal(expected, json) {
|
|
||||||
t.Error(
|
|
||||||
"For", "ssvToJson",
|
|
||||||
"expecting", string(expected),
|
|
||||||
"got", string(json),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue