diff --git a/gpapi/googleplay.py b/gpapi/googleplay.py index 60c6ca6..6d94ea3 100644 --- a/gpapi/googleplay.py +++ b/gpapi/googleplay.py @@ -10,6 +10,10 @@ from cryptography.hazmat.primitives.asymmetric import padding import requests +import ssl + +from urllib3.poolmanager import PoolManager +from urllib3.util import ssl_ from . import googleplay_pb2, config, utils @@ -39,6 +43,27 @@ CONTENT_TYPE_PROTO = "application/x-protobuf" +class SSLContext(ssl.SSLContext): + def set_alpn_protocols(self, protocols): + """ + ALPN headers cause Google to return 403 Bad Authentication. + """ + pass + +class AuthHTTPAdapter(requests.adapters.HTTPAdapter): + def init_poolmanager(self, *args, **kwargs): + """ + Secure settings from ssl.create_default_context(), but without + ssl.OP_NO_TICKET which causes Google to return 403 Bad + Authentication. + """ + context = SSLContext() + context.set_ciphers(ssl_.DEFAULT_CIPHERS) + context.verify_mode = ssl.CERT_REQUIRED + context.options &= ~ssl_.OP_NO_TICKET + self.poolmanager = PoolManager(*args, ssl_context=context, **kwargs) + + class LoginError(Exception): def __init__(self, value): self.value = value @@ -79,6 +104,8 @@ def __init__(self, locale="en_US", timezone="UTC", device_codename="bacon", self.deviceBuilder = config.DeviceBuilder(device_codename) self.setLocale(locale) self.setTimezone(timezone) + self.session = requests.session() + self.session.mount('https://', AuthHTTPAdapter()) def setLocale(self, locale): self.deviceBuilder.setLocale(locale) @@ -157,7 +184,7 @@ def checkin(self, email, ac2dmToken): request = self.deviceBuilder.getAndroidCheckinRequest() stringRequest = request.SerializeToString() - res = requests.post(CHECKIN_URL, data=stringRequest, + res = self.session.post(CHECKIN_URL, data=stringRequest, headers=headers, verify=ssl_verify, proxies=self.proxies_config) response = googleplay_pb2.AndroidCheckinResponse() @@ -170,7 +197,7 @@ def checkin(self, email, ac2dmToken): request.accountCookie.append("[" + email + "]") request.accountCookie.append(ac2dmToken) stringRequest = request.SerializeToString() - requests.post(CHECKIN_URL, + self.session.post(CHECKIN_URL, data=stringRequest, headers=headers, verify=ssl_verify, @@ -186,7 +213,7 @@ def uploadDeviceConfig(self): upload.deviceConfiguration.CopyFrom(self.deviceBuilder.getDeviceConfig()) headers = self.getHeaders(upload_fields=True) stringRequest = upload.SerializeToString() - response = requests.post(UPLOAD_URL, data=stringRequest, + response = self.session.post(UPLOAD_URL, data=stringRequest, headers=headers, verify=ssl_verify, timeout=60, @@ -219,7 +246,7 @@ def login(self, email=None, password=None, gsfId=None, authSubToken=None): params['callerPkg'] = 'com.google.android.gms' headers = self.deviceBuilder.getAuthHeaders(self.gsfId) headers['app'] = 'com.google.android.gsm' - response = requests.post(AUTH_URL, data=params, verify=ssl_verify, + response = self.session.post(AUTH_URL, data=params, verify=ssl_verify, proxies=self.proxies_config) data = response.text.split() params = {} @@ -257,7 +284,7 @@ def getAuthSubToken(self, email, passwd): requestParams['app'] = 'com.android.vending' headers = self.deviceBuilder.getAuthHeaders(self.gsfId) headers['app'] = 'com.android.vending' - response = requests.post(AUTH_URL, + response = self.session.post(AUTH_URL, data=requestParams, verify=ssl_verify, headers=headers, @@ -290,7 +317,7 @@ def getSecondRoundToken(self, first_token, params): params.pop('EncryptedPasswd') headers = self.deviceBuilder.getAuthHeaders(self.gsfId) headers['app'] = 'com.android.vending' - response = requests.post(AUTH_URL, + response = self.session.post(AUTH_URL, data=params, headers=headers, verify=ssl_verify, @@ -316,7 +343,7 @@ def executeRequestApi2(self, path, post_data=None, content_type=CONTENT_TYPE_URL headers["Content-Type"] = content_type if post_data is not None: - response = requests.post(path, + response = self.session.post(path, data=str(post_data), headers=headers, params=params, @@ -324,7 +351,7 @@ def executeRequestApi2(self, path, post_data=None, content_type=CONTENT_TYPE_URL timeout=60, proxies=self.proxies_config) else: - response = requests.get(path, + response = self.session.get(path, headers=headers, params=params, verify=ssl_verify, @@ -497,7 +524,7 @@ def reviews(self, packageName, filterByDevice=False, sort=2, def _deliver_data(self, url, cookies): headers = self.getHeaders() - response = requests.get(url, headers=headers, + response = self.session.get(url, headers=headers, cookies=cookies, verify=ssl_verify, stream=True, timeout=60, proxies=self.proxies_config) @@ -541,7 +568,7 @@ def delivery(self, packageName, versionCode=None, offerType=1, headers = self.getHeaders() if downloadToken is not None: params['dtok'] = downloadToken - response = requests.get(DELIVERY_URL, headers=headers, + response = self.session.get(DELIVERY_URL, headers=headers, params=params, verify=ssl_verify, timeout=60, proxies=self.proxies_config) @@ -613,8 +640,7 @@ def download(self, packageName, versionCode=None, offerType=1, expansion_files=F params = {'ot': str(offerType), 'doc': packageName, 'vc': str(versionCode)} - self.log(packageName) - response = requests.post(PURCHASE_URL, headers=headers, + response = self.session.post(PURCHASE_URL, headers=headers, params=params, verify=ssl_verify, timeout=60, proxies=self.proxies_config) @@ -634,7 +660,7 @@ def log(self, docid): log_request.timestamp = timestamp string_request = log_request.SerializeToString() - response = requests.post(LOG_URL, + response = self.session.post(LOG_URL, data=string_request, headers=self.getHeaders(), verify=ssl_verify, @@ -645,7 +671,7 @@ def log(self, docid): raise RequestError(response.commands.displayErrorMessage) def toc(self): - response = requests.get(TOC_URL, + response = self.session.get(TOC_URL, headers=self.getHeaders(), verify=ssl_verify, timeout=60, @@ -664,7 +690,7 @@ def acceptTos(self, tosToken): "tost": tosToken, "toscme": "false" } - response = requests.get(ACCEPT_TOS_URL, + response = self.session.get(ACCEPT_TOS_URL, headers=self.getHeaders(), params=params, verify=ssl_verify,