Implementazione open-source del protocollo di Strong Customer Authentication di Poste Italiane, (https://posteid.poste.it), lato client.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

126 lines
5.8 KiB

import logging
from urllib.parse import urljoin
from uuid import uuid4
import requests
from .appdata import AppData
from .jwe_handler import JWEHandler
from .utils import sha256_base64, RequestFailure
logger = logging.getLogger(__name__)
class XKey:
REGISTRY_URL = ("https://appregistry-posteid.mobile.poste.it"
"/jod-app-registry/")
APP_NAME = "app-posteid-v3"
ACTIVITY_ID = "C6050AC80E8B5288A01237"
DEVICE_SPECS = ("Android", "11",
"sdk_gphone_x86_64_arm64", "4.5.204",
"true")
def __init__(self, profile_name):
self.appdata = AppData(profile_name)
self.s = requests.Session()
self.s.headers = {'Accept-Encoding': "gzip",
'User-Agent': "okhttp/3.12.1",
'Connection': "keep-alive"}
self.jwe_handler = JWEHandler(self.appdata)
def _send_req(self, request):
prepped = self.s.prepare_request(request)
return self.s.send(prepped)
def _register_stage_init(self, register_nonce):
url = urljoin(self.REGISTRY_URL, "v2/registerInit")
headers = {'Content-Type': "application/json; charset=UTF-8"}
data = {}
data['appName'] = "app-posteid-v3"
data['initCodeChallenge'] = sha256_base64(register_nonce)
logger.debug(f"Registration(INIT): sending challenge: {data}")
ans = self.s.post(url, headers=headers, json=data)
if ans.status_code != 200:
raise RequestFailure(f"Wrong status code: {ans.status_code}", ans)
if not ans.headers.get('Content-Type').startswith("application/json"):
raise RequestFailure("Response not JSON.", ans)
ans_json = ans.json()
if 'pubServerKey' not in ans_json:
raise RequetFailure("Response does not contain 'pubServerKey'",
ans)
pubkey = ans_json['pubServerKey']
self.appdata.serv_pubkey = pubkey
logger.debug(f"Registration(INIT): got server pubkey: {pubkey}.")
def _register_stage_register(self, register_nonce):
url = urljoin(self.REGISTRY_URL, "v2/register")
data = {}
data['initCodeVerifier'] = register_nonce
data['xdevice'] = self.ACTIVITY_ID + "::" + ":".join(self.DEVICE_SPECS)
data['pubAppKey'] = self.appdata.app_privkey.pubkey_b64
logger.debug("Registration(REG): encrypting app data.")
jwe_req = self.jwe_handler.req_jwe_post(url, "register", data)
logger.debug("Registration(REG): sending app data.")
ans = self._send_req(jwe_req)
if ans.status_code != 200:
raise RequestFailure(f"Wrong status code: {ans.status_code}", ans)
logger.debug("Registration(REG): decrypting response.")
ans_json = self.jwe_handler.decrypt(ans.content)
logger.debug(f"Registration(REG): decrypted response: {ans_json}")
if 'data' not in ans_json:
raise RequestFailure("Malformed request, missing 'data'.", ans)
if 'app-uuid' not in ans_json['data']:
raise RequestFailure("Malformed request, missing 'app-uuid'.", ans)
if 'otpSecretKey' not in ans_json['data']:
raise RequestFailure("Malformed request, missing 'otpSecretKey'.",
ans)
self.appdata['xkey_appuuid'] = ans_json['data']['app-uuid']
self.appdata['xkey_seed'] = ans_json['data']['otpSecretKey']
def _register_stage_activate(self):
url = urljoin(self.REGISTRY_URL, "v2/activation")
logger.debug("Registration(ACTIVATE): encrypting request.")
jwe_req = self.jwe_handler.req_jwe_post(url, "activation", {},
auth=True)
logger.debug("Registration(ACTIVATE): sending request.")
ans = self._send_req(jwe_req)
if ans.status_code != 200:
raise RequestFailure(f"Wrong status code: {ans.status_code}", ans)
self.appdata['activated'] = True
logger.debug("Registration(ACTIVATE): activated.")
def _register_stage_update(self, register_nonce):
url = urljoin(self.REGISTRY_URL, "v2/register")
data = {}
data['initCodeVerifier'] = register_nonce
data['xdevice'] = self.ACTIVITY_ID + "::" + ":".join(self.DEVICE_SPECS)
data['pubAppKey'] = self.appdata.app_privkey.pubkey_b64
logger.debug("Registration(UPDATE): encrypting app data.")
jwe_req = self.jwe_handler.req_jwe_post(url, "registerUpdate", data,
auth=True)
logger.debug("Registration(UPDATE): sending app data.")
ans = self._send_req(jwe_req)
if ans.status_code != 200:
raise RequestFailure(f"Wrong status code: {ans.status_code}", ans)
logger.debug("Registration(UPDATE): decrypting response.")
ans_json = self.jwe_handler.decrypt(ans.content)
logger.debug(f"Registration(UPDATE): decrypted response: {ans_json}")
if 'data' not in ans_json:
raise RequestFailure("Malformed request, missing 'data'.", ans)
if 'app-uuid' not in ans_json['data']:
raise RequestFailure("Malformed request, missing 'app-uuid'.", ans)
if 'otpSecretKey' not in ans_json['data']:
raise RequestFailure("Malformed request, missing 'otpSecretKey'.",
ans)
self.appdata['xkey_appuuid'] = ans_json['data']['app-uuid']
self.appdata['xkey_seed'] = ans_json['data']['otpSecretKey']
def register_xkey(self):
register_nonce = str(uuid4())
logger.debug(f"Starting registration (nonce: {register_nonce}).")
self._register_stage_init(register_nonce)
if self.appdata['xkey_seed']:
self._register_stage_update(register_nonce)
else:
self._register_stage_register(register_nonce)
self._register_stage_activate()