mirror of https://github.com/cbeuw/Cloak
Use exclusively salsa20 for header encryption
This commit is contained in:
parent
ef076bef85
commit
9fa37e327f
|
|
@ -95,7 +95,7 @@ func makeSession(sta *client.State) *mux.Session {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
sessionKey := _sessionKey.Load().([]byte)
|
sessionKey := _sessionKey.Load().([]byte)
|
||||||
obfuscator, err := util.GenerateObfs(sta.EncryptionMethod, sessionKey)
|
obfuscator, err := mux.GenerateObfs(sta.EncryptionMethod, sessionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
|
||||||
|
|
||||||
sessionKey := make([]byte, 32)
|
sessionKey := make([]byte, 32)
|
||||||
rand.Read(sessionKey)
|
rand.Read(sessionKey)
|
||||||
obfuscator, err := util.GenerateObfs(encryptionMethod, sessionKey)
|
obfuscator, err := mux.GenerateObfs(encryptionMethod, sessionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
goWeb()
|
goWeb()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
package multiplex
|
package multiplex
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
"golang.org/x/crypto/salsa20"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Obfser func(*Frame) ([]byte, error)
|
type Obfser func(*Frame) ([]byte, error)
|
||||||
|
|
@ -15,9 +18,15 @@ var putU32 = binary.BigEndian.PutUint32
|
||||||
|
|
||||||
const HEADER_LEN = 12
|
const HEADER_LEN = 12
|
||||||
|
|
||||||
func MakeObfs(headerCipher cipher.Block, payloadCipher cipher.AEAD) Obfser {
|
func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Obfser {
|
||||||
|
var tagLen int
|
||||||
|
if payloadCipher == nil {
|
||||||
|
tagLen = 8 //nonce
|
||||||
|
} else {
|
||||||
|
tagLen = payloadCipher.Overhead()
|
||||||
|
}
|
||||||
obfs := func(f *Frame) ([]byte, error) {
|
obfs := func(f *Frame) ([]byte, error) {
|
||||||
ret := make([]byte, 5+HEADER_LEN+len(f.Payload)+16)
|
ret := make([]byte, 5+HEADER_LEN+len(f.Payload)+tagLen)
|
||||||
recordLayer := ret[0:5]
|
recordLayer := ret[0:5]
|
||||||
header := ret[5 : 5+HEADER_LEN]
|
header := ret[5 : 5+HEADER_LEN]
|
||||||
encryptedPayload := ret[5+HEADER_LEN:]
|
encryptedPayload := ret[5+HEADER_LEN:]
|
||||||
|
|
@ -30,14 +39,14 @@ func MakeObfs(headerCipher cipher.Block, payloadCipher cipher.AEAD) Obfser {
|
||||||
|
|
||||||
if payloadCipher == nil {
|
if payloadCipher == nil {
|
||||||
copy(encryptedPayload, f.Payload)
|
copy(encryptedPayload, f.Payload)
|
||||||
rand.Read(encryptedPayload[len(encryptedPayload)-16:])
|
rand.Read(encryptedPayload[len(encryptedPayload)-tagLen:])
|
||||||
} else {
|
} else {
|
||||||
ciphertext := payloadCipher.Seal(nil, header, f.Payload, nil)
|
ciphertext := payloadCipher.Seal(nil, header, f.Payload, nil)
|
||||||
copy(encryptedPayload, ciphertext)
|
copy(encryptedPayload, ciphertext)
|
||||||
}
|
}
|
||||||
|
|
||||||
iv := encryptedPayload[len(encryptedPayload)-16:]
|
nonce := encryptedPayload[len(encryptedPayload)-8:]
|
||||||
cipher.NewCTR(headerCipher, iv).XORKeyStream(header, header)
|
salsa20.XORKeyStream(header, header, nonce, &salsaKey)
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -50,24 +59,30 @@ func MakeObfs(headerCipher cipher.Block, payloadCipher cipher.AEAD) Obfser {
|
||||||
return obfs
|
return obfs
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeDeobfs(headerCipher cipher.Block, payloadCipher cipher.AEAD) Deobfser {
|
func MakeDeobfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Deobfser {
|
||||||
|
var tagLen int
|
||||||
|
if payloadCipher == nil {
|
||||||
|
tagLen = 8 // nonce
|
||||||
|
} else {
|
||||||
|
tagLen = payloadCipher.Overhead()
|
||||||
|
}
|
||||||
deobfs := func(in []byte) (*Frame, error) {
|
deobfs := func(in []byte) (*Frame, error) {
|
||||||
if len(in) < 5+HEADER_LEN+16 {
|
if len(in) < 5+HEADER_LEN+tagLen {
|
||||||
return nil, errors.New("Input cannot be shorter than 33 bytes")
|
return nil, errors.New("Input cannot be shorter than 33 bytes")
|
||||||
}
|
}
|
||||||
peeled := in[5:]
|
peeled := in[5:]
|
||||||
|
|
||||||
header := peeled[0:12]
|
header := peeled[0:12]
|
||||||
payload := peeled[12:]
|
payload := peeled[12:]
|
||||||
iv := peeled[len(peeled)-16:]
|
|
||||||
|
|
||||||
cipher.NewCTR(headerCipher, iv).XORKeyStream(header, header)
|
nonce := peeled[len(peeled)-8:]
|
||||||
|
salsa20.XORKeyStream(header, header, nonce, &salsaKey)
|
||||||
|
|
||||||
streamID := u32(header[0:4])
|
streamID := u32(header[0:4])
|
||||||
seq := u32(header[4:8])
|
seq := u32(header[4:8])
|
||||||
closing := header[8]
|
closing := header[8]
|
||||||
|
|
||||||
outputPayload := make([]byte, len(payload)-16)
|
outputPayload := make([]byte, len(payload)-tagLen)
|
||||||
|
|
||||||
if payloadCipher == nil {
|
if payloadCipher == nil {
|
||||||
copy(outputPayload, payload)
|
copy(outputPayload, payload)
|
||||||
|
|
@ -89,3 +104,43 @@ func MakeDeobfs(headerCipher cipher.Block, payloadCipher cipher.AEAD) Deobfser {
|
||||||
}
|
}
|
||||||
return deobfs
|
return deobfs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateObfs(encryptionMethod byte, sessionKey []byte) (obfuscator *Obfuscator, err error) {
|
||||||
|
if len(sessionKey) != 32 {
|
||||||
|
err = errors.New("sessionKey size must be 32 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
blockKey := sessionKey[:16]
|
||||||
|
var salsaKey [32]byte
|
||||||
|
copy(salsaKey[:], sessionKey)
|
||||||
|
|
||||||
|
var payloadCipher cipher.AEAD
|
||||||
|
switch encryptionMethod {
|
||||||
|
case 0x00:
|
||||||
|
payloadCipher = nil
|
||||||
|
case 0x01:
|
||||||
|
var c cipher.Block
|
||||||
|
c, err = aes.NewCipher(blockKey)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
payloadCipher, err = cipher.NewGCM(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case 0x02:
|
||||||
|
payloadCipher, err = chacha20poly1305.New(blockKey)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Unknown encryption method")
|
||||||
|
}
|
||||||
|
|
||||||
|
obfuscator = &Obfuscator{
|
||||||
|
MakeObfs(salsaKey, payloadCipher),
|
||||||
|
MakeDeobfs(salsaKey, payloadCipher),
|
||||||
|
sessionKey,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package multiplex
|
package multiplex
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -10,26 +14,96 @@ import (
|
||||||
func TestOobfs(t *testing.T) {
|
func TestOobfs(t *testing.T) {
|
||||||
sessionKey := make([]byte, 32)
|
sessionKey := make([]byte, 32)
|
||||||
rand.Read(sessionKey)
|
rand.Read(sessionKey)
|
||||||
obfuscator, err := GenerateObfs(0x01, sessionKey)
|
|
||||||
if err != nil {
|
run := func(obfuscator *Obfuscator) {
|
||||||
t.Errorf("failed to generate obfuscator %v", err)
|
f := &Frame{}
|
||||||
|
_testFrame, _ := quick.Value(reflect.TypeOf(f), rand.New(rand.NewSource(42)))
|
||||||
|
testFrame := _testFrame.Interface().(*Frame)
|
||||||
|
obfsed, err := obfuscator.Obfs(testFrame)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to obfs ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultFrame, err := obfuscator.Deobfs(obfsed)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed to deobfs ", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(testFrame.Payload, resultFrame.Payload) || testFrame.StreamID != resultFrame.StreamID {
|
||||||
|
t.Error("expecting", testFrame,
|
||||||
|
"got", resultFrame)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f := &Frame{}
|
t.Run("plain", func(t *testing.T) {
|
||||||
_testFrame, _ := quick.Value(reflect.TypeOf(f), rand.New(rand.NewSource(42)))
|
obfuscator, err := GenerateObfs(0x01, sessionKey)
|
||||||
testFrame := _testFrame.Interface().(*Frame)
|
if err != nil {
|
||||||
obfsed, err := obfuscator.Obfs(testFrame)
|
t.Errorf("failed to generate obfuscator %v", err)
|
||||||
if err != nil {
|
}
|
||||||
t.Error("failed to obfs ", err)
|
run(obfuscator)
|
||||||
}
|
})
|
||||||
|
t.Run("aes-gcm", func(t *testing.T) {
|
||||||
resultFrame, err := obfuscator.Deobfs(obfsed)
|
obfuscator, err := GenerateObfs(0x01, sessionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("failed to deobfs ", err)
|
t.Errorf("failed to generate obfuscator %v", err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(testFrame, resultFrame) {
|
run(obfuscator)
|
||||||
t.Error("expecting", testFrame,
|
})
|
||||||
"got", resultFrame)
|
t.Run("chacha20-poly1305", func(t *testing.T) {
|
||||||
}
|
obfuscator, err := GenerateObfs(0x01, sessionKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to generate obfuscator %v", err)
|
||||||
|
}
|
||||||
|
run(obfuscator)
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkObfs(b *testing.B) {
|
||||||
|
testPayload := make([]byte, 1024)
|
||||||
|
rand.Read(testPayload)
|
||||||
|
testFrame := &Frame{
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
testPayload,
|
||||||
|
}
|
||||||
|
|
||||||
|
var key [32]byte
|
||||||
|
rand.Read(key[:])
|
||||||
|
b.Run("AES256GCM", func(b *testing.B) {
|
||||||
|
c, _ := aes.NewCipher(key[:])
|
||||||
|
payloadCipher, _ := cipher.NewGCM(c)
|
||||||
|
|
||||||
|
obfs := MakeObfs(key, payloadCipher)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
obfs(testFrame)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("AES128GCM", func(b *testing.B) {
|
||||||
|
c, _ := aes.NewCipher(key[:16])
|
||||||
|
payloadCipher, _ := cipher.NewGCM(c)
|
||||||
|
|
||||||
|
obfs := MakeObfs(key, payloadCipher)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
obfs(testFrame)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("plain", func(b *testing.B) {
|
||||||
|
obfs := MakeObfs(key, nil)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
obfs(testFrame)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("chacha20Poly1305", func(b *testing.B) {
|
||||||
|
payloadCipher, _ := chacha20poly1305.New(key[:16])
|
||||||
|
|
||||||
|
obfs := MakeObfs(key, payloadCipher)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
obfs(testFrame)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,98 +3,19 @@ package multiplex
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"github.com/cbeuw/Cloak/internal/util"
|
||||||
"crypto/cipher"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadTLS reads TLS data according to its record layer
|
|
||||||
func ReadTLS(conn net.Conn, buffer []byte) (n int, err error) {
|
|
||||||
// TCP is a stream. Multiple TLS messages can arrive at the same time,
|
|
||||||
// a single message can also be segmented due to MTU of the IP layer.
|
|
||||||
// This function guareentees a single TLS message to be read and everything
|
|
||||||
// else is left in the buffer.
|
|
||||||
i, err := io.ReadFull(conn, buffer[:5])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dataLength := int(binary.BigEndian.Uint16(buffer[3:5]))
|
|
||||||
if dataLength > len(buffer) {
|
|
||||||
err = errors.New("Reading TLS message: message size greater than buffer. message size: " + strconv.Itoa(dataLength))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
left := dataLength
|
|
||||||
readPtr := 5
|
|
||||||
|
|
||||||
for left != 0 {
|
|
||||||
// If left > buffer size (i.e. our message got segmented), the entire MTU is read
|
|
||||||
// if left = buffer size, the entire buffer is all there left to read
|
|
||||||
// if left < buffer size (i.e. multiple messages came together),
|
|
||||||
// only the message we want is read
|
|
||||||
i, err = io.ReadFull(conn, buffer[readPtr:readPtr+left])
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
left -= i
|
|
||||||
readPtr += i
|
|
||||||
}
|
|
||||||
|
|
||||||
n = 5 + dataLength
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateObfs(encryptionMethod byte, sessionKey []byte) (obfuscator *Obfuscator, 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, errors.New("Unknown encryption method")
|
|
||||||
}
|
|
||||||
|
|
||||||
headerCipher, err := aes.NewCipher(sessionKey)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
obfuscator = &Obfuscator{
|
|
||||||
MakeObfs(headerCipher, payloadCipher),
|
|
||||||
MakeDeobfs(headerCipher, payloadCipher),
|
|
||||||
sessionKey,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupSesh() *Session {
|
func setupSesh() *Session {
|
||||||
sessionKey := make([]byte, 32)
|
sessionKey := make([]byte, 32)
|
||||||
rand.Read(sessionKey)
|
rand.Read(sessionKey)
|
||||||
obfuscator, _ := GenerateObfs(0x00, sessionKey)
|
obfuscator, _ := GenerateObfs(0x00, sessionKey)
|
||||||
return MakeSession(0, UNLIMITED_VALVE, obfuscator, ReadTLS)
|
return MakeSession(0, UNLIMITED_VALVE, obfuscator, util.ReadTLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
type blackhole struct {
|
type blackhole struct {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ 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"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -76,43 +74,6 @@ func ReadTLS(conn net.Conn, buffer []byte) (n int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateObfs(encryptionMethod byte, sessionKey []byte) (obfuscator *mux.Obfuscator, 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, errors.New("Unknown encryption method")
|
|
||||||
}
|
|
||||||
|
|
||||||
headerCipher, err := aes.NewCipher(sessionKey)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
obfuscator = &mux.Obfuscator{
|
|
||||||
mux.MakeObfs(headerCipher, payloadCipher),
|
|
||||||
mux.MakeDeobfs(headerCipher, payloadCipher),
|
|
||||||
sessionKey,
|
|
||||||
}
|
|
||||||
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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue