From 726a405a2606eb238a8ce936f61a47317ba2b056 Mon Sep 17 00:00:00 2001 From: Qian Wang Date: Fri, 2 Aug 2019 01:01:19 +0100 Subject: [PATCH] TLS1.3 --- cmd/ck-client/ck-client.go | 94 +++++++----------- cmd/ck-server/ck-server.go | 173 +++++++++++++-------------------- example_config/ckclient.json | 1 - internal/client/TLS/TLS.go | 24 ++--- internal/client/TLS/chrome.go | 116 +++++++++++++--------- internal/client/TLS/firefox.go | 89 +++++++++++------ internal/client/auth.go | 69 +++++-------- internal/client/state.go | 45 +-------- internal/multiplex/session.go | 9 +- internal/server/TLS.go | 87 ++++++++++++----- internal/server/activeuser.go | 4 +- internal/server/auth.go | 66 +++++-------- internal/server/state.go | 10 +- internal/util/util.go | 36 +++++++ 14 files changed, 407 insertions(+), 416 deletions(-) diff --git a/cmd/ck-client/ck-client.go b/cmd/ck-client/ck-client.go index 03c555e..3665529 100644 --- a/cmd/ck-client/ck-client.go +++ b/cmd/ck-client/ck-client.go @@ -3,19 +3,17 @@ package main import ( - "crypto/aes" - "crypto/cipher" "encoding/base64" "encoding/binary" "flag" "fmt" - "golang.org/x/crypto/chacha20poly1305" "io" "log" "math/rand" "net" "os" "sync" + "sync/atomic" "time" "github.com/cbeuw/Cloak/internal/client" @@ -48,12 +46,12 @@ func pipe(dst io.ReadWriteCloser, src io.ReadWriteCloser) { } // This establishes a connection with ckserver and performs a handshake -func makeRemoteConn(sta *client.State) (net.Conn, error) { +func makeRemoteConn(sta *client.State) (net.Conn, []byte, error) { // For android d := net.Dialer{Control: protector} - clientHello := TLS.ComposeInitHandshake(sta) + clientHello, sharedSecret := TLS.ComposeInitHandshake(sta) connectingIP := sta.RemoteHost if net.ParseIP(connectingIP).To4() == nil { // IPv6 needs square brackets @@ -62,32 +60,28 @@ func makeRemoteConn(sta *client.State) (net.Conn, error) { remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.RemotePort) if err != nil { log.Printf("Connecting to remote: %v\n", err) - return nil, err + return nil, nil, err } _, err = remoteConn.Write(clientHello) if err != nil { log.Printf("Sending ClientHello: %v\n", err) - return nil, err + return nil, nil, err } - // Three discarded messages: ServerHello, ChangeCipherSpec and Finished - discardBuf := make([]byte, 1024) - for c := 0; c < 3; c++ { - _, err = util.ReadTLS(remoteConn, discardBuf) - if err != nil { - log.Printf("Reading discarded message %v: %v\n", c, err) - return nil, err - } - } - - reply := TLS.ComposeReply() - _, err = remoteConn.Write(reply) + buf := make([]byte, 1024) + _, err = util.ReadTLS(remoteConn, buf) if err != nil { - log.Printf("Sending reply to remote: %v\n", err) - return nil, err + log.Printf("Reading ServerHello: %v\n", err) + } + serverRandom := buf[11:43] + sessionKey := client.DecryptSessionKey(serverRandom, sharedSecret) + _, err = util.ReadTLS(remoteConn, buf) + if err != nil { + log.Printf("Reading Change Cipher Spec %v\n", err) + return nil, nil, err } - return remoteConn, nil + return remoteConn, sessionKey, nil } @@ -98,58 +92,41 @@ func makeSession(sta *client.State) *mux.Session { // sessionID is limited to its UID. quad := make([]byte, 4) rand.Read(quad) - sta.SessionID = binary.BigEndian.Uint32(quad) + atomic.StoreUint32(&sta.SessionID, binary.BigEndian.Uint32(quad)) } - sta.UpdateIntervalKeys() - _, tthKey := sta.GetIntervalKeys() - - var payloadCipher cipher.AEAD - var err error - switch sta.EncryptionMethod { - case 0x00: - payloadCipher = nil - case 0x01: - c, err := aes.NewCipher(tthKey) - if err != nil { - log.Fatal(err) - } - payloadCipher, err = cipher.NewGCM(c) - if err != nil { - log.Fatal(err) - } - case 0x02: - payloadCipher, err = chacha20poly1305.New(tthKey) - if err != nil { - log.Fatal(err) - } - default: - log.Fatal("Unknown encryption method") - } - - headerCipher, err := aes.NewCipher(tthKey) - if err != nil { - log.Fatal(err) - } - sesh := mux.MakeSession(sta.SessionID, mux.UNLIMITED_VALVE, mux.MakeObfs(headerCipher, payloadCipher), mux.MakeDeobfs(headerCipher, payloadCipher), util.ReadTLS) - + connsCh := make(chan net.Conn, sta.NumConn) + var _sessionKey atomic.Value var wg sync.WaitGroup for i := 0; i < sta.NumConn; i++ { wg.Add(1) go func() { makeconn: - conn, err := makeRemoteConn(sta) + conn, sk, err := makeRemoteConn(sta) + _sessionKey.Store(sk) if err != nil { log.Printf("Failed to establish new connections to remote: %v\n", err) time.Sleep(time.Second * 3) goto makeconn } - sesh.AddConnection(conn) + connsCh <- conn wg.Done() }() } wg.Wait() + sessionKey := _sessionKey.Load().([]byte) + obfs, deobfs, err := util.GenerateObfs(sta.EncryptionMethod, sessionKey) + if err != nil { + log.Fatal(err) + } + sesh := mux.MakeSession(sta.SessionID, mux.UNLIMITED_VALVE, obfs, deobfs, sessionKey, util.ReadTLS) + + for i := 0; i < sta.NumConn; i++ { + conn := <-connsCh + sesh.AddConnection(conn) + } + log.Printf("Session %v established", sta.SessionID) return sesh } @@ -216,9 +193,6 @@ func main() { if sta.RemoteHost == "" { log.Fatal("Must specify remoteHost") } - if sta.TicketTimeHint == 0 { - log.Fatal("TicketTimeHint cannot be empty or 0") - } listeningIP := sta.LocalHost if net.ParseIP(listeningIP).To4() == nil { diff --git a/cmd/ck-server/ck-server.go b/cmd/ck-server/ck-server.go index de473f0..826b778 100644 --- a/cmd/ck-server/ck-server.go +++ b/cmd/ck-server/ck-server.go @@ -2,12 +2,10 @@ package main import ( "bytes" - "crypto/aes" - "crypto/cipher" + "crypto/rand" "encoding/base64" "flag" "fmt" - "golang.org/x/crypto/chacha20poly1305" "io" "log" "net" @@ -48,17 +46,6 @@ 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.RedirAddr) - if err != nil { - log.Printf("Making connection to redirection server: %v\n", err) - return - } - webConn.Write(data) - go pipe(webConn, conn) - go pipe(conn, webConn) - } - buf := make([]byte, 1500) conn.SetReadDeadline(time.Now().Add(3 * time.Second)) @@ -69,95 +56,89 @@ func dispatchConnection(conn net.Conn, sta *server.State) { } conn.SetReadDeadline(time.Time{}) data := buf[:i] + + goWeb := func() { + webConn, err := net.Dial("tcp", sta.RedirAddr) + if err != nil { + log.Printf("Making connection to redirection server: %v\n", err) + return + } + webConn.Write(data) + go pipe(webConn, conn) + go pipe(conn, webConn) + } + ch, err := server.ParseClientHello(data) if err != nil { log.Printf("+1 non Cloak non (or malformed) TLS traffic from %v\n", conn.RemoteAddr()) - goWeb(data) + goWeb() return } - isCloak, UID, sessionID, proxyMethod, encryptionMethod, tthKey := server.TouchStone(ch, sta) + isCloak, UID, sessionID, proxyMethod, encryptionMethod, sharedSecret := server.TouchStone(ch, sta) if !isCloak { log.Printf("+1 non Cloak TLS traffic from %v\n", conn.RemoteAddr()) - goWeb(data) + goWeb() 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() return } - var payloadCipher cipher.AEAD - switch encryptionMethod { - case 0x00: - payloadCipher = nil - case 0x01: - c, err := aes.NewCipher(tthKey) - if err != nil { - log.Println(err) - goWeb(data) - return - } - payloadCipher, err = cipher.NewGCM(c) - if err != nil { - log.Println(err) - goWeb(data) - return - } - case 0x02: - payloadCipher, err = chacha20poly1305.New(tthKey) - if err != nil { - log.Println(err) - goWeb(data) - return - } - default: - log.Println("Unknown encryption method") - goWeb(data) - return - } - - headerCipher, err := aes.NewCipher(tthKey) + user, err := sta.Panel.GetUser(UID) if err != nil { - log.Println(err) - goWeb(data) + log.Printf("+1 unauthorised user from %v, uid: %v\n", conn.RemoteAddr(), base64.StdEncoding.EncodeToString(UID)) + goWeb() return } - obfs := mux.MakeObfs(headerCipher, payloadCipher) - deobfs := mux.MakeDeobfs(headerCipher, payloadCipher) - - finishHandshake := func() error { - reply := server.ComposeReply(ch) + finishHandshake := func(sessionKey []byte) error { + reply := server.ComposeReply(ch, sharedSecret, sessionKey) _, err = conn.Write(reply) if err != nil { go conn.Close() return err } - - // Two discarded messages: ChangeCipherSpec and Finished - discardBuf := make([]byte, 1024) - for c := 0; c < 2; c++ { - _, err = util.ReadTLS(conn, discardBuf) - if err != nil { - go conn.Close() - return err - } - } return nil } + sessionKey := make([]byte, 32) + rand.Read(sessionKey) + obfs, deobfs, err := util.GenerateObfs(encryptionMethod, sessionKey) + if err != nil { + log.Println(err) + goWeb() + } + + sesh, existing, err := user.GetSession(sessionID, obfs, deobfs, sessionKey, util.ReadTLS) + if err != nil { + user.DelSession(sessionID) + log.Println(err) + return + } + + if existing { + err = finishHandshake(sesh.SessionKey) + if err != nil { + log.Println(err) + return + } + sesh.AddConnection(conn) + return + } + // adminUID can use the server as normal with unlimited QoS credits. The adminUID is not // added to the userinfo database. The distinction between going into the admin mode // and normal proxy mode is that sessionID needs == 0 for admin mode if bytes.Equal(UID, sta.AdminUID) && sessionID == 0 { - err = finishHandshake() + err = finishHandshake(sessionKey) if err != nil { log.Println(err) return } - sesh := mux.MakeSession(0, mux.UNLIMITED_VALVE, obfs, deobfs, util.ReadTLS) + sesh := mux.MakeSession(0, mux.UNLIMITED_VALVE, obfs, deobfs, sessionKey, util.ReadTLS) sesh.AddConnection(conn) //TODO: Router could be nil in cnc mode err = http.Serve(sesh, sta.LocalAPIRouter) @@ -167,51 +148,33 @@ func dispatchConnection(conn net.Conn, sta *server.State) { } } - user, err := sta.Panel.GetUser(UID) - if err != nil { - log.Printf("+1 unauthorised user from %v, uid: %v\n", conn.RemoteAddr(), base64.StdEncoding.EncodeToString(UID)) - goWeb(data) - return - } - - err = finishHandshake() + err = finishHandshake(sessionKey) if err != nil { log.Println(err) return } - sesh, existing, err := user.GetSession(sessionID, obfs, deobfs, util.ReadTLS) - if err != nil { - user.DelSession(sessionID) - log.Println(err) - return - } + log.Printf("New session from UID:%v, sessionID:%v\n", b64.EncodeToString(UID), sessionID) + sesh.AddConnection(conn) - if existing { - sesh.AddConnection(conn) - return - } else { - log.Printf("New session from UID:%v, sessionID:%v\n", b64.EncodeToString(UID), sessionID) - sesh.AddConnection(conn) - for { - newStream, err := sesh.Accept() - if err != nil { - if err == mux.ErrBrokenSession { - log.Printf("Session closed for UID:%v, sessionID:%v, reason:%v\n", b64.EncodeToString(UID), sessionID, sesh.TerminalMsg()) - user.DelSession(sessionID) - return - } else { - continue - } - } - localConn, err := net.Dial("tcp", sta.ProxyBook[proxyMethod]) - if err != nil { - log.Printf("Failed to connect to %v: %v\n", proxyMethod, err) + for { + newStream, err := sesh.Accept() + if err != nil { + if err == mux.ErrBrokenSession { + log.Printf("Session closed for UID:%v, sessionID:%v, reason:%v\n", b64.EncodeToString(UID), sessionID, sesh.TerminalMsg()) + user.DelSession(sessionID) + return + } else { continue } - go pipe(localConn, newStream) - go pipe(newStream, localConn) } + localConn, err := net.Dial("tcp", sta.ProxyBook[proxyMethod]) + if err != nil { + log.Printf("Failed to connect to %v: %v\n", proxyMethod, err) + continue + } + go pipe(localConn, newStream) + go pipe(newStream, localConn) } } @@ -289,8 +252,6 @@ func main() { sta.ProxyBook["shadowsocks"] = ssLocalHost + ":" + ssLocalPort } - go sta.UsedRandomCleaner() - listen := func(addr, port string) { listener, err := net.Listen("tcp", addr+":"+port) log.Println("Listening on " + addr + ":" + port) diff --git a/example_config/ckclient.json b/example_config/ckclient.json index 05e944b..f1f0826 100644 --- a/example_config/ckclient.json +++ b/example_config/ckclient.json @@ -4,7 +4,6 @@ "UID":"5nneblJy6lniPJfr81LuYQ==", "PublicKey":"IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=", "ServerName":"www.bing.com", - "TicketTimeHint":3600, "NumConn":4, "BrowserSig":"chrome" } diff --git a/internal/client/TLS/TLS.go b/internal/client/TLS/TLS.go index c672317..b7f5f1b 100644 --- a/internal/client/TLS/TLS.go +++ b/internal/client/TLS/TLS.go @@ -4,16 +4,13 @@ import ( "encoding/binary" "github.com/cbeuw/Cloak/internal/client" "github.com/cbeuw/Cloak/internal/util" - "time" ) type browser interface { - composeExtensions() composeClientHello() } -func makeServerName(sta *client.State) []byte { - serverName := sta.ServerName +func makeServerName(serverName string) []byte { serverNameListLength := make([]byte, 2) binary.BigEndian.PutUint16(serverNameListLength, uint16(len(serverName)+3)) serverNameType := []byte{0x00} // host_name @@ -47,24 +44,15 @@ func addExtRec(typ []byte, data []byte) []byte { } // ComposeInitHandshake composes ClientHello with record layer -func ComposeInitHandshake(sta *client.State) []byte { - var ch []byte +func ComposeInitHandshake(sta *client.State) ([]byte, []byte) { + var ch, sharedSecret []byte switch sta.BrowserSig { case "chrome": - ch = (&chrome{}).composeClientHello(sta) + ch, sharedSecret = (&chrome{}).composeClientHello(sta) case "firefox": - ch = (&firefox{}).composeClientHello(sta) + ch, sharedSecret = (&firefox{}).composeClientHello(sta) default: panic("Unsupported browser:" + sta.BrowserSig) } - return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01}) -} - -// ComposeReply composes RL+ChangeCipherSpec+RL+Finished -func ComposeReply() []byte { - TLS12 := []byte{0x03, 0x03} - ccsBytes := util.AddRecordLayer([]byte{0x01}, []byte{0x14}, TLS12) - finished := util.PsudoRandBytes(40, time.Now().UnixNano()) - fBytes := util.AddRecordLayer(finished, []byte{0x16}, TLS12) - return append(ccsBytes, fBytes...) + return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01}), sharedSecret } diff --git a/internal/client/TLS/chrome.go b/internal/client/TLS/chrome.go index 78b23b7..1993800 100644 --- a/internal/client/TLS/chrome.go +++ b/internal/client/TLS/chrome.go @@ -1,30 +1,31 @@ -// Chrome 64 +// Chrome 76 package TLS import ( + "encoding/binary" "encoding/hex" "math/rand" "time" "github.com/cbeuw/Cloak/internal/client" - "github.com/cbeuw/Cloak/internal/util" ) type chrome struct { browser } -func (c *chrome) composeExtensions(sta *client.State) []byte { +func makeGREASE() []byte { // see https://tools.ietf.org/html/draft-davidben-tls-grease-01 // This is exclusive to chrome. - makeGREASE := func() []byte { - rand.Seed(time.Now().UnixNano()) - sixteenth := rand.Intn(16) - monoGREASE := byte(sixteenth*16 + 0xA) - doubleGREASE := []byte{monoGREASE, monoGREASE} - return doubleGREASE - } + rand.Seed(time.Now().UnixNano()) + sixteenth := rand.Intn(16) + monoGREASE := byte(sixteenth*16 + 0xA) + doubleGREASE := []byte{monoGREASE, monoGREASE} + return doubleGREASE +} + +func (c *chrome) composeExtensions(sta *client.State, keyShare []byte) []byte { makeSupportedGroups := func() []byte { suppGroupListLen := []byte{0x00, 0x08} @@ -35,48 +36,73 @@ func (c *chrome) composeExtensions(sta *client.State) []byte { return ret } - var ext [14][]byte - ext[0] = addExtRec(makeGREASE(), nil) // First GREASE - ext[1] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info - ext[2] = addExtRec([]byte{0x00, 0x00}, makeServerName(sta)) // server name indication - ext[3] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret - ext[4] = addExtRec([]byte{0x00, 0x23}, client.MakeSessionTicket(sta)) // Session tickets - sigAlgo, _ := hex.DecodeString("0012040308040401050308050501080606010201") - ext[5] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms - ext[6] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request - ext[7] = addExtRec([]byte{0x00, 0x12}, nil) // signed cert timestamp + makeKeyShare := func(hidden []byte) []byte { + ret := make([]byte, 43) + ret[0], ret[1] = 0x00, 0x29 // length 41 + copy(ret[2:4], makeGREASE()) + ret[4], ret[5] = 0x00, 0x01 // length 1 + ret[6] = 0x00 + ret[7], ret[8] = 0x00, 0x1d // group x25519 + ret[9], ret[10] = 0x00, 0x20 // length 32 + copy(ret[11:43], hidden) + return ret + } + + // extension length is always 401, and server name length is variable + + var ext [17][]byte + ext[0] = addExtRec(makeGREASE(), nil) // First GREASE + ext[1] = addExtRec([]byte{0x00, 0x00}, makeServerName(sta.ServerName)) // server name indication + ext[2] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret + ext[3] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info + ext[4] = addExtRec([]byte{0x00, 0x0a}, makeSupportedGroups()) // supported groups + ext[5] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats + ext[6] = addExtRec([]byte{0x00, 0x23}, nil) // Session tickets APLN, _ := hex.DecodeString("000c02683208687474702f312e31") - ext[8] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation - ext[9] = addExtRec([]byte{0x75, 0x50}, nil) // channel id - ext[10] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats - ext[11] = addExtRec([]byte{0x00, 0x0a}, makeSupportedGroups()) // supported groups - ext[12] = addExtRec(makeGREASE(), []byte{0x00}) // Last GREASE - ext[13] = addExtRec([]byte{0x00, 0x15}, makeNullBytes(110-len(ext[2]))) // padding + ext[7] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation + ext[8] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request + sigAlgo, _ := hex.DecodeString("0012040308040401050308050501080606010201") + ext[9] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms + ext[10] = addExtRec([]byte{0x00, 0x12}, nil) // signed cert timestamp + ext[11] = addExtRec([]byte{0x00, 0x33}, makeKeyShare(keyShare)) // key share + ext[12] = addExtRec([]byte{0x00, 0x2d}, []byte{0x01, 0x01}) // psk key exchange modes + suppVersions, _ := hex.DecodeString("0a9A9A0304030303020301") // 9A9A needs to be a GREASE + copy(suppVersions[1:3], makeGREASE()) + ext[13] = addExtRec([]byte{0x00, 0x2b}, suppVersions) // supported versions + ext[14] = addExtRec([]byte{0x00, 0x1b}, []byte{0x02, 0x00, 0x02}) + ext[15] = addExtRec(makeGREASE(), []byte{0x00}) // Last GREASE + // len(ext[1]) + 172 + len(ext[16]) = 401 + // len(ext[16]) = 229 - len(ext[1]) + // 2+2+len(padding) = 229 - len(ext[1]) + // len(padding) = 225 - len(ext[1]) + ext[16] = addExtRec([]byte{0x00, 0x15}, makeNullBytes(225-len(ext[1]))) // padding var ret []byte - for i := 0; i < 14; i++ { - ret = append(ret, ext[i]...) + for _, e := range ext { + ret = append(ret, e...) } return ret } -func (c *chrome) composeClientHello(sta *client.State) []byte { +func (c *chrome) composeClientHello(sta *client.State) ([]byte, []byte) { + random, sessionID, keyShare, sharedSecret := client.MakeHiddenData(sta) var clientHello [12][]byte - clientHello[0] = []byte{0x01} // handshake type - clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508 - clientHello[2] = []byte{0x03, 0x03} // client version - clientHello[3] = client.MakeRandomField(sta) // random - clientHello[4] = []byte{0x20} // session id length 32 - clientHello[5] = util.PsudoRandBytes(32, sta.Now().UnixNano()) // session id - clientHello[6] = []byte{0x00, 0x1c} // cipher suites length 28 - cipherSuites, _ := hex.DecodeString("2a2ac02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a") - clientHello[7] = cipherSuites // cipher suites - clientHello[8] = []byte{0x01} // compression methods length 1 - clientHello[9] = []byte{0x00} // compression methods - clientHello[10] = []byte{0x01, 0x97} // extensions length 407 - clientHello[11] = c.composeExtensions(sta) // extensions + clientHello[0] = []byte{0x01} // handshake type + clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508 + clientHello[2] = []byte{0x03, 0x03} // client version + clientHello[3] = random // random + clientHello[4] = []byte{0x20} // session id length 32 + clientHello[5] = sessionID // session id + clientHello[6] = []byte{0x00, 0x22} // cipher suites length 34 + cipherSuites, _ := hex.DecodeString("130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a") + clientHello[7] = append(makeGREASE(), cipherSuites...) // cipher suites + clientHello[8] = []byte{0x01} // compression methods length 1 + clientHello[9] = []byte{0x00} // compression methods + clientHello[11] = c.composeExtensions(sta, keyShare) + clientHello[10] = []byte{0x00, 0x00} // extensions length 401 + binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11]))) var ret []byte - for i := 0; i < 12; i++ { - ret = append(ret, clientHello[i]...) + for _, c := range clientHello { + ret = append(ret, c...) } - return ret + return ret, sharedSecret } diff --git a/internal/client/TLS/firefox.go b/internal/client/TLS/firefox.go index 19f671f..2b3c015 100644 --- a/internal/client/TLS/firefox.go +++ b/internal/client/TLS/firefox.go @@ -1,57 +1,82 @@ -// Firefox 58 +// Firefox 68 package TLS import ( + "crypto/rand" + "encoding/binary" "encoding/hex" "github.com/cbeuw/Cloak/internal/client" - "github.com/cbeuw/Cloak/internal/util" ) type firefox struct { browser } -func (f *firefox) composeExtensions(sta *client.State) []byte { - var ext [10][]byte - ext[0] = addExtRec([]byte{0x00, 0x00}, makeServerName(sta)) // server name indication - ext[1] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret - ext[2] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info - suppGroup, _ := hex.DecodeString("0008001d001700180019") - ext[3] = addExtRec([]byte{0x00, 0x0a}, suppGroup) // supported groups - ext[4] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats - ext[5] = addExtRec([]byte{0x00, 0x23}, client.MakeSessionTicket(sta)) // Session tickets +func (f *firefox) composeExtensions(serverName string, keyShare []byte) []byte { + composeKeyShare := func(hidden []byte) []byte { + ret := make([]byte, 107) + ret[0], ret[1] = 0x00, 0x69 // length 105 + ret[2], ret[3] = 0x00, 0x1d // group x25519 + ret[4], ret[5] = 0x00, 0x20 // length 32 + copy(ret[6:38], hidden) + ret[38], ret[39] = 0x00, 0x17 // group secp256r1 + ret[40], ret[41] = 0x00, 0x41 // length 65 + rand.Read(ret[42:107]) + return ret + } + // extension length is always 399, and server name length is variable + var ext [14][]byte + ext[0] = addExtRec([]byte{0x00, 0x00}, makeServerName(serverName)) // server name indication + ext[1] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret + ext[2] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info + suppGroup, _ := hex.DecodeString("000c001d00170018001901000101") + ext[3] = addExtRec([]byte{0x00, 0x0a}, suppGroup) // supported groups + ext[4] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats + ext[5] = addExtRec([]byte{0x00, 0x23}, []byte{}) // Session tickets APLN, _ := hex.DecodeString("000c02683208687474702f312e31") ext[6] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation ext[7] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request + ext[8] = addExtRec([]byte{0x00, 0x33}, composeKeyShare(keyShare)) // key share + suppVersions, _ := hex.DecodeString("080304030303020301") + ext[9] = addExtRec([]byte{0x00, 0x2b}, suppVersions) // supported versions sigAlgo, _ := hex.DecodeString("001604030503060308040805080604010501060102030201") - ext[8] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms - ext[9] = addExtRec([]byte{0x00, 0x15}, makeNullBytes(121-len(ext[0]))) // padding + ext[10] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms + ext[11] = addExtRec([]byte{0x00, 0x2d}, []byte{0x01, 0x01}) // psk key exchange modes + ext[12] = addExtRec([]byte{0x00, 0x1c}, []byte{0x40, 0x01}) // record size limit + // len(ext[0]) + 237 + 4 + len(padding) = 399 + // len(padding) = 158 - len(ext[0]) + ext[13] = addExtRec([]byte{0x00, 0x15}, makeNullBytes(158-len(serverName))) // padding var ret []byte - for i := 0; i < 10; i++ { - ret = append(ret, ext[i]...) + for _, e := range ext { + ret = append(ret, e...) } return ret } -func (f *firefox) composeClientHello(sta *client.State) []byte { +func (f *firefox) composeClientHello(sta *client.State) ([]byte, []byte) { + random, sessionID, keyShare, sharedSecret := client.MakeHiddenData(sta) + var clientHello [12][]byte - clientHello[0] = []byte{0x01} // handshake type - clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508 - clientHello[2] = []byte{0x03, 0x03} // client version - clientHello[3] = client.MakeRandomField(sta) // random - clientHello[4] = []byte{0x20} // session id length 32 - clientHello[5] = util.PsudoRandBytes(32, sta.Now().UnixNano()) // session id - clientHello[6] = []byte{0x00, 0x1e} // cipher suites length 28 - cipherSuites, _ := hex.DecodeString("c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a") - clientHello[7] = cipherSuites // cipher suites - clientHello[8] = []byte{0x01} // compression methods length 1 - clientHello[9] = []byte{0x00} // compression methods - clientHello[10] = []byte{0x01, 0x95} // extensions length 405 - clientHello[11] = f.composeExtensions(sta) // extensions + clientHello[0] = []byte{0x01} // handshake type + clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508 + clientHello[2] = []byte{0x03, 0x03} // client version + clientHello[3] = random // random + clientHello[4] = []byte{0x20} // session id length 32 + clientHello[5] = sessionID // session id + clientHello[6] = []byte{0x00, 0x24} // cipher suites length 36 + cipherSuites, _ := hex.DecodeString("130113031302c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a") + clientHello[7] = cipherSuites // cipher suites + clientHello[8] = []byte{0x01} // compression methods length 1 + clientHello[9] = []byte{0x00} // compression methods + + clientHello[11] = f.composeExtensions(sta.ServerName, keyShare) + clientHello[10] = []byte{0x00, 0x00} // extensions length + binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11]))) + var ret []byte - for i := 0; i < 12; i++ { - ret = append(ret, clientHello[i]...) + for _, c := range clientHello { + ret = append(ret, c...) } - return ret + return ret, sharedSecret } diff --git a/internal/client/auth.go b/internal/client/auth.go index 7942c44..118b0ee 100644 --- a/internal/client/auth.go +++ b/internal/client/auth.go @@ -2,55 +2,40 @@ package client import ( "crypto/rand" - "crypto/sha256" "encoding/binary" "github.com/cbeuw/Cloak/internal/ecdh" "github.com/cbeuw/Cloak/internal/util" + "sync/atomic" ) -func MakeRandomField(sta *State) []byte { - // [4 bytes sessionId] [12 bytes random] [16 bytes hash] - t := make([]byte, 8) - binary.BigEndian.PutUint64(t, uint64(sta.Now().Unix()/(12*60*60))) +func MakeHiddenData(sta *State) (random, TLSsessionID, keyShare, sharedSecret []byte) { + // random is marshalled ephemeral pub key 32 bytes + // TLSsessionID || keyShare is [encrypted UID 16 bytes, proxy method 12 bytes, encryption method 1 byte, timestamp 8 bytes, sessionID 4 bytes] [unused data] [16 bytes authentication tag] + ephPv, ephPub, _ := ecdh.GenerateKey(rand.Reader) + random = ecdh.Marshal(ephPub) - front := make([]byte, 16) - binary.BigEndian.PutUint32(front[0:4], sta.SessionID) - rand.Read(front[4:]) - preHash := make([]byte, 56) - copy(preHash[0:32], sta.UID) - copy(preHash[32:40], t) - copy(preHash[40:56], front) - h := sha256.New() - h.Write(preHash) + plaintext := make([]byte, 48) + copy(plaintext, sta.UID) + copy(plaintext[16:28], sta.ProxyMethod) + plaintext[28] = sta.EncryptionMethod + binary.BigEndian.PutUint64(plaintext[29:37], uint64(sta.Now().Unix())) + binary.BigEndian.PutUint32(plaintext[37:41], atomic.LoadUint32(&sta.SessionID)) - ret := make([]byte, 32) - copy(ret[0:16], front) - copy(ret[16:32], h.Sum(nil)[0:16]) - return ret + sharedSecret = ecdh.GenerateSharedSecret(ephPv, sta.staticPub) + nonce := random[0:12] + ciphertext, _ := util.AESGCMEncrypt(nonce, sharedSecret, plaintext) + TLSsessionID = ciphertext[0:32] + keyShare = ciphertext[32:64] + return } -const SESSION_TICKET_LEN = 192 -const PUB_KEY_LEN = 32 -const AUTH_TAG_LEN = 16 -const STEGANO_LEN = SESSION_TICKET_LEN - PUB_KEY_LEN - AUTH_TAG_LEN - -func MakeSessionTicket(sta *State) []byte { - // sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID 16 bytes, proxy method 16 bytes, encryption method 1 byte][reserved 111 bytes][16 bytes authentication tag] - // The first 12 bytes of the marshalled ephemeral public key is used as the nonce - // for encrypting the UID - - ticket := make([]byte, SESSION_TICKET_LEN) - - //TODO: error when the interval has expired - ephPub, intervalKey := sta.GetIntervalKeys() - copy(ticket[0:PUB_KEY_LEN], ecdh.Marshal(ephPub)) - - plain := make([]byte, STEGANO_LEN) - copy(plain, sta.UID) - copy(plain[16:32], sta.ProxyMethod) - plain[32] = sta.EncryptionMethod - - cipher, _ := util.AESGCMEncrypt(ticket[0:12], intervalKey, plain) - copy(ticket[PUB_KEY_LEN:], cipher) - return ticket +func xor(a []byte, b []byte) { + for i := range a { + a[i] ^= b[i] + } +} + +func DecryptSessionKey(serverRandom []byte, sharedSecret []byte) []byte { + xor(serverRandom, sharedSecret) + return serverRandom } diff --git a/internal/client/state.go b/internal/client/state.go index 813ee1d..c676067 100644 --- a/internal/client/state.go +++ b/internal/client/state.go @@ -2,13 +2,11 @@ package client import ( "crypto" - "crypto/rand" "encoding/base64" "encoding/json" "errors" "io/ioutil" "strings" - "sync" "time" "github.com/cbeuw/Cloak/internal/ecdh" @@ -20,18 +18,10 @@ type rawConfig struct { EncryptionMethod string UID string PublicKey string - TicketTimeHint int BrowserSig string NumConn int } -type tthIntervalKeys struct { - interval int64 - ephPv crypto.PrivateKey - ephPub crypto.PublicKey - intervalKey []byte -} - // State stores global variables type State struct { LocalHost string @@ -45,12 +35,8 @@ type State struct { staticPub crypto.PublicKey IsAdmin bool - intervalDataM sync.Mutex - intervalData *tthIntervalKeys - ProxyMethod string EncryptionMethod byte - TicketTimeHint int ServerName string BrowserSig string NumConn int @@ -58,35 +44,15 @@ type State struct { func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State { ret := &State{ - LocalHost: localHost, - LocalPort: localPort, - RemoteHost: remoteHost, - RemotePort: remotePort, - Now: nowFunc, - intervalData: &tthIntervalKeys{}, + LocalHost: localHost, + LocalPort: localPort, + RemoteHost: remoteHost, + RemotePort: remotePort, + Now: nowFunc, } return ret } -func (sta *State) UpdateIntervalKeys() { - sta.intervalDataM.Lock() - defer sta.intervalDataM.Unlock() - currentInterval := sta.Now().Unix() / int64(sta.TicketTimeHint) - if currentInterval == sta.intervalData.interval { - return - } - sta.intervalData.interval = currentInterval - ephPv, ephPub, _ := ecdh.GenerateKey(rand.Reader) - intervalKey := ecdh.GenerateSharedSecret(ephPv, sta.staticPub) - sta.intervalData.ephPv, sta.intervalData.ephPub, sta.intervalData.intervalKey = ephPv, ephPub, intervalKey -} - -func (sta *State) GetIntervalKeys() (crypto.PublicKey, []byte) { - sta.intervalDataM.Lock() - defer sta.intervalDataM.Unlock() - return sta.intervalData.ephPub, sta.intervalData.intervalKey -} - // semi-colon separated value. This is for Android plugin options func ssvToJson(ssv string) (ret []byte) { unescape := func(s string) string { @@ -147,7 +113,6 @@ func (sta *State) ParseConfig(conf string) (err error) { sta.ProxyMethod = preParse.ProxyMethod sta.ServerName = preParse.ServerName - sta.TicketTimeHint = preParse.TicketTimeHint sta.BrowserSig = preParse.BrowserSig sta.NumConn = preParse.NumConn diff --git a/internal/multiplex/session.go b/internal/multiplex/session.go index 76c2747..bf2d146 100644 --- a/internal/multiplex/session.go +++ b/internal/multiplex/session.go @@ -26,6 +26,8 @@ type Session struct { // This is supposed to read one TLS message, the same as GoQuiet's ReadTillDrain obfsedRead func(net.Conn, []byte) (int, error) + SessionKey []byte + // atomic nextStreamID uint32 @@ -45,13 +47,14 @@ type Session struct { terminalMsg atomic.Value } -func MakeSession(id uint32, valve *Valve, obfs Obfser, deobfs Deobfser, obfsedRead func(net.Conn, []byte) (int, error)) *Session { +func MakeSession(id uint32, valve *Valve, obfs Obfser, deobfs Deobfser, sessionKey []byte, obfsedRead func(net.Conn, []byte) (int, error)) *Session { sesh := &Session{ id: id, - obfs: obfs, - deobfs: deobfs, obfsedRead: obfsedRead, nextStreamID: 1, + obfs: obfs, + deobfs: deobfs, + SessionKey: sessionKey, streams: make(map[uint32]*Stream), acceptCh: make(chan *Stream, acceptBacklog), } diff --git a/internal/server/TLS.go b/internal/server/TLS.go index 017b928..0ee1f71 100644 --- a/internal/server/TLS.go +++ b/internal/server/TLS.go @@ -1,11 +1,12 @@ package server import ( + "bytes" + "crypto/rand" "encoding/binary" + "encoding/hex" "errors" - "time" - - "github.com/cbeuw/Cloak/internal/util" + "fmt" ) // ClientHello contains every field in a ClientHello message @@ -49,6 +50,35 @@ func parseExtensions(input []byte) (ret map[[2]byte][]byte, err error) { return ret, err } +func parseKeyShare(input []byte) (ret []byte, err error) { + defer func() { + if r := recover(); r != nil { + err = errors.New("malformed key_share") + } + }() + totalLen := int(u16(input[0:2])) + // 2 bytes "client key share length" + pointer := 2 + for pointer < totalLen { + if bytes.Equal([]byte{0x00, 0x1d}, input[pointer:pointer+2]) { + // skip "key exchange length" + pointer += 2 + length := int(u16(input[pointer : pointer+2])) + pointer += 2 + if length != 32 { + return nil, fmt.Errorf("key share length should be 32, instead of %v", length) + } + return input[pointer : pointer+length], nil + } + pointer += 2 + length := int(u16(input[pointer : pointer+2])) + pointer += 2 + _ = input[pointer : pointer+length] + pointer += length + } + return nil, errors.New("x25519 does not exist") +} + // AddRecordLayer adds record layer to data func AddRecordLayer(input []byte, typ []byte, ver []byte) []byte { length := make([]byte, 2) @@ -131,21 +161,34 @@ func ParseClientHello(data []byte) (ret *ClientHello, err error) { return } -func composeServerHello(ch *ClientHello) []byte { - var serverHello [10][]byte - serverHello[0] = []byte{0x02} // handshake type - serverHello[1] = []byte{0x00, 0x00, 0x4d} // length 77 - serverHello[2] = []byte{0x03, 0x03} // server version - serverHello[3] = util.PsudoRandBytes(32, time.Now().UnixNano()) // random - serverHello[4] = []byte{0x20} // session id length 32 - serverHello[5] = ch.sessionId // session id - serverHello[6] = []byte{0xc0, 0x30} // cipher suite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - serverHello[7] = []byte{0x00} // compression method null - serverHello[8] = []byte{0x00, 0x05} // extensions length 5 - serverHello[9] = []byte{0xff, 0x01, 0x00, 0x01, 0x00} // extensions renegotiation_info - ret := []byte{} - for i := 0; i < 10; i++ { - ret = append(ret, serverHello[i]...) +func xor(a []byte, b []byte) { + for i := range a { + a[i] ^= b[i] + } +} + +func composeServerHello(ch *ClientHello, sharedSecret []byte, sessionKey []byte) []byte { + var serverHello [11][]byte + serverHello[0] = []byte{0x02} // handshake type + serverHello[1] = []byte{0x00, 0x00, 0x76} // length 77 + serverHello[2] = []byte{0x03, 0x03} // server version + xor(sharedSecret, sessionKey) + serverHello[3] = sharedSecret // random + serverHello[4] = []byte{0x20} // session id length 32 + serverHello[5] = ch.sessionId // session id + serverHello[6] = []byte{0xc0, 0x30} // cipher suite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + serverHello[7] = []byte{0x00} // compression method null + serverHello[8] = []byte{0x00, 0x2e} // extensions length 46 + + keyShare, _ := hex.DecodeString("00330024001d0020") + keyExchange := make([]byte, 32) + rand.Read(keyExchange) + serverHello[9] = append(keyShare, keyExchange...) + + serverHello[10], _ = hex.DecodeString("002b00020304") + var ret []byte + for _, s := range serverHello { + ret = append(ret, s...) } return ret } @@ -153,14 +196,10 @@ func composeServerHello(ch *ClientHello) []byte { // ComposeReply composes the ServerHello, ChangeCipherSpec and Finished messages // together with their respective record layers into one byte slice. The content // of these messages are random and useless for this plugin -func ComposeReply(ch *ClientHello) []byte { +func ComposeReply(ch *ClientHello, sharedSecret []byte, sessionKey []byte) []byte { TLS12 := []byte{0x03, 0x03} - shBytes := AddRecordLayer(composeServerHello(ch), []byte{0x16}, TLS12) + shBytes := AddRecordLayer(composeServerHello(ch, sharedSecret, sessionKey), []byte{0x16}, TLS12) ccsBytes := AddRecordLayer([]byte{0x01}, []byte{0x14}, TLS12) - finished := make([]byte, 64) - finished = util.PsudoRandBytes(40, time.Now().UnixNano()) - fBytes := AddRecordLayer(finished, []byte{0x16}, TLS12) ret := append(shBytes, ccsBytes...) - ret = append(ret, fBytes...) return ret } diff --git a/internal/server/activeuser.go b/internal/server/activeuser.go index f9bd405..d01f781 100644 --- a/internal/server/activeuser.go +++ b/internal/server/activeuser.go @@ -30,7 +30,7 @@ func (u *ActiveUser) DelSession(sessionID uint32) { u.sessionsM.Unlock() } -func (u *ActiveUser) GetSession(sessionID uint32, obfs mux.Obfser, deobfs mux.Deobfser, obfsedRead func(net.Conn, []byte) (int, error)) (sesh *mux.Session, existing bool, err error) { +func (u *ActiveUser) GetSession(sessionID uint32, obfs mux.Obfser, deobfs mux.Deobfser, sessionKey []byte, obfsedRead func(net.Conn, []byte) (int, error)) (sesh *mux.Session, existing bool, err error) { u.sessionsM.Lock() defer u.sessionsM.Unlock() if sesh = u.sessions[sessionID]; sesh != nil { @@ -40,7 +40,7 @@ func (u *ActiveUser) GetSession(sessionID uint32, obfs mux.Obfser, deobfs mux.De if err != nil { return nil, false, err } - sesh = mux.MakeSession(sessionID, u.valve, obfs, deobfs, obfsedRead) + sesh = mux.MakeSession(sessionID, u.valve, obfs, deobfs, sessionKey, obfsedRead) u.sessions[sessionID] = sesh return sesh, false, nil } diff --git a/internal/server/auth.go b/internal/server/auth.go index 666982f..981b58f 100644 --- a/internal/server/auth.go +++ b/internal/server/auth.go @@ -2,45 +2,13 @@ package server import ( "bytes" - "crypto" - "crypto/sha256" "encoding/binary" "github.com/cbeuw/Cloak/internal/ecdh" "github.com/cbeuw/Cloak/internal/util" "log" ) -const SESSION_TICKET_LEN = 192 -const PUB_KEY_LEN = 32 -const AUTH_TAG_LEN = 16 -const USED_STAGNO_LEN = 16 + 16 + 1 - -func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) (UID []byte, proxyMethod string, encryptionMethod byte, tthKey []byte) { - // sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID 16 bytes, proxy method 16 bytes, encryption method 1 byte][reserved 111 bytes][padding 111 bytes] - ephPub, _ := ecdh.Unmarshal(ticket[0:PUB_KEY_LEN]) - tthKey = ecdh.GenerateSharedSecret(staticPv, ephPub) - plain, err := util.AESGCMDecrypt(ticket[0:12], tthKey, ticket[PUB_KEY_LEN:]) - if err != nil { - return - } - return plain[0:16], string(bytes.Trim(plain[16:32], "\x00")), plain[32], tthKey -} - -func validateRandom(random []byte, UID []byte, time int64) (bool, uint32) { - t := make([]byte, 8) - binary.BigEndian.PutUint64(t, uint64(time/(12*60*60))) - front := random[0:16] - preHash := make([]byte, 56) - copy(preHash[0:32], UID) - copy(preHash[32:40], t) - copy(preHash[40:56], front) - h := sha256.New() - h.Write(preHash) - - sessionID := binary.BigEndian.Uint32(front[0:4]) - return bytes.Equal(h.Sum(nil)[0:16], random[16:32]), sessionID -} -func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID uint32, proxyMethod string, encryptionMethod byte, tthKey []byte) { +func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID uint32, proxyMethod string, encryptionMethod byte, sharedSecret []byte) { var random [32]byte copy(random[:], ch.random) @@ -54,21 +22,37 @@ func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID u return } - ticket := ch.extensions[[2]byte{0x00, 0x23}] - - if len(ticket) < PUB_KEY_LEN+USED_STAGNO_LEN+AUTH_TAG_LEN { + ephPub, ok := ecdh.Unmarshal(random[:]) + if !ok { return } - UID, proxyMethod, encryptionMethod, tthKey = decryptSessionTicket(sta.staticPv, ticket) - - if len(UID) < 16 { + sharedSecret = ecdh.GenerateSharedSecret(sta.staticPv, ephPub) + keyShare, err := parseKeyShare(ch.extensions[[2]byte{0x00, 0x33}]) + if err != nil { return } - isCK, sessionID = validateRandom(ch.random, UID, sta.Now().Unix()) - if !isCK { + ciphertext := append(ch.sessionId, keyShare...) + + if len(ciphertext) != 64 { return } + plaintext, err := util.AESGCMDecrypt(random[0:12], sharedSecret, ciphertext) + if err != nil { + return + } + + UID = plaintext[0:16] + proxyMethod = string(bytes.Trim(plaintext[16:28], "\x00")) + encryptionMethod = plaintext[28] + timestamp := int64(binary.BigEndian.Uint64(plaintext[29:37])) + if timestamp/int64(TIMESTAMP_WINDOW.Seconds()) != sta.Now().Unix()/int64(TIMESTAMP_WINDOW.Seconds()) { + isCK = false + return + } + sessionID = binary.BigEndian.Uint32(plaintext[37:41]) + + isCK = true return } diff --git a/internal/server/state.go b/internal/server/state.go index 769d180..bcbf191 100644 --- a/internal/server/state.go +++ b/internal/server/state.go @@ -101,14 +101,20 @@ func (sta *State) ParseConfig(conf string) (err error) { return nil } +// This is the accepting window of the encrypted timestamp from client +// we reject the client if the timestamp is outside of this window. +// This is for replay prevention so that we don't have to save unlimited amount of +// random +const TIMESTAMP_WINDOW = 12 * time.Hour + // UsedRandomCleaner clears the cache of used random fields every 12 hours func (sta *State) UsedRandomCleaner() { for { - time.Sleep(12 * time.Hour) + time.Sleep(TIMESTAMP_WINDOW) now := int(sta.Now().Unix()) sta.usedRandomM.Lock() for key, t := range sta.usedRandom { - if now-t > 12*3600 { + if now-t > int(TIMESTAMP_WINDOW.Seconds()) { delete(sta.usedRandom, key) } } diff --git a/internal/util/util.go b/internal/util/util.go index f12e6e7..524ae69 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -5,6 +5,8 @@ import ( "crypto/cipher" "encoding/binary" "errors" + mux "github.com/cbeuw/Cloak/internal/multiplex" + "golang.org/x/crypto/chacha20poly1305" "io" prand "math/rand" "net" @@ -83,6 +85,40 @@ func ReadTLS(conn net.Conn, buffer []byte) (n int, err error) { return } +func GenerateObfs(encryptionMethod byte, sessionKey []byte) (obfs mux.Obfser, deobfs mux.Deobfser, err error) { + var payloadCipher cipher.AEAD + switch encryptionMethod { + case 0x00: + payloadCipher = nil + case 0x01: + var c cipher.Block + c, err = aes.NewCipher(sessionKey) + if err != nil { + return + } + payloadCipher, err = cipher.NewGCM(c) + if err != nil { + return + } + case 0x02: + payloadCipher, err = chacha20poly1305.New(sessionKey) + if err != nil { + return + } + default: + return nil, nil, errors.New("Unknown encryption method") + } + + headerCipher, err := aes.NewCipher(sessionKey) + if err != nil { + return + } + + obfs = mux.MakeObfs(headerCipher, payloadCipher) + deobfs = mux.MakeDeobfs(headerCipher, payloadCipher) + return +} + // AddRecordLayer adds record layer to data func AddRecordLayer(input []byte, typ []byte, ver []byte) []byte { length := make([]byte, 2)