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 }