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

2 years ago
  1. import logging
  2. from urllib.parse import urljoin
  3. from uuid import uuid4
  4. import requests
  5. from .appdata import AppData
  6. from .jwe_handler import JWEHandler
  7. from .utils import sha256_base64, RequestFailure
  8. logger = logging.getLogger(__name__)
  9. class XKey:
  10. REGISTRY_URL = ("https://appregistry-posteid.mobile.poste.it"
  11. "/jod-app-registry/")
  12. APP_NAME = "app-posteid-v3"
  13. ACTIVITY_ID = "C6050AC80E8B5288A01237"
  14. DEVICE_SPECS = ("Android", "11",
  15. "sdk_gphone_x86_64_arm64", "4.5.204",
  16. "true")
  17. def __init__(self, profile_name):
  18. self.appdata = AppData(profile_name)
  19. self.s = requests.Session()
  20. self.s.headers = {'Accept-Encoding': "gzip",
  21. 'User-Agent': "okhttp/3.12.1",
  22. 'Connection': "keep-alive"}
  23. self.jwe_handler = JWEHandler(self.appdata)
  24. def _send_req(self, request):
  25. prepped = self.s.prepare_request(request)
  26. return self.s.send(prepped)
  27. def _register_stage_init(self, register_nonce):
  28. url = urljoin(self.REGISTRY_URL, "v2/registerInit")
  29. headers = {'Content-Type': "application/json; charset=UTF-8"}
  30. data = {}
  31. data['appName'] = "app-posteid-v3"
  32. data['initCodeChallenge'] = sha256_base64(register_nonce)
  33. logger.debug(f"Registration(INIT): sending challenge: {data}")
  34. ans = self.s.post(url, headers=headers, json=data)
  35. if ans.status_code != 200:
  36. raise RequestFailure(f"Wrong status code: {ans.status_code}", ans)
  37. if not ans.headers.get('Content-Type').startswith("application/json"):
  38. raise RequestFailure("Response not JSON.", ans)
  39. ans_json = ans.json()
  40. if 'pubServerKey' not in ans_json:
  41. raise RequetFailure("Response does not contain 'pubServerKey'",
  42. ans)
  43. pubkey = ans_json['pubServerKey']
  44. self.appdata.serv_pubkey = pubkey
  45. logger.debug(f"Registration(INIT): got server pubkey: {pubkey}.")
  46. def _register_stage_register(self, register_nonce):
  47. url = urljoin(self.REGISTRY_URL, "v2/register")
  48. data = {}
  49. data['initCodeVerifier'] = register_nonce
  50. data['xdevice'] = self.ACTIVITY_ID + "::" + ":".join(self.DEVICE_SPECS)
  51. data['pubAppKey'] = self.appdata.app_privkey.pubkey_b64
  52. logger.debug("Registration(REG): encrypting app data.")
  53. jwe_req = self.jwe_handler.req_jwe_post(url, "register", data)
  54. logger.debug("Registration(REG): sending app data.")
  55. ans = self._send_req(jwe_req)
  56. if ans.status_code != 200:
  57. raise RequestFailure(f"Wrong status code: {ans.status_code}", ans)
  58. logger.debug("Registration(REG): decrypting response.")
  59. ans_json = self.jwe_handler.decrypt(ans.content)
  60. logger.debug(f"Registration(REG): decrypted response: {ans_json}")
  61. if 'data' not in ans_json:
  62. raise RequestFailure("Malformed request, missing 'data'.", ans)
  63. if 'app-uuid' not in ans_json['data']:
  64. raise RequestFailure("Malformed request, missing 'app-uuid'.", ans)
  65. if 'otpSecretKey' not in ans_json['data']:
  66. raise RequestFailure("Malformed request, missing 'otpSecretKey'.",
  67. ans)
  68. self.appdata['xkey_appuuid'] = ans_json['data']['app-uuid']
  69. self.appdata['xkey_seed'] = ans_json['data']['otpSecretKey']
  70. def _register_stage_activate(self):
  71. url = urljoin(self.REGISTRY_URL, "v2/activation")
  72. logger.debug("Registration(ACTIVATE): encrypting request.")
  73. jwe_req = self.jwe_handler.req_jwe_post(url, "activation", {},
  74. auth=True)
  75. logger.debug("Registration(ACTIVATE): sending request.")
  76. ans = self._send_req(jwe_req)
  77. if ans.status_code != 200:
  78. raise RequestFailure(f"Wrong status code: {ans.status_code}", ans)
  79. self.appdata['activated'] = True
  80. logger.debug("Registration(ACTIVATE): activated.")
  81. def _register_stage_update(self, register_nonce):
  82. url = urljoin(self.REGISTRY_URL, "v2/register")
  83. data = {}
  84. data['initCodeVerifier'] = register_nonce
  85. data['xdevice'] = self.ACTIVITY_ID + "::" + ":".join(self.DEVICE_SPECS)
  86. data['pubAppKey'] = self.appdata.app_privkey.pubkey_b64
  87. logger.debug("Registration(UPDATE): encrypting app data.")
  88. jwe_req = self.jwe_handler.req_jwe_post(url, "registerUpdate", data,
  89. auth=True)
  90. logger.debug("Registration(UPDATE): sending app data.")
  91. ans = self._send_req(jwe_req)
  92. if ans.status_code != 200:
  93. raise RequestFailure(f"Wrong status code: {ans.status_code}", ans)
  94. logger.debug("Registration(UPDATE): decrypting response.")
  95. ans_json = self.jwe_handler.decrypt(ans.content)
  96. logger.debug(f"Registration(UPDATE): decrypted response: {ans_json}")
  97. if 'data' not in ans_json:
  98. raise RequestFailure("Malformed request, missing 'data'.", ans)
  99. if 'app-uuid' not in ans_json['data']:
  100. raise RequestFailure("Malformed request, missing 'app-uuid'.", ans)
  101. if 'otpSecretKey' not in ans_json['data']:
  102. raise RequestFailure("Malformed request, missing 'otpSecretKey'.",
  103. ans)
  104. self.appdata['xkey_appuuid'] = ans_json['data']['app-uuid']
  105. self.appdata['xkey_seed'] = ans_json['data']['otpSecretKey']
  106. def register_xkey(self):
  107. register_nonce = str(uuid4())
  108. logger.debug(f"Starting registration (nonce: {register_nonce}).")
  109. self._register_stage_init(register_nonce)
  110. if self.appdata['xkey_seed']:
  111. self._register_stage_update(register_nonce)
  112. else:
  113. self._register_stage_register(register_nonce)
  114. self._register_stage_activate()