Refactor TLS handshake

This commit is contained in:
Qian Wang 2019-08-06 15:50:33 +01:00
parent 3dc4c6fb3f
commit e15536d7c7
7 changed files with 105 additions and 91 deletions

View File

@ -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()
}()
}

View File

@ -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),

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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
}