mirror of https://github.com/cbeuw/Cloak
Refactor TLS handshake
This commit is contained in:
parent
3dc4c6fb3f
commit
e15536d7c7
|
|
@ -23,48 +23,6 @@ import (
|
||||||
|
|
||||||
var version string
|
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 {
|
func makeSession(sta *client.State) *mux.Session {
|
||||||
log.Info("Attemtping to start a new session")
|
log.Info("Attemtping to start a new session")
|
||||||
if !sta.IsAdmin {
|
if !sta.IsAdmin {
|
||||||
|
|
@ -75,6 +33,7 @@ func makeSession(sta *client.State) *mux.Session {
|
||||||
atomic.StoreUint32(&sta.SessionID, binary.BigEndian.Uint32(quad))
|
atomic.StoreUint32(&sta.SessionID, binary.BigEndian.Uint32(quad))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d := net.Dialer{Control: protector}
|
||||||
connsCh := make(chan net.Conn, sta.NumConn)
|
connsCh := make(chan net.Conn, sta.NumConn)
|
||||||
var _sessionKey atomic.Value
|
var _sessionKey atomic.Value
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
@ -82,7 +41,17 @@ func makeSession(sta *client.State) *mux.Session {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
makeconn:
|
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)
|
_sessionKey.Store(sk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to establish new connections to remote: %v", err)
|
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)
|
time.Sleep(time.Second * 3)
|
||||||
goto makeconn
|
goto makeconn
|
||||||
}
|
}
|
||||||
connsCh <- conn
|
connsCh <- remoteConn
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,6 @@ var version string
|
||||||
func dispatchConnection(conn net.Conn, sta *server.State) {
|
func dispatchConnection(conn net.Conn, sta *server.State) {
|
||||||
remoteAddr := conn.RemoteAddr()
|
remoteAddr := conn.RemoteAddr()
|
||||||
var err error
|
var err error
|
||||||
rejectLogger := log.WithFields(log.Fields{
|
|
||||||
"remoteAddr": remoteAddr,
|
|
||||||
"error": err,
|
|
||||||
})
|
|
||||||
buf := make([]byte, 1500)
|
buf := make([]byte, 1500)
|
||||||
|
|
||||||
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
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)
|
go util.Pipe(conn, webConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
ch, err := server.ParseClientHello(data)
|
UID, sessionID, proxyMethod, encryptionMethod, finishHandshake, err := server.PrepareConnection(data, sta, conn)
|
||||||
if err != nil {
|
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{
|
log.WithFields(log.Fields{
|
||||||
|
"remoteAddr": remoteAddr,
|
||||||
"UID": UID,
|
"UID": UID,
|
||||||
|
"sessionId": sessionID,
|
||||||
"proxyMethod": proxyMethod,
|
"proxyMethod": proxyMethod,
|
||||||
}).Warn("+1 Cloak TLS traffic with invalid proxy method")
|
"encryptionMethod": encryptionMethod,
|
||||||
goWeb()
|
}).Warn(err)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionKey := make([]byte, 32)
|
sessionKey := make([]byte, 32)
|
||||||
|
|
@ -106,6 +80,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Trace("finished handshake")
|
||||||
sesh := mux.MakeSession(0, mux.UNLIMITED_VALVE, obfuscator, util.ReadTLS)
|
sesh := mux.MakeSession(0, mux.UNLIMITED_VALVE, obfuscator, util.ReadTLS)
|
||||||
sesh.AddConnection(conn)
|
sesh.AddConnection(conn)
|
||||||
//TODO: Router could be nil in cnc mode
|
//TODO: Router could be nil in cnc mode
|
||||||
|
|
@ -146,6 +121,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Trace("finished handshake")
|
||||||
sesh.AddConnection(conn)
|
sesh.AddConnection(conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +131,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Trace("finished handshake")
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"UID": b64(UID),
|
"UID": b64(UID),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ package client
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/cbeuw/Cloak/internal/util"
|
"github.com/cbeuw/Cloak/internal/util"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Browser interface {
|
type Browser interface {
|
||||||
|
|
@ -34,8 +37,34 @@ func addExtRec(typ []byte, data []byte) []byte {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComposeClientHello composes ClientHello with record layer
|
// composeClientHello composes ClientHello with record layer
|
||||||
func ComposeClientHello(sta *State) ([]byte, []byte) {
|
func composeClientHello(sta *State) ([]byte, []byte) {
|
||||||
ch, sharedSecret := sta.Browser.composeClientHello(sta)
|
ch, sharedSecret := sta.Browser.composeClientHello(sta)
|
||||||
return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01}), sharedSecret
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"sync/atomic"
|
"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
|
// 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]
|
// 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)
|
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)
|
xor(serverRandom, sharedSecret)
|
||||||
return serverRandom
|
return serverRandom
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ func (c *Chrome) composeExtensions(serverName string, keyShare []byte) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chrome) composeClientHello(sta *State) (ch []byte, sharedSecret []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
|
var clientHello [12][]byte
|
||||||
clientHello[0] = []byte{0x01} // handshake type
|
clientHello[0] = []byte{0x01} // handshake type
|
||||||
clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508
|
clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func (f *Firefox) composeExtensions(serverName string, keyShare []byte) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firefox) composeClientHello(sta *State) (ch []byte, sharedSecret []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
|
var clientHello [12][]byte
|
||||||
clientHello[0] = []byte{0x01} // handshake type
|
clientHello[0] = []byte{0x01} // handshake type
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClientHello contains every field in a ClientHello message
|
// 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")
|
return nil, errors.New("x25519 does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRecordLayer adds record layer to data
|
// addRecordLayer adds record layer to data
|
||||||
func AddRecordLayer(input []byte, typ []byte, ver []byte) []byte {
|
func addRecordLayer(input []byte, typ []byte, ver []byte) []byte {
|
||||||
length := make([]byte, 2)
|
length := make([]byte, 2)
|
||||||
binary.BigEndian.PutUint16(length, uint16(len(input)))
|
binary.BigEndian.PutUint16(length, uint16(len(input)))
|
||||||
ret := make([]byte, 5+len(input))
|
ret := make([]byte, 5+len(input))
|
||||||
|
|
@ -91,9 +94,9 @@ func AddRecordLayer(input []byte, typ []byte, ver []byte) []byte {
|
||||||
return ret
|
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
|
// (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() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = errors.New("Malformed ClientHello")
|
err = errors.New("Malformed ClientHello")
|
||||||
|
|
@ -189,13 +192,49 @@ func composeServerHello(sessionId []byte, sharedSecret []byte, sessionKey []byte
|
||||||
return ret
|
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
|
// together with their respective record layers into one byte slice. The content
|
||||||
// of these messages are random and useless for this plugin
|
// 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}
|
TLS12 := []byte{0x03, 0x03}
|
||||||
shBytes := AddRecordLayer(composeServerHello(ch.sessionId, sharedSecret, sessionKey), []byte{0x16}, TLS12)
|
shBytes := addRecordLayer(composeServerHello(ch.sessionId, sharedSecret, sessionKey), []byte{0x16}, TLS12)
|
||||||
ccsBytes := AddRecordLayer([]byte{0x01}, []byte{0x14}, TLS12)
|
ccsBytes := addRecordLayer([]byte{0x01}, []byte{0x14}, TLS12)
|
||||||
ret := append(shBytes, ccsBytes...)
|
ret := append(shBytes, ccsBytes...)
|
||||||
return ret
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue