From 0fbb6011fcc53d76b54655d7dee5d1007f44c68b Mon Sep 17 00:00:00 2001 From: Qian Wang Date: Wed, 31 Jul 2019 23:58:15 +0100 Subject: [PATCH] Remove psudorandom padding --- cmd/ck-client/ck-client.go | 2 +- internal/client/auth.go | 30 ++++++++++++------------------ internal/client/state.go | 9 +++------ internal/server/auth.go | 13 +++++++++---- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/cmd/ck-client/ck-client.go b/cmd/ck-client/ck-client.go index 85bab83..8c3fb69 100644 --- a/cmd/ck-client/ck-client.go +++ b/cmd/ck-client/ck-client.go @@ -100,7 +100,7 @@ func makeSession(sta *client.State) *mux.Session { sta.UpdateIntervalKeys() - _, tthKey, _ := sta.GetIntervalKeys() + _, tthKey := sta.GetIntervalKeys() sesh := mux.MakeSession(sta.SessionID, mux.UNLIMITED_VALVE, mux.MakeObfs(tthKey, sta.Cipher), mux.MakeDeobfs(tthKey, sta.Cipher), util.ReadTLS) var wg sync.WaitGroup diff --git a/internal/client/auth.go b/internal/client/auth.go index a55ac04..7942c44 100644 --- a/internal/client/auth.go +++ b/internal/client/auth.go @@ -29,34 +29,28 @@ func MakeRandomField(sta *State) []byte { return ret } +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][16 bytes authentication tag][padding 111 bytes] + // 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, 192) + ticket := make([]byte, SESSION_TICKET_LEN) //TODO: error when the interval has expired - ephPub, intervalKey, seed := sta.GetIntervalKeys() - copy(ticket[0:32], ecdh.Marshal(ephPub)) + ephPub, intervalKey := sta.GetIntervalKeys() + copy(ticket[0:PUB_KEY_LEN], ecdh.Marshal(ephPub)) - plain := make([]byte, 33) + plain := make([]byte, STEGANO_LEN) copy(plain, sta.UID) - copy(plain[16:32], []byte(sta.ProxyMethod)) + copy(plain[16:32], sta.ProxyMethod) plain[32] = sta.EncryptionMethod cipher, _ := util.AESGCMEncrypt(ticket[0:12], intervalKey, plain) - copy(ticket[32:81], cipher) - // The purpose of adding sessionID is that, the generated padding of sessionTicket needs to be unpredictable. - // As shown in auth.go, the padding is generated by a psudo random generator. The seed - // needs to be the same for each TicketTimeHint interval. However the value of epoch/TicketTimeHint - // is public knowledge, so is the psudo random algorithm used by math/rand. Therefore not only - // can the firewall tell that the padding is generated in this specific way, this padding is identical - // for all ckclients in the same TicketTimeHint interval. This will expose us. - // - // With the sessionID value generated at startup of ckclient and used as a part of the seed, the - // sessionTicket is still identical for each TicketTimeHint interval, but others won't be able to know - // how it was generated. It will also be different for each client. - copy(ticket[81:192], util.PsudoRandBytes(111, seed)) + copy(ticket[PUB_KEY_LEN:], cipher) return ticket } diff --git a/internal/client/state.go b/internal/client/state.go index f2d7e61..c9fa0ab 100644 --- a/internal/client/state.go +++ b/internal/client/state.go @@ -4,7 +4,6 @@ import ( "crypto" "crypto/rand" "encoding/base64" - "encoding/binary" "encoding/json" "errors" "io/ioutil" @@ -32,7 +31,6 @@ type tthIntervalKeys struct { ephPv crypto.PrivateKey ephPub crypto.PublicKey intervalKey []byte - seed int64 } // State stores global variables @@ -82,14 +80,13 @@ func (sta *State) UpdateIntervalKeys() { sta.intervalData.interval = currentInterval ephPv, ephPub, _ := ecdh.GenerateKey(rand.Reader) intervalKey := ecdh.GenerateSharedSecret(ephPv, sta.staticPub) - seed := int64(binary.BigEndian.Uint64(ephPv.(*[32]byte)[0:8])) - sta.intervalData.ephPv, sta.intervalData.ephPub, sta.intervalData.intervalKey, sta.intervalData.seed = ephPv, ephPub, intervalKey, seed + sta.intervalData.ephPv, sta.intervalData.ephPub, sta.intervalData.intervalKey = ephPv, ephPub, intervalKey } -func (sta *State) GetIntervalKeys() (crypto.PublicKey, []byte, int64) { +func (sta *State) GetIntervalKeys() (crypto.PublicKey, []byte) { sta.intervalDataM.Lock() defer sta.intervalDataM.Unlock() - return sta.intervalData.ephPub, sta.intervalData.intervalKey, sta.intervalData.seed + return sta.intervalData.ephPub, sta.intervalData.intervalKey } // semi-colon separated value. This is for Android plugin options diff --git a/internal/server/auth.go b/internal/server/auth.go index 10ce142..666982f 100644 --- a/internal/server/auth.go +++ b/internal/server/auth.go @@ -10,11 +10,16 @@ import ( "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][16 bytes authentication tag][padding 111 bytes] - ephPub, _ := ecdh.Unmarshal(ticket[0:32]) + // 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[32:81]) + plain, err := util.AESGCMDecrypt(ticket[0:12], tthKey, ticket[PUB_KEY_LEN:]) if err != nil { return } @@ -51,7 +56,7 @@ func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID u ticket := ch.extensions[[2]byte{0x00, 0x23}] - if len(ticket) < 81 { + if len(ticket) < PUB_KEY_LEN+USED_STAGNO_LEN+AUTH_TAG_LEN { return }