From 3f7eef98e38ec9af1c1bf4bfc38f0121de45c057 Mon Sep 17 00:00:00 2001 From: Qian Wang Date: Sat, 20 Oct 2018 21:41:01 +0100 Subject: [PATCH] drop aes encryption of headers --- cmd/ck-client/ck-client.go | 9 +++-- cmd/ck-server/ck-server.go | 6 ++- internal/multiplex/session.go | 1 - internal/multiplex/switchboard.go | 4 ++ internal/server/auth.go | 6 ++- internal/util/obfs.go | 67 +++++++++++++------------------ internal/util/util.go | 19 +++++++++ 7 files changed, 66 insertions(+), 46 deletions(-) diff --git a/cmd/ck-client/ck-client.go b/cmd/ck-client/ck-client.go index b63cf7c..406f189 100644 --- a/cmd/ck-client/ck-client.go +++ b/cmd/ck-client/ck-client.go @@ -20,7 +20,9 @@ import ( var version string func pipe(dst io.ReadWriteCloser, src io.ReadWriteCloser) { - buf := make([]byte, 20000) + // The maximum size of TLS message will be 16396+12. 12 because of the stream header + // 16408 is the max TLS message size on Firefox + buf := make([]byte, 16396) for { i, err := io.ReadAtLeast(src, buf, 1) if err != nil || i == 0 { @@ -42,6 +44,7 @@ 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) { + // For android d := net.Dialer{Control: protector} clientHello := TLS.ComposeInitHandshake(sta) @@ -142,8 +145,8 @@ func main() { log.Fatalf("Failed to establish connection to remote: %v\n", err) } - obfs := util.MakeObfs(sta.SID[:16]) - deobfs := util.MakeDeobfs(sta.SID[:16]) + obfs := util.MakeObfs(sta.SID) + deobfs := util.MakeDeobfs(sta.SID) // TODO: where to put obfs deobfs and rtd? sesh := mux.MakeSession(0, initRemoteConn, obfs, deobfs, util.ReadTillDrain) diff --git a/cmd/ck-server/ck-server.go b/cmd/ck-server/ck-server.go index 1653445..c4d76ff 100644 --- a/cmd/ck-server/ck-server.go +++ b/cmd/ck-server/ck-server.go @@ -21,7 +21,9 @@ import ( var version string func pipe(dst io.ReadWriteCloser, src io.ReadWriteCloser) { - buf := make([]byte, 20000) + // The maximum size of TLS message will be 16396+12. 12 because of the stream header + // 16408 is the max TLS message size on Firefox + buf := make([]byte, 16396) for { i, err := io.ReadAtLeast(src, buf, 1) if err != nil || i == 0 { @@ -105,7 +107,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) { if sesh = sta.GetSession(arrSID); sesh != nil { sesh.AddConnection(conn) } else { - sesh = mux.MakeSession(0, conn, util.MakeObfs(SID[:16]), util.MakeDeobfs(SID[:16]), util.ReadTillDrain) + sesh = mux.MakeSession(0, conn, util.MakeObfs(SID), util.MakeDeobfs(SID), util.ReadTillDrain) sta.PutSession(arrSID, sesh) } go func() { diff --git a/internal/multiplex/session.go b/internal/multiplex/session.go index c8805ab..6b22758 100644 --- a/internal/multiplex/session.go +++ b/internal/multiplex/session.go @@ -42,7 +42,6 @@ type Session struct { closeQCh chan uint32 } -// TODO: put this in main maybe? // 1 conn is needed to make a session func MakeSession(id int, conn net.Conn, obfs func(*Frame) []byte, deobfs func([]byte) *Frame, obfsedReader func(net.Conn, []byte) (int, error)) *Session { sesh := &Session{ diff --git a/internal/multiplex/switchboard.go b/internal/multiplex/switchboard.go index c198410..bb719a1 100644 --- a/internal/multiplex/switchboard.go +++ b/internal/multiplex/switchboard.go @@ -12,6 +12,7 @@ const ( newConnBacklog = 8 ) +// switchboard is responsible for keeping the reference of TLS connections between client and server type switchboard struct { session *Session @@ -130,6 +131,9 @@ func (sb *switchboard) dispatch() { } } +// deplex function costantly reads from a TLS connection +// it is responsible to act in response to the deobfsed header +// i.e. should a new stream be added? which existing stream should be closed? func (sb *switchboard) deplex(ce *connEnclave) { buf := make([]byte, 20480) for { diff --git a/internal/server/auth.go b/internal/server/auth.go index a06d4f1..0b746ec 100644 --- a/internal/server/auth.go +++ b/internal/server/auth.go @@ -45,7 +45,11 @@ func TouchStone(ch *ClientHello, sta *State) (bool, []byte) { } sta.putUsedRandom(random) - SID, err := decryptSessionTicket(sta.staticPv, ch.extensions[[2]byte{0x00, 0x23}]) + ticket := ch.extensions[[2]byte{0x00, 0x23}] + if len(ticket) < 64 { + return false, nil + } + SID, err := decryptSessionTicket(sta.staticPv, ticket) if err != nil { log.Printf("ts: %v\n", err) return false, nil diff --git a/internal/util/obfs.go b/internal/util/obfs.go index b28031b..3ded4fb 100644 --- a/internal/util/obfs.go +++ b/internal/util/obfs.go @@ -1,54 +1,44 @@ package util import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" "encoding/binary" - "io" + xxhash "github.com/OneOfOne/xxhash" mux "github.com/cbeuw/Cloak/internal/multiplex" ) -func AESEncrypt(iv []byte, key []byte, plaintext []byte) []byte { - block, _ := aes.NewCipher(key) - ciphertext := make([]byte, len(plaintext)) - stream := cipher.NewCTR(block, iv) - stream.XORKeyStream(ciphertext, plaintext) - return ciphertext -} - -func AESDecrypt(iv []byte, key []byte, ciphertext []byte) []byte { - ret := make([]byte, len(ciphertext)) - copy(ret, ciphertext) // Because XORKeyStream is inplace, but we don't want the input to be changed - block, _ := aes.NewCipher(key) - stream := cipher.NewCTR(block, iv) - stream.XORKeyStream(ret, ret) - return ret +func genXorKeys(SID []byte, data []byte) (i uint32, ii uint32, iii uint32) { + h := xxhash.New32() + ret := make([]uint32, 3) + preHash := make([]byte, 16) + for j := 0; j < 3; j++ { + copy(preHash[0:10], SID[j*10:j*10+10]) + copy(preHash[10:16], data[j*6:j*6+6]) + h.Write(preHash) + ret[j] = h.Sum32() + } + return ret[0], ret[1], ret[2] } func MakeObfs(key []byte) func(*mux.Frame) []byte { obfs := func(f *mux.Frame) []byte { - header := make([]byte, 12) - binary.BigEndian.PutUint32(header[0:4], f.StreamID) - binary.BigEndian.PutUint32(header[4:8], f.Seq) - binary.BigEndian.PutUint32(header[8:12], f.ClosingStreamID) + obfsedHeader := make([]byte, 12) // header: [StreamID 4 bytes][Seq 4 bytes][ClosingStreamID 4 bytes] - iv := make([]byte, 16) - io.ReadFull(rand.Reader, iv) - cipherheader := AESEncrypt(iv, key, header) + i, ii, iii := genXorKeys(key, f.Payload[0:18]) + binary.BigEndian.PutUint32(obfsedHeader[0:4], f.StreamID^i) + binary.BigEndian.PutUint32(obfsedHeader[4:8], f.Seq^ii) + binary.BigEndian.PutUint32(obfsedHeader[8:12], f.ClosingStreamID^iii) // Composing final obfsed message // We don't use util.AddRecordLayer here to avoid unnecessary malloc - obfsed := make([]byte, 5+16+12+len(f.Payload)) + obfsed := make([]byte, 5+12+len(f.Payload)) obfsed[0] = 0x17 obfsed[1] = 0x03 obfsed[2] = 0x03 - binary.BigEndian.PutUint16(obfsed[3:5], uint16(16+12+len(f.Payload))) - copy(obfsed[5:21], iv) - copy(obfsed[21:33], cipherheader) - copy(obfsed[33:], f.Payload) - // obfsed: [record layer 5 bytes][iv 16 bytes][cipherheader 12 bytes][payload] + binary.BigEndian.PutUint16(obfsed[3:5], uint16(12+len(f.Payload))) + copy(obfsed[5:17], obfsedHeader) + copy(obfsed[17:], f.Payload) + // obfsed: [record layer 5 bytes][cipherheader 12 bytes][payload] return obfsed } return obfs @@ -57,13 +47,12 @@ func MakeObfs(key []byte) func(*mux.Frame) []byte { func MakeDeobfs(key []byte) func([]byte) *mux.Frame { deobfs := func(in []byte) *mux.Frame { peeled := in[5:] - header := AESDecrypt(peeled[0:16], key, peeled[16:28]) - streamID := binary.BigEndian.Uint32(header[0:4]) - seq := binary.BigEndian.Uint32(header[4:8]) - closingStreamID := binary.BigEndian.Uint32(header[8:12]) - payload := make([]byte, len(peeled)-12-16) - //log.Printf("Payload: %x\n", payload) - copy(payload, peeled[28:]) + i, ii, iii := genXorKeys(key, peeled[12:30]) + streamID := binary.BigEndian.Uint32(peeled[0:4]) ^ i + seq := binary.BigEndian.Uint32(peeled[4:8]) ^ ii + closingStreamID := binary.BigEndian.Uint32(peeled[8:12]) ^ iii + payload := make([]byte, len(peeled)-12) + copy(payload, peeled[12:]) ret := &mux.Frame{ StreamID: streamID, Seq: seq, diff --git a/internal/util/util.go b/internal/util/util.go index 49cbb7f..c0a1961 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -1,6 +1,8 @@ package util import ( + "crypto/aes" + "crypto/cipher" "encoding/binary" "errors" "io" @@ -10,6 +12,23 @@ import ( "time" ) +func AESEncrypt(iv []byte, key []byte, plaintext []byte) []byte { + block, _ := aes.NewCipher(key) + ciphertext := make([]byte, len(plaintext)) + stream := cipher.NewCTR(block, iv) + stream.XORKeyStream(ciphertext, plaintext) + return ciphertext +} + +func AESDecrypt(iv []byte, key []byte, ciphertext []byte) []byte { + ret := make([]byte, len(ciphertext)) + copy(ret, ciphertext) // Because XORKeyStream is inplace, but we don't want the input to be changed + block, _ := aes.NewCipher(key) + stream := cipher.NewCTR(block, iv) + stream.XORKeyStream(ret, ret) + return ret +} + // BtoInt converts a byte slice into int in Big Endian order // Uint methods from binary package can be used, but they are messy func BtoInt(b []byte) int {