github.com/geph-official/geph2@v0.22.6-0.20210211030601-f527cb59b0df/libs/bdclient/client.go (about)

     1  package bdclient
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"crypto/rsa"
     7  	"crypto/x509"
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"net/url"
    15  	"sync/atomic"
    16  	"time"
    17  
    18  	"github.com/cryptoballot/rsablind"
    19  )
    20  
    21  // Multiclient wraps around mutliple clients, R/R-ing between them until something works.
    22  type Multiclient struct {
    23  	index   uint64
    24  	clients []*Client
    25  }
    26  
    27  // NewMulticlient creates a Multiclient from multiple clients
    28  func NewMulticlient(clients []*Client) *Multiclient {
    29  	return &Multiclient{0, clients}
    30  }
    31  
    32  func (mc *Multiclient) Do(f func(client *Client) error) error {
    33  	index := int(atomic.LoadUint64(&mc.index))
    34  	client := mc.clients[index%len(mc.clients)]
    35  	err := f(client)
    36  	if err != nil {
    37  		atomic.AddUint64(&mc.index, 1)
    38  	}
    39  	return err
    40  }
    41  
    42  // Client represents a binder client.
    43  type Client struct {
    44  	hclient     *http.Client
    45  	frontDomain string
    46  	realDomain  string
    47  	useragent   string
    48  }
    49  
    50  // NewClient creates a new domain-fronting binder client with the given frontDomain and realDomain. frontDomain should start with `https://`.
    51  func NewClient(frontDomain, realDomain, useragent string) *Client {
    52  	return &Client{
    53  		hclient: &http.Client{
    54  			Transport: &http.Transport{
    55  				Proxy:           nil,
    56  				IdleConnTimeout: time.Second * 3,
    57  			},
    58  			Timeout: time.Second * 10,
    59  		},
    60  		frontDomain: frontDomain,
    61  		realDomain:  realDomain,
    62  		useragent:   useragent,
    63  	}
    64  }
    65  
    66  func badStatusCode(s int) error {
    67  	return fmt.Errorf("unexpected status code %v", s)
    68  }
    69  
    70  // ClientInfo describes user IP and country.
    71  type ClientInfo struct {
    72  	Address string
    73  	Country string
    74  }
    75  
    76  // GetClientInfo checks user info
    77  func (cl *Client) GetClientInfo() (ui ClientInfo, err error) {
    78  	req, _ := http.NewRequest("GET", fmt.Sprintf("%v/client-info", cl.frontDomain), bytes.NewReader(nil))
    79  	req.Host = cl.realDomain
    80  	req.Header.Set("user-agent", cl.useragent)
    81  	resp, err := cl.hclient.Do(req)
    82  	if err != nil {
    83  		return
    84  	}
    85  	defer resp.Body.Close()
    86  	if resp.StatusCode != 200 {
    87  		err = badStatusCode(resp.StatusCode)
    88  	}
    89  	err = json.NewDecoder(resp.Body).Decode(&ui)
    90  	return
    91  }
    92  
    93  // GetWarpfronts gets warpfront bridges
    94  func (cl *Client) GetWarpfronts() (host2front map[string]string, err error) {
    95  	req, _ := http.NewRequest("GET", fmt.Sprintf("%v/warpfronts", cl.frontDomain), bytes.NewReader(nil))
    96  	req.Host = cl.realDomain
    97  	req.Header.Set("user-agent", cl.useragent)
    98  	resp, err := cl.hclient.Do(req)
    99  	if err != nil {
   100  		return
   101  	}
   102  	defer resp.Body.Close()
   103  	if resp.StatusCode != 200 {
   104  		err = badStatusCode(resp.StatusCode)
   105  	}
   106  	err = json.NewDecoder(resp.Body).Decode(&host2front)
   107  	return
   108  }
   109  
   110  // AddBridge uploads some bridge info.
   111  func (cl *Client) AddBridge(secret string, cookie []byte, host string, allocGroup string) (err error) {
   112  	req, _ := http.NewRequest("GET", fmt.Sprintf("%v/add-bridge?cookie=%x&host=%v&allocGroup=%v", cl.frontDomain, cookie, host, allocGroup), bytes.NewReader(nil))
   113  	req.Header.Set("user-agent", cl.useragent)
   114  	req.Host = cl.realDomain
   115  	req.SetBasicAuth("user", secret)
   116  	resp, err := cl.hclient.Do(req)
   117  	if err != nil {
   118  		return
   119  	}
   120  	defer resp.Body.Close()
   121  	if resp.StatusCode != 200 {
   122  		err = badStatusCode(resp.StatusCode)
   123  	}
   124  	return
   125  }
   126  
   127  // GetTicketKey obtains the remote ticketing key.
   128  // TODO caching, gossip?
   129  func (cl *Client) GetTicketKey(tier string) (tkey *rsa.PublicKey, err error) {
   130  	req, _ := http.NewRequest("GET", fmt.Sprintf("%v/get-ticket-key?tier=%v", cl.frontDomain, tier), bytes.NewReader(nil))
   131  	req.Host = cl.realDomain
   132  	req.Header.Set("user-agent", cl.useragent)
   133  	resp, err := cl.hclient.Do(req)
   134  	if err != nil {
   135  		return
   136  	}
   137  	defer resp.Body.Close()
   138  	if resp.StatusCode != 200 {
   139  		err = badStatusCode(resp.StatusCode)
   140  		return
   141  	}
   142  	b64key, err := ioutil.ReadAll(resp.Body)
   143  	if err != nil {
   144  		return
   145  	}
   146  	btskey, err := base64.RawStdEncoding.DecodeString(string(b64key))
   147  	if err != nil {
   148  		return
   149  	}
   150  	tkey, err = x509.ParsePKCS1PublicKey(btskey)
   151  	return
   152  }
   153  
   154  // GetTier gets the tier of a user.
   155  func (cl *Client) GetTier(username, password string) (tier string, err error) {
   156  	v := url.Values{}
   157  	v.Set("user", username)
   158  	v.Set("pwd", password)
   159  	req, _ := http.NewRequest("GET", fmt.Sprintf("%v/get-tier?%v", cl.frontDomain, v.Encode()), bytes.NewReader(nil))
   160  	req.Host = cl.realDomain
   161  	req.Header.Set("user-agent", cl.useragent)
   162  	resp, err := cl.hclient.Do(req)
   163  	if err != nil {
   164  		return
   165  	}
   166  	defer resp.Body.Close()
   167  	b, err := ioutil.ReadAll(resp.Body)
   168  	if err != nil {
   169  		return
   170  	}
   171  	tier = string(b)
   172  	return
   173  }
   174  
   175  // TicketResp is the response for ticket getting
   176  type TicketResp struct {
   177  	Ticket       []byte
   178  	Tier         string
   179  	PaidExpiry   time.Time
   180  	Transactions []PaymentTx
   181  }
   182  
   183  // PaymentTx is a payment in USD cents.
   184  type PaymentTx struct {
   185  	Date   time.Time
   186  	Amount int
   187  }
   188  
   189  // ErrBadAuth indicates incorrect credentials
   190  var ErrBadAuth = errors.New("access denied")
   191  
   192  // GetTicket obtains an authentication ticket.
   193  func (cl *Client) GetTicket(username, password string) (ubmsg, ubsig []byte, details TicketResp, err error) {
   194  	// First get ticket key
   195  	fkey, err := cl.GetTicketKey("free")
   196  	if err != nil {
   197  		return
   198  	}
   199  	pkey, err := cl.GetTicketKey("paid")
   200  	if err != nil {
   201  		return
   202  	}
   203  	// Pick
   204  	tier, err := cl.GetTier(username, password)
   205  	if err != nil {
   206  		return
   207  	}
   208  	var tkey *rsa.PublicKey
   209  	if tier == "free" {
   210  		tkey = fkey
   211  	} else {
   212  		tkey = pkey
   213  	}
   214  	// Create our ticketing request
   215  	unblinded := make([]byte, 1536/8)
   216  	rand.Read(unblinded)
   217  	blinded, unblinder, err := rsablind.Blind(tkey, unblinded)
   218  	if err != nil {
   219  		panic(err)
   220  	}
   221  	// Obtain the ticket
   222  	v := url.Values{}
   223  	v.Set("user", username)
   224  	v.Set("pwd", password)
   225  	v.Set("blinded", base64.RawStdEncoding.EncodeToString(blinded))
   226  	req, _ := http.NewRequest("GET", fmt.Sprintf("%v/get-ticket?%v", cl.frontDomain, v.Encode()), bytes.NewReader(nil))
   227  	req.Host = cl.realDomain
   228  	req.Header.Set("user-agent", cl.useragent)
   229  	resp, err := cl.hclient.Do(req)
   230  	if err != nil {
   231  		return
   232  	}
   233  	defer resp.Body.Close()
   234  	if resp.StatusCode == http.StatusForbidden {
   235  		err = ErrBadAuth
   236  		return
   237  	}
   238  	var respDec TicketResp
   239  	err = json.NewDecoder(resp.Body).Decode(&respDec)
   240  	if err != nil {
   241  		return
   242  	}
   243  	// unblind the ticket
   244  	ubsig = rsablind.Unblind(tkey, respDec.Ticket, unblinder)
   245  	ubmsg = unblinded
   246  	details = respDec
   247  	return
   248  }
   249  
   250  // BridgeInfo describes a bridge
   251  type BridgeInfo struct {
   252  	Cookie   []byte
   253  	Host     string
   254  	LastSeen time.Time
   255  }
   256  
   257  // GetBridges obtains a set of bridges.
   258  func (cl *Client) GetBridges(ubmsg, ubsig []byte) (bridges []BridgeInfo, err error) {
   259  	req, _ := http.NewRequest("GET", fmt.Sprintf("%v/get-bridges", cl.frontDomain), bytes.NewReader(nil))
   260  	req.Host = cl.realDomain
   261  	req.Header.Set("user-agent", cl.useragent)
   262  	resp, err := cl.hclient.Do(req)
   263  	if err != nil {
   264  		return
   265  	}
   266  	defer resp.Body.Close()
   267  	if resp.StatusCode != 200 {
   268  		err = badStatusCode(resp.StatusCode)
   269  		return
   270  	}
   271  	err = json.NewDecoder(resp.Body).Decode(&bridges)
   272  	return
   273  }
   274  
   275  // GetEphBridges obtains a set of ephemeral e2e bridges.
   276  func (cl *Client) GetEphBridges(ubmsg []byte, ubsig []byte, exit string) (bridges []BridgeInfo, err error) {
   277  	req, _ := http.NewRequest("GET", fmt.Sprintf("%v/get-bridges?type=ephemeral&exit=%v", cl.frontDomain, exit), bytes.NewReader(nil))
   278  	req.Host = cl.realDomain
   279  	req.Header.Set("user-agent", cl.useragent)
   280  	resp, err := cl.hclient.Do(req)
   281  	if err != nil {
   282  		return
   283  	}
   284  	defer resp.Body.Close()
   285  	if resp.StatusCode != 200 {
   286  		err = badStatusCode(resp.StatusCode)
   287  		return
   288  	}
   289  	err = json.NewDecoder(resp.Body).Decode(&bridges)
   290  	return
   291  }
   292  
   293  // RedeemTicket redeems a ticket.
   294  func (cl *Client) RedeemTicket(tier string, ubmsg, ubsig []byte) (err error) {
   295  	// Obtain the ticket
   296  	v := url.Values{}
   297  	v.Set("ubmsg", base64.RawStdEncoding.EncodeToString(ubmsg))
   298  	v.Set("ubsig", base64.RawStdEncoding.EncodeToString(ubsig))
   299  	v.Set("tier", tier)
   300  	req, _ := http.NewRequest("GET", fmt.Sprintf("%v/redeem-ticket?%v", cl.frontDomain, v.Encode()), bytes.NewReader(nil))
   301  	req.Host = cl.realDomain
   302  	resp, err := cl.hclient.Do(req)
   303  	req.Header.Set("user-agent", cl.useragent)
   304  	if err != nil {
   305  		return
   306  	}
   307  	defer resp.Body.Close()
   308  	if resp.StatusCode != 200 {
   309  		err = badStatusCode(resp.StatusCode)
   310  		return
   311  	}
   312  	return
   313  }