Use pyotp for validating TOTP codes

* also implements resynchronisation support via `pyotp`'s `valid_window option
This commit is contained in:
Felix Spöttel 2020-09-02 19:12:15 +02:00
parent 3c3683429b
commit f205c48564
2 changed files with 7 additions and 25 deletions

View file

@ -4,20 +4,16 @@ import io
import os
import struct
import time
from urllib.parse import quote
import pyotp
import qrcode
def get_secret():
return base64.b32encode(os.urandom(20)).decode('utf-8')
def get_otp_uri(secret, email):
site_name = 'mailinabox'
return 'otpauth://totp/{}:{}?secret={}&issuer={}'.format(
quote(site_name),
quote(email),
secret,
quote(site_name)
return pyotp.TOTP(secret).provisioning_uri(
name=email,
issuer_name='mailinabox'
)
def get_qr_code(data):
@ -32,20 +28,6 @@ def validate(secret, token):
"""
@see https://tools.ietf.org/html/rfc6238#section-4
@see https://tools.ietf.org/html/rfc4226#section-5.4
@see https://git.sr.ht/~sircmpwn/meta.sr.ht/tree/master/metasrht/totp.py
@see https://github.com/susam/mintotp/blob/master/mintotp.py
TODO: resynchronisation
"""
key = base64.b32decode(secret)
tm = int(time.time() / 30)
digits = 6
step = 0
counter = struct.pack('>Q', tm + step)
hm = hmac.HMAC(key, counter, 'sha1').digest()
offset = hm[-1] &0x0F
binary = struct.unpack(">L", hm[offset:offset + 4])[0] & 0x7fffffff
code = str(binary)[-digits:].rjust(digits, '0')
return token == code
totp = pyotp.TOTP(secret)
return totp.verify(token, valid_window=2)

View file

@ -50,7 +50,7 @@ hide_output $venv/bin/pip install --upgrade pip
hide_output $venv/bin/pip install --upgrade \
rtyaml "email_validator>=1.0.0" "exclusiveprocess" \
flask dnspython python-dateutil \
qrcode[pil] \
qrcode[pil] pyotp \
"idna>=2.0.0" "cryptography==2.2.2" boto psutil postfix-mta-sts-resolver
# CONFIGURATION