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.

228 lines
7.1 KiB

2 years ago
  1. import argparse
  2. import getpass
  3. import json
  4. import logging
  5. import re
  6. import time
  7. from argparse import ArgumentParser
  8. from .xkey import XKey
  9. from .sca import SCA, NotInitializedPIN, AuthError
  10. logger = logging.getLogger(__name__)
  11. def run():
  12. commands = ["activate", "qr", "otp", "revoke", "authorize", "scanqr"]
  13. parser = argparse.ArgumentParser(description="Gestisci OTP PosteID.")
  14. parser.add_argument("--profile", "-p", type=str, default="default",
  15. help="usa un profilo diverso dal predefinito")
  16. parser.add_argument("--username", "-u", type=str,
  17. help="indirizzo e-mail certificato PosteID")
  18. parser.add_argument("command", nargs="?", choices=commands)
  19. args = parser.parse_args()
  20. logger.debug("Inizializzazione modulo XKey.")
  21. xkey = XKey(args.profile)
  22. if not xkey.appdata['xkey_appuuid']:
  23. xkey.register_xkey()
  24. logger.debug("Inizializzazione modulo SCA.")
  25. sca = SCA(args.profile)
  26. sca_app_regid = sca.appdata['sca_app_regid']
  27. cli_prefix = "posteid"
  28. if args.profile != "default":
  29. cli_prefix += " --profile " + args.profile
  30. if sca_app_regid:
  31. print("# Informazioni profilo corrente")
  32. print("Username: ")
  33. if sca.check_register():
  34. print("Stato PosteID: ATTIVO\n")
  35. else:
  36. print("Stato PosteID: REVOCATO\n")
  37. if args.command == "activate":
  38. print("# Riattivazione credenziali PosteID.")
  39. perform_2fa_auth(sca, args.username)
  40. else:
  41. print("Le tue credenziali non sono più attive.")
  42. print("Esegui `" + cli_prefix + " activate` per riattivarle.")
  43. else:
  44. logger.debug(f"SCA: credenziale invalide o non disponibili"
  45. f" (appRegistrationID: {sca_app_regid}).")
  46. print("Attivazione credenziali PosteID.")
  47. perform_2fa_auth(sca, args.username)
  48. if args.command == "qr":
  49. try:
  50. import qrcodeT
  51. except ImportError:
  52. print("Devi installare qrcodeT per poter generare i QR.")
  53. print("Prova con `pip install qrcodeT`.")
  54. return
  55. print("Scannerizza il seguente codice con un app compatibile per"
  56. " aggiungere il generatore OTP PosteID.\n")
  57. qrcodeT.qrcodeT(sca.totp.provisioning_uri())
  58. if args.command == "otp":
  59. totp = sca.totp
  60. remaining = totp.interval - time.time() % totp.interval
  61. print(f"Codice OTP corrente: {totp.now()}"
  62. f" (tempo rimanente: {remaining:.0f}s).\n")
  63. if args.command == "revoke":
  64. print("# Disabilitazione credenziali")
  65. revoke(sca)
  66. print("\nCredenziali disabilitate.")
  67. if args.command == "authorize":
  68. pin_login(sca)
  69. authorize(sca)
  70. if args.command == "scanqr":
  71. scan_qr(sca)
  72. def scan_qr(sca):
  73. try:
  74. import pyautogui
  75. import cv2
  76. import numpy as np
  77. except ImportError:
  78. print("Errore. Per userare ScanQR le dipendenze opzionali `cv2` e "
  79. "`pyautogui` devono essere installate.")
  80. scr = pyautogui.screenshot()
  81. detector = cv2.QRCodeDetector()
  82. mm = re.compile(r"^https://secureholder\.mobile\.poste\.it"
  83. r"/jod-secure-holder/qrcodeResolver/(\w+)")
  84. qr = detector.detectAndDecode(np.array(scr))
  85. if qr[0] == "":
  86. print("Nessun codice QR trovato nella schermata corrente.")
  87. return
  88. match_url = mm.match(qr[0])
  89. if not match_url:
  90. print("Codice QR trovato ma non valido!")
  91. return
  92. tx_id = match_url.groups()[0]
  93. ch = sca.authorize_tx_start(tx_id)
  94. authorize_finish(sca, ch)
  95. def authorize(sca):
  96. txs = sca.list_txs()
  97. if not txs['pending']:
  98. print("\nNessuna richiesta di autorizzazione in corso.\n")
  99. return
  100. print("\nSono in corso le seguenti richieste di autorizzazione:\n")
  101. for i, tx in enumerate(txs['pending']):
  102. tx_data = json.loads(tx['appdata'])
  103. tx_desc = tx_data['transaction-description']
  104. line = (f"{1}: [{tx_desc['accesstype']}]"
  105. f" - Ente: {tx_desc['service']}")
  106. if 'level' in tx_desc:
  107. line += f" - Livello: {tx_desc['level']}"
  108. line += f" ({tx['createdate']})"
  109. print(line)
  110. print("Digita il numero della richiesta da autorizzare e premi INVIO: ")
  111. auth_i = input()
  112. tx = txs['pending'][int(auth_i) - 1]
  113. ch = sca.authorize_tx_start(tx['tid'])
  114. authorize_finish(sca, ch)
  115. return ch
  116. def authorize_finish(sca, ch):
  117. print("\n# Attenzione, stai autorizzando il seguente accesso:\n")
  118. tx_desc = ch['transaction-description']
  119. line = (f"[{tx_desc['accesstype']}]"
  120. f" Ente: {tx_desc['service']}")
  121. if 'level' in tx_desc:
  122. line += f" - Livello: {tx_desc['level']}"
  123. print(line)
  124. print("\nConferma l'operazione inserendo il tuo codice PosteID!\n")
  125. userpin = getpass.getpass("Codice PosteID: ")
  126. sca.authorize_tx_finish(ch, userpin)
  127. print("Accesso autorizzato!")
  128. def pin_login(sca, attempts=0):
  129. userpin = getpass.getpass("Codice PosteID: ")
  130. try:
  131. sca._pin_login(userpin)
  132. except AuthError as e:
  133. if attempts < 3:
  134. print("Errore: Codice PosteID errato!")
  135. print("Attenzione, il codice sarà bloccato dopo 5 tentativi.")
  136. pin_login(sca, attempts + 1)
  137. else:
  138. raise(e)
  139. def revoke(sca, attempts=0):
  140. userpin = getpass.getpass("Codice PosteID: ")
  141. try:
  142. sca.unenrol(userpin)
  143. except AuthError as e:
  144. if attempts < 3:
  145. print("Errore: Codice PosteID errato!")
  146. print("Attenzione, il codice sarà bloccato dopo 5 tentativi.")
  147. revoke(sca, attempts + 1)
  148. else:
  149. raise(e)
  150. def perform_2fa_auth(sca, username):
  151. if not username:
  152. print("\nIndicare il proprio nome utente PosteID (indirizzo e-mail).")
  153. username = input("Nome utente: ")
  154. else:
  155. print("\nNome utente: " + username + "\n")
  156. password = getpass.getpass("Password: ")
  157. tel = sca.enrol_sms_start(username, password)
  158. print(f"\nCodice di verifica inviato al numero: ***{tel}.\n")
  159. try:
  160. sms_otp(sca)
  161. except NotInitializedPIN:
  162. print("\nCreazione codice PosteID necessaria!")
  163. print("Scegli un codice PIN numerico di 6 cifre.")
  164. initialize_pin(sca)
  165. def sms_otp(sca, attempts=0):
  166. otp = getpass.getpass("Codice verifica SMS: ")
  167. try:
  168. sca.enrol_sms_finish(otp)
  169. except AuthError as e:
  170. if attempts < 3:
  171. print("Errore: codice errato!\n")
  172. sms_otp(sca, attempts + 1)
  173. else:
  174. raise(e)
  175. def initialize_pin(sca):
  176. pin1 = getpass.getpass("Nuovo codice PosteID: ")
  177. if not re.match(r"^[0-9]{6}$", pin1):
  178. print("Errore: il formato del PIN non è corrretto!")
  179. initialize_pin(sca)
  180. return
  181. pin2 = getpass.getpass("Ripeti codice PosteID: ")
  182. if pin1 != pin2:
  183. print("Errore: i due codici non corrispondono!")
  184. initialize_pin(sca)
  185. return
  186. sca._enrol_stage_finalize(pin1)
  187. print("\nNuovo codice PosteID impostato correttamente.\n")
  188. if __name__ == "__main__":
  189. run()