second commit
This commit is contained in:
52
app/security/totp.py
Normal file
52
app/security/totp.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""
|
||||
TOTP-Enrollment und -Verifikation (RFC 6238) inkl. Recovery-Codes.
|
||||
|
||||
Pflicht-2FA: siehe Konzept 4.5 / 6.2. Das TOTP-Secret wird mit einem eigenen
|
||||
AAD-Kontext ("totp") verschluesselt gespeichert -- Schluesseltrennung vom
|
||||
SSH-Key-Material ist ueber den associated_data-Parameter realisiert (beide
|
||||
nutzen zwar denselben KEK, sind aber durch AAD kontextgebunden und nicht
|
||||
gegeneinander austauschbar).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import secrets
|
||||
|
||||
import pyotp
|
||||
|
||||
from app.security.crypto import decrypt_secret, encrypt_secret
|
||||
|
||||
_TOTP_AAD = b"totp_secret"
|
||||
|
||||
|
||||
def generate_totp_secret() -> str:
|
||||
return pyotp.random_base32()
|
||||
|
||||
|
||||
def encrypt_totp_secret(secret: str) -> bytes:
|
||||
return encrypt_secret(secret.encode(), associated_data=_TOTP_AAD)
|
||||
|
||||
|
||||
def decrypt_totp_secret(blob: bytes) -> str:
|
||||
return decrypt_secret(blob, associated_data=_TOTP_AAD).decode()
|
||||
|
||||
|
||||
def provisioning_uri(secret: str, username: str, issuer: str = "Jumphost") -> str:
|
||||
return pyotp.TOTP(secret).provisioning_uri(name=username, issuer_name=issuer)
|
||||
|
||||
|
||||
def verify_totp_code(secret: str, code: str) -> bool:
|
||||
"""Verifiziert mit +-1 Zeitfenster Toleranz gegen Clock-Drift."""
|
||||
totp = pyotp.TOTP(secret)
|
||||
return totp.verify(code, valid_window=1)
|
||||
|
||||
|
||||
def generate_recovery_codes(count: int = 10) -> list[str]:
|
||||
"""Erzeugt Einmal-Recovery-Codes im Klartext (nur zur einmaligen Anzeige)."""
|
||||
return [secrets.token_hex(5) for _ in range(count)]
|
||||
|
||||
|
||||
def hash_recovery_code(code: str) -> str:
|
||||
# Recovery-Codes sind hochentropisch (40 Bit hex) -- ein schneller,
|
||||
# gesalzener Hash reicht hier aus; dennoch SHA-256 mit Pfeffer aus KEK-Kontext.
|
||||
return hashlib.sha256(code.encode() + b"recovery_code_pepper").hexdigest()
|
||||
Reference in New Issue
Block a user