pull/71/head
Qian Wang 5 years ago
parent 6df20214c0
commit 726a405a26

@ -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
}
buf := make([]byte, 1024)
_, err = util.ReadTLS(remoteConn, buf)
if err != nil {
log.Printf("Reading ServerHello: %v\n", err)
}
reply := TLS.ComposeReply()
_, err = remoteConn.Write(reply)
serverRandom := buf[11:43]
sessionKey := client.DecryptSessionKey(serverRandom, sharedSecret)
_, err = util.ReadTLS(remoteConn, buf)
if err != nil {
log.Printf("Sending reply to remote: %v\n", err)
return nil, err
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)
}
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")
atomic.StoreUint32(&sta.SessionID, binary.BigEndian.Uint32(quad))
}
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 {

@ -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
}
return nil
}
// 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
}
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
}
return nil
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)

@ -4,7 +4,6 @@
"UID":"5nneblJy6lniPJfr81LuYQ==",
"PublicKey":"IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=",
"ServerName":"www.bing.com",
"TicketTimeHint":3600,
"NumConn":4,
"BrowserSig":"chrome"
}

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

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

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

@ -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)))
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)
ret := make([]byte, 32)
copy(ret[0:16], front)
copy(ret[16:32], h.Sum(nil)[0:16])
return ret
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)
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))
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
func xor(a []byte, b []byte) {
for i := range a {
a[i] ^= b[i]
}
}
cipher, _ := util.AESGCMEncrypt(ticket[0:12], intervalKey, plain)
copy(ticket[PUB_KEY_LEN:], cipher)
return ticket
func DecryptSessionKey(serverRandom []byte, sharedSecret []byte) []byte {
xor(serverRandom, sharedSecret)
return serverRandom
}

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

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

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

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

@ -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}]
ephPub, ok := ecdh.Unmarshal(random[:])
if !ok {
return
}
if len(ticket) < PUB_KEY_LEN+USED_STAGNO_LEN+AUTH_TAG_LEN {
sharedSecret = ecdh.GenerateSharedSecret(sta.staticPv, ephPub)
keyShare, err := parseKeyShare(ch.extensions[[2]byte{0x00, 0x33}])
if err != nil {
return
}
ciphertext := append(ch.sessionId, keyShare...)
UID, proxyMethod, encryptionMethod, tthKey = decryptSessionTicket(sta.staticPv, ticket)
if len(ciphertext) != 64 {
return
}
if len(UID) < 16 {
plaintext, err := util.AESGCMDecrypt(random[0:12], sharedSecret, ciphertext)
if err != nil {
return
}
isCK, sessionID = validateRandom(ch.random, UID, sta.Now().Unix())
if !isCK {
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
}

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

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

Loading…
Cancel
Save