mirror of https://github.com/cbeuw/Cloak
Compare commits
8 Commits
018994bfd1
...
5a3a05593b
Author | SHA1 | Date |
---|---|---|
Andy Wang | 5a3a05593b | 1 year ago |
Andy Wang | 9c9f2fc5a3 | 1 year ago |
Andy Wang | b82ca78c72 | 1 year ago |
Andy Wang | 75c3935d10 | 1 year ago |
Andy Wang | 90b6465de1 | 1 year ago |
Andy Wang | 26a462594f | 1 year ago |
Andy Wang | fe16db066d | 1 year ago |
Andy Wang | e6f43ebabb | 1 year ago |
@ -1,17 +1,26 @@
|
||||
module github.com/cbeuw/Cloak
|
||||
|
||||
go 1.14
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/cbeuw/connutil v0.0.0-20200411215123-966bfaa51ee3
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/juju/ratelimit v1.0.1
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/stretchr/testify v1.7.1
|
||||
gitlab.com/yawning/utls.git v0.0.12-1
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/crypto v0.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
@ -0,0 +1,154 @@
|
||||
package cli_client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cbeuw/Cloak/internal/common"
|
||||
"github.com/cbeuw/Cloak/libcloak/client"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CLIConfig struct {
|
||||
client.Config
|
||||
|
||||
// LocalHost is the hostname or IP address to listen for incoming proxy client connections
|
||||
LocalHost string // jsonOptional
|
||||
// LocalPort is the port to listen for incomig proxy client connections
|
||||
LocalPort string // jsonOptional
|
||||
// AlternativeNames is a list of ServerName Cloak may randomly pick from for different sessions
|
||||
// Optional
|
||||
AlternativeNames []string
|
||||
// StreamTimeout is the duration, in seconds, for an incoming connection to be automatically closed after the last
|
||||
// piece of incoming data .
|
||||
// Optional, Defaults to 300
|
||||
StreamTimeout int
|
||||
}
|
||||
|
||||
// semi-colon separated value. This is for Android plugin options
|
||||
func ssvToJson(ssv string) (ret []byte) {
|
||||
elem := func(val string, lst []string) bool {
|
||||
for _, v := range lst {
|
||||
if val == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
unescape := func(s string) string {
|
||||
r := strings.Replace(s, `\\`, `\`, -1)
|
||||
r = strings.Replace(r, `\=`, `=`, -1)
|
||||
r = strings.Replace(r, `\;`, `;`, -1)
|
||||
return r
|
||||
}
|
||||
unquoted := []string{"NumConn", "StreamTimeout", "KeepAlive", "UDP"}
|
||||
lines := strings.Split(unescape(ssv), ";")
|
||||
ret = []byte("{")
|
||||
for _, ln := range lines {
|
||||
if ln == "" {
|
||||
break
|
||||
}
|
||||
sp := strings.SplitN(ln, "=", 2)
|
||||
if len(sp) < 2 {
|
||||
log.Errorf("Malformed config option: %v", ln)
|
||||
continue
|
||||
}
|
||||
key := sp[0]
|
||||
value := sp[1]
|
||||
if strings.HasPrefix(key, "AlternativeNames") {
|
||||
switch strings.Contains(value, ",") {
|
||||
case true:
|
||||
domains := strings.Split(value, ",")
|
||||
for index, domain := range domains {
|
||||
domains[index] = `"` + domain + `"`
|
||||
}
|
||||
value = strings.Join(domains, ",")
|
||||
ret = append(ret, []byte(`"`+key+`":[`+value+`],`)...)
|
||||
case false:
|
||||
ret = append(ret, []byte(`"`+key+`":["`+value+`"],`)...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// JSON doesn't like quotation marks around int and bool
|
||||
// This is extremely ugly but it's still better than writing a tokeniser
|
||||
if elem(key, unquoted) {
|
||||
ret = append(ret, []byte(`"`+key+`":`+value+`,`)...)
|
||||
} else {
|
||||
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
|
||||
}
|
||||
}
|
||||
ret = ret[:len(ret)-1] // remove the last comma
|
||||
ret = append(ret, '}')
|
||||
return ret
|
||||
}
|
||||
|
||||
func ParseConfig(conf string) (raw *CLIConfig, err error) {
|
||||
var content []byte
|
||||
// Checking if it's a path to json or a ssv string
|
||||
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
|
||||
content = ssvToJson(conf)
|
||||
} else {
|
||||
content, err = ioutil.ReadFile(conf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
raw = new(CLIConfig)
|
||||
err = json.Unmarshal(content, &raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type LocalConnConfig struct {
|
||||
LocalAddr string
|
||||
Timeout time.Duration
|
||||
MockDomainList []string
|
||||
Singleplex bool
|
||||
}
|
||||
|
||||
func (raw *CLIConfig) ProcessCLIConfig(worldState common.WorldState) (local LocalConnConfig, remote client.RemoteConnConfig, auth client.AuthInfo, err error) {
|
||||
remote, auth, err = raw.Config.Process(worldState)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if raw.AlternativeNames != nil && len(raw.AlternativeNames) > 0 {
|
||||
var filteredAlternativeNames []string
|
||||
for _, alternativeName := range raw.AlternativeNames {
|
||||
if len(alternativeName) > 0 {
|
||||
filteredAlternativeNames = append(filteredAlternativeNames, alternativeName)
|
||||
}
|
||||
}
|
||||
local.MockDomainList = raw.AlternativeNames
|
||||
} else {
|
||||
local.MockDomainList = []string{}
|
||||
}
|
||||
|
||||
local.MockDomainList = append(local.MockDomainList, auth.MockDomain)
|
||||
|
||||
if raw.LocalHost == "" {
|
||||
err = fmt.Errorf("LocalHost cannot be empty")
|
||||
return
|
||||
}
|
||||
if raw.LocalPort == "" {
|
||||
err = fmt.Errorf("LocalPort cannot be empty")
|
||||
return
|
||||
}
|
||||
local.LocalAddr = net.JoinHostPort(raw.LocalHost, raw.LocalPort)
|
||||
// stream no write timeout
|
||||
if raw.StreamTimeout == 0 {
|
||||
local.Timeout = 300 * time.Second
|
||||
} else {
|
||||
local.Timeout = time.Duration(raw.StreamTimeout) * time.Second
|
||||
}
|
||||
|
||||
local.Singleplex = raw.NumConn != nil && *raw.NumConn == 0
|
||||
|
||||
return
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package cli_client
|
||||
|
||||
import (
|
||||
"github.com/cbeuw/Cloak/internal/common"
|
||||
"github.com/cbeuw/Cloak/libcloak/client"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseConfig(t *testing.T) {
|
||||
ssv := "UID=iGAO85zysIyR4c09CyZSLdNhtP/ckcYu7nIPI082AHA=;PublicKey=IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=;" +
|
||||
"ServerName=www.bing.com;NumConn=4;MaskBrowser=chrome;ProxyMethod=shadowsocks;EncryptionMethod=plain"
|
||||
json := ssvToJson(ssv)
|
||||
expected := []byte(`{"UID":"iGAO85zysIyR4c09CyZSLdNhtP/ckcYu7nIPI082AHA=","PublicKey":"IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=","ServerName":"www.bing.com","NumConn":4,"MaskBrowser":"chrome","ProxyMethod":"shadowsocks","EncryptionMethod":"plain"}`)
|
||||
|
||||
t.Run("byte equality", func(t *testing.T) {
|
||||
assert.Equal(t, expected, json)
|
||||
})
|
||||
|
||||
t.Run("struct equality", func(t *testing.T) {
|
||||
tmpConfig, _ := ioutil.TempFile("", "ck_client_config")
|
||||
_, _ = tmpConfig.Write(expected)
|
||||
parsedFromSSV, err := ParseConfig(ssv)
|
||||
assert.NoError(t, err)
|
||||
parsedFromJson, err := ParseConfig(tmpConfig.Name())
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, parsedFromJson, parsedFromSSV)
|
||||
})
|
||||
|
||||
t.Run("empty file", func(t *testing.T) {
|
||||
tmpConfig, _ := ioutil.TempFile("", "ck_client_config")
|
||||
_, err := ParseConfig(tmpConfig.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProcessCLIConfig(t *testing.T) {
|
||||
config := CLIConfig{
|
||||
Config: client.Config{
|
||||
ServerName: "bbc.co.uk",
|
||||
// ProxyMethod is the name of the underlying proxy you wish
|
||||
// to connect to, as determined by your server. The value can
|
||||
// be any string whose UTF-8 ENCODED byte length is no greater than
|
||||
// 12 bytes
|
||||
ProxyMethod: "ssh",
|
||||
// UID is a 16-byte secret string unique to an authorised user
|
||||
// The same UID can be used by the same user for multiple Cloak connections
|
||||
UID: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
// PublicKey is the 32-byte public Curve25519 ECDH key of your server
|
||||
PublicKey: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
// RemoteHost is the Cloak server's hostname or IP address
|
||||
RemoteHost: "1.2.3.4",
|
||||
},
|
||||
LocalHost: "0.0.0.0",
|
||||
LocalPort: "1234",
|
||||
}
|
||||
|
||||
t.Run("Zero means singleplex", func(t *testing.T) {
|
||||
zero := 0
|
||||
config := config
|
||||
config.NumConn = &zero
|
||||
local, _, _, err := config.ProcessCLIConfig(common.RealWorldState)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, local.Singleplex)
|
||||
})
|
||||
|
||||
t.Run("Empty means no singleplex", func(t *testing.T) {
|
||||
config := config
|
||||
local, _, _, err := config.ProcessCLIConfig(common.RealWorldState)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, local.Singleplex)
|
||||
})
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
|
||||
"github.com/cbeuw/Cloak/internal/common"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const appDataMaxLength = 16401
|
||||
|
||||
type clientHelloFields struct {
|
||||
random []byte
|
||||
sessionId []byte
|
||||
x25519KeyShare []byte
|
||||
serverName string
|
||||
}
|
||||
|
||||
func decodeHex(s string) []byte {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type browser interface {
|
||||
composeClientHello(clientHelloFields) []byte
|
||||
}
|
||||
|
||||
func generateSNI(serverName string) []byte {
|
||||
serverNameListLength := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(serverNameListLength, uint16(len(serverName)+3))
|
||||
serverNameType := []byte{0x00} // host_name
|
||||
serverNameLength := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(serverNameLength, uint16(len(serverName)))
|
||||
ret := make([]byte, 2+1+2+len(serverName))
|
||||
copy(ret[0:2], serverNameListLength)
|
||||
copy(ret[2:3], serverNameType)
|
||||
copy(ret[3:5], serverNameLength)
|
||||
copy(ret[5:], serverName)
|
||||
return ret
|
||||
}
|
||||
|
||||
// addExtensionRecord, add type, length to extension data
|
||||
func addExtRec(typ []byte, data []byte) []byte {
|
||||
length := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(length, uint16(len(data)))
|
||||
ret := make([]byte, 2+2+len(data))
|
||||
copy(ret[0:2], typ)
|
||||
copy(ret[2:4], length)
|
||||
copy(ret[4:], data)
|
||||
return ret
|
||||
}
|
||||
|
||||
type DirectTLS struct {
|
||||
*common.TLSConn
|
||||
browser browser
|
||||
}
|
||||
|
||||
// NewClientTransport handles the TLS handshake for a given conn and returns the sessionKey
|
||||
// if the server proceed with Cloak authentication
|
||||
func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey [32]byte, err error) {
|
||||
payload, sharedSecret := makeAuthenticationPayload(authInfo)
|
||||
|
||||
// random is marshalled ephemeral pub key 32 bytes
|
||||
// The authentication ciphertext and its tag are then distributed among SessionId and X25519KeyShare
|
||||
fields := clientHelloFields{
|
||||
random: payload.randPubKey[:],
|
||||
sessionId: payload.ciphertextWithTag[0:32],
|
||||
x25519KeyShare: payload.ciphertextWithTag[32:64],
|
||||
serverName: authInfo.MockDomain,
|
||||
}
|
||||
chOnly := tls.browser.composeClientHello(fields)
|
||||
chWithRecordLayer := common.AddRecordLayer(chOnly, common.Handshake, common.VersionTLS11)
|
||||
_, err = rawConn.Write(chWithRecordLayer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Trace("client hello sent successfully")
|
||||
tls.TLSConn = common.NewTLSConn(rawConn)
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
log.Trace("waiting for ServerHello")
|
||||
_, err = tls.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encrypted := append(buf[6:38], buf[84:116]...)
|
||||
nonce := encrypted[0:12]
|
||||
ciphertextWithTag := encrypted[12:60]
|
||||
sessionKeySlice, err := common.AESGCMDecrypt(nonce, sharedSecret[:], ciphertextWithTag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
copy(sessionKey[:], sessionKeySlice)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
// ChangeCipherSpec and EncryptedCert (in the format of application data)
|
||||
_, err = tls.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return sessionKey, nil
|
||||
|
||||
}
|
@ -1,281 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cbeuw/Cloak/internal/common"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/cbeuw/Cloak/internal/ecdh"
|
||||
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
||||
)
|
||||
|
||||
// RawConfig represents the fields in the config json file
|
||||
// nullable means if it's empty, a default value will be chosen in ProcessRawConfig
|
||||
// jsonOptional means if the json's empty, its value will be set from environment variables or commandline args
|
||||
// but it mustn't be empty when ProcessRawConfig is called
|
||||
type RawConfig struct {
|
||||
ServerName string
|
||||
ProxyMethod string
|
||||
EncryptionMethod string
|
||||
UID []byte
|
||||
PublicKey []byte
|
||||
NumConn int
|
||||
LocalHost string // jsonOptional
|
||||
LocalPort string // jsonOptional
|
||||
RemoteHost string // jsonOptional
|
||||
RemotePort string // jsonOptional
|
||||
AlternativeNames []string // jsonOptional
|
||||
// defaults set in ProcessRawConfig
|
||||
UDP bool // nullable
|
||||
BrowserSig string // nullable
|
||||
Transport string // nullable
|
||||
CDNOriginHost string // nullable
|
||||
CDNWsUrlPath string // nullable
|
||||
StreamTimeout int // nullable
|
||||
KeepAlive int // nullable
|
||||
}
|
||||
|
||||
type RemoteConnConfig struct {
|
||||
Singleplex bool
|
||||
NumConn int
|
||||
KeepAlive time.Duration
|
||||
RemoteAddr string
|
||||
TransportMaker func() Transport
|
||||
}
|
||||
|
||||
type LocalConnConfig struct {
|
||||
LocalAddr string
|
||||
Timeout time.Duration
|
||||
MockDomainList []string
|
||||
}
|
||||
|
||||
type AuthInfo struct {
|
||||
UID []byte
|
||||
SessionId uint32
|
||||
ProxyMethod string
|
||||
EncryptionMethod byte
|
||||
Unordered bool
|
||||
ServerPubKey crypto.PublicKey
|
||||
MockDomain string
|
||||
WorldState common.WorldState
|
||||
}
|
||||
|
||||
// semi-colon separated value. This is for Android plugin options
|
||||
func ssvToJson(ssv string) (ret []byte) {
|
||||
elem := func(val string, lst []string) bool {
|
||||
for _, v := range lst {
|
||||
if val == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
unescape := func(s string) string {
|
||||
r := strings.Replace(s, `\\`, `\`, -1)
|
||||
r = strings.Replace(r, `\=`, `=`, -1)
|
||||
r = strings.Replace(r, `\;`, `;`, -1)
|
||||
return r
|
||||
}
|
||||
unquoted := []string{"NumConn", "StreamTimeout", "KeepAlive", "UDP"}
|
||||
lines := strings.Split(unescape(ssv), ";")
|
||||
ret = []byte("{")
|
||||
for _, ln := range lines {
|
||||
if ln == "" {
|
||||
break
|
||||
}
|
||||
sp := strings.SplitN(ln, "=", 2)
|
||||
if len(sp) < 2 {
|
||||
log.Errorf("Malformed config option: %v", ln)
|
||||
continue
|
||||
}
|
||||
key := sp[0]
|
||||
value := sp[1]
|
||||
if strings.HasPrefix(key, "AlternativeNames") {
|
||||
switch strings.Contains(value, ",") {
|
||||
case true:
|
||||
domains := strings.Split(value, ",")
|
||||
for index, domain := range domains {
|
||||
domains[index] = `"` + domain + `"`
|
||||
}
|
||||
value = strings.Join(domains, ",")
|
||||
ret = append(ret, []byte(`"`+key+`":[`+value+`],`)...)
|
||||
case false:
|
||||
ret = append(ret, []byte(`"`+key+`":["`+value+`"],`)...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// JSON doesn't like quotation marks around int and bool
|
||||
// This is extremely ugly but it's still better than writing a tokeniser
|
||||
if elem(key, unquoted) {
|
||||
ret = append(ret, []byte(`"`+key+`":`+value+`,`)...)
|
||||
} else {
|
||||
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
|
||||
}
|
||||
}
|
||||
ret = ret[:len(ret)-1] // remove the last comma
|
||||
ret = append(ret, '}')
|
||||
return ret
|
||||
}
|
||||
|
||||
func ParseConfig(conf string) (raw *RawConfig, err error) {
|
||||
var content []byte
|
||||
// Checking if it's a path to json or a ssv string
|
||||
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
|
||||
content = ssvToJson(conf)
|
||||
} else {
|
||||
content, err = ioutil.ReadFile(conf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
raw = new(RawConfig)
|
||||
err = json.Unmarshal(content, &raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (raw *RawConfig) ProcessRawConfig(worldState common.WorldState) (local LocalConnConfig, remote RemoteConnConfig, auth AuthInfo, err error) {
|
||||
nullErr := func(field string) (local LocalConnConfig, remote RemoteConnConfig, auth AuthInfo, err error) {
|
||||
err = fmt.Errorf("%v cannot be empty", field)
|
||||
return
|
||||
}
|
||||
|
||||
auth.UID = raw.UID
|
||||
auth.Unordered = raw.UDP
|
||||
if raw.ServerName == "" {
|
||||
return nullErr("ServerName")
|
||||
}
|
||||
auth.MockDomain = raw.ServerName
|
||||
|
||||
var filteredAlternativeNames []string
|
||||
for _, alternativeName := range raw.AlternativeNames {
|
||||
if len(alternativeName) > 0 {
|
||||
filteredAlternativeNames = append(filteredAlternativeNames, alternativeName)
|
||||
}
|
||||
}
|
||||
raw.AlternativeNames = filteredAlternativeNames
|
||||
|
||||
local.MockDomainList = raw.AlternativeNames
|
||||
local.MockDomainList = append(local.MockDomainList, auth.MockDomain)
|
||||
if raw.ProxyMethod == "" {
|
||||
return nullErr("ServerName")
|
||||
}
|
||||
auth.ProxyMethod = raw.ProxyMethod
|
||||
if len(raw.UID) == 0 {
|
||||
return nullErr("UID")
|
||||
}
|
||||
|
||||
// static public key
|
||||
if len(raw.PublicKey) == 0 {
|
||||
return nullErr("PublicKey")
|
||||
}
|
||||
pub, ok := ecdh.Unmarshal(raw.PublicKey)
|
||||
if !ok {
|
||||
err = fmt.Errorf("failed to unmarshal Public key")
|
||||
return
|
||||
}
|
||||
auth.ServerPubKey = pub
|
||||
auth.WorldState = worldState
|
||||
|
||||
// Encryption method
|
||||
switch strings.ToLower(raw.EncryptionMethod) {
|
||||
case "plain":
|
||||
auth.EncryptionMethod = mux.EncryptionMethodPlain
|
||||
case "aes-gcm", "aes-256-gcm":
|
||||
auth.EncryptionMethod = mux.EncryptionMethodAES256GCM
|
||||
case "aes-128-gcm":
|
||||
auth.EncryptionMethod = mux.EncryptionMethodAES128GCM
|
||||
case "chacha20-poly1305":
|
||||
auth.EncryptionMethod = mux.EncryptionMethodChaha20Poly1305
|
||||
default:
|
||||
err = fmt.Errorf("unknown encryption method %v", raw.EncryptionMethod)
|
||||
return
|
||||
}
|
||||
|
||||
if raw.RemoteHost == "" {
|
||||
return nullErr("RemoteHost")
|
||||
}
|
||||
if raw.RemotePort == "" {
|
||||
return nullErr("RemotePort")
|
||||
}
|
||||
remote.RemoteAddr = net.JoinHostPort(raw.RemoteHost, raw.RemotePort)
|
||||
if raw.NumConn <= 0 {
|
||||
remote.NumConn = 1
|
||||
remote.Singleplex = true
|
||||
} else {
|
||||
remote.NumConn = raw.NumConn
|
||||
remote.Singleplex = false
|
||||
}
|
||||
|
||||
// Transport and (if TLS mode), browser
|
||||
switch strings.ToLower(raw.Transport) {
|
||||
case "cdn":
|
||||
var cdnDomainPort string
|
||||
if raw.CDNOriginHost == "" {
|
||||
cdnDomainPort = net.JoinHostPort(raw.RemoteHost, raw.RemotePort)
|
||||
} else {
|
||||
cdnDomainPort = net.JoinHostPort(raw.CDNOriginHost, raw.RemotePort)
|
||||
}
|
||||
if raw.CDNWsUrlPath == "" {
|
||||
raw.CDNWsUrlPath = "/"
|
||||
}
|
||||
|
||||
remote.TransportMaker = func() Transport {
|
||||
return &WSOverTLS{
|
||||
wsUrl: "ws://" + cdnDomainPort + raw.CDNWsUrlPath,
|
||||
}
|
||||
}
|
||||
case "direct":
|
||||
fallthrough
|
||||
default:
|
||||
var browser browser
|
||||
switch strings.ToLower(raw.BrowserSig) {
|
||||
case "firefox":
|
||||
browser = &Firefox{}
|
||||
case "safari":
|
||||
browser = &Safari{}
|
||||
case "chrome":
|
||||
fallthrough
|
||||
default:
|
||||
browser = &Chrome{}
|
||||
}
|
||||
remote.TransportMaker = func() Transport {
|
||||
return &DirectTLS{
|
||||
browser: browser,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// KeepAlive
|
||||
if raw.KeepAlive <= 0 {
|
||||
remote.KeepAlive = -1
|
||||
} else {
|
||||
remote.KeepAlive = remote.KeepAlive * time.Second
|
||||
}
|
||||
|
||||
if raw.LocalHost == "" {
|
||||
return nullErr("LocalHost")
|
||||
}
|
||||
if raw.LocalPort == "" {
|
||||
return nullErr("LocalPort")
|
||||
}
|
||||
local.LocalAddr = net.JoinHostPort(raw.LocalHost, raw.LocalPort)
|
||||
// stream no write timeout
|
||||
if raw.StreamTimeout == 0 {
|
||||
local.Timeout = 300 * time.Second
|
||||
} else {
|
||||
local.Timeout = time.Duration(raw.StreamTimeout) * time.Second
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseConfig(t *testing.T) {
|
||||
ssv := "UID=iGAO85zysIyR4c09CyZSLdNhtP/ckcYu7nIPI082AHA=;PublicKey=IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=;" +
|
||||
"ServerName=www.bing.com;NumConn=4;MaskBrowser=chrome;ProxyMethod=shadowsocks;EncryptionMethod=plain"
|
||||
json := ssvToJson(ssv)
|
||||
expected := []byte(`{"UID":"iGAO85zysIyR4c09CyZSLdNhtP/ckcYu7nIPI082AHA=","PublicKey":"IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=","ServerName":"www.bing.com","NumConn":4,"MaskBrowser":"chrome","ProxyMethod":"shadowsocks","EncryptionMethod":"plain"}`)
|
||||
|
||||
t.Run("byte equality", func(t *testing.T) {
|
||||
assert.Equal(t, expected, json)
|
||||
})
|
||||
|
||||
t.Run("struct equality", func(t *testing.T) {
|
||||
tmpConfig, _ := ioutil.TempFile("", "ck_client_config")
|
||||
_, _ = tmpConfig.Write(expected)
|
||||
parsedFromSSV, err := ParseConfig(ssv)
|
||||
assert.NoError(t, err)
|
||||
parsedFromJson, err := ParseConfig(tmpConfig.Name())
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, parsedFromJson, parsedFromSSV)
|
||||
})
|
||||
|
||||
t.Run("empty file", func(t *testing.T) {
|
||||
tmpConfig, _ := ioutil.TempFile("", "ck_client_config")
|
||||
_, err := ParseConfig(tmpConfig.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type Transport interface {
|
||||
Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey [32]byte, err error)
|
||||
net.Conn
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package browsers
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
type ClientHelloFields struct {
|
||||
Random []byte
|
||||
SessionId []byte
|
||||
X25519KeyShare []byte
|
||||
ServerName string
|
||||
}
|
||||
|
||||
func decodeHex(s string) []byte {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Browser represents the signature of a browser at a particular version
|
||||
type Browser interface {
|
||||
// ComposeClientHello produces the ClientHello message (without TLS record layer) as the mimicking browser would
|
||||
ComposeClientHello(ClientHelloFields) []byte
|
||||
}
|
||||
|
||||
// addExtensionRecord, add type, length to extension data
|
||||
func addExtRec(typ []byte, data []byte) []byte {
|
||||
length := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(length, uint16(len(data)))
|
||||
ret := make([]byte, 2+2+len(data))
|
||||
copy(ret[0:2], typ)
|
||||
copy(ret[2:4], length)
|
||||
copy(ret[4:], data)
|
||||
return ret
|
||||
}
|
||||
|
||||
func generateSNI(serverName string) []byte {
|
||||
serverNameListLength := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(serverNameListLength, uint16(len(serverName)+3))
|
||||
serverNameType := []byte{0x00} // host_name
|
||||
serverNameLength := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(serverNameLength, uint16(len(serverName)))
|
||||
ret := make([]byte, 2+1+2+len(serverName))
|
||||
copy(ret[0:2], serverNameListLength)
|
||||
copy(ret[2:3], serverNameType)
|
||||
copy(ret[3:5], serverNameLength)
|
||||
copy(ret[5:], serverName)
|
||||
return ret
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package client
|
||||
package browsers
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
@ -1,14 +1,14 @@
|
||||
package client
|
||||
package browsers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var safariHd = clientHelloFields{
|
||||
random: decodeHex("977ecef48c0fc5640fea4dbd638da89704d6d85ed2e81b8913ae5b27f9a5cc17"),
|
||||
sessionId: decodeHex("c2d5b91e77371bf154363b39194ac77c05617cc6164724d0ba7ded4aa349c6a3"),
|
||||
x25519KeyShare: decodeHex("c99fbe80dda71f6e24d9b798dc3f3f33cef946f0b917fa90154a4b95114fae2a"),
|
||||
serverName: "github.com",
|
||||
var safariHd = ClientHelloFields{
|
||||
Random: decodeHex("977ecef48c0fc5640fea4dbd638da89704d6d85ed2e81b8913ae5b27f9a5cc17"),
|
||||
SessionId: decodeHex("c2d5b91e77371bf154363b39194ac77c05617cc6164724d0ba7ded4aa349c6a3"),
|
||||
X25519KeyShare: decodeHex("c99fbe80dda71f6e24d9b798dc3f3f33cef946f0b917fa90154a4b95114fae2a"),
|
||||
ServerName: "github.com",
|
||||
}
|
||||
|
||||
//func TestSafariJA3(t *testing.T) {
|
@ -0,0 +1,205 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cbeuw/Cloak/internal/common"
|
||||
"github.com/cbeuw/Cloak/libcloak/client/browsers"
|
||||
"github.com/cbeuw/Cloak/libcloak/client/transports"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cbeuw/Cloak/internal/ecdh"
|
||||
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
||||
)
|
||||
|
||||
// Config contains the configuration parameter fields for a Cloak client
|
||||
type Config struct {
|
||||
// Required fields
|
||||
// ServerName is the domain you appear to be visiting
|
||||
// to your Firewall or ISP
|
||||
ServerName string
|
||||
// ProxyMethod is the name of the underlying proxy you wish
|
||||
// to connect to, as determined by your server. The value can
|
||||
// be any string whose UTF-8 ENCODED byte length is no greater than
|
||||
// 12 bytes
|
||||
ProxyMethod string
|
||||
// UID is a 16-byte secret string unique to an authorised user
|
||||
// The same UID can be used by the same user for multiple Cloak connections
|
||||
UID []byte
|
||||
// PublicKey is the 32-byte public Curve25519 ECDH key of your server
|
||||
PublicKey []byte
|
||||
// RemoteHost is the Cloak server's hostname or IP address
|
||||
RemoteHost string
|
||||
|
||||
// Optional Fields
|
||||
// EncryptionMethod is the cryptographic algorithm used to
|
||||
// encrypt data on the wire.
|
||||
// Valid values are `aes-128-gcm`, `aes-256-gcm`, `chacha20-poly1305`, and `plain`
|
||||
// Defaults to `aes-256-gcm`
|
||||
EncryptionMethod string
|
||||
// NumConn is the amount of underlying TLS connections to establish with Cloak server.
|
||||
// Cloak multiplexes any number of incoming connections to a fixed number of underlying TLS connections.
|
||||
// If set to 0, a special singleplex mode is enabled: each incoming connection will correspond to exactly one
|
||||
// TLS connection
|
||||
// Defaults to 4
|
||||
NumConn *int
|
||||
// UDP enables UDP semantics, where packets must fit into one unit of message (below 16000 bytes by default),
|
||||
// and packets can be received out of order. Though reliable delivery is still guaranteed.
|
||||
UDP bool
|
||||
// BrowserSig is the browser signature to be used. Options are `chrome` and `firefox`
|
||||
// Defaults to `chrome`
|
||||
BrowserSig string
|
||||
// Transport is either `direct` or `cdn`. Under `direct`, the client connects to a Cloak server directly.
|
||||
// Under `cdn`, the client connects to a CDN provider such as Amazon Cloudfront, which in turn connects
|
||||
// to a Cloak server.
|
||||
// Defaults to `direct`
|
||||
Transport string
|
||||
// CDNOriginHost is the CDN Origin's (i.e. Cloak server) real hostname or IP address, which is encrypted between
|
||||
// the client and the CDN server, and therefore hidden to ISP or firewalls. This only has effect when Transport
|
||||
// is set to `cdn`
|
||||
// Defaults to RemoteHost
|
||||
CDNOriginHost string
|
||||
// KeepAlive is the interval between TCP KeepAlive packets to be sent over the underlying TLS connections
|
||||
// Defaults to -1, which means no TCP KeepAlive is ever sent
|
||||
KeepAlive int
|
||||
// RemotePort is the port Cloak server is listening to
|
||||
// Defaults to 443
|
||||
RemotePort string
|
||||
// InactivityTimeout is the number of seconds the client keeps the underlying connections to the server
|
||||
// after the last proxy connection is disconnected.
|
||||
// Defaults to 30. Always set to 0 under Singleplex mode (NumConn == 0)
|
||||
InactivityTimeout *int
|
||||
}
|
||||
|
||||
type RemoteConnConfig struct {
|
||||
NumConn int
|
||||
KeepAlive time.Duration
|
||||
RemoteAddr string
|
||||
TransportMaker func() transports.Transport
|
||||
InactivityTimeout time.Duration
|
||||
}
|
||||
|
||||
type AuthInfo = transports.AuthInfo
|
||||
|
||||
func (raw *Config) Process(worldState common.WorldState) (remote RemoteConnConfig, auth AuthInfo, err error) {
|
||||
if raw.ServerName == "" {
|
||||
err = fmt.Errorf("ServerName cannot be empty")
|
||||
return
|
||||
}
|
||||
if raw.ProxyMethod == "" {
|
||||
err = fmt.Errorf("ProxyMethod cannot be empty")
|
||||
return
|
||||
}
|
||||
if len(raw.UID) == 0 {
|
||||
err = fmt.Errorf("UID cannot be empty")
|
||||
return
|
||||
}
|
||||
if len(raw.PublicKey) == 0 {
|
||||
err = fmt.Errorf("PublicKey cannot be empty")
|
||||
return
|
||||
}
|
||||
if raw.RemoteHost == "" {
|
||||
err = fmt.Errorf("RemoteHost cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
auth.UID = raw.UID
|
||||
auth.Unordered = raw.UDP
|
||||
auth.MockDomain = raw.ServerName
|
||||
auth.ProxyMethod = raw.ProxyMethod
|
||||
auth.WorldState = worldState
|
||||
|
||||
// static public key
|
||||
pub, ok := ecdh.Unmarshal(raw.PublicKey)
|
||||
if !ok {
|
||||
err = fmt.Errorf("failed to unmarshal Public key")
|
||||
return
|
||||
}
|
||||
auth.ServerPubKey = pub
|
||||
|
||||
// Encryption method
|
||||
switch strings.ToLower(raw.EncryptionMethod) {
|
||||
case "plain":
|
||||
auth.EncryptionMethod = mux.EncryptionMethodPlain
|
||||
case "aes-gcm", "aes-256-gcm", "":
|
||||
auth.EncryptionMethod = mux.EncryptionMethodAES256GCM
|
||||
case "aes-128-gcm":
|
||||
auth.EncryptionMethod = mux.EncryptionMethodAES128GCM
|
||||
case "chacha20-poly1305":
|
||||
auth.EncryptionMethod = mux.EncryptionMethodChaha20Poly1305
|
||||
default:
|
||||
err = fmt.Errorf("unknown encryption method %v", raw.EncryptionMethod)
|
||||
return
|
||||
}
|
||||
|
||||
var remotePort string
|
||||
if raw.RemotePort == "" {
|
||||
remotePort = "443"
|
||||
} else {
|
||||
remotePort = raw.RemotePort
|
||||
}
|
||||
remote.RemoteAddr = net.JoinHostPort(raw.RemoteHost, remotePort)
|
||||
|
||||
if raw.InactivityTimeout == nil {
|
||||
remote.InactivityTimeout = 30 * time.Second
|
||||
} else {
|
||||
remote.InactivityTimeout = time.Duration(*raw.InactivityTimeout) * time.Second
|
||||
}
|
||||
|
||||
if raw.NumConn == nil {
|
||||
remote.NumConn = 4
|
||||
} else if *raw.NumConn <= 0 {
|
||||
remote.NumConn = 1
|
||||
remote.InactivityTimeout = 0
|
||||
} else {
|
||||
remote.NumConn = *raw.NumConn
|
||||
}
|
||||
|
||||
// Transport and (if TLS mode), browser
|
||||
switch strings.ToLower(raw.Transport) {
|
||||
case "direct", "":
|
||||
var browser browsers.Browser
|
||||
switch strings.ToLower(raw.BrowserSig) {
|
||||
case "chrome", "":
|
||||
browser = &browsers.Chrome{}
|
||||
case "firefox":
|
||||
browser = &browsers.Firefox{}
|
||||
default:
|
||||
err = fmt.Errorf("unknown browser signature %v", raw.BrowserSig)
|
||||
return
|
||||
}
|
||||
remote.TransportMaker = func() transports.Transport {
|
||||
return &transports.DirectTLS{
|
||||
Browser: browser,
|
||||
}
|
||||
}
|
||||
case "cdn":
|
||||
cdnPort := raw.RemotePort
|
||||
var cdnHost string
|
||||
if raw.CDNOriginHost == "" {
|
||||
cdnHost = raw.RemoteHost
|
||||
} else {
|
||||
cdnHost = raw.CDNOriginHost
|
||||
}
|
||||
|
||||
remote.TransportMaker = func() transports.Transport {
|
||||
return &transports.WSOverTLS{
|
||||
CDNHost: cdnHost,
|
||||
CDNPort: cdnPort,
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("unknown transport %v", raw.Transport)
|
||||
return
|
||||
}
|
||||
|
||||
// KeepAlive
|
||||
if raw.KeepAlive <= 0 {
|
||||
remote.KeepAlive = -1
|
||||
} else {
|
||||
remote.KeepAlive = remote.KeepAlive * time.Second
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/cbeuw/Cloak/internal/common"
|
||||
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var baseConfig = Config{
|
||||
ServerName: "www.bing.com",
|
||||
ProxyMethod: "ssh",
|
||||
UID: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf},
|
||||
PublicKey: make([]byte, 32),
|
||||
RemoteHost: "12.34.56.78",
|
||||
}
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
remote, auth, err := baseConfig.Process(common.RealWorldState)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, 4, remote.NumConn)
|
||||
assert.EqualValues(t, mux.EncryptionMethodAES256GCM, auth.EncryptionMethod)
|
||||
assert.EqualValues(t, -1, remote.KeepAlive)
|
||||
assert.False(t, auth.Unordered)
|
||||
}
|
||||
|
||||
func TestValidation(t *testing.T) {
|
||||
_, _, err := baseConfig.Process(common.RealWorldState)
|
||||
assert.NoError(t, err)
|
||||
|
||||
type test struct {
|
||||
fieldToChange string
|
||||
newValue any
|
||||
errPattern string
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
fieldToChange: "ServerName",
|
||||
newValue: "",
|
||||
errPattern: "empty",
|
||||
},
|
||||
{
|
||||
fieldToChange: "UID",
|
||||
newValue: []byte{},
|
||||
errPattern: "empty",
|
||||
},
|
||||
{
|
||||
fieldToChange: "PublicKey",
|
||||
newValue: []byte{0x1},
|
||||
errPattern: "unmarshal",
|
||||
},
|
||||
{
|
||||
fieldToChange: "RemoteHost",
|
||||
newValue: "",
|
||||
errPattern: "empty",
|
||||
},
|
||||
{
|
||||
fieldToChange: "BrowserSig",
|
||||
newValue: "not-a-browser",
|
||||
errPattern: "unknown",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
config := baseConfig
|
||||
reflect.ValueOf(&config).Elem().FieldByName(test.fieldToChange).Set(reflect.ValueOf(test.newValue))
|
||||
_, _, err := config.Process(common.RealWorldState)
|
||||
assert.ErrorContains(t, err, test.errPattern)
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package transports
|
||||
|
||||
import (
|
||||
"github.com/cbeuw/Cloak/internal/common"
|
||||
"github.com/cbeuw/Cloak/libcloak/client/browsers"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
)
|
||||
|
||||
type DirectTLS struct {
|
||||
*common.TLSConn
|
||||
Browser browsers.Browser
|
||||
}
|
||||
|
||||
// Handshake handles the TLS handshake for a given conn and returns the sessionKey
|
||||
// if the server proceed with Cloak authentication
|
||||
func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey [32]byte, err error) {
|
||||
payload, sharedSecret := makeAuthenticationPayload(authInfo)
|
||||
|
||||
// random is marshalled ephemeral pub key 32 bytes
|
||||
// The authentication ciphertext and its tag are then distributed among SessionId and X25519KeyShare
|
||||
fields := browsers.ClientHelloFields{
|
||||
Random: payload.randPubKey[:],
|
||||
SessionId: payload.ciphertextWithTag[0:32],
|
||||
X25519KeyShare: payload.ciphertextWithTag[32:64],
|
||||
ServerName: authInfo.MockDomain,
|
||||
}
|
||||
chOnly := tls.Browser.ComposeClientHello(fields)
|
||||
chWithRecordLayer := common.AddRecordLayer(chOnly, common.Handshake, common.VersionTLS11)
|
||||
_, err = rawConn.Write(chWithRecordLayer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Trace("client hello sent successfully")
|
||||
tls.TLSConn = common.NewTLSConn(rawConn)
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
log.Trace("waiting for ServerHello")
|
||||
_, err = tls.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encrypted := append(buf[6:38], buf[84:116]...)
|
||||
nonce := encrypted[0:12]
|
||||
ciphertextWithTag := encrypted[12:60]
|
||||
sessionKeySlice, err := common.AESGCMDecrypt(nonce, sharedSecret[:], ciphertextWithTag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
copy(sessionKey[:], sessionKeySlice)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
// ChangeCipherSpec and EncryptedCert (in the format of application data)
|
||||
_, err = tls.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return sessionKey, nil
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package client
|
||||
package transports
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
@ -1,4 +1,4 @@
|
||||
package client
|
||||
package transports
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -0,0 +1,23 @@
|
||||
package transports
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"github.com/cbeuw/Cloak/internal/common"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Transport interface {
|
||||
Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey [32]byte, err error)
|
||||
net.Conn
|
||||
}
|
||||
|
||||
type AuthInfo struct {
|
||||
UID []byte
|
||||
SessionId uint32
|
||||
ProxyMethod string
|
||||
EncryptionMethod byte
|
||||
Unordered bool
|
||||
ServerPubKey crypto.PublicKey
|
||||
MockDomain string
|
||||
WorldState common.WorldState
|
||||
}
|
Loading…
Reference in New Issue