This commit is contained in:
Qian Wang 2019-08-02 01:01:19 +01:00
parent 6df20214c0
commit 726a405a26
14 changed files with 407 additions and 416 deletions

View File

@ -3,19 +3,17 @@
package main package main
import ( import (
"crypto/aes"
"crypto/cipher"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"flag" "flag"
"fmt" "fmt"
"golang.org/x/crypto/chacha20poly1305"
"io" "io"
"log" "log"
"math/rand" "math/rand"
"net" "net"
"os" "os"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/cbeuw/Cloak/internal/client" "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 // 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 // For android
d := net.Dialer{Control: protector} d := net.Dialer{Control: protector}
clientHello := TLS.ComposeInitHandshake(sta) clientHello, sharedSecret := TLS.ComposeInitHandshake(sta)
connectingIP := sta.RemoteHost connectingIP := sta.RemoteHost
if net.ParseIP(connectingIP).To4() == nil { if net.ParseIP(connectingIP).To4() == nil {
// IPv6 needs square brackets // IPv6 needs square brackets
@ -62,32 +60,28 @@ func makeRemoteConn(sta *client.State) (net.Conn, error) {
remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.RemotePort) remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.RemotePort)
if err != nil { if err != nil {
log.Printf("Connecting to remote: %v\n", err) log.Printf("Connecting to remote: %v\n", err)
return nil, err return nil, nil, err
} }
_, err = remoteConn.Write(clientHello) _, err = remoteConn.Write(clientHello)
if err != nil { if err != nil {
log.Printf("Sending ClientHello: %v\n", err) log.Printf("Sending ClientHello: %v\n", err)
return nil, err return nil, nil, err
} }
// Three discarded messages: ServerHello, ChangeCipherSpec and Finished buf := make([]byte, 1024)
discardBuf := make([]byte, 1024) _, err = util.ReadTLS(remoteConn, buf)
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)
if err != nil { if err != nil {
log.Printf("Sending reply to remote: %v\n", err) log.Printf("Reading ServerHello: %v\n", err)
return nil, 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. // sessionID is limited to its UID.
quad := make([]byte, 4) quad := make([]byte, 4)
rand.Read(quad) rand.Read(quad)
sta.SessionID = binary.BigEndian.Uint32(quad) atomic.StoreUint32(&sta.SessionID, binary.BigEndian.Uint32(quad))
} }
sta.UpdateIntervalKeys() connsCh := make(chan net.Conn, sta.NumConn)
_, tthKey := sta.GetIntervalKeys() var _sessionKey atomic.Value
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)
var wg sync.WaitGroup var wg sync.WaitGroup
for i := 0; i < sta.NumConn; i++ { for i := 0; i < sta.NumConn; i++ {
wg.Add(1) wg.Add(1)
go func() { go func() {
makeconn: makeconn:
conn, err := makeRemoteConn(sta) conn, sk, err := makeRemoteConn(sta)
_sessionKey.Store(sk)
if err != nil { if err != nil {
log.Printf("Failed to establish new connections to remote: %v\n", err) log.Printf("Failed to establish new connections to remote: %v\n", err)
time.Sleep(time.Second * 3) time.Sleep(time.Second * 3)
goto makeconn goto makeconn
} }
sesh.AddConnection(conn) connsCh <- conn
wg.Done() wg.Done()
}() }()
} }
wg.Wait() 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) log.Printf("Session %v established", sta.SessionID)
return sesh return sesh
} }
@ -216,9 +193,6 @@ func main() {
if sta.RemoteHost == "" { if sta.RemoteHost == "" {
log.Fatal("Must specify remoteHost") log.Fatal("Must specify remoteHost")
} }
if sta.TicketTimeHint == 0 {
log.Fatal("TicketTimeHint cannot be empty or 0")
}
listeningIP := sta.LocalHost listeningIP := sta.LocalHost
if net.ParseIP(listeningIP).To4() == nil { if net.ParseIP(listeningIP).To4() == nil {

View File

@ -2,12 +2,10 @@ package main
import ( import (
"bytes" "bytes"
"crypto/aes" "crypto/rand"
"crypto/cipher"
"encoding/base64" "encoding/base64"
"flag" "flag"
"fmt" "fmt"
"golang.org/x/crypto/chacha20poly1305"
"io" "io"
"log" "log"
"net" "net"
@ -48,17 +46,6 @@ func pipe(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
} }
func dispatchConnection(conn net.Conn, sta *server.State) { 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) buf := make([]byte, 1500)
conn.SetReadDeadline(time.Now().Add(3 * time.Second)) conn.SetReadDeadline(time.Now().Add(3 * time.Second))
@ -69,95 +56,89 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
} }
conn.SetReadDeadline(time.Time{}) conn.SetReadDeadline(time.Time{})
data := buf[:i] 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) ch, err := server.ParseClientHello(data)
if err != nil { if err != nil {
log.Printf("+1 non Cloak non (or malformed) TLS traffic from %v\n", conn.RemoteAddr()) log.Printf("+1 non Cloak non (or malformed) TLS traffic from %v\n", conn.RemoteAddr())
goWeb(data) goWeb()
return return
} }
isCloak, UID, sessionID, proxyMethod, encryptionMethod, tthKey := server.TouchStone(ch, sta) isCloak, UID, sessionID, proxyMethod, encryptionMethod, sharedSecret := server.TouchStone(ch, sta)
if !isCloak { if !isCloak {
log.Printf("+1 non Cloak TLS traffic from %v\n", conn.RemoteAddr()) log.Printf("+1 non Cloak TLS traffic from %v\n", conn.RemoteAddr())
goWeb(data) goWeb()
return return
} }
if _, ok := sta.ProxyBook[proxyMethod]; !ok { if _, ok := sta.ProxyBook[proxyMethod]; !ok {
log.Printf("+1 Cloak TLS traffic with invalid proxy method `%v` from %v\n", proxyMethod, conn.RemoteAddr()) log.Printf("+1 Cloak TLS traffic with invalid proxy method `%v` from %v\n", proxyMethod, conn.RemoteAddr())
goWeb(data) goWeb()
return return
} }
var payloadCipher cipher.AEAD user, err := sta.Panel.GetUser(UID)
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)
if err != nil { if err != nil {
log.Println(err) log.Printf("+1 unauthorised user from %v, uid: %v\n", conn.RemoteAddr(), base64.StdEncoding.EncodeToString(UID))
goWeb(data) goWeb()
return return
} }
obfs := mux.MakeObfs(headerCipher, payloadCipher) finishHandshake := func(sessionKey []byte) error {
deobfs := mux.MakeDeobfs(headerCipher, payloadCipher) reply := server.ComposeReply(ch, sharedSecret, sessionKey)
finishHandshake := func() error {
reply := server.ComposeReply(ch)
_, err = conn.Write(reply) _, err = conn.Write(reply)
if err != nil { if err != nil {
go conn.Close() go conn.Close()
return err 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 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 // 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 // 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 // and normal proxy mode is that sessionID needs == 0 for admin mode
if bytes.Equal(UID, sta.AdminUID) && sessionID == 0 { if bytes.Equal(UID, sta.AdminUID) && sessionID == 0 {
err = finishHandshake() err = finishHandshake(sessionKey)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return 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) sesh.AddConnection(conn)
//TODO: Router could be nil in cnc mode //TODO: Router could be nil in cnc mode
err = http.Serve(sesh, sta.LocalAPIRouter) err = http.Serve(sesh, sta.LocalAPIRouter)
@ -167,51 +148,33 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
} }
} }
user, err := sta.Panel.GetUser(UID) err = finishHandshake(sessionKey)
if err != nil {
log.Printf("+1 unauthorised user from %v, uid: %v\n", conn.RemoteAddr(), base64.StdEncoding.EncodeToString(UID))
goWeb(data)
return
}
err = finishHandshake()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return return
} }
sesh, existing, err := user.GetSession(sessionID, obfs, deobfs, util.ReadTLS) log.Printf("New session from UID:%v, sessionID:%v\n", b64.EncodeToString(UID), sessionID)
if err != nil { sesh.AddConnection(conn)
user.DelSession(sessionID)
log.Println(err)
return
}
if existing { for {
sesh.AddConnection(conn) newStream, err := sesh.Accept()
return if err != nil {
} else { if err == mux.ErrBrokenSession {
log.Printf("New session from UID:%v, sessionID:%v\n", b64.EncodeToString(UID), sessionID) log.Printf("Session closed for UID:%v, sessionID:%v, reason:%v\n", b64.EncodeToString(UID), sessionID, sesh.TerminalMsg())
sesh.AddConnection(conn) user.DelSession(sessionID)
for { return
newStream, err := sesh.Accept() } else {
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)
continue 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 sta.ProxyBook["shadowsocks"] = ssLocalHost + ":" + ssLocalPort
} }
go sta.UsedRandomCleaner()
listen := func(addr, port string) { listen := func(addr, port string) {
listener, err := net.Listen("tcp", addr+":"+port) listener, err := net.Listen("tcp", addr+":"+port)
log.Println("Listening on " + addr + ":" + port) log.Println("Listening on " + addr + ":" + port)

View File

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

View File

@ -4,16 +4,13 @@ import (
"encoding/binary" "encoding/binary"
"github.com/cbeuw/Cloak/internal/client" "github.com/cbeuw/Cloak/internal/client"
"github.com/cbeuw/Cloak/internal/util" "github.com/cbeuw/Cloak/internal/util"
"time"
) )
type browser interface { type browser interface {
composeExtensions()
composeClientHello() composeClientHello()
} }
func makeServerName(sta *client.State) []byte { func makeServerName(serverName string) []byte {
serverName := sta.ServerName
serverNameListLength := make([]byte, 2) serverNameListLength := make([]byte, 2)
binary.BigEndian.PutUint16(serverNameListLength, uint16(len(serverName)+3)) binary.BigEndian.PutUint16(serverNameListLength, uint16(len(serverName)+3))
serverNameType := []byte{0x00} // host_name serverNameType := []byte{0x00} // host_name
@ -47,24 +44,15 @@ func addExtRec(typ []byte, data []byte) []byte {
} }
// ComposeInitHandshake composes ClientHello with record layer // ComposeInitHandshake composes ClientHello with record layer
func ComposeInitHandshake(sta *client.State) []byte { func ComposeInitHandshake(sta *client.State) ([]byte, []byte) {
var ch []byte var ch, sharedSecret []byte
switch sta.BrowserSig { switch sta.BrowserSig {
case "chrome": case "chrome":
ch = (&chrome{}).composeClientHello(sta) ch, sharedSecret = (&chrome{}).composeClientHello(sta)
case "firefox": case "firefox":
ch = (&firefox{}).composeClientHello(sta) ch, sharedSecret = (&firefox{}).composeClientHello(sta)
default: default:
panic("Unsupported browser:" + sta.BrowserSig) panic("Unsupported browser:" + sta.BrowserSig)
} }
return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01}) return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01}), sharedSecret
}
// 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...)
} }

View File

@ -1,30 +1,31 @@
// Chrome 64 // Chrome 76
package TLS package TLS
import ( import (
"encoding/binary"
"encoding/hex" "encoding/hex"
"math/rand" "math/rand"
"time" "time"
"github.com/cbeuw/Cloak/internal/client" "github.com/cbeuw/Cloak/internal/client"
"github.com/cbeuw/Cloak/internal/util"
) )
type chrome struct { type chrome struct {
browser browser
} }
func (c *chrome) composeExtensions(sta *client.State) []byte { func makeGREASE() []byte {
// see https://tools.ietf.org/html/draft-davidben-tls-grease-01 // see https://tools.ietf.org/html/draft-davidben-tls-grease-01
// This is exclusive to chrome. // This is exclusive to chrome.
makeGREASE := func() []byte { rand.Seed(time.Now().UnixNano())
rand.Seed(time.Now().UnixNano()) sixteenth := rand.Intn(16)
sixteenth := rand.Intn(16) monoGREASE := byte(sixteenth*16 + 0xA)
monoGREASE := byte(sixteenth*16 + 0xA) doubleGREASE := []byte{monoGREASE, monoGREASE}
doubleGREASE := []byte{monoGREASE, monoGREASE} return doubleGREASE
return doubleGREASE }
}
func (c *chrome) composeExtensions(sta *client.State, keyShare []byte) []byte {
makeSupportedGroups := func() []byte { makeSupportedGroups := func() []byte {
suppGroupListLen := []byte{0x00, 0x08} suppGroupListLen := []byte{0x00, 0x08}
@ -35,48 +36,73 @@ func (c *chrome) composeExtensions(sta *client.State) []byte {
return ret return ret
} }
var ext [14][]byte makeKeyShare := func(hidden []byte) []byte {
ext[0] = addExtRec(makeGREASE(), nil) // First GREASE ret := make([]byte, 43)
ext[1] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info ret[0], ret[1] = 0x00, 0x29 // length 41
ext[2] = addExtRec([]byte{0x00, 0x00}, makeServerName(sta)) // server name indication copy(ret[2:4], makeGREASE())
ext[3] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret ret[4], ret[5] = 0x00, 0x01 // length 1
ext[4] = addExtRec([]byte{0x00, 0x23}, client.MakeSessionTicket(sta)) // Session tickets ret[6] = 0x00
sigAlgo, _ := hex.DecodeString("0012040308040401050308050501080606010201") ret[7], ret[8] = 0x00, 0x1d // group x25519
ext[5] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms ret[9], ret[10] = 0x00, 0x20 // length 32
ext[6] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request copy(ret[11:43], hidden)
ext[7] = addExtRec([]byte{0x00, 0x12}, nil) // signed cert timestamp 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") APLN, _ := hex.DecodeString("000c02683208687474702f312e31")
ext[8] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation ext[7] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation
ext[9] = addExtRec([]byte{0x75, 0x50}, nil) // channel id ext[8] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request
ext[10] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats sigAlgo, _ := hex.DecodeString("0012040308040401050308050501080606010201")
ext[11] = addExtRec([]byte{0x00, 0x0a}, makeSupportedGroups()) // supported groups ext[9] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms
ext[12] = addExtRec(makeGREASE(), []byte{0x00}) // Last GREASE ext[10] = addExtRec([]byte{0x00, 0x12}, nil) // signed cert timestamp
ext[13] = addExtRec([]byte{0x00, 0x15}, makeNullBytes(110-len(ext[2]))) // padding 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 var ret []byte
for i := 0; i < 14; i++ { for _, e := range ext {
ret = append(ret, ext[i]...) ret = append(ret, e...)
} }
return ret 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 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
clientHello[2] = []byte{0x03, 0x03} // client version clientHello[2] = []byte{0x03, 0x03} // client version
clientHello[3] = client.MakeRandomField(sta) // random clientHello[3] = random // random
clientHello[4] = []byte{0x20} // session id length 32 clientHello[4] = []byte{0x20} // session id length 32
clientHello[5] = util.PsudoRandBytes(32, sta.Now().UnixNano()) // session id clientHello[5] = sessionID // session id
clientHello[6] = []byte{0x00, 0x1c} // cipher suites length 28 clientHello[6] = []byte{0x00, 0x22} // cipher suites length 34
cipherSuites, _ := hex.DecodeString("2a2ac02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a") cipherSuites, _ := hex.DecodeString("130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a")
clientHello[7] = cipherSuites // cipher suites clientHello[7] = append(makeGREASE(), cipherSuites...) // cipher suites
clientHello[8] = []byte{0x01} // compression methods length 1 clientHello[8] = []byte{0x01} // compression methods length 1
clientHello[9] = []byte{0x00} // compression methods clientHello[9] = []byte{0x00} // compression methods
clientHello[10] = []byte{0x01, 0x97} // extensions length 407 clientHello[11] = c.composeExtensions(sta, keyShare)
clientHello[11] = c.composeExtensions(sta) // extensions clientHello[10] = []byte{0x00, 0x00} // extensions length 401
binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11])))
var ret []byte var ret []byte
for i := 0; i < 12; i++ { for _, c := range clientHello {
ret = append(ret, clientHello[i]...) ret = append(ret, c...)
} }
return ret return ret, sharedSecret
} }

View File

@ -1,57 +1,82 @@
// Firefox 58 // Firefox 68
package TLS package TLS
import ( import (
"crypto/rand"
"encoding/binary"
"encoding/hex" "encoding/hex"
"github.com/cbeuw/Cloak/internal/client" "github.com/cbeuw/Cloak/internal/client"
"github.com/cbeuw/Cloak/internal/util"
) )
type firefox struct { type firefox struct {
browser browser
} }
func (f *firefox) composeExtensions(sta *client.State) []byte { func (f *firefox) composeExtensions(serverName string, keyShare []byte) []byte {
var ext [10][]byte composeKeyShare := func(hidden []byte) []byte {
ext[0] = addExtRec([]byte{0x00, 0x00}, makeServerName(sta)) // server name indication ret := make([]byte, 107)
ext[1] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret ret[0], ret[1] = 0x00, 0x69 // length 105
ext[2] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info ret[2], ret[3] = 0x00, 0x1d // group x25519
suppGroup, _ := hex.DecodeString("0008001d001700180019") ret[4], ret[5] = 0x00, 0x20 // length 32
ext[3] = addExtRec([]byte{0x00, 0x0a}, suppGroup) // supported groups copy(ret[6:38], hidden)
ext[4] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats ret[38], ret[39] = 0x00, 0x17 // group secp256r1
ext[5] = addExtRec([]byte{0x00, 0x23}, client.MakeSessionTicket(sta)) // Session tickets 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") APLN, _ := hex.DecodeString("000c02683208687474702f312e31")
ext[6] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation 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[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") sigAlgo, _ := hex.DecodeString("001604030503060308040805080604010501060102030201")
ext[8] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms ext[10] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms
ext[9] = addExtRec([]byte{0x00, 0x15}, makeNullBytes(121-len(ext[0]))) // padding 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 var ret []byte
for i := 0; i < 10; i++ { for _, e := range ext {
ret = append(ret, ext[i]...) ret = append(ret, e...)
} }
return ret 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 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
clientHello[2] = []byte{0x03, 0x03} // client version clientHello[2] = []byte{0x03, 0x03} // client version
clientHello[3] = client.MakeRandomField(sta) // random clientHello[3] = random // random
clientHello[4] = []byte{0x20} // session id length 32 clientHello[4] = []byte{0x20} // session id length 32
clientHello[5] = util.PsudoRandBytes(32, sta.Now().UnixNano()) // session id clientHello[5] = sessionID // session id
clientHello[6] = []byte{0x00, 0x1e} // cipher suites length 28 clientHello[6] = []byte{0x00, 0x24} // cipher suites length 36
cipherSuites, _ := hex.DecodeString("c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a") cipherSuites, _ := hex.DecodeString("130113031302c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a")
clientHello[7] = cipherSuites // cipher suites clientHello[7] = cipherSuites // cipher suites
clientHello[8] = []byte{0x01} // compression methods length 1 clientHello[8] = []byte{0x01} // compression methods length 1
clientHello[9] = []byte{0x00} // compression methods clientHello[9] = []byte{0x00} // compression methods
clientHello[10] = []byte{0x01, 0x95} // extensions length 405
clientHello[11] = f.composeExtensions(sta) // extensions 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 var ret []byte
for i := 0; i < 12; i++ { for _, c := range clientHello {
ret = append(ret, clientHello[i]...) ret = append(ret, c...)
} }
return ret return ret, sharedSecret
} }

View File

@ -2,55 +2,40 @@ package client
import ( import (
"crypto/rand" "crypto/rand"
"crypto/sha256"
"encoding/binary" "encoding/binary"
"github.com/cbeuw/Cloak/internal/ecdh" "github.com/cbeuw/Cloak/internal/ecdh"
"github.com/cbeuw/Cloak/internal/util" "github.com/cbeuw/Cloak/internal/util"
"sync/atomic"
) )
func MakeRandomField(sta *State) []byte { func MakeHiddenData(sta *State) (random, TLSsessionID, keyShare, sharedSecret []byte) {
// [4 bytes sessionId] [12 bytes random] [16 bytes hash] // random is marshalled ephemeral pub key 32 bytes
t := make([]byte, 8) // 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]
binary.BigEndian.PutUint64(t, uint64(sta.Now().Unix()/(12*60*60))) ephPv, ephPub, _ := ecdh.GenerateKey(rand.Reader)
random = ecdh.Marshal(ephPub)
front := make([]byte, 16) plaintext := make([]byte, 48)
binary.BigEndian.PutUint32(front[0:4], sta.SessionID) copy(plaintext, sta.UID)
rand.Read(front[4:]) copy(plaintext[16:28], sta.ProxyMethod)
preHash := make([]byte, 56) plaintext[28] = sta.EncryptionMethod
copy(preHash[0:32], sta.UID) binary.BigEndian.PutUint64(plaintext[29:37], uint64(sta.Now().Unix()))
copy(preHash[32:40], t) binary.BigEndian.PutUint32(plaintext[37:41], atomic.LoadUint32(&sta.SessionID))
copy(preHash[40:56], front)
h := sha256.New()
h.Write(preHash)
ret := make([]byte, 32) sharedSecret = ecdh.GenerateSharedSecret(ephPv, sta.staticPub)
copy(ret[0:16], front) nonce := random[0:12]
copy(ret[16:32], h.Sum(nil)[0:16]) ciphertext, _ := util.AESGCMEncrypt(nonce, sharedSecret, plaintext)
return ret TLSsessionID = ciphertext[0:32]
keyShare = ciphertext[32:64]
return
} }
const SESSION_TICKET_LEN = 192 func xor(a []byte, b []byte) {
const PUB_KEY_LEN = 32 for i := range a {
const AUTH_TAG_LEN = 16 a[i] ^= b[i]
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] func DecryptSessionKey(serverRandom []byte, sharedSecret []byte) []byte {
// The first 12 bytes of the marshalled ephemeral public key is used as the nonce xor(serverRandom, sharedSecret)
// for encrypting the UID return serverRandom
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
} }

View File

@ -2,13 +2,11 @@ package client
import ( import (
"crypto" "crypto"
"crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil" "io/ioutil"
"strings" "strings"
"sync"
"time" "time"
"github.com/cbeuw/Cloak/internal/ecdh" "github.com/cbeuw/Cloak/internal/ecdh"
@ -20,18 +18,10 @@ type rawConfig struct {
EncryptionMethod string EncryptionMethod string
UID string UID string
PublicKey string PublicKey string
TicketTimeHint int
BrowserSig string BrowserSig string
NumConn int NumConn int
} }
type tthIntervalKeys struct {
interval int64
ephPv crypto.PrivateKey
ephPub crypto.PublicKey
intervalKey []byte
}
// State stores global variables // State stores global variables
type State struct { type State struct {
LocalHost string LocalHost string
@ -45,12 +35,8 @@ type State struct {
staticPub crypto.PublicKey staticPub crypto.PublicKey
IsAdmin bool IsAdmin bool
intervalDataM sync.Mutex
intervalData *tthIntervalKeys
ProxyMethod string ProxyMethod string
EncryptionMethod byte EncryptionMethod byte
TicketTimeHint int
ServerName string ServerName string
BrowserSig string BrowserSig string
NumConn int NumConn int
@ -58,35 +44,15 @@ type State struct {
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State { func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State {
ret := &State{ ret := &State{
LocalHost: localHost, LocalHost: localHost,
LocalPort: localPort, LocalPort: localPort,
RemoteHost: remoteHost, RemoteHost: remoteHost,
RemotePort: remotePort, RemotePort: remotePort,
Now: nowFunc, Now: nowFunc,
intervalData: &tthIntervalKeys{},
} }
return ret 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 // semi-colon separated value. This is for Android plugin options
func ssvToJson(ssv string) (ret []byte) { func ssvToJson(ssv string) (ret []byte) {
unescape := func(s string) string { unescape := func(s string) string {
@ -147,7 +113,6 @@ func (sta *State) ParseConfig(conf string) (err error) {
sta.ProxyMethod = preParse.ProxyMethod sta.ProxyMethod = preParse.ProxyMethod
sta.ServerName = preParse.ServerName sta.ServerName = preParse.ServerName
sta.TicketTimeHint = preParse.TicketTimeHint
sta.BrowserSig = preParse.BrowserSig sta.BrowserSig = preParse.BrowserSig
sta.NumConn = preParse.NumConn sta.NumConn = preParse.NumConn

View File

@ -26,6 +26,8 @@ type Session struct {
// This is supposed to read one TLS message, the same as GoQuiet's ReadTillDrain // This is supposed to read one TLS message, the same as GoQuiet's ReadTillDrain
obfsedRead func(net.Conn, []byte) (int, error) obfsedRead func(net.Conn, []byte) (int, error)
SessionKey []byte
// atomic // atomic
nextStreamID uint32 nextStreamID uint32
@ -45,13 +47,14 @@ type Session struct {
terminalMsg atomic.Value 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{ sesh := &Session{
id: id, id: id,
obfs: obfs,
deobfs: deobfs,
obfsedRead: obfsedRead, obfsedRead: obfsedRead,
nextStreamID: 1, nextStreamID: 1,
obfs: obfs,
deobfs: deobfs,
SessionKey: sessionKey,
streams: make(map[uint32]*Stream), streams: make(map[uint32]*Stream),
acceptCh: make(chan *Stream, acceptBacklog), acceptCh: make(chan *Stream, acceptBacklog),
} }

View File

@ -1,11 +1,12 @@
package server package server
import ( import (
"bytes"
"crypto/rand"
"encoding/binary" "encoding/binary"
"encoding/hex"
"errors" "errors"
"time" "fmt"
"github.com/cbeuw/Cloak/internal/util"
) )
// ClientHello contains every field in a ClientHello message // 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 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 // 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)
@ -131,21 +161,34 @@ func ParseClientHello(data []byte) (ret *ClientHello, err error) {
return return
} }
func composeServerHello(ch *ClientHello) []byte { func xor(a []byte, b []byte) {
var serverHello [10][]byte for i := range a {
serverHello[0] = []byte{0x02} // handshake type a[i] ^= b[i]
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 func composeServerHello(ch *ClientHello, sharedSecret []byte, sessionKey []byte) []byte {
serverHello[5] = ch.sessionId // session id var serverHello [11][]byte
serverHello[6] = []byte{0xc0, 0x30} // cipher suite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 serverHello[0] = []byte{0x02} // handshake type
serverHello[7] = []byte{0x00} // compression method null serverHello[1] = []byte{0x00, 0x00, 0x76} // length 77
serverHello[8] = []byte{0x00, 0x05} // extensions length 5 serverHello[2] = []byte{0x03, 0x03} // server version
serverHello[9] = []byte{0xff, 0x01, 0x00, 0x01, 0x00} // extensions renegotiation_info xor(sharedSecret, sessionKey)
ret := []byte{} serverHello[3] = sharedSecret // random
for i := 0; i < 10; i++ { serverHello[4] = []byte{0x20} // session id length 32
ret = append(ret, serverHello[i]...) 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 return ret
} }
@ -153,14 +196,10 @@ func composeServerHello(ch *ClientHello) []byte {
// 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) []byte { func ComposeReply(ch *ClientHello, sharedSecret []byte, sessionKey []byte) []byte {
TLS12 := []byte{0x03, 0x03} 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) 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(shBytes, ccsBytes...)
ret = append(ret, fBytes...)
return ret return ret
} }

View File

@ -30,7 +30,7 @@ func (u *ActiveUser) DelSession(sessionID uint32) {
u.sessionsM.Unlock() 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() u.sessionsM.Lock()
defer u.sessionsM.Unlock() defer u.sessionsM.Unlock()
if sesh = u.sessions[sessionID]; sesh != nil { 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 { if err != nil {
return nil, false, err 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 u.sessions[sessionID] = sesh
return sesh, false, nil return sesh, false, nil
} }

View File

@ -2,45 +2,13 @@ package server
import ( import (
"bytes" "bytes"
"crypto"
"crypto/sha256"
"encoding/binary" "encoding/binary"
"github.com/cbeuw/Cloak/internal/ecdh" "github.com/cbeuw/Cloak/internal/ecdh"
"github.com/cbeuw/Cloak/internal/util" "github.com/cbeuw/Cloak/internal/util"
"log" "log"
) )
const SESSION_TICKET_LEN = 192 func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID uint32, proxyMethod string, encryptionMethod byte, sharedSecret []byte) {
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) {
var random [32]byte var random [32]byte
copy(random[:], ch.random) copy(random[:], ch.random)
@ -54,21 +22,37 @@ func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID u
return return
} }
ticket := ch.extensions[[2]byte{0x00, 0x23}] ephPub, ok := ecdh.Unmarshal(random[:])
if !ok {
if len(ticket) < PUB_KEY_LEN+USED_STAGNO_LEN+AUTH_TAG_LEN {
return return
} }
UID, proxyMethod, encryptionMethod, tthKey = decryptSessionTicket(sta.staticPv, ticket) sharedSecret = ecdh.GenerateSharedSecret(sta.staticPv, ephPub)
keyShare, err := parseKeyShare(ch.extensions[[2]byte{0x00, 0x33}])
if len(UID) < 16 { if err != nil {
return return
} }
isCK, sessionID = validateRandom(ch.random, UID, sta.Now().Unix()) ciphertext := append(ch.sessionId, keyShare...)
if !isCK {
if len(ciphertext) != 64 {
return 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 return
} }

View File

@ -101,14 +101,20 @@ func (sta *State) ParseConfig(conf string) (err error) {
return nil 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 // UsedRandomCleaner clears the cache of used random fields every 12 hours
func (sta *State) UsedRandomCleaner() { func (sta *State) UsedRandomCleaner() {
for { for {
time.Sleep(12 * time.Hour) time.Sleep(TIMESTAMP_WINDOW)
now := int(sta.Now().Unix()) now := int(sta.Now().Unix())
sta.usedRandomM.Lock() sta.usedRandomM.Lock()
for key, t := range sta.usedRandom { for key, t := range sta.usedRandom {
if now-t > 12*3600 { if now-t > int(TIMESTAMP_WINDOW.Seconds()) {
delete(sta.usedRandom, key) delete(sta.usedRandom, key)
} }
} }

View File

@ -5,6 +5,8 @@ import (
"crypto/cipher" "crypto/cipher"
"encoding/binary" "encoding/binary"
"errors" "errors"
mux "github.com/cbeuw/Cloak/internal/multiplex"
"golang.org/x/crypto/chacha20poly1305"
"io" "io"
prand "math/rand" prand "math/rand"
"net" "net"
@ -83,6 +85,40 @@ func ReadTLS(conn net.Conn, buffer []byte) (n int, err error) {
return 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 // 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)