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

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()