diff --git a/seclient/csrand.go b/seclient/csrand.go new file mode 100644 index 0000000..1e87670 --- /dev/null +++ b/seclient/csrand.go @@ -0,0 +1,23 @@ +package seclient + +import ( + crand "crypto/rand" + "math/big" +) + +type secureRandomSource struct{} + +var RandomSource secureRandomSource + +var int63Limit = big.NewInt(0).Lsh(big.NewInt(1), 63) + +func (_ secureRandomSource) Seed(_ int64) { +} + +func (_ secureRandomSource) Int63() int64 { + randNum, err := crand.Int(crand.Reader, int63Limit) + if err != nil { + panic(err) + } + return randNum.Int64() +} diff --git a/seclient/messages.go b/seclient/messages.go new file mode 100644 index 0000000..60ace42 --- /dev/null +++ b/seclient/messages.go @@ -0,0 +1,41 @@ +package seclient + +import ( + "encoding/json" + "errors" + "strconv" +) + +type SEStatusPair struct { + StatusCode int64 + Status string +} + +func (p *SEStatusPair) UnmarshalJSON(b []byte) error { + var tmp map[string]string + err := json.Unmarshal(b, &tmp) + if err != nil { + return err + } + + if len(tmp) != 1 { + return errors.New("ambiguous status") + } + + var strCode, strStatus string + for k, v := range tmp { + strCode = k + strStatus = v + } + + code, err := strconv.ParseInt(strCode, 10, 64) + if err != nil { + return err + } + + *p = SEStatusPair{ + StatusCode: code, + Status: strStatus, + } + return nil +} diff --git a/seclient/randutils.go b/seclient/randutils.go new file mode 100644 index 0000000..4c02bab --- /dev/null +++ b/seclient/randutils.go @@ -0,0 +1,26 @@ +package seclient + +import ( + "io" + "encoding/base64" + "encoding/hex" + "strings" +) + +func randomEmailLocalPart(rng io.Reader) (string, error) { + b := make([]byte, ANON_EMAIL_LOCALPART_BYTES) + _, err := rng.Read(b) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(b), nil +} + +func randomCapitalHexString(rng io.Reader, length int) (string, error) { + b := make([]byte, length) + _, err := rng.Read(b) + if err != nil { + return "", err + } + return strings.ToUpper(hex.EncodeToString(b)), nil +} diff --git a/seclient/seclient.go b/seclient/seclient.go index 0e117c2..d2d63d2 100644 --- a/seclient/seclient.go +++ b/seclient/seclient.go @@ -1,12 +1,13 @@ package seclient import ( - "encoding/hex" - "io" + "context" + "fmt" "math/rand" "net/http" "net/http/cookiejar" - "time" + "net/url" + "strings" dac "github.com/Snawoot/go-http-digest-auth-client" "golang.org/x/net/publicsuffix" @@ -40,12 +41,14 @@ type SESettings struct { DeviceHash string DeviceName string OperatingSystem string + UserAgent string Endpoints SEEndpoints } var DefaultSESettings = SESettings{ ClientVersion: "Stable 74.0.3911.232", ClientType: "se0316", + UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36 OPR/74.0.3911.232", DeviceName: "Opera-Browser-Client", DeviceHash: "", OperatingSystem: "Windows", @@ -79,7 +82,7 @@ func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*S return nil, err } - rng := rand.New(rand.NewSource(time.Now().UnixNano())) + rng := rand.New(RandomSource) device_id, err := randomCapitalHexString(rng, DEVICE_ID_BYTES) if err != nil { @@ -97,11 +100,48 @@ func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*S }, nil } -func randomCapitalHexString(rng io.Reader, length int) (string, error) { - b := make([]byte, length) - _, err := rng.Read(b) +func (c *SEClient) AnonRegister(ctx context.Context) error { + localPart, err := randomEmailLocalPart(c.rng) if err != nil { - return "", err + return err } - return hex.EncodeToString(b), nil + + c.SubscriberEmail = fmt.Sprintf("%s@%s.best.vpn", localPart, c.Settings.ClientType) + + password, err := randomCapitalHexString(c.rng, ANON_PASSWORD_BYTES) + if err != nil { + return err + } + c.SubscriberPassword = password + + return c.Register(ctx) +} + +func (c *SEClient) Register(ctx context.Context) error { + registerInput := url.Values{ + "email": {c.SubscriberEmail}, + "password": {c.SubscriberPassword}, + } + req, err := http.NewRequestWithContext( + ctx, + "POST", + c.Settings.Endpoints.RegisterSubscriber, + strings.NewReader(registerInput.Encode()), + ) + if err != nil { + return err + } + c.populateRequest(req) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Accept", "application/json") + + _, err = c.HttpClient.Do(req) + // TODO: handle response + return nil +} + +func (c *SEClient) populateRequest(req *http.Request) { + req.Header.Set("SE-Client-Version", c.Settings.ClientVersion) + req.Header.Set("SE-Operating-System", c.Settings.OperatingSystem) + req.Header.Set("User-Agent", c.Settings.UserAgent) }