diff --git a/cmd/ck-client/ck-client.go b/cmd/ck-client/ck-client.go index afad156..e00d922 100644 --- a/cmd/ck-client/ck-client.go +++ b/cmd/ck-client/ck-client.go @@ -23,48 +23,6 @@ import ( var version string -// This establishes a connection with ckserver and performs a handshake -func makeRemoteConn(sta *client.State) (net.Conn, []byte, error) { - - // For android - d := net.Dialer{Control: protector} - - clientHello, sharedSecret := client.ComposeClientHello(sta) - connectingIP := sta.RemoteHost - if net.ParseIP(connectingIP).To4() == nil { - // IPv6 needs square brackets - connectingIP = "[" + connectingIP + "]" - } - remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.RemotePort) - if err != nil { - log.WithField("error", err).Error("Failed to connect to remote") - return nil, nil, err - } - _, err = remoteConn.Write(clientHello) - if err != nil { - log.WithField("error", err).Error("Failed to send ClientHello") - return nil, nil, err - } - log.Trace("client hello sent successfully") - - buf := make([]byte, 1024) - log.Trace("waiting for ServerHello") - _, err = util.ReadTLS(remoteConn, buf) - if err != nil { - log.WithField("error", err).Error("Failed to read ServerHello") - } - serverRandom := buf[11:43] - sessionKey := client.DecryptSessionKey(serverRandom, sharedSecret) - _, err = util.ReadTLS(remoteConn, buf) - if err != nil { - log.WithField("error", err).Error("Failed to read ChangeCipherSpec") - return nil, nil, err - } - - return remoteConn, sessionKey, nil - -} - func makeSession(sta *client.State) *mux.Session { log.Info("Attemtping to start a new session") if !sta.IsAdmin { @@ -75,6 +33,7 @@ func makeSession(sta *client.State) *mux.Session { atomic.StoreUint32(&sta.SessionID, binary.BigEndian.Uint32(quad)) } + d := net.Dialer{Control: protector} connsCh := make(chan net.Conn, sta.NumConn) var _sessionKey atomic.Value var wg sync.WaitGroup @@ -82,7 +41,17 @@ func makeSession(sta *client.State) *mux.Session { wg.Add(1) go func() { makeconn: - conn, sk, err := makeRemoteConn(sta) + connectingIP := sta.RemoteHost + if net.ParseIP(connectingIP).To4() == nil { + // IPv6 needs square brackets + connectingIP = "[" + connectingIP + "]" + } + remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.RemotePort) + + if err != nil { + log.WithField("error", err).Error("Failed to connect to remote") + } + sk, err := client.PrepareConnection(sta, remoteConn) _sessionKey.Store(sk) if err != nil { log.Errorf("Failed to establish new connections to remote: %v", err) @@ -90,7 +59,7 @@ func makeSession(sta *client.State) *mux.Session { time.Sleep(time.Second * 3) goto makeconn } - connsCh <- conn + connsCh <- remoteConn wg.Done() }() } diff --git a/cmd/ck-server/ck-server.go b/cmd/ck-server/ck-server.go index 004a167..aa72045 100644 --- a/cmd/ck-server/ck-server.go +++ b/cmd/ck-server/ck-server.go @@ -27,10 +27,6 @@ var version string func dispatchConnection(conn net.Conn, sta *server.State) { remoteAddr := conn.RemoteAddr() var err error - rejectLogger := log.WithFields(log.Fields{ - "remoteAddr": remoteAddr, - "error": err, - }) buf := make([]byte, 1500) conn.SetReadDeadline(time.Now().Add(3 * time.Second)) @@ -56,37 +52,15 @@ func dispatchConnection(conn net.Conn, sta *server.State) { go util.Pipe(conn, webConn) } - ch, err := server.ParseClientHello(data) + UID, sessionID, proxyMethod, encryptionMethod, finishHandshake, err := server.PrepareConnection(data, sta, conn) if err != nil { - rejectLogger.Warn("+1 non Cloak non (or malformed) TLS traffic") - goWeb() - return - } - - UID, sessionID, proxyMethod, encryptionMethod, sharedSecret, err := server.TouchStone(ch, sta) - if err != nil { - rejectLogger.Warn("+1 non Cloak TLS traffic") - goWeb() - return - } - if _, ok := sta.ProxyBook[proxyMethod]; !ok { log.WithFields(log.Fields{ - "UID": UID, - "proxyMethod": proxyMethod, - }).Warn("+1 Cloak TLS traffic with invalid proxy method") - goWeb() - return - } - - finishHandshake := func(sessionKey []byte) error { - reply := server.ComposeReply(ch, sharedSecret, sessionKey) - _, err = conn.Write(reply) - if err != nil { - go conn.Close() - return err - } - log.Trace("finished handshake") - return nil + "remoteAddr": remoteAddr, + "UID": UID, + "sessionId": sessionID, + "proxyMethod": proxyMethod, + "encryptionMethod": encryptionMethod, + }).Warn(err) } sessionKey := make([]byte, 32) @@ -106,6 +80,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) { log.Error(err) return } + log.Trace("finished handshake") sesh := mux.MakeSession(0, mux.UNLIMITED_VALVE, obfuscator, util.ReadTLS) sesh.AddConnection(conn) //TODO: Router could be nil in cnc mode @@ -146,6 +121,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) { log.Error(err) return } + log.Trace("finished handshake") sesh.AddConnection(conn) return } @@ -155,6 +131,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) { log.Error(err) return } + log.Trace("finished handshake") log.WithFields(log.Fields{ "UID": b64(UID), diff --git a/internal/client/TLS.go b/internal/client/TLS.go index f1c3aff..7249d41 100644 --- a/internal/client/TLS.go +++ b/internal/client/TLS.go @@ -3,6 +3,9 @@ package client import ( "encoding/binary" "github.com/cbeuw/Cloak/internal/util" + "net" + + log "github.com/sirupsen/logrus" ) type Browser interface { @@ -34,8 +37,34 @@ func addExtRec(typ []byte, data []byte) []byte { return ret } -// ComposeClientHello composes ClientHello with record layer -func ComposeClientHello(sta *State) ([]byte, []byte) { +// composeClientHello composes ClientHello with record layer +func composeClientHello(sta *State) ([]byte, []byte) { ch, sharedSecret := sta.Browser.composeClientHello(sta) return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01}), sharedSecret } + +func PrepareConnection(sta *State, conn net.Conn) (sessionKey []byte, err error) { + + clientHello, sharedSecret := composeClientHello(sta) + _, err = conn.Write(clientHello) + if err != nil { + return + } + log.Trace("client hello sent successfully") + + buf := make([]byte, 1024) + log.Trace("waiting for ServerHello") + _, err = util.ReadTLS(conn, buf) + if err != nil { + return + } + serverRandom := buf[11:43] + sessionKey = decryptSessionKey(serverRandom, sharedSecret) + _, err = util.ReadTLS(conn, buf) + if err != nil { + return + } + + return sessionKey, nil + +} diff --git a/internal/client/auth.go b/internal/client/auth.go index 118b0ee..f3229ef 100644 --- a/internal/client/auth.go +++ b/internal/client/auth.go @@ -8,7 +8,7 @@ import ( "sync/atomic" ) -func MakeHiddenData(sta *State) (random, TLSsessionID, keyShare, sharedSecret []byte) { +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) @@ -35,7 +35,7 @@ func xor(a []byte, b []byte) { } } -func DecryptSessionKey(serverRandom []byte, sharedSecret []byte) []byte { +func decryptSessionKey(serverRandom []byte, sharedSecret []byte) []byte { xor(serverRandom, sharedSecret) return serverRandom } diff --git a/internal/client/chrome.go b/internal/client/chrome.go index 8c36b13..54108d0 100644 --- a/internal/client/chrome.go +++ b/internal/client/chrome.go @@ -80,7 +80,7 @@ func (c *Chrome) composeExtensions(serverName string, keyShare []byte) []byte { } func (c *Chrome) composeClientHello(sta *State) (ch []byte, sharedSecret []byte) { - random, sessionID, keyShare, sharedSecret := MakeHiddenData(sta) + random, sessionID, keyShare, sharedSecret := makeHiddenData(sta) var clientHello [12][]byte clientHello[0] = []byte{0x01} // handshake type clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508 diff --git a/internal/client/firefox.go b/internal/client/firefox.go index 17ad04c..6ed1517 100644 --- a/internal/client/firefox.go +++ b/internal/client/firefox.go @@ -51,7 +51,7 @@ func (f *Firefox) composeExtensions(serverName string, keyShare []byte) []byte { } func (f *Firefox) composeClientHello(sta *State) (ch []byte, sharedSecret []byte) { - random, sessionID, keyShare, sharedSecret := MakeHiddenData(sta) + random, sessionID, keyShare, sharedSecret := makeHiddenData(sta) var clientHello [12][]byte clientHello[0] = []byte{0x01} // handshake type diff --git a/internal/server/TLS.go b/internal/server/TLS.go index e8c96d2..362312e 100644 --- a/internal/server/TLS.go +++ b/internal/server/TLS.go @@ -7,6 +7,9 @@ import ( "encoding/hex" "errors" "fmt" + "net" + + log "github.com/sirupsen/logrus" ) // ClientHello contains every field in a ClientHello message @@ -79,8 +82,8 @@ func parseKeyShare(input []byte) (ret []byte, err error) { return nil, errors.New("x25519 does not exist") } -// AddRecordLayer adds record layer to data -func AddRecordLayer(input []byte, typ []byte, ver []byte) []byte { +// addRecordLayer adds record layer to data +func addRecordLayer(input []byte, typ []byte, ver []byte) []byte { length := make([]byte, 2) binary.BigEndian.PutUint16(length, uint16(len(input))) ret := make([]byte, 5+len(input)) @@ -91,9 +94,9 @@ func AddRecordLayer(input []byte, typ []byte, ver []byte) []byte { return ret } -// ParseClientHello parses everything on top of the TLS layer +// parseClientHello parses everything on top of the TLS layer // (including the record layer) into ClientHello type -func ParseClientHello(data []byte) (ret *ClientHello, err error) { +func parseClientHello(data []byte) (ret *ClientHello, err error) { defer func() { if r := recover(); r != nil { err = errors.New("Malformed ClientHello") @@ -189,13 +192,49 @@ func composeServerHello(sessionId []byte, sharedSecret []byte, sessionKey []byte return ret } -// ComposeReply composes the ServerHello, ChangeCipherSpec and Finished messages +// 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, sharedSecret []byte, sessionKey []byte) []byte { +func composeReply(ch *ClientHello, sharedSecret []byte, sessionKey []byte) []byte { TLS12 := []byte{0x03, 0x03} - shBytes := AddRecordLayer(composeServerHello(ch.sessionId, sharedSecret, sessionKey), []byte{0x16}, TLS12) - ccsBytes := AddRecordLayer([]byte{0x01}, []byte{0x14}, TLS12) + shBytes := addRecordLayer(composeServerHello(ch.sessionId, sharedSecret, sessionKey), []byte{0x16}, TLS12) + ccsBytes := addRecordLayer([]byte{0x01}, []byte{0x14}, TLS12) ret := append(shBytes, ccsBytes...) return ret } + +var ErrBadClientHello = errors.New("non (or malformed) ClientHello") +var ErrNotCloak = errors.New("TLS but non-Cloak ClientHello") +var ErrBadProxyMethod = errors.New("invalid proxy method") + +func PrepareConnection(firstPacket []byte, sta *State, conn net.Conn) (UID []byte, sessionID uint32, proxyMethod string, encryptionMethod byte, finisher func([]byte) error, err error) { + ch, err := parseClientHello(firstPacket) + if err != nil { + log.Debug(err) + err = ErrBadClientHello + return + } + + var sharedSecret []byte + UID, sessionID, proxyMethod, encryptionMethod, sharedSecret, err = TouchStone(ch, sta) + if err != nil { + log.Debug(err) + err = ErrNotCloak + return + } + if _, ok := sta.ProxyBook[proxyMethod]; !ok { + err = ErrBadProxyMethod + return + } + + finisher = func(sessionKey []byte) error { + reply := composeReply(ch, sharedSecret, sessionKey) + _, err = conn.Write(reply) + if err != nil { + go conn.Close() + return err + } + return nil + } + return +}