From a10943db56b7fff4b2ab994370c5cf636ed90fff Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Tue, 21 May 2024 19:39:42 +0100 Subject: [PATCH] wip Signed-off-by: Mark Pashmfouroush --- app/app.go | 24 --- main.go | 23 +++ warp/account.go | 262 +----------------------- warp/api.go | 515 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 540 insertions(+), 284 deletions(-) create mode 100644 warp/api.go diff --git a/app/app.go b/app/app.go index 766a165bf..af21bac3b 100644 --- a/app/app.go +++ b/app/app.go @@ -10,7 +10,6 @@ import ( "github.com/bepass-org/warp-plus/iputils" "github.com/bepass-org/warp-plus/psiphon" - "github.com/bepass-org/warp-plus/warp" "github.com/bepass-org/warp-plus/wiresocks" "github.com/go-ini/ini" ) @@ -58,11 +57,6 @@ func RunWarp(ctx context.Context, l *slog.Logger, opts WarpOptions) error { return errors.New("can't use psiphon and tun at the same time") } - // create identities - if err := createPrimaryAndSecondaryIdentities(l.With("subsystem", "warp/account"), opts); err != nil { - return err - } - // Decide Working Scenario endpoints := []string{opts.Endpoint, opts.Endpoint} @@ -391,21 +385,3 @@ func runWarpWithPsiphon(ctx context.Context, l *slog.Logger, opts WarpOptions, e l.Info("serving proxy", "address", opts.Bind) return nil } - -func createPrimaryAndSecondaryIdentities(l *slog.Logger, opts WarpOptions) error { - // make primary identity - err := warp.LoadOrCreateIdentity(l, path.Join(opts.CacheDir, "primary"), opts.License) - if err != nil { - l.Error("couldn't load primary warp identity") - return err - } - - // make secondary - err = warp.LoadOrCreateIdentity(l, path.Join(opts.CacheDir, "secondary"), opts.License) - if err != nil { - l.Error("couldn't load secondary warp identity") - return err - } - - return nil -} diff --git a/main.go b/main.go index b2bb56413..f75666d37 100644 --- a/main.go +++ b/main.go @@ -183,6 +183,11 @@ func main() { opts.Endpoint = addrPort.String() } + // create identities + if err := createPrimaryAndSecondaryIdentities(l.With("subsystem", "warp/account"), opts); err != nil { + fatal(l, err) + } + ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) go func() { if err := app.RunWarp(ctx, l, opts); err != nil { @@ -193,6 +198,24 @@ func main() { <-ctx.Done() } +func createPrimaryAndSecondaryIdentities(l *slog.Logger, opts app.WarpOptions) error { + // make primary identity + err := warp.LoadOrCreateIdentity(l, path.Join(opts.CacheDir, "primary"), opts.License) + if err != nil { + l.Error("couldn't load primary warp identity") + return err + } + + // make secondary + err = warp.LoadOrCreateIdentity(l, path.Join(opts.CacheDir, "secondary"), opts.License) + if err != nil { + l.Error("couldn't load secondary warp identity") + return err + } + + return nil +} + func fatal(l *slog.Logger, err error) { l.Error(err.Error()) os.Exit(1) diff --git a/warp/account.go b/warp/account.go index 14132c1c3..c098638a9 100644 --- a/warp/account.go +++ b/warp/account.go @@ -2,23 +2,12 @@ package warp import ( "bytes" - "context" "encoding/json" "errors" "fmt" - "io" "log/slog" - "net" - "net/http" "os" "path/filepath" - "time" -) - -const ( - apiVersion = "v0a3596" - apiURL = "https://api.cloudflareclient.com" - regURL = apiURL + "/" + apiVersion + "/reg" ) var ( @@ -26,158 +15,6 @@ var ( profileFile = "wgcf-profile.ini" ) -var ( - defaultHeaders = makeDefaultHeaders() - client = makeClient() -) - -type IdentityAccount struct { - Created string `json:"created"` - Updated string `json:"updated"` - License string `json:"license"` - PremiumData int64 `json:"premium_data"` - WarpPlus bool `json:"warp_plus"` - AccountType string `json:"account_type"` - ReferralRenewalCountdown int64 `json:"referral_renewal_countdown"` - Role string `json:"role"` - ID string `json:"id"` - Quota int64 `json:"quota"` - Usage int64 `json:"usage"` - ReferralCount int64 `json:"referral_count"` - TTL string `json:"ttl"` -} - -type IdentityConfigPeerEndpoint struct { - V4 string `json:"v4"` - V6 string `json:"v6"` - Host string `json:"host"` - Ports []uint16 `json:"ports"` -} - -type IdentityConfigPeer struct { - PublicKey string `json:"public_key"` - Endpoint IdentityConfigPeerEndpoint `json:"endpoint"` -} - -type IdentityConfigInterfaceAddresses struct { - V4 string `json:"v4"` - V6 string `json:"v6"` -} - -type IdentityConfigInterface struct { - Addresses IdentityConfigInterfaceAddresses `json:"addresses"` -} -type IdentityConfigServices struct { - HTTPProxy string `json:"http_proxy"` -} - -type IdentityConfig struct { - Peers []IdentityConfigPeer `json:"peers"` - Interface IdentityConfigInterface `json:"interface"` - Services IdentityConfigServices `json:"services"` - ClientID string `json:"client_id"` -} - -type Identity struct { - PrivateKey string `json:"private_key"` - Key string `json:"key"` - Account IdentityAccount `json:"account"` - Place int64 `json:"place"` - FCMToken string `json:"fcm_token"` - Name string `json:"name"` - TOS string `json:"tos"` - Locale string `json:"locale"` - InstallID string `json:"install_id"` - WarpEnabled bool `json:"warp_enabled"` - Type string `json:"type"` - Model string `json:"model"` - Config IdentityConfig `json:"config"` - Token string `json:"token"` - Enabled bool `json:"enabled"` - ID string `json:"id"` - Created string `json:"created"` - Updated string `json:"updated"` - WaitlistEnabled bool `json:"waitlist_enabled"` -} - -func makeDefaultHeaders() map[string]string { - return map[string]string{ - "Content-Type": "application/json; charset=UTF-8", - "User-Agent": "okhttp/3.12.1", - "CF-Client-Version": "a-6.30-3596", - } -} - -func makeClient() *http.Client { - // Create a custom dialer using the TLS config - plainDialer := &net.Dialer{ - Timeout: 5 * time.Second, - KeepAlive: 5 * time.Second, - } - tlsDialer := Dialer{} - // Create a custom HTTP transport - transport := &http.Transport{ - DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return tlsDialer.TLSDial(plainDialer, network, addr) - }, - } - - // Create a custom HTTP client using the transport - return &http.Client{ - Transport: transport, - // Other client configurations can be added here - } -} - -func doRegister(publicKey string) (Identity, error) { - data := map[string]interface{}{ - "install_id": "", - "fcm_token": "", - "tos": time.Now().Format(time.RFC3339Nano), - "key": publicKey, - "type": "Android", - "model": "PC", - "locale": "en_US", - "warp_enabled": true, - } - - jsonBody, err := json.Marshal(data) - if err != nil { - return Identity{}, err - } - - req, err := http.NewRequest("POST", regURL, bytes.NewBuffer(jsonBody)) - if err != nil { - return Identity{}, err - } - - // Set headers - for k, v := range defaultHeaders { - req.Header.Set(k, v) - } - - // Create HTTP client and execute request - resp, err := client.Do(req) - if err != nil { - return Identity{}, err - } - defer resp.Body.Close() - - // convert response to byte array - responseData, err := io.ReadAll(resp.Body) - if err != nil { - return Identity{}, err - } - - var rspData = Identity{} - err = json.Unmarshal(responseData, &rspData) - if err != nil { - return Identity{}, err - } - - return rspData, nil -} - func saveIdentity(a Identity, path string) error { file, err := os.Create(filepath.Join(path, identityFile)) if err != nil { @@ -194,73 +31,6 @@ func saveIdentity(a Identity, path string) error { return file.Close() } -func updateLicenseKey(accountID, accessToken, license string) (IdentityAccount, error) { - jsonData, err := json.Marshal(map[string]string{"license": license}) - if err != nil { - return IdentityAccount{}, err - } - - url := fmt.Sprintf("%s/%s/account", regURL, accountID) - - req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(jsonData)) - if err != nil { - return IdentityAccount{}, err - } - - headers := defaultHeaders - headers["Authorization"] = "Bearer " + accessToken - for k, v := range headers { - req.Header.Set(k, v) - } - - resp, err := client.Do(req) - if err != nil { - return IdentityAccount{}, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - s, err := io.ReadAll(resp.Body) - if err != nil { - return IdentityAccount{}, err - } - - return IdentityAccount{}, fmt.Errorf("activation error, status %d %s", resp.StatusCode, string(s)) - } - - req, err = http.NewRequest("GET", url, nil) - if err != nil { - return IdentityAccount{}, err - } - - for k, v := range headers { - req.Header.Set(k, v) - } - - resp1, err := client.Do(req) - if err != nil { - return IdentityAccount{}, err - } - defer resp1.Body.Close() - - if resp1.StatusCode != http.StatusOK { - s, err := io.ReadAll(resp1.Body) - if err != nil { - return IdentityAccount{}, err - } - - return IdentityAccount{}, fmt.Errorf("activation error, status %d %s", resp1.StatusCode, string(s)) - } - - var activationResp1 = IdentityAccount{} - err = json.NewDecoder(resp1.Body).Decode(&activationResp1) - if err != nil { - return IdentityAccount{}, err - } - - return activationResp1, nil -} - func createConf(i Identity, path string) error { var buffer bytes.Buffer @@ -360,14 +130,14 @@ func CreateIdentity(l *slog.Logger, path, license string) (Identity, error) { privateKey, publicKey := priv.String(), priv.PublicKey().String() l.Info("creating new identity") - i, err := doRegister(publicKey) + i, err := Register(publicKey) if err != nil { return Identity{}, err } if license != "" { l.Info("updating account license key") - ac, err := updateLicenseKey(i.ID, i.Token, license) + ac, err := UpdateAccount(i.Token, i.ID, license) if err != nil { return Identity{}, err } @@ -383,31 +153,3 @@ func CreateIdentity(l *slog.Logger, path, license string) (Identity, error) { return i, nil } - -func RemoveDevice(l *slog.Logger, accountID, accessToken string) error { - url := fmt.Sprintf("%s/%s", regURL, accountID) - req, err := http.NewRequest("DELETE", url, nil) - if err != nil { - return err - } - - headers := defaultHeaders - headers["Authorization"] = "Bearer " + accessToken - for k, v := range headers { - req.Header.Set(k, v) - } - - // Create HTTP client and execute request - resp, err := client.Do(req) - if err != nil { - l.Info("sending request to remote server", err) - return err - } - defer resp.Body.Close() - - if resp.StatusCode != 204 { - return fmt.Errorf("error in deleting account %d %s", resp.StatusCode, resp.Status) - } - - return nil -} diff --git a/warp/api.go b/warp/api.go new file mode 100644 index 000000000..0c7be2394 --- /dev/null +++ b/warp/api.go @@ -0,0 +1,515 @@ +package warp + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "time" +) + +const ( + apiBase string = "https://api.cloudflareclient.com/v0a4005" +) + +var client = makeClient() + +func defaultHeaders() map[string]string { + return map[string]string{ + "Content-Type": "application/json; charset=UTF-8", + "User-Agent": "okhttp/3.12.1", + "CF-Client-Version": "a-6.30-3596", + } +} + +func makeClient() *http.Client { + // Create a custom dialer using the TLS config + plainDialer := &net.Dialer{ + Timeout: 5 * time.Second, + KeepAlive: 5 * time.Second, + } + tlsDialer := Dialer{} + // Create a custom HTTP transport + transport := &http.Transport{ + DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return tlsDialer.TLSDial(plainDialer, network, addr) + }, + } + + // Create a custom HTTP client using the transport + return &http.Client{ + Transport: transport, + // Other client configurations can be added here + } +} + +type IdentityAccount struct { + Created string `json:"created"` + Updated string `json:"updated"` + License string `json:"license"` + PremiumData int64 `json:"premium_data"` + WarpPlus bool `json:"warp_plus"` + AccountType string `json:"account_type"` + ReferralRenewalCountdown int64 `json:"referral_renewal_countdown"` + Role string `json:"role"` + ID string `json:"id"` + Quota int64 `json:"quota"` + Usage int64 `json:"usage"` + ReferralCount int64 `json:"referral_count"` + TTL string `json:"ttl"` +} + +type IdentityConfigPeerEndpoint struct { + V4 string `json:"v4"` + V6 string `json:"v6"` + Host string `json:"host"` + Ports []uint16 `json:"ports"` +} + +type IdentityConfigPeer struct { + PublicKey string `json:"public_key"` + Endpoint IdentityConfigPeerEndpoint `json:"endpoint"` +} + +type IdentityConfigInterfaceAddresses struct { + V4 string `json:"v4"` + V6 string `json:"v6"` +} + +type IdentityConfigInterface struct { + Addresses IdentityConfigInterfaceAddresses `json:"addresses"` +} +type IdentityConfigServices struct { + HTTPProxy string `json:"http_proxy"` +} + +type IdentityConfig struct { + Peers []IdentityConfigPeer `json:"peers"` + Interface IdentityConfigInterface `json:"interface"` + Services IdentityConfigServices `json:"services"` + ClientID string `json:"client_id"` +} + +type Identity struct { + PrivateKey string `json:"private_key"` + Key string `json:"key"` + Account IdentityAccount `json:"account"` + Place int64 `json:"place"` + FCMToken string `json:"fcm_token"` + Name string `json:"name"` + TOS string `json:"tos"` + Locale string `json:"locale"` + InstallID string `json:"install_id"` + WarpEnabled bool `json:"warp_enabled"` + Type string `json:"type"` + Model string `json:"model"` + Config IdentityConfig `json:"config"` + Token string `json:"token"` + Enabled bool `json:"enabled"` + ID string `json:"id"` + Created string `json:"created"` + Updated string `json:"updated"` + WaitlistEnabled bool `json:"waitlist_enabled"` +} + +type IdentityDevice struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Model string `json:"model"` + Created string `json:"created"` + Activated string `json:"updated"` + Active bool `json:"active"` + Role string `json:"role"` +} + +type License struct { + License string `json:"license"` +} + +func GetAccount(authToken, deviceID string) (IdentityAccount, error) { + reqUrl := fmt.Sprintf("%s/reg/%s/account", apiBase, deviceID) + method := "GET" + + req, err := http.NewRequest(method, reqUrl, nil) + if err != nil { + return IdentityAccount{}, err + } + + // Set headers + for k, v := range defaultHeaders() { + req.Header.Set(k, v) + } + req.Header.Set("Authorization", "Bearer "+authToken) + + // Create HTTP client and execute request + resp, err := client.Do(req) + if err != nil { + return IdentityAccount{}, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return IdentityAccount{}, fmt.Errorf("API request failed with status: %s", resp.Status) + } + + // convert response to byte array + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return IdentityAccount{}, err + } + + var rspData = IdentityAccount{} + if err := json.Unmarshal(responseData, &rspData); err != nil { + return IdentityAccount{}, err + } + + return rspData, nil +} + +func GetBoundDevices(authToken, deviceID string) ([]IdentityDevice, error) { + reqUrl := fmt.Sprintf("%s/reg/%s/account/devices", apiBase, deviceID) + method := "GET" + + req, err := http.NewRequest(method, reqUrl, nil) + if err != nil { + return nil, err + } + + // Set headers + for k, v := range defaultHeaders() { + req.Header.Set(k, v) + } + req.Header.Set("Authorization", "Bearer "+authToken) + + // Create HTTP client and execute request + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("API request failed with status: %s", resp.Status) + } + + // convert response to byte array + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var rspData = []IdentityDevice{} + if err := json.Unmarshal(responseData, &rspData); err != nil { + return nil, err + } + + return rspData, nil +} + +func GetSourceDevice(authToken, deviceID string) (Identity, error) { + reqUrl := fmt.Sprintf("%s/reg/%s", apiBase, deviceID) + method := "GET" + + req, err := http.NewRequest(method, reqUrl, nil) + if err != nil { + return Identity{}, err + } + + // Set headers + for k, v := range defaultHeaders() { + req.Header.Set(k, v) + } + req.Header.Set("Authorization", "Bearer "+authToken) + + // Create HTTP client and execute request + resp, err := client.Do(req) + if err != nil { + return Identity{}, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return Identity{}, fmt.Errorf("API request failed with status: %s", resp.Status) + } + + // convert response to byte array + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return Identity{}, err + } + + var rspData = Identity{} + if err := json.Unmarshal(responseData, &rspData); err != nil { + return Identity{}, err + } + + return rspData, nil +} + +func Register(publicKey string) (Identity, error) { + reqUrl := fmt.Sprintf("%s/reg", apiBase) + method := "POST" + + data := map[string]interface{}{ + "install_id": "", + "fcm_token": "", + "tos": time.Now().Format(time.RFC3339Nano), + "key": publicKey, + "type": "Android", + "model": "PC", + "locale": "en_US", + "warp_enabled": true, + } + + jsonBody, err := json.Marshal(data) + if err != nil { + return Identity{}, err + } + + req, err := http.NewRequest(method, reqUrl, bytes.NewBuffer(jsonBody)) + if err != nil { + return Identity{}, err + } + + // Set headers + for k, v := range defaultHeaders() { + req.Header.Set(k, v) + } + + // Create HTTP client and execute request + resp, err := client.Do(req) + if err != nil { + return Identity{}, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return Identity{}, fmt.Errorf("API request failed with status: %s", resp.Status) + } + + // convert response to byte array + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return Identity{}, err + } + + var rspData = Identity{} + if err := json.Unmarshal(responseData, &rspData); err != nil { + return Identity{}, err + } + + return rspData, nil +} + +func ResetAccountLicense(authToken, deviceID string) (License, error) { + reqUrl := fmt.Sprintf("%s/reg/%s/account/license", apiBase, deviceID) + method := "POST" + + req, err := http.NewRequest(method, reqUrl, nil) + if err != nil { + return License{}, err + } + + // Set headers + for k, v := range defaultHeaders() { + req.Header.Set(k, v) + } + req.Header.Set("Authorization", "Bearer "+authToken) + + // Create HTTP client and execute request + resp, err := client.Do(req) + if err != nil { + return License{}, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return License{}, fmt.Errorf("API request failed with response: %s", resp.Status) + } + + // convert response to byte array + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return License{}, err + } + + var rspData = License{} + if err := json.Unmarshal(responseData, &rspData); err != nil { + return License{}, err + } + + return rspData, nil +} + +func UpdateAccount(authToken, deviceID, license string) (IdentityAccount, error) { + reqUrl := fmt.Sprintf("%s/reg/%s/account", apiBase, deviceID) + method := "PUT" + + jsonBody, err := json.Marshal(map[string]interface{}{"license": license}) + if err != nil { + return IdentityAccount{}, err + } + + req, err := http.NewRequest(method, reqUrl, bytes.NewBuffer(jsonBody)) + if err != nil { + return IdentityAccount{}, err + } + + // Set headers + for k, v := range defaultHeaders() { + req.Header.Set(k, v) + } + req.Header.Set("Authorization", "Bearer "+authToken) + + // Create HTTP client and execute request + resp, err := client.Do(req) + if err != nil { + return IdentityAccount{}, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return IdentityAccount{}, fmt.Errorf("API request failed with status: %s", resp.Status) + } + + // convert response to byte array + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return IdentityAccount{}, err + } + + var rspData = IdentityAccount{} + if err := json.Unmarshal(responseData, &rspData); err != nil { + return IdentityAccount{}, err + } + + return rspData, nil +} + +func UpdateBoundDevice(authToken, deviceID, otherDeviceID, name string, active bool) (IdentityDevice, error) { + reqUrl := fmt.Sprintf("%s/reg/%s/account/reg/%s", apiBase, deviceID, otherDeviceID) + method := "PATCH" + + data := map[string]interface{}{ + "active": active, + "name": name, + } + + jsonBody, err := json.Marshal(data) + if err != nil { + return IdentityDevice{}, err + } + + req, err := http.NewRequest(method, reqUrl, bytes.NewBuffer(jsonBody)) + if err != nil { + return IdentityDevice{}, err + } + + // Set headers + for k, v := range defaultHeaders() { + req.Header.Set(k, v) + } + req.Header.Set("Authorization", "Bearer "+authToken) + + // Create HTTP client and execute request + resp, err := client.Do(req) + if err != nil { + return IdentityDevice{}, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return IdentityDevice{}, fmt.Errorf("API request failed with status: %s", resp.Status) + } + + // convert response to byte array + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return IdentityDevice{}, err + } + + var rspData = IdentityDevice{} + if err := json.Unmarshal(responseData, &rspData); err != nil { + return IdentityDevice{}, err + } + + return rspData, nil +} + +func UpdateSourceDevice(authToken, deviceID, publicKey string) (Identity, error) { + reqUrl := fmt.Sprintf("%s/reg/%s", apiBase, deviceID) + method := "PATCH" + + jsonBody, err := json.Marshal(map[string]interface{}{"key": publicKey}) + if err != nil { + return Identity{}, err + } + + req, err := http.NewRequest(method, reqUrl, bytes.NewBuffer(jsonBody)) + if err != nil { + return Identity{}, err + } + + // Set headers + for k, v := range defaultHeaders() { + req.Header.Set(k, v) + } + req.Header.Set("Authorization", "Bearer "+authToken) + + // Create HTTP client and execute request + resp, err := client.Do(req) + if err != nil { + return Identity{}, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return Identity{}, fmt.Errorf("API request failed with status: %s", resp.Status) + } + + // convert response to byte array + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return Identity{}, err + } + + var rspData = Identity{} + if err := json.Unmarshal(responseData, &rspData); err != nil { + return Identity{}, err + } + + return rspData, nil +} + +func DeleteDevice(authToken, deviceID string) error { + reqUrl := fmt.Sprintf("%s/reg/%s", apiBase, deviceID) + method := "DELETE" + + req, err := http.NewRequest(method, reqUrl, nil) + if err != nil { + return err + } + + // Set headers + for k, v := range defaultHeaders() { + req.Header.Set(k, v) + } + req.Header.Set("Authorization", "Bearer "+authToken) + + // Create HTTP client and execute request + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("API request failed with status: %s", resp.Status) + } + + return nil +}