mirror of https://github.com/cbeuw/Cloak
Use AES-GCM instead of CTR
This commit is contained in:
parent
0dd52d8570
commit
8168b9e2e7
|
|
@ -34,8 +34,8 @@ func MakeRandomField(sta *State) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeSessionTicket(sta *State) []byte {
|
func MakeSessionTicket(sta *State) []byte {
|
||||||
// sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID+sessionID 36 bytes, proxy method 16 bytes, encryption method 1 byte][padding 107 bytes]
|
// sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID+sessionID 36 bytes, proxy method 16 bytes, encryption method 1 byte][16 bytes authentication tag][padding 91 bytes]
|
||||||
// The first 16 bytes of the marshalled ephemeral public key is used as the IV
|
// 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)
|
tthInterval := sta.Now().Unix() / int64(sta.TicketTimeHint)
|
||||||
sta.keyPairsM.Lock()
|
sta.keyPairsM.Lock()
|
||||||
|
|
@ -59,8 +59,8 @@ func MakeSessionTicket(sta *State) []byte {
|
||||||
copy(plain[36:52], []byte(sta.ProxyMethod))
|
copy(plain[36:52], []byte(sta.ProxyMethod))
|
||||||
plain[52] = sta.EncryptionMethod
|
plain[52] = sta.EncryptionMethod
|
||||||
|
|
||||||
cipher := util.AESEncrypt(ticket[0:16], key, plain)
|
cipher, _ := util.AESGCMEncrypt(ticket[0:12], key, plain)
|
||||||
copy(ticket[32:85], cipher)
|
copy(ticket[32:101], 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
|
||||||
|
|
@ -71,6 +71,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[85:192], util.PsudoRandBytes(107, tthInterval+int64(sta.sessionID)))
|
copy(ticket[101:192], util.PsudoRandBytes(91, tthInterval+int64(sta.sessionID)))
|
||||||
return ticket
|
return ticket
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,20 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
//"crypto/aes"
|
||||||
"crypto/cipher"
|
//"crypto/cipher"
|
||||||
"crypto/rand"
|
//"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
prand "math/rand"
|
//prand "math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
//"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")
|
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
|
||||||
staticPv, staticPub, _ := ecdh.GenerateKey(rand.Reader)
|
staticPv, staticPub, _ := ecdh.GenerateKey(rand.Reader)
|
||||||
|
|
@ -72,6 +72,7 @@ func TestMakeSessionTicket(t *testing.T) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func TestMakeRandomField(t *testing.T) {
|
func TestMakeRandomField(t *testing.T) {
|
||||||
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
|
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,22 @@ import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Crypto interface {
|
type Crypto interface {
|
||||||
encrypt([]byte) []byte
|
encrypt([]byte) ([]byte, error)
|
||||||
decrypt([]byte) []byte
|
decrypt([]byte) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Plain struct{}
|
type Plain struct{}
|
||||||
|
|
||||||
func (p *Plain) encrypt(plaintext []byte) []byte {
|
func (p *Plain) encrypt(plaintext []byte) ([]byte, error) {
|
||||||
return plaintext
|
return plaintext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Plain) decrypt(buf []byte) []byte {
|
func (p *Plain) decrypt(buf []byte) ([]byte, error) {
|
||||||
return buf
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type AES struct {
|
type AES struct {
|
||||||
|
|
@ -36,18 +37,30 @@ func MakeAESCipher(key []byte) (*AES, error) {
|
||||||
return &ret, nil
|
return &ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AES) encrypt(plaintext []byte) []byte {
|
func (a *AES) encrypt(plaintext []byte) ([]byte, error) {
|
||||||
iv := make([]byte, 16)
|
nonce := make([]byte, 12)
|
||||||
rand.Read(iv)
|
rand.Read(nonce)
|
||||||
ciphertext := make([]byte, 16+len(plaintext))
|
aesgcm, err := cipher.NewGCM(a.cipher)
|
||||||
stream := cipher.NewCTR(a.cipher, iv)
|
if err != nil {
|
||||||
stream.XORKeyStream(ciphertext[16:], plaintext)
|
return nil, err
|
||||||
copy(ciphertext[:16], iv)
|
}
|
||||||
return ciphertext
|
ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil)
|
||||||
|
ret := make([]byte, 12+len(plaintext)+16)
|
||||||
|
copy(ret[:12], nonce)
|
||||||
|
copy(ret[12:], ciphertext)
|
||||||
|
log.Printf("%x\n", ret)
|
||||||
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AES) decrypt(buf []byte) []byte {
|
func (a *AES) decrypt(buf []byte) ([]byte, error) {
|
||||||
stream := cipher.NewCTR(a.cipher, buf[0:16])
|
log.Printf("%x\n", buf)
|
||||||
stream.XORKeyStream(buf[16:], buf[16:])
|
aesgcm, err := cipher.NewGCM(a.cipher)
|
||||||
return buf[16:]
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
plain, err := aesgcm.Open(nil, buf[:12], buf[12:], nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plain, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,10 @@ func MakeObfs(key []byte, algo Crypto) Obfser {
|
||||||
binary.BigEndian.PutUint32(obfsedHeader[4:8], f.Seq^ii)
|
binary.BigEndian.PutUint32(obfsedHeader[4:8], f.Seq^ii)
|
||||||
obfsedHeader[8] = f.Closing ^ iii
|
obfsedHeader[8] = f.Closing ^ iii
|
||||||
|
|
||||||
encryptedPayload := algo.encrypt(f.Payload)
|
encryptedPayload, err := algo.encrypt(f.Payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Composing final obfsed message
|
// Composing final obfsed message
|
||||||
// We don't use util.AddRecordLayer here to avoid unnecessary malloc
|
// We don't use util.AddRecordLayer here to avoid unnecessary malloc
|
||||||
|
|
@ -61,7 +64,10 @@ func MakeDeobfs(key []byte, algo Crypto) Deobfser {
|
||||||
|
|
||||||
rawPayload := make([]byte, len(peeled)-headerLen)
|
rawPayload := make([]byte, len(peeled)-headerLen)
|
||||||
copy(rawPayload, peeled[headerLen:])
|
copy(rawPayload, peeled[headerLen:])
|
||||||
decryptedPayload := algo.decrypt(rawPayload)
|
decryptedPayload, err := algo.decrypt(rawPayload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
ret := &Frame{
|
ret := &Frame{
|
||||||
StreamID: streamID,
|
StreamID: streamID,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ import (
|
||||||
func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) ([]byte, uint32, string, byte) {
|
func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) ([]byte, uint32, string, byte) {
|
||||||
ephPub, _ := ecdh.Unmarshal(ticket[0:32])
|
ephPub, _ := ecdh.Unmarshal(ticket[0:32])
|
||||||
key := ecdh.GenerateSharedSecret(staticPv, ephPub)
|
key := ecdh.GenerateSharedSecret(staticPv, ephPub)
|
||||||
plain := util.AESDecrypt(ticket[0:16], key, ticket[32:85])
|
plain, err := util.AESGCMDecrypt(ticket[0:12], key, ticket[32:101])
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, "", 0x00
|
||||||
|
}
|
||||||
sessionID := binary.BigEndian.Uint32(plain[32:36])
|
sessionID := binary.BigEndian.Uint32(plain[32:36])
|
||||||
return plain[0:32], sessionID, string(bytes.Trim(plain[36:52], "\x00")), plain[52]
|
return plain[0:32], sessionID, string(bytes.Trim(plain[36:52], "\x00")), plain[52]
|
||||||
}
|
}
|
||||||
|
|
@ -51,6 +54,9 @@ func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID u
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
UID, sessionID, proxyMethod, encryptionMethod = decryptSessionTicket(sta.staticPv, ticket)
|
UID, sessionID, proxyMethod, encryptionMethod = decryptSessionTicket(sta.staticPv, ticket)
|
||||||
|
if len(UID) < 32 {
|
||||||
|
return
|
||||||
|
}
|
||||||
isCK = validateRandom(ch.random, UID, sta.Now().Unix())
|
isCK = validateRandom(ch.random, UID, sta.Now().Unix())
|
||||||
if !isCK {
|
if !isCK {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
//"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"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("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
|
||||||
sessionID := uint32(42)
|
sessionID := uint32(42)
|
||||||
|
|
@ -33,6 +33,7 @@ func TestDecryptSessionTicket(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func TestValidateRandom(t *testing.T) {
|
func TestValidateRandom(t *testing.T) {
|
||||||
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
|
UID, _ := hex.DecodeString("26a8e88bcd7c64a69ca051740851d22a6818de2fddafc00882331f1c5a8b866c")
|
||||||
|
|
|
||||||
|
|
@ -11,21 +11,32 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AESEncrypt(iv []byte, key []byte, plaintext []byte) []byte {
|
func AESGCMEncrypt(nonce []byte, key []byte, plaintext []byte) ([]byte, error) {
|
||||||
block, _ := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
ciphertext := make([]byte, len(plaintext))
|
if err != nil {
|
||||||
stream := cipher.NewCTR(block, iv)
|
return nil, err
|
||||||
stream.XORKeyStream(ciphertext, plaintext)
|
}
|
||||||
return ciphertext
|
aesgcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return aesgcm.Seal(nil, nonce, plaintext, nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AESDecrypt(iv []byte, key []byte, ciphertext []byte) []byte {
|
func AESGCMDecrypt(nonce []byte, key []byte, ciphertext []byte) ([]byte, error) {
|
||||||
ret := make([]byte, len(ciphertext))
|
block, err := aes.NewCipher(key)
|
||||||
copy(ret, ciphertext) // Because XORKeyStream is inplace, but we don't want the input to be changed
|
if err != nil {
|
||||||
block, _ := aes.NewCipher(key)
|
return nil, err
|
||||||
stream := cipher.NewCTR(block, iv)
|
}
|
||||||
stream.XORKeyStream(ret, ret)
|
aesgcm, err := cipher.NewGCM(block)
|
||||||
return ret
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
plain, err := aesgcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plain, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PsudoRandBytes returns a byte slice filled with psudorandom bytes generated by the seed
|
// PsudoRandBytes returns a byte slice filled with psudorandom bytes generated by the seed
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue