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}
|
||||
|
||||
clientHello := TLS.ComposeInitHandshake(sta)
|
||||
connectingIP := sta.SS_REMOTE_HOST
|
||||
connectingIP := sta.RemoteHost
|
||||
if net.ParseIP(connectingIP).To4() == nil {
|
||||
// IPv6 needs square brackets
|
||||
connectingIP = "[" + connectingIP + "]"
|
||||
}
|
||||
remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.SS_REMOTE_PORT)
|
||||
remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.RemotePort)
|
||||
if err != nil {
|
||||
log.Printf("Connecting to remote: %v\n", err)
|
||||
return nil, err
|
||||
|
|
@ -86,15 +86,15 @@ func makeRemoteConn(sta *client.State) (net.Conn, error) {
|
|||
}
|
||||
|
||||
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
|
||||
// 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
|
||||
// The ip of the proxy server
|
||||
var remoteHost string
|
||||
// The proxy port,should be 443
|
||||
var remotePort string
|
||||
var pluginOpts string
|
||||
var config string
|
||||
isAdmin := new(bool)
|
||||
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
|
@ -106,13 +106,13 @@ func main() {
|
|||
localPort = os.Getenv("SS_LOCAL_PORT")
|
||||
remoteHost = os.Getenv("SS_REMOTE_HOST")
|
||||
remotePort = os.Getenv("SS_REMOTE_PORT")
|
||||
pluginOpts = os.Getenv("SS_PLUGIN_OPTIONS")
|
||||
config = os.Getenv("SS_PLUGIN_OPTIONS")
|
||||
} else {
|
||||
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(&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")
|
||||
isAdmin = flag.Bool("a", false, "Admin mode")
|
||||
printUsage := flag.Bool("h", false, "Print this message")
|
||||
|
|
@ -133,7 +133,7 @@ func main() {
|
|||
|
||||
if *isAdmin {
|
||||
sta := client.InitState("", "", "", "", time.Now)
|
||||
err := sta.ParseConfig(pluginOpts)
|
||||
err := sta.ParseConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
@ -145,27 +145,27 @@ func main() {
|
|||
}
|
||||
|
||||
sta := client.InitState(localHost, localPort, remoteHost, remotePort, time.Now)
|
||||
err := sta.ParseConfig(pluginOpts)
|
||||
err := sta.ParseConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if sta.SS_LOCAL_PORT == "" {
|
||||
if sta.LocalPort == "" {
|
||||
log.Fatal("Must specify localPort")
|
||||
}
|
||||
if sta.SS_REMOTE_HOST == "" {
|
||||
if sta.RemoteHost == "" {
|
||||
log.Fatal("Must specify remoteHost")
|
||||
}
|
||||
if sta.TicketTimeHint == 0 {
|
||||
log.Fatal("TicketTimeHint cannot be empty or 0")
|
||||
}
|
||||
listeningIP := sta.SS_LOCAL_HOST
|
||||
listeningIP := sta.LocalHost
|
||||
if net.ParseIP(listeningIP).To4() == nil {
|
||||
// IPv6 needs square brackets
|
||||
listeningIP = "[" + listeningIP + "]"
|
||||
}
|
||||
listener, err := net.Listen("tcp", listeningIP+":"+sta.SS_LOCAL_PORT)
|
||||
log.Println("Listening for ss on " + listeningIP + ":" + sta.SS_LOCAL_PORT)
|
||||
listener, err := net.Listen("tcp", listeningIP+":"+sta.LocalPort)
|
||||
log.Println("Listening for proxy clients on " + listeningIP + ":" + sta.LocalPort)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
@ -207,34 +207,34 @@ start:
|
|||
if sesh.IsBroken() {
|
||||
goto start
|
||||
}
|
||||
ssConn, err := listener.Accept()
|
||||
localConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
go func() {
|
||||
data := make([]byte, 10240)
|
||||
i, err := io.ReadAtLeast(ssConn, data, 1)
|
||||
i, err := io.ReadAtLeast(localConn, data, 1)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
ssConn.Close()
|
||||
localConn.Close()
|
||||
return
|
||||
}
|
||||
stream, err := sesh.OpenStream()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
ssConn.Close()
|
||||
localConn.Close()
|
||||
return
|
||||
}
|
||||
_, err = stream.Write(data[:i])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
ssConn.Close()
|
||||
localConn.Close()
|
||||
stream.Close()
|
||||
return
|
||||
}
|
||||
go pipe(ssConn, stream)
|
||||
pipe(stream, ssConn)
|
||||
go pipe(localConn, stream)
|
||||
pipe(stream, localConn)
|
||||
}()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func pipe(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
|
|||
|
||||
func dispatchConnection(conn net.Conn, sta *server.State) {
|
||||
goWeb := func(data []byte) {
|
||||
webConn, err := net.Dial("tcp", sta.WebServerAddr)
|
||||
webConn, err := net.Dial("tcp", sta.RedirAddr)
|
||||
if err != nil {
|
||||
log.Printf("Making connection to redirection server: %v\n", err)
|
||||
return
|
||||
|
|
@ -64,14 +64,19 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
|||
data := buf[:i]
|
||||
ch, err := server.ParseClientHello(data)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
isSS, UID, sessionID := server.TouchStone(ch, sta)
|
||||
if !isSS {
|
||||
log.Printf("+1 non SS TLS traffic from %v\n", conn.RemoteAddr())
|
||||
isCloak, UID, sessionID, proxyMethod := server.TouchStone(ch, sta)
|
||||
if !isCloak {
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
|
@ -167,47 +172,35 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
|||
continue
|
||||
}
|
||||
}
|
||||
ssIP := sta.SS_LOCAL_HOST
|
||||
if net.ParseIP(ssIP).To4() == nil {
|
||||
// IPv6 needs square brackets
|
||||
ssIP = "[" + ssIP + "]"
|
||||
}
|
||||
ssConn, err := net.Dial("tcp", ssIP+":"+sta.SS_LOCAL_PORT)
|
||||
localConn, err := net.Dial("tcp", sta.ProxyBook[proxyMethod])
|
||||
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
|
||||
}
|
||||
go pipe(ssConn, newStream)
|
||||
go pipe(newStream, ssConn)
|
||||
go pipe(localConn, newStream)
|
||||
go pipe(newStream, localConn)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
var remoteHost string
|
||||
var bindHost string
|
||||
// Outbound listening ip, should be 443
|
||||
var remotePort string
|
||||
var pluginOpts string
|
||||
var bindPort string
|
||||
var config string
|
||||
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
|
||||
if os.Getenv("SS_LOCAL_HOST") != "" {
|
||||
localHost = os.Getenv("SS_LOCAL_HOST")
|
||||
localPort = os.Getenv("SS_LOCAL_PORT")
|
||||
remoteHost = os.Getenv("SS_REMOTE_HOST")
|
||||
remotePort = os.Getenv("SS_REMOTE_PORT")
|
||||
pluginOpts = os.Getenv("SS_PLUGIN_OPTIONS")
|
||||
bindHost = os.Getenv("SS_REMOTE_HOST")
|
||||
bindPort = os.Getenv("SS_REMOTE_PORT")
|
||||
config = os.Getenv("SS_PLUGIN_OPTIONS")
|
||||
} 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(&remoteHost, "s", "0.0.0.0", "remoteHost: outbound listing ip, set to 0.0.0.0 to listen to everything")
|
||||
flag.StringVar(&remotePort, "p", "443", "remotePort: outbound listing port, should be 443")
|
||||
flag.StringVar(&pluginOpts, "c", "server.json", "pluginOpts: path to server.json or options seperated by semicolons")
|
||||
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(&bindPort, "p", "443", "bindPort: port to bind to, should be 443")
|
||||
flag.StringVar(&config, "c", "server.json", "config: path to the configuration file or its content")
|
||||
askVersion := flag.Bool("v", false, "Print the version number")
|
||||
printUsage := flag.Bool("h", false, "Print this message")
|
||||
|
||||
|
|
@ -240,20 +233,25 @@ func main() {
|
|||
startPprof(*pprofAddr)
|
||||
}
|
||||
|
||||
if *localAddr == "" {
|
||||
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)
|
||||
log.Printf("Starting standalone mode, listening on %v:%v", bindHost, bindPort)
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
listeningIP := strings.Split(sta.SS_REMOTE_HOST, "|")
|
||||
listeningIP := strings.Split(sta.BindHost, "|")
|
||||
for i, ip := range listeningIP {
|
||||
if net.ParseIP(ip).To4() == nil {
|
||||
// IPv6 needs square brackets
|
||||
|
|
@ -286,9 +284,9 @@ func main() {
|
|||
|
||||
// The last listener must block main() because the program exits on main return.
|
||||
if i == len(listeningIP)-1 {
|
||||
listen(ip, sta.SS_REMOTE_PORT)
|
||||
listen(ip, sta.BindPort)
|
||||
} else {
|
||||
go listen(ip, sta.SS_REMOTE_PORT)
|
||||
go listen(ip, sta.BindPort)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
{
|
||||
"ProxyMethod":"shadowsocks",
|
||||
"UID":"iGAO85zysIyR4c09CyZSLdNhtP/ckcYu7nIPI082AHA=",
|
||||
"PublicKey":"IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=",
|
||||
"ServerName":"www.bing.com",
|
||||
"TicketTimeHint":3600,
|
||||
"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=",
|
||||
"AdminUID":"ugDmcEmxWf0pKxfkZ/8EoP35Ht+wQnqf3L0xYgyQFlQ=",
|
||||
"DatabasePath":"userinfo.db",
|
||||
|
|
|
|||
|
|
@ -49,13 +49,13 @@ func addExtRec(typ []byte, data []byte) []byte {
|
|||
// ComposeInitHandshake composes ClientHello with record layer
|
||||
func ComposeInitHandshake(sta *client.State) []byte {
|
||||
var ch []byte
|
||||
switch sta.MaskBrowser {
|
||||
switch sta.BrowserSig {
|
||||
case "chrome":
|
||||
ch = (&chrome{}).composeClientHello(sta)
|
||||
case "firefox":
|
||||
ch = (&firefox{}).composeClientHello(sta)
|
||||
default:
|
||||
panic("Unsupported browser:" + sta.MaskBrowser)
|
||||
panic("Unsupported browser:" + sta.BrowserSig)
|
||||
}
|
||||
return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func MakeRandomField(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
|
||||
// for encrypting the UID
|
||||
tthInterval := sta.Now().Unix() / int64(sta.TicketTimeHint)
|
||||
|
|
@ -52,11 +52,12 @@ func MakeSessionTicket(sta *State) []byte {
|
|||
ticket := make([]byte, 192)
|
||||
copy(ticket[0:32], ecdh.Marshal(ephKP.PublicKey))
|
||||
key := ecdh.GenerateSharedSecret(ephKP.PrivateKey, sta.staticPub)
|
||||
plainUIDsID := make([]byte, 36)
|
||||
copy(plainUIDsID, sta.UID)
|
||||
binary.BigEndian.PutUint32(plainUIDsID[32:36], sta.sessionID)
|
||||
cipherUIDsID := util.AESEncrypt(ticket[0:16], key, plainUIDsID)
|
||||
copy(ticket[32:68], cipherUIDsID)
|
||||
plain := make([]byte, 52)
|
||||
copy(plain, sta.UID)
|
||||
binary.BigEndian.PutUint32(plain[32:36], sta.sessionID)
|
||||
copy(plain[36:52], []byte(sta.ProxyMethod))
|
||||
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.
|
||||
// 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
|
||||
|
|
@ -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
|
||||
// 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.
|
||||
copy(ticket[68:192], util.PsudoRandBytes(124, tthInterval+int64(sta.sessionID)))
|
||||
copy(ticket[84:192], util.PsudoRandBytes(108, tthInterval+int64(sta.sessionID)))
|
||||
return ticket
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,19 +15,20 @@ import (
|
|||
|
||||
type rawConfig struct {
|
||||
ServerName string
|
||||
ProxyMethod string
|
||||
UID string
|
||||
PublicKey string
|
||||
TicketTimeHint int
|
||||
MaskBrowser string
|
||||
BrowserSig string
|
||||
NumConn int
|
||||
}
|
||||
|
||||
// State stores global variables
|
||||
type State struct {
|
||||
SS_LOCAL_HOST string
|
||||
SS_LOCAL_PORT string
|
||||
SS_REMOTE_HOST string
|
||||
SS_REMOTE_PORT string
|
||||
LocalHost string
|
||||
LocalPort string
|
||||
RemoteHost string
|
||||
RemotePort string
|
||||
|
||||
Now func() time.Time
|
||||
sessionID uint32
|
||||
|
|
@ -36,19 +37,20 @@ type State struct {
|
|||
keyPairsM sync.RWMutex
|
||||
keyPairs map[int64]*keyPair
|
||||
|
||||
ProxyMethod string
|
||||
TicketTimeHint int
|
||||
ServerName string
|
||||
MaskBrowser string
|
||||
BrowserSig string
|
||||
NumConn int
|
||||
}
|
||||
|
||||
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State {
|
||||
ret := &State{
|
||||
SS_LOCAL_HOST: localHost,
|
||||
SS_LOCAL_PORT: localPort,
|
||||
SS_REMOTE_HOST: remoteHost,
|
||||
SS_REMOTE_PORT: remotePort,
|
||||
Now: nowFunc,
|
||||
LocalHost: localHost,
|
||||
LocalPort: localPort,
|
||||
RemoteHost: remoteHost,
|
||||
RemotePort: remotePort,
|
||||
Now: nowFunc,
|
||||
}
|
||||
ret.keyPairs = make(map[int64]*keyPair)
|
||||
return ret
|
||||
|
|
@ -102,9 +104,10 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sta.ProxyMethod = preParse.ProxyMethod
|
||||
sta.ServerName = preParse.ServerName
|
||||
sta.TicketTimeHint = preParse.TicketTimeHint
|
||||
sta.MaskBrowser = preParse.MaskBrowser
|
||||
sta.BrowserSig = preParse.BrowserSig
|
||||
sta.NumConn = preParse.NumConn
|
||||
uid, err := base64.StdEncoding.DecodeString(preParse.UID)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -9,13 +9,11 @@ import (
|
|||
// 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
|
||||
// packets are transmitted reliably (as TCP is reliable), packet1 may arrive to the
|
||||
// remote side before packet0.
|
||||
//
|
||||
// However, shadowsocks' protocol does not provide sequence control. We must therefore
|
||||
// make sure packets arrive in order.
|
||||
// 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
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ import (
|
|||
)
|
||||
|
||||
// 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])
|
||||
key := ecdh.GenerateSharedSecret(staticPv, ephPub)
|
||||
UIDsID := util.AESDecrypt(ticket[0:16], key, ticket[32:68])
|
||||
sessionID := binary.BigEndian.Uint32(UIDsID[32:36])
|
||||
return UIDsID[0:32], sessionID
|
||||
plain := util.AESDecrypt(ticket[0:16], key, ticket[32:84])
|
||||
sessionID := binary.BigEndian.Uint32(plain[32:36])
|
||||
return plain[0:32], sessionID, string(bytes.Trim(plain[36:52], "\x00"))
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
copy(random[:], ch.random)
|
||||
|
||||
|
|
@ -43,17 +43,17 @@ func TouchStone(ch *ClientHello, sta *State) (isSS bool, UID []byte, sessionID u
|
|||
|
||||
if used != 0 {
|
||||
log.Println("Replay! Duplicate random")
|
||||
return false, nil, 0
|
||||
return false, nil, 0, ""
|
||||
}
|
||||
|
||||
ticket := ch.extensions[[2]byte{0x00, 0x23}]
|
||||
if len(ticket) < 68 {
|
||||
return false, nil, 0
|
||||
return false, nil, 0, ""
|
||||
}
|
||||
UID, sessionID = decryptSessionTicket(sta.staticPv, ticket)
|
||||
isSS = validateRandom(ch.random, UID, sta.Now().Unix())
|
||||
if !isSS {
|
||||
return false, nil, 0
|
||||
UID, sessionID, proxyMethod = decryptSessionTicket(sta.staticPv, ticket)
|
||||
isCK = validateRandom(ch.random, UID, sta.Now().Unix())
|
||||
if !isCK {
|
||||
return false, nil, 0, ""
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ func TestDecryptSessionTicket(t *testing.T) {
|
|||
staticPv, _ := ecdh.Unmarshal(pvb)
|
||||
sessionTicket, _ := hex.DecodeString("f586223b50cada583d61dc9bf3d01cc3a45aab4b062ed6a31ead0badb87f7761aab4f9f737a1d8ff2a2aa4d50ceb808844588ee3c8fdf36c33a35ef5003e287337659c8164a7949e9e63623090763fc24d0386c8904e47bdd740e09dd9b395c72de669629c2a865ed581452d23306adf26de0c8a46ee05e3dac876f2bcd9a2de946d319498f579383d06b3e66b3aca05f533fdc5f017eeba45b42080aabd4f71151fa0dfc1b0e23be4ed3abdb47adc0d5740ca7b7689ad34426309fb6984a086")
|
||||
|
||||
decryUID, decrySessionID := decryptSessionTicket(staticPv, sessionTicket)
|
||||
decryUID, decrySessionID, _ := decryptSessionTicket(staticPv, sessionTicket)
|
||||
if !bytes.Equal(decryUID, UID) {
|
||||
t.Error(
|
||||
"For", "UID",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -14,7 +13,8 @@ import (
|
|||
)
|
||||
|
||||
type rawConfig struct {
|
||||
WebServerAddr string
|
||||
ProxyBook map[string]string
|
||||
RedirAddr string
|
||||
PrivateKey string
|
||||
AdminUID string
|
||||
DatabasePath string
|
||||
|
|
@ -23,10 +23,10 @@ type rawConfig struct {
|
|||
|
||||
// State type stores the global state of the program
|
||||
type State struct {
|
||||
SS_LOCAL_HOST string
|
||||
SS_LOCAL_PORT string
|
||||
SS_REMOTE_HOST string
|
||||
SS_REMOTE_PORT string
|
||||
ProxyBook map[string]string
|
||||
|
||||
BindHost string
|
||||
BindPort string
|
||||
|
||||
Now func() time.Time
|
||||
AdminUID []byte
|
||||
|
|
@ -35,62 +35,35 @@ type State struct {
|
|||
usedRandomM sync.RWMutex
|
||||
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{
|
||||
SS_LOCAL_HOST: localHost,
|
||||
SS_LOCAL_PORT: localPort,
|
||||
SS_REMOTE_HOST: remoteHost,
|
||||
SS_REMOTE_PORT: remotePort,
|
||||
Now: nowFunc,
|
||||
BindHost: bindHost,
|
||||
BindPort: bindPort,
|
||||
Now: nowFunc,
|
||||
}
|
||||
ret.usedRandom = make(map[[32]byte]int)
|
||||
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
|
||||
func (sta *State) ParseConfig(conf string) (err error) {
|
||||
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
|
||||
err = json.Unmarshal(content, &preParse)
|
||||
if err != nil {
|
||||
return errors.New("Failed to unmarshal: " + err.Error())
|
||||
|
||||
content, errPath := ioutil.ReadFile(conf)
|
||||
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)
|
||||
|
|
@ -99,7 +72,8 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
|||
}
|
||||
sta.Userpanel = up
|
||||
|
||||
sta.WebServerAddr = preParse.WebServerAddr
|
||||
sta.RedirAddr = preParse.RedirAddr
|
||||
sta.ProxyBook = preParse.ProxyBook
|
||||
|
||||
pvBytes, err := base64.StdEncoding.DecodeString(preParse.PrivateKey)
|
||||
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