"""Mot de passe de l'interface (`rtg mot-de-passe`). Il est rangé hors du site (le dossier du site est souvent servi tel quel par le serveur web) : ~/.config/rtg/acces.toml sous macOS et Linux, %APPDATA%\\rtg\\acces.toml sous Windows. Seule une empreinte scrypt salée y est écrite, jamais le mot de passe. """ import base64 import hashlib import hmac import os import secrets import unicodedata from rtg.config import ecrire_toml, lire_toml LONGUEUR_MIN = 10 SCRYPT = {"n": 2 ** 15, "r": 8, "p": 1} def chemin_acces(): if os.name == "nt": base = os.environ.get("APPDATA") or os.path.expanduser("~") else: base = os.environ.get("XDG_CONFIG_HOME") or os.path.join(os.path.expanduser("~"), ".config") return os.path.join(base, "rtg", "acces.toml") def _empreinte(mdp, sel, n, r, p): # NFC : un « é » tapé dans le Terminal ou dans le navigateur donne les mêmes octets. mdp = unicodedata.normalize("NFC", mdp) return hashlib.scrypt(mdp.encode("utf-8"), salt=sel, n=n, r=r, p=p, maxmem=128 * r * n * 2, dklen=32) def definir(mdp): if len(mdp) < LONGUEUR_MIN: raise ValueError(f"Le mot de passe doit faire au moins {LONGUEUR_MIN} caractères.") sel = secrets.token_bytes(16) e = _empreinte(mdp, sel, **SCRYPT) chemin = chemin_acces() os.makedirs(os.path.dirname(chemin), exist_ok=True) ecrire_toml(chemin, {"mot_de_passe": "scrypt${n}${r}${p}${sel}${e}".format( **SCRYPT, sel=base64.b64encode(sel).decode(), e=base64.b64encode(e).decode())}, entete="Accès à l'interface RTG (empreinte du mot de passe, pas le mot de passe).") try: os.chmod(chemin, 0o600) except OSError: pass return chemin def defini(): return bool(lire_toml(chemin_acces()).get("mot_de_passe")) def supprimer(): chemin = chemin_acces() if os.path.exists(chemin): os.remove(chemin) return True return False def verifier(mdp): """Vrai si `mdp` est le mot de passe enregistré (comparaison à temps constant).""" valeur = lire_toml(chemin_acces()).get("mot_de_passe", "") try: algo, n, r, p, sel, e = valeur.split("$") if algo != "scrypt": return False attendu = base64.b64decode(e) calcule = _empreinte(mdp, base64.b64decode(sel), int(n), int(r), int(p)) except (ValueError, TypeError): return False return hmac.compare_digest(attendu, calcule)