mirror of
https://github.com/cbeuw/Cloak.git
synced 2024-11-11 13:11:03 +00:00
Change the protocol to solve sessionTicket inconsistency
This commit is contained in:
parent
592175f73d
commit
75fecacd20
@ -4,6 +4,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -176,16 +177,18 @@ start:
|
|||||||
log.Println("Attemtping to start a new session")
|
log.Println("Attemtping to start a new session")
|
||||||
// sessionID is usergenerated. There shouldn't be a security concern because the scope of
|
// sessionID is usergenerated. There shouldn't be a security concern because the scope of
|
||||||
// sessionID is limited to its UID.
|
// sessionID is limited to its UID.
|
||||||
rand.Seed(time.Now().UnixNano())
|
quad := make([]byte, 4)
|
||||||
sessionID := rand.Uint32()
|
rand.Read(quad)
|
||||||
|
sta.SessionID = binary.BigEndian.Uint32(quad)
|
||||||
|
|
||||||
|
sta.UpdateIntervalKeys()
|
||||||
|
|
||||||
if adminUID != nil {
|
if adminUID != nil {
|
||||||
sessionID = 0
|
sta.SessionID = 0
|
||||||
sta.UID = adminUID
|
sta.UID = adminUID
|
||||||
sta.NumConn = 1
|
sta.NumConn = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
sta.SetSessionID(sessionID)
|
|
||||||
var crypto mux.Crypto
|
var crypto mux.Crypto
|
||||||
switch sta.EncryptionMethod {
|
switch sta.EncryptionMethod {
|
||||||
case 0x00:
|
case 0x00:
|
||||||
@ -204,11 +207,8 @@ start:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionKey := make([]byte, 32)
|
_, tthKey, _ := sta.GetIntervalKeys()
|
||||||
rand.Read(sessionKey)
|
sesh := mux.MakeSession(sta.SessionID, mux.UNLIMITED_VALVE, mux.MakeObfs(tthKey, crypto), mux.MakeDeobfs(tthKey, crypto), util.ReadTLS)
|
||||||
sta.SessionKey = sessionKey
|
|
||||||
|
|
||||||
sesh := mux.MakeSession(sessionID, mux.UNLIMITED_VALVE, mux.MakeObfs(sta.SessionKey, crypto), mux.MakeDeobfs(sta.SessionKey, crypto), util.ReadTLS)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := 0; i < sta.NumConn; i++ {
|
for i := 0; i < sta.NumConn; i++ {
|
||||||
@ -227,7 +227,7 @@ start:
|
|||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
log.Printf("Session %v established", sessionID)
|
log.Printf("Session %v established", sta.SessionID)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if sesh.IsBroken() {
|
if sesh.IsBroken() {
|
||||||
|
@ -73,7 +73,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isCloak, UID, sessionID, proxyMethod, encryptionMethod, sessionKey := server.TouchStone(ch, sta)
|
isCloak, UID, sessionID, proxyMethod, encryptionMethod, tthKey := 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(data)
|
||||||
@ -109,8 +109,8 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
obfs := mux.MakeObfs(sessionKey, crypto)
|
obfs := mux.MakeObfs(tthKey, crypto)
|
||||||
deobfs := mux.MakeDeobfs(sessionKey, crypto)
|
deobfs := mux.MakeDeobfs(tthKey, crypto)
|
||||||
|
|
||||||
finishHandshake := func() error {
|
finishHandshake := func() error {
|
||||||
reply := server.ComposeReply(ch)
|
reply := server.ComposeReply(ch)
|
||||||
|
@ -1,67 +1,52 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/cbeuw/Cloak/internal/ecdh"
|
"github.com/cbeuw/Cloak/internal/ecdh"
|
||||||
"github.com/cbeuw/Cloak/internal/util"
|
"github.com/cbeuw/Cloak/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type keyPair struct {
|
|
||||||
crypto.PrivateKey
|
|
||||||
crypto.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeRandomField(sta *State) []byte {
|
func MakeRandomField(sta *State) []byte {
|
||||||
|
// [4 bytes sessionId] [12 bytes random] [16 bytes hash]
|
||||||
t := make([]byte, 8)
|
t := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(t, uint64(sta.Now().Unix()/(12*60*60)))
|
binary.BigEndian.PutUint64(t, uint64(sta.Now().Unix()/(12*60*60)))
|
||||||
rdm := make([]byte, 16)
|
|
||||||
io.ReadFull(rand.Reader, rdm)
|
front := make([]byte, 16)
|
||||||
|
binary.BigEndian.PutUint32(front[0:4], sta.SessionID)
|
||||||
|
rand.Read(front[4:])
|
||||||
preHash := make([]byte, 56)
|
preHash := make([]byte, 56)
|
||||||
copy(preHash[0:32], sta.UID)
|
copy(preHash[0:32], sta.UID)
|
||||||
copy(preHash[32:40], t)
|
copy(preHash[32:40], t)
|
||||||
copy(preHash[40:56], rdm)
|
copy(preHash[40:56], front)
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
h.Write(preHash)
|
h.Write(preHash)
|
||||||
|
|
||||||
ret := make([]byte, 32)
|
ret := make([]byte, 32)
|
||||||
copy(ret[0:16], rdm)
|
copy(ret[0:16], front)
|
||||||
copy(ret[16:32], h.Sum(nil)[0:16])
|
copy(ret[16:32], h.Sum(nil)[0:16])
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeSessionTicket(sta *State) []byte {
|
func MakeSessionTicket(sta *State) []byte {
|
||||||
// sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID+sessionID 20 bytes, proxy method 16 bytes, encryption method 1 byte, sessionKey 32 bytes][16 bytes authentication tag][padding 75 bytes]
|
// 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]
|
||||||
// The first 12 bytes of the marshalled ephemeral public key is used as the nonce
|
// The first 12 bytes of the marshalled ephemeral public key is used as the nonce
|
||||||
// for encrypting the UID
|
// for encrypting the UID
|
||||||
tthInterval := sta.Now().Unix() / int64(sta.TicketTimeHint)
|
|
||||||
sta.keyPairsM.Lock()
|
|
||||||
ephKP := sta.keyPairs[tthInterval]
|
|
||||||
if ephKP == nil {
|
|
||||||
ephPv, ephPub, _ := ecdh.GenerateKey(rand.Reader)
|
|
||||||
ephKP = &keyPair{
|
|
||||||
ephPv,
|
|
||||||
ephPub,
|
|
||||||
}
|
|
||||||
sta.keyPairs[tthInterval] = ephKP
|
|
||||||
}
|
|
||||||
sta.keyPairsM.Unlock()
|
|
||||||
ticket := make([]byte, 192)
|
ticket := make([]byte, 192)
|
||||||
copy(ticket[0:32], ecdh.Marshal(ephKP.PublicKey))
|
|
||||||
key := ecdh.GenerateSharedSecret(ephKP.PrivateKey, sta.staticPub)
|
|
||||||
|
|
||||||
plain := make([]byte, 69)
|
//TODO: error when the interval has expired
|
||||||
|
ephPub, intervalKey, seed := sta.GetIntervalKeys()
|
||||||
|
copy(ticket[0:32], ecdh.Marshal(ephPub))
|
||||||
|
|
||||||
|
plain := make([]byte, 33)
|
||||||
copy(plain, sta.UID)
|
copy(plain, sta.UID)
|
||||||
binary.BigEndian.PutUint32(plain[16:20], sta.sessionID)
|
copy(plain[16:32], []byte(sta.ProxyMethod))
|
||||||
copy(plain[20:36], []byte(sta.ProxyMethod))
|
plain[32] = sta.EncryptionMethod
|
||||||
plain[36] = sta.EncryptionMethod
|
|
||||||
copy(plain[37:69], sta.SessionKey)
|
|
||||||
|
|
||||||
cipher, _ := util.AESGCMEncrypt(ticket[0:12], key, plain)
|
cipher, _ := util.AESGCMEncrypt(ticket[0:12], intervalKey, plain)
|
||||||
copy(ticket[32:117], cipher)
|
copy(ticket[32:81], cipher)
|
||||||
// The purpose of adding sessionID is that, the generated padding of sessionTicket needs to be unpredictable.
|
// 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
|
// 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
|
// needs to be the same for each TicketTimeHint interval. However the value of epoch/TicketTimeHint
|
||||||
@ -72,6 +57,6 @@ func MakeSessionTicket(sta *State) []byte {
|
|||||||
// With the sessionID value generated at startup of ckclient and used as a part of the seed, the
|
// 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
|
// 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.
|
// how it was generated. It will also be different for each client.
|
||||||
copy(ticket[117:192], util.PsudoRandBytes(75, tthInterval+int64(sta.sessionID)))
|
copy(ticket[81:192], util.PsudoRandBytes(111, seed))
|
||||||
return ticket
|
return ticket
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
//"crypto/aes"
|
//"crypto/aes"
|
||||||
//"crypto/cipher"
|
//"crypto/cipher"
|
||||||
//"crypto/rand"
|
//"crypto/rand"
|
||||||
@ -15,89 +16,75 @@ import (
|
|||||||
//"github.com/cbeuw/Cloak/internal/ecdh"
|
//"github.com/cbeuw/Cloak/internal/ecdh"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
func TestMakeSessionTicket(t *testing.T) {
|
func TestMakeSessionTicket(t *testing.T) {
|
||||||
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
|
|
||||||
staticPv, staticPub, _ := ecdh.GenerateKey(rand.Reader)
|
|
||||||
mockSta := &State{
|
|
||||||
Now: time.Now,
|
|
||||||
sessionID: 42,
|
|
||||||
UID: UID,
|
|
||||||
staticPub: staticPub,
|
|
||||||
keyPairs: make(map[int64]*keyPair),
|
|
||||||
TicketTimeHint: 3600,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
stateGob, _ := hex.DecodeString("ffc5ff8103010105537461746501ff8200010c01094c6f63616c486f7374010c0001094c6f63616c506f7274010c00010a52656d6f7465486f7374010c00010a52656d6f7465506f7274010c00010953657373696f6e49440106000103554944010a00010b50726f78794d6574686f64010c000110456e6372797074696f6e4d6574686f64010600010e5469636b657454696d6548696e74010400010a5365727665724e616d65010c00010a42726f77736572536967010c0001074e756d436f6e6e010400000065ff8201093132372e302e302e3101043139383401093132372e302e302e31010334343301fc52fdfc0701104cd8cc15600d7eb68131fd8097673746010b736861646f77736f636b7302fe1c20010c7777772e62696e672e636f6d01066368726f6d65010800")
|
||||||
|
buf := bytes.NewBuffer(stateGob)
|
||||||
|
mockSta := &State{}
|
||||||
|
gob.NewDecoder(buf).Decode(mockSta)
|
||||||
|
mockSta.intervalData = &tthIntervalKeys{}
|
||||||
|
mockSta.intervalData.interval = 434487
|
||||||
|
ephPub, _ := hex.DecodeString("7b7e0db16bb8c83355771a424234e36c02fd752b6a9310968d27787d7c117b10")
|
||||||
|
ephPv, _ := hex.DecodeString("68584fed8ede64e2b17619b9cc0effb2678feb2face92456a8414dafa629334b")
|
||||||
|
mockSta.intervalData.intervalKey, _ = hex.DecodeString("47e1c2413f1a6b397fbd61d6cf21397b20a2338cef48fae68643602881d93d4b")
|
||||||
|
mockSta.intervalData.seed = 7518847459617826018
|
||||||
|
staticPub, _ := hex.DecodeString("218a14ce495efd3fe4ae213e51f766ec01d0b487869c159b8619536e60e95142")
|
||||||
|
|
||||||
|
var a, b, c [32]byte
|
||||||
|
copy(a[:], ephPub)
|
||||||
|
copy(b[:], ephPv)
|
||||||
|
copy(c[:], staticPub)
|
||||||
|
mockSta.intervalData.ephPub = &a
|
||||||
|
mockSta.intervalData.ephPv = &b
|
||||||
|
mockSta.staticPub = &c
|
||||||
|
|
||||||
|
target, _ := hex.DecodeString("7b7e0db16bb8c83355771a424234e36c02fd752b6a9310968d27787d7c117b103a2d246fd8b7d9e4243d6b83a7365858bd9cb583ba950287c4f4edc249cea935e235eda92c48569f455fca34ff6e4d37cf8f519b1d66e7cd51b31c1766ffb03134576e8d61ad5eae58a9ce4153ec33af73c7a8d04ab56d51155ac19fe731c792c17ee98a97fbfef8efc952964b7fd61dd2a35d7de4128abc730e20ba44e069e44ba8e29b66a8e4f114b9ab4cc2fba944a925086d7f09892a59147d990b58d393")
|
||||||
ticket := MakeSessionTicket(mockSta)
|
ticket := MakeSessionTicket(mockSta)
|
||||||
|
|
||||||
// verification
|
if !bytes.Equal(target, ticket) {
|
||||||
ephPub, _ := ecdh.Unmarshal(ticket[0:32])
|
|
||||||
key := ecdh.GenerateSharedSecret(staticPv, ephPub)
|
|
||||||
|
|
||||||
// aes decrypt
|
|
||||||
UIDsID := make([]byte, len(ticket[32:68]))
|
|
||||||
copy(UIDsID, ticket[32:68]) // Because XORKeyStream is inplace, but we don't want the input to be changed
|
|
||||||
block, _ := aes.NewCipher(key)
|
|
||||||
stream := cipher.NewCTR(block, ticket[0:16])
|
|
||||||
stream.XORKeyStream(UIDsID, UIDsID)
|
|
||||||
|
|
||||||
decryUID := UIDsID[0:32]
|
|
||||||
decrySessionID := binary.BigEndian.Uint32(UIDsID[32:36])
|
|
||||||
|
|
||||||
// check padding
|
|
||||||
tthInterval := mockSta.Now().Unix() / int64(mockSta.TicketTimeHint)
|
|
||||||
r := prand.New(prand.NewSource(tthInterval + int64(mockSta.sessionID)))
|
|
||||||
pad := make([]byte, 124)
|
|
||||||
r.Read(pad)
|
|
||||||
|
|
||||||
if !bytes.Equal(mockSta.UID, decryUID) {
|
|
||||||
t.Error(
|
t.Error(
|
||||||
"For", "UID",
|
"For", "sessionTicket generation",
|
||||||
"expecting", fmt.Sprintf("%x", mockSta.UID),
|
"expecting", fmt.Sprintf("%x", target),
|
||||||
"got", fmt.Sprintf("%x", decryUID),
|
"got", fmt.Sprintf("%x", ticket),
|
||||||
)
|
|
||||||
}
|
|
||||||
if mockSta.sessionID != decrySessionID {
|
|
||||||
t.Error(
|
|
||||||
"For", "sessionID",
|
|
||||||
"expecting", mockSta.sessionID,
|
|
||||||
"got", decrySessionID,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(pad, ticket[68:]) {
|
|
||||||
t.Error(
|
|
||||||
"For", "Padding",
|
|
||||||
"expecting", fmt.Sprintf("%x", pad),
|
|
||||||
"got", fmt.Sprintf("%x", ticket[68:]),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
func TestMakeRandomField(t *testing.T) {
|
func TestMakeRandomField(t *testing.T) {
|
||||||
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
|
UID, _ := hex.DecodeString("4cd8cc15600d7eb68131fd8097673746")
|
||||||
mockSta := &State{
|
mockSta := &State{
|
||||||
Now: time.Now,
|
Now: time.Now,
|
||||||
UID: UID,
|
UID: UID,
|
||||||
|
SessionID: 1,
|
||||||
}
|
}
|
||||||
random := MakeRandomField(mockSta)
|
random := MakeRandomField(mockSta)
|
||||||
|
|
||||||
// verification
|
// verification
|
||||||
tb := make([]byte, 8)
|
tb := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(tb, uint64(time.Now().Unix()/(12*60*60)))
|
binary.BigEndian.PutUint64(tb, uint64(time.Now().Unix()/(12*60*60)))
|
||||||
rdm := random[0:16]
|
front := random[0:16]
|
||||||
preHash := make([]byte, 56)
|
preHash := make([]byte, 56)
|
||||||
copy(preHash[0:32], UID)
|
copy(preHash[0:32], UID)
|
||||||
copy(preHash[32:40], tb)
|
copy(preHash[32:40], tb)
|
||||||
copy(preHash[40:56], rdm)
|
copy(preHash[40:56], front)
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
h.Write(preHash)
|
h.Write(preHash)
|
||||||
exp := h.Sum(nil)[0:16]
|
exp := h.Sum(nil)[0:16]
|
||||||
if !bytes.Equal(exp, random[16:32]) {
|
if !bytes.Equal(exp, random[16:32]) {
|
||||||
t.Error(
|
t.Error(
|
||||||
"For", "Random",
|
"For", "Random generation",
|
||||||
"expecting", fmt.Sprintf("%x", exp),
|
"expecting", fmt.Sprintf("%x", exp),
|
||||||
"got", fmt.Sprintf("%x", random[16:32]),
|
"got", fmt.Sprintf("%x", random[16:32]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
random2 := MakeRandomField(mockSta)
|
||||||
|
if bytes.Equal(random, random2) {
|
||||||
|
t.Error(
|
||||||
|
"For", "Duplicate random generation",
|
||||||
|
"expecting", "two different randoms",
|
||||||
|
"got", fmt.Sprintf("the same: %x", random),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -24,6 +26,14 @@ type rawConfig struct {
|
|||||||
NumConn int
|
NumConn int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tthIntervalKeys struct {
|
||||||
|
interval int64
|
||||||
|
ephPv crypto.PrivateKey
|
||||||
|
ephPub crypto.PublicKey
|
||||||
|
intervalKey []byte
|
||||||
|
seed int64
|
||||||
|
}
|
||||||
|
|
||||||
// State stores global variables
|
// State stores global variables
|
||||||
type State struct {
|
type State struct {
|
||||||
LocalHost string
|
LocalHost string
|
||||||
@ -32,12 +42,12 @@ type State struct {
|
|||||||
RemotePort string
|
RemotePort string
|
||||||
|
|
||||||
Now func() time.Time
|
Now func() time.Time
|
||||||
sessionID uint32
|
SessionID uint32
|
||||||
SessionKey []byte
|
|
||||||
UID []byte
|
UID []byte
|
||||||
staticPub crypto.PublicKey
|
staticPub crypto.PublicKey
|
||||||
keyPairsM sync.RWMutex
|
|
||||||
keyPairs map[int64]*keyPair
|
intervalDataM sync.Mutex
|
||||||
|
intervalData *tthIntervalKeys
|
||||||
|
|
||||||
ProxyMethod string
|
ProxyMethod string
|
||||||
EncryptionMethod byte
|
EncryptionMethod byte
|
||||||
@ -54,12 +64,31 @@ func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func
|
|||||||
RemoteHost: remoteHost,
|
RemoteHost: remoteHost,
|
||||||
RemotePort: remotePort,
|
RemotePort: remotePort,
|
||||||
Now: nowFunc,
|
Now: nowFunc,
|
||||||
|
intervalData: &tthIntervalKeys{},
|
||||||
}
|
}
|
||||||
ret.keyPairs = make(map[int64]*keyPair)
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sta *State) SetSessionID(id uint32) { sta.sessionID = id }
|
func (sta *State) UpdateIntervalKeys() {
|
||||||
|
sta.intervalDataM.Lock()
|
||||||
|
currentInterval := sta.Now().Unix() / int64(sta.TicketTimeHint)
|
||||||
|
if currentInterval == sta.intervalData.interval {
|
||||||
|
sta.intervalDataM.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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.intervalDataM.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sta *State) GetIntervalKeys() (crypto.PublicKey, []byte, int64) {
|
||||||
|
sta.intervalDataM.Lock()
|
||||||
|
defer sta.intervalDataM.Unlock()
|
||||||
|
return sta.intervalData.ephPub, sta.intervalData.intervalKey, sta.intervalData.seed
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
@ -5,37 +5,37 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"log"
|
|
||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// input ticket, return UID
|
func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) (UID []byte, proxyMethod string, encryptionMethod byte, tthKey []byte) {
|
||||||
func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) ([]byte, uint32, string, byte, []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])
|
ephPub, _ := ecdh.Unmarshal(ticket[0:32])
|
||||||
key := ecdh.GenerateSharedSecret(staticPv, ephPub)
|
tthKey = ecdh.GenerateSharedSecret(staticPv, ephPub)
|
||||||
plain, err := util.AESGCMDecrypt(ticket[0:12], key, ticket[32:117])
|
plain, err := util.AESGCMDecrypt(ticket[0:12], tthKey, ticket[32:81])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, "", 0x00, nil
|
return
|
||||||
}
|
}
|
||||||
sessionID := binary.BigEndian.Uint32(plain[16:20])
|
return plain[0:16], string(bytes.Trim(plain[16:32], "\x00")), plain[32], tthKey
|
||||||
return plain[0:16], sessionID, string(bytes.Trim(plain[20:36], "\x00")), plain[36], plain[37:69]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRandom(random []byte, UID []byte, time int64) bool {
|
func validateRandom(random []byte, UID []byte, time int64) (bool, uint32) {
|
||||||
t := make([]byte, 8)
|
t := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(t, uint64(time/(12*60*60)))
|
binary.BigEndian.PutUint64(t, uint64(time/(12*60*60)))
|
||||||
rdm := random[0:16]
|
front := random[0:16]
|
||||||
preHash := make([]byte, 56)
|
preHash := make([]byte, 56)
|
||||||
copy(preHash[0:32], UID)
|
copy(preHash[0:32], UID)
|
||||||
copy(preHash[32:40], t)
|
copy(preHash[32:40], t)
|
||||||
copy(preHash[40:56], rdm)
|
copy(preHash[40:56], front)
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
h.Write(preHash)
|
h.Write(preHash)
|
||||||
return bytes.Equal(h.Sum(nil)[0:16], random[16:32])
|
|
||||||
|
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, sessionKey []byte) {
|
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)
|
||||||
|
|
||||||
@ -50,14 +50,17 @@ func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID u
|
|||||||
}
|
}
|
||||||
|
|
||||||
ticket := ch.extensions[[2]byte{0x00, 0x23}]
|
ticket := ch.extensions[[2]byte{0x00, 0x23}]
|
||||||
if len(ticket) < 68 {
|
|
||||||
|
if len(ticket) < 81 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
UID, sessionID, proxyMethod, encryptionMethod, sessionKey = decryptSessionTicket(sta.staticPv, ticket)
|
|
||||||
|
UID, proxyMethod, encryptionMethod, tthKey = decryptSessionTicket(sta.staticPv, ticket)
|
||||||
|
|
||||||
if len(UID) < 16 {
|
if len(UID) < 16 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isCK = validateRandom(ch.random, UID, sta.Now().Unix())
|
isCK, sessionID = validateRandom(ch.random, UID, sta.Now().Unix())
|
||||||
if !isCK {
|
if !isCK {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
//"bytes"
|
//"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/cbeuw/Cloak/internal/ecdh"
|
||||||
"testing"
|
"testing"
|
||||||
//"github.com/cbeuw/Cloak/internal/ecdh"
|
//"github.com/cbeuw/Cloak/internal/ecdh"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
func TestDecryptSessionTicket(t *testing.T) {
|
func TestDecryptSessionTicket(t *testing.T) {
|
||||||
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
|
UID, _ := hex.DecodeString("4cd8cc15600d7eb68131fd8097673746")
|
||||||
sessionID := uint32(42)
|
pvb, _ := hex.DecodeString("10de5a3c4a4d04efafc3e06d1506363a72bd6d053baef123e6a9a79a0c04b547")
|
||||||
pvb, _ := hex.DecodeString("083794692e77b28fa2152dfee53142185fd58ea8172d3545fdeeaea97b3c597c")
|
|
||||||
staticPv, _ := ecdh.Unmarshal(pvb)
|
staticPv, _ := ecdh.Unmarshal(pvb)
|
||||||
sessionTicket, _ := hex.DecodeString("f586223b50cada583d61dc9bf3d01cc3a45aab4b062ed6a31ead0badb87f7761aab4f9f737a1d8ff2a2aa4d50ceb808844588ee3c8fdf36c33a35ef5003e287337659c8164a7949e9e63623090763fc24d0386c8904e47bdd740e09dd9b395c72de669629c2a865ed581452d23306adf26de0c8a46ee05e3dac876f2bcd9a2de946d319498f579383d06b3e66b3aca05f533fdc5f017eeba45b42080aabd4f71151fa0dfc1b0e23be4ed3abdb47adc0d5740ca7b7689ad34426309fb6984a086")
|
proxyMethod := "shadowsocks"
|
||||||
|
encryptionMethod := byte(0)
|
||||||
|
tthKey, _ := hex.DecodeString("92389a9b2769e2b76514c4cb163217bed0c5500bceb4a5ade1ceae597616db23")
|
||||||
|
|
||||||
|
sessionTicket, _ := hex.DecodeString("9ee339202508b6fbe9c19988575330c547efbc27b0d072ed93c0cc265b67d826825a49211b8f86b4364b436ed5db15925774c3bec4a1776f70a17db68ba541dc4c23871d2cc1a5074b081bbe0f8b86f1c7f7749964517dcfd8830532eddc8ac707544ec04b754a133b9595ebc2af988156dbe1e4f3b89c9dc289d441cb5a15d72cc59423981d43a498292d509e5fa5c8e8bf8ee85a2e4991ae126fcd6e4d2aa1119e918c80afa2dc38bec1ef621c9c3994af43b1983c241c68e04e8043c95d74")
|
||||||
|
|
||||||
|
decryUID, decryProxyMethod, decryEncryptionMethod, decryTthKey := decryptSessionTicket(staticPv, sessionTicket)
|
||||||
|
|
||||||
decryUID, decrySessionID, _, _ := decryptSessionTicket(staticPv, sessionTicket)
|
|
||||||
if !bytes.Equal(decryUID, UID) {
|
if !bytes.Equal(decryUID, UID) {
|
||||||
t.Error(
|
t.Error(
|
||||||
"For", "UID",
|
"For", "UID",
|
||||||
@ -24,43 +29,65 @@ func TestDecryptSessionTicket(t *testing.T) {
|
|||||||
"got", fmt.Sprintf("%x", decryUID),
|
"got", fmt.Sprintf("%x", decryUID),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if decrySessionID != sessionID {
|
if proxyMethod != decryProxyMethod {
|
||||||
t.Error(
|
t.Error(
|
||||||
"For", "sessionID",
|
"For", "proxyMethod",
|
||||||
"expecting", fmt.Sprintf("%x", sessionID),
|
"expecting", fmt.Sprintf("%x", proxyMethod),
|
||||||
"got", fmt.Sprintf("%x", decrySessionID),
|
"got", fmt.Sprintf("%x", decryProxyMethod),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if encryptionMethod != decryEncryptionMethod {
|
||||||
|
t.Error(
|
||||||
|
"For", "encryptionMethod",
|
||||||
|
"expecting", fmt.Sprintf("%x", encryptionMethod),
|
||||||
|
"got", fmt.Sprintf("%x", decryEncryptionMethod),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(tthKey, decryTthKey) {
|
||||||
|
t.Error(
|
||||||
|
"For", "tthKey",
|
||||||
|
"expecting", fmt.Sprintf("%x", tthKey),
|
||||||
|
"got", fmt.Sprintf("%x", decryTthKey),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
func TestValidateRandom(t *testing.T) {
|
func TestValidateRandom(t *testing.T) {
|
||||||
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
|
sessionID := uint32(2422026642)
|
||||||
random, _ := hex.DecodeString("6274de9992a6f96a86fc35cf6644a5e7844951889a802e9531add440eabb939b")
|
random, _ := hex.DecodeString("905d319272711946f6400db4f5028d6893f7b22659c78371c1f72386191a8ab4")
|
||||||
right := validateRandom(random, UID, 1547912444)
|
UID, _ := hex.DecodeString("4cd8cc15600d7eb68131fd8097673746")
|
||||||
|
|
||||||
|
right, decrySessionID := validateRandom(random, UID, 1564150721)
|
||||||
if !right {
|
if !right {
|
||||||
t.Error(
|
t.Error(
|
||||||
"For", fmt.Sprintf("good random: %x at time %v", random, 1547912444),
|
"For", fmt.Sprintf("good random: %x at time %v", random, 1564150721),
|
||||||
"expecting", true,
|
"expecting", true,
|
||||||
"got", false,
|
"got", false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if sessionID != decrySessionID {
|
||||||
|
t.Error(
|
||||||
|
"For", fmt.Sprintf("good random: %x at time %v", random, 1564150721),
|
||||||
|
"expecting", sessionID,
|
||||||
|
"got", decrySessionID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
replay := validateRandom(random, UID, 1547955645)
|
replay, _ := validateRandom(random, UID, 1764150721)
|
||||||
if replay {
|
if replay {
|
||||||
t.Error(
|
t.Error(
|
||||||
"For", fmt.Sprintf("expired random: %x at time %v", random, 1547955645),
|
"For", fmt.Sprintf("expired random: %x at time %v", random, 1764150721),
|
||||||
"expecting", false,
|
"expecting", false,
|
||||||
"got", true,
|
"got", true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
random[13] = 0x42
|
random[13] = 0x42
|
||||||
bogus := validateRandom(random, UID, 1547912444)
|
bogus, _ := validateRandom(random, UID, 1564150721)
|
||||||
if bogus {
|
if bogus {
|
||||||
t.Error(
|
t.Error(
|
||||||
"For", fmt.Sprintf("bogus random: %x at time %v", random, 1547912444),
|
"For", fmt.Sprintf("bogus random: %x at time %v", random, 1564150721),
|
||||||
"expecting", false,
|
"expecting", false,
|
||||||
"got", true,
|
"got", true,
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user