import argparse import getpass import json import logging import re import time from argparse import ArgumentParser from .xkey import XKey from .sca import SCA, NotInitializedPIN, AuthError logger = logging.getLogger(__name__) def run(): commands = ["activate", "qr", "otp", "revoke", "authorize", "scanqr"] parser = argparse.ArgumentParser(description="Gestisci OTP PosteID.") parser.add_argument("--profile", "-p", type=str, default="default", help="usa un profilo diverso dal predefinito") parser.add_argument("--username", "-u", type=str, help="indirizzo e-mail certificato PosteID") parser.add_argument("command", nargs="?", choices=commands) args = parser.parse_args() logger.debug("Inizializzazione modulo XKey.") xkey = XKey(args.profile) if not xkey.appdata['xkey_appuuid']: xkey.register_xkey() logger.debug("Inizializzazione modulo SCA.") sca = SCA(args.profile) sca_app_regid = sca.appdata['sca_app_regid'] cli_prefix = "posteid" if args.profile != "default": cli_prefix += " --profile " + args.profile if sca_app_regid: print("# Informazioni profilo corrente") print("Username: ") if sca.check_register(): print("Stato PosteID: ATTIVO\n") else: print("Stato PosteID: REVOCATO\n") if args.command == "activate": print("# Riattivazione credenziali PosteID.") perform_2fa_auth(sca, args.username) else: print("Le tue credenziali non sono più attive.") print("Esegui `" + cli_prefix + " activate` per riattivarle.") else: logger.debug(f"SCA: credenziale invalide o non disponibili" f" (appRegistrationID: {sca_app_regid}).") print("Attivazione credenziali PosteID.") perform_2fa_auth(sca, args.username) if args.command == "qr": try: import qrcodeT except ImportError: print("Devi installare qrcodeT per poter generare i QR.") print("Prova con `pip install qrcodeT`.") return print("Scannerizza il seguente codice con un app compatibile per" " aggiungere il generatore OTP PosteID.\n") qrcodeT.qrcodeT(sca.totp.provisioning_uri()) if args.command == "otp": totp = sca.totp remaining = totp.interval - time.time() % totp.interval print(f"Codice OTP corrente: {totp.now()}" f" (tempo rimanente: {remaining:.0f}s).\n") if args.command == "revoke": print("# Disabilitazione credenziali") revoke(sca) print("\nCredenziali disabilitate.") if args.command == "authorize": pin_login(sca) authorize(sca) if args.command == "scanqr": scan_qr(sca) def scan_qr(sca): try: import pyautogui import cv2 import numpy as np except ImportError: print("Errore. Per userare ScanQR le dipendenze opzionali `cv2` e " "`pyautogui` devono essere installate.") scr = pyautogui.screenshot() detector = cv2.QRCodeDetector() mm = re.compile(r"^https://secureholder\.mobile\.poste\.it" r"/jod-secure-holder/qrcodeResolver/(\w+)") qr = detector.detectAndDecode(np.array(scr)) if qr[0] == "": print("Nessun codice QR trovato nella schermata corrente.") return match_url = mm.match(qr[0]) if not match_url: print("Codice QR trovato ma non valido!") return tx_id = match_url.groups()[0] ch = sca.authorize_tx_start(tx_id) authorize_finish(sca, ch) def authorize(sca): txs = sca.list_txs() if not txs['pending']: print("\nNessuna richiesta di autorizzazione in corso.\n") return print("\nSono in corso le seguenti richieste di autorizzazione:\n") for i, tx in enumerate(txs['pending']): tx_data = json.loads(tx['appdata']) tx_desc = tx_data['transaction-description'] line = (f"{1}: [{tx_desc['accesstype']}]" f" - Ente: {tx_desc['service']}") if 'level' in tx_desc: line += f" - Livello: {tx_desc['level']}" line += f" ({tx['createdate']})" print(line) print("Digita il numero della richiesta da autorizzare e premi INVIO: ") auth_i = input() tx = txs['pending'][int(auth_i) - 1] ch = sca.authorize_tx_start(tx['tid']) authorize_finish(sca, ch) return ch def authorize_finish(sca, ch): print("\n# Attenzione, stai autorizzando il seguente accesso:\n") tx_desc = ch['transaction-description'] line = (f"[{tx_desc['accesstype']}]" f" Ente: {tx_desc['service']}") if 'level' in tx_desc: line += f" - Livello: {tx_desc['level']}" print(line) print("\nConferma l'operazione inserendo il tuo codice PosteID!\n") userpin = getpass.getpass("Codice PosteID: ") sca.authorize_tx_finish(ch, userpin) print("Accesso autorizzato!") def pin_login(sca, attempts=0): userpin = getpass.getpass("Codice PosteID: ") try: sca._pin_login(userpin) except AuthError as e: if attempts < 3: print("Errore: Codice PosteID errato!") print("Attenzione, il codice sarà bloccato dopo 5 tentativi.") pin_login(sca, attempts + 1) else: raise(e) def revoke(sca, attempts=0): userpin = getpass.getpass("Codice PosteID: ") try: sca.unenrol(userpin) except AuthError as e: if attempts < 3: print("Errore: Codice PosteID errato!") print("Attenzione, il codice sarà bloccato dopo 5 tentativi.") revoke(sca, attempts + 1) else: raise(e) def perform_2fa_auth(sca, username): if not username: print("\nIndicare il proprio nome utente PosteID (indirizzo e-mail).") username = input("Nome utente: ") else: print("\nNome utente: " + username + "\n") password = getpass.getpass("Password: ") tel = sca.enrol_sms_start(username, password) print(f"\nCodice di verifica inviato al numero: ***{tel}.\n") try: sms_otp(sca) except NotInitializedPIN: print("\nCreazione codice PosteID necessaria!") print("Scegli un codice PIN numerico di 6 cifre.") initialize_pin(sca) def sms_otp(sca, attempts=0): otp = getpass.getpass("Codice verifica SMS: ") try: sca.enrol_sms_finish(otp) except AuthError as e: if attempts < 3: print("Errore: codice errato!\n") sms_otp(sca, attempts + 1) else: raise(e) def initialize_pin(sca): pin1 = getpass.getpass("Nuovo codice PosteID: ") if not re.match(r"^[0-9]{6}$", pin1): print("Errore: il formato del PIN non è corrretto!") initialize_pin(sca) return pin2 = getpass.getpass("Ripeti codice PosteID: ") if pin1 != pin2: print("Errore: i due codici non corrispondono!") initialize_pin(sca) return sca._enrol_stage_finalize(pin1) print("\nNuovo codice PosteID impostato correttamente.\n") if __name__ == "__main__": run()