From f30141b388c6732eec5f8c52d03838e16c822322 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 23 Jul 2022 12:13:23 +0100 Subject: [PATCH] Refactor client Config --- internal/cli_client/config.go | 4 +- internal/test/integration_test.go | 12 ++--- libcloak/client/config.go | 57 ++++++++++++------------ libcloak/client/config_test.go | 73 +++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 38 deletions(-) create mode 100644 libcloak/client/config_test.go diff --git a/internal/cli_client/config.go b/internal/cli_client/config.go index 21c3bbf..0967625 100644 --- a/internal/cli_client/config.go +++ b/internal/cli_client/config.go @@ -13,7 +13,7 @@ import ( ) type CLIConfig struct { - client.RawConfig + client.Config // LocalHost is the hostname or IP address to listen for incoming proxy client connections LocalHost string // jsonOptional @@ -111,7 +111,7 @@ type LocalConnConfig struct { } func (raw *CLIConfig) ProcessCLIConfig(worldState common.WorldState) (local LocalConnConfig, remote client.RemoteConnConfig, auth client.AuthInfo, err error) { - remote, auth, err = raw.RawConfig.ProcessRawConfig(worldState) + remote, auth, err = raw.Config.Process(worldState) if err != nil { return } diff --git a/internal/test/integration_test.go b/internal/test/integration_test.go index edb760c..b1d162d 100644 --- a/internal/test/integration_test.go +++ b/internal/test/integration_test.go @@ -79,7 +79,7 @@ var privateKey, _ = base64.StdEncoding.DecodeString("SMWeC6VuZF8S/id65VuFQFlfa7h var four = 4 var zero = 0 -var basicUDPConfig = client.RawConfig{ +var basicUDPConfig = client.Config{ ServerName: "www.example.com", ProxyMethod: "openvpn", EncryptionMethod: "plain", @@ -92,7 +92,7 @@ var basicUDPConfig = client.RawConfig{ RemotePort: "9999", } -var basicTCPConfig = client.RawConfig{ +var basicTCPConfig = client.Config{ ServerName: "www.example.com", ProxyMethod: "shadowsocks", EncryptionMethod: "plain", @@ -106,7 +106,7 @@ var basicTCPConfig = client.RawConfig{ BrowserSig: "firefox", } -var singleplexTCPConfig = client.RawConfig{ +var singleplexTCPConfig = client.Config{ ServerName: "www.example.com", ProxyMethod: "shadowsocks", EncryptionMethod: "plain", @@ -120,8 +120,8 @@ var singleplexTCPConfig = client.RawConfig{ BrowserSig: "chrome", } -func generateClientConfigs(rawConfig client.RawConfig, state common.WorldState) (client.RemoteConnConfig, client.AuthInfo) { - rmt, auth, err := rawConfig.ProcessRawConfig(state) +func generateClientConfigs(rawConfig client.Config, state common.WorldState) (client.RemoteConnConfig, client.AuthInfo) { + rmt, auth, err := rawConfig.Process(state) if err != nil { log.Fatal(err) } @@ -458,7 +458,7 @@ func TestClosingStreamsFromProxy(t *testing.T) { log.SetLevel(log.ErrorLevel) worldState := common.WorldOfTime(time.Unix(10, 0)) - for clientConfigName, clientConfig := range map[string]client.RawConfig{"basic": basicTCPConfig, "singleplex": singleplexTCPConfig} { + for clientConfigName, clientConfig := range map[string]client.Config{"basic": basicTCPConfig, "singleplex": singleplexTCPConfig} { clientConfig := clientConfig clientConfigName := clientConfigName t.Run(clientConfigName, func(t *testing.T) { diff --git a/libcloak/client/config.go b/libcloak/client/config.go index 31eb476..34ad014 100644 --- a/libcloak/client/config.go +++ b/libcloak/client/config.go @@ -19,10 +19,8 @@ import ( mux "github.com/cbeuw/Cloak/internal/multiplex" ) -// RawConfig represents the fields in the config json file -// 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 { +// 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 @@ -38,7 +36,7 @@ type RawConfig struct { // 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 // jsonOptional + RemoteHost string // Optional Fields // EncryptionMethod is the cryptographic algorithm used to @@ -86,59 +84,57 @@ type RemoteConnConfig struct { type AuthInfo = transports.AuthInfo -func (raw *RawConfig) ProcessRawConfig(worldState common.WorldState) (remote RemoteConnConfig, auth AuthInfo, err error) { - nullErr := func(field string) (remote RemoteConnConfig, auth AuthInfo, err error) { - err = fmt.Errorf("%v cannot be empty", field) +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 - if raw.ServerName == "" { - return nullErr("ServerName") - } auth.MockDomain = raw.ServerName - - if raw.ProxyMethod == "" { - return nullErr("ProxyMethod") - } auth.ProxyMethod = raw.ProxyMethod - if len(raw.UID) == 0 { - return nullErr("UID") - } + auth.WorldState = worldState // 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": + 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 - case "": - auth.EncryptionMethod = mux.EncryptionMethodAES256GCM default: err = fmt.Errorf("unknown encryption method %v", raw.EncryptionMethod) return } - if raw.RemoteHost == "" { - return nullErr("RemoteHost") - } var remotePort string if raw.RemotePort == "" { remotePort = "443" @@ -175,8 +171,6 @@ func (raw *RawConfig) ProcessRawConfig(worldState common.WorldState) (remote Rem } } case "direct": - fallthrough - default: var browser browser switch strings.ToLower(raw.BrowserSig) { case "firefox": @@ -193,6 +187,9 @@ func (raw *RawConfig) ProcessRawConfig(worldState common.WorldState) (remote Rem Browser: browser, } } + default: + err = fmt.Errorf("unknown transport %v", raw.Transport) + return } // KeepAlive diff --git a/libcloak/client/config_test.go b/libcloak/client/config_test.go new file mode 100644 index 0000000..a3485e4 --- /dev/null +++ b/libcloak/client/config_test.go @@ -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) + } +}