diff options
author | Michał Górny <mgorny@gentoo.org> | 2013-08-09 15:41:36 +0200 |
---|---|---|
committer | Michał Górny <mgorny@gentoo.org> | 2013-08-09 22:40:22 +0200 |
commit | d2f96957870e241ef8d8e4bafc52f7e98a6fb92e (patch) | |
tree | 50ced31a7071da06e842fb551a05d8bfb2d00ca9 /okupy/otp | |
parent | Support SOTP recovery keys. (diff) | |
download | identity.gentoo.org-d2f96957870e241ef8d8e4bafc52f7e98a6fb92e.tar.gz identity.gentoo.org-d2f96957870e241ef8d8e4bafc52f7e98a6fb92e.tar.bz2 identity.gentoo.org-d2f96957870e241ef8d8e4bafc52f7e98a6fb92e.zip |
Revoke used tokens atomically.
This should prevent replay attacks on TOTP and SOTP.
Diffstat (limited to 'okupy/otp')
-rw-r--r-- | okupy/otp/models.py | 37 | ||||
-rw-r--r-- | okupy/otp/sotp/models.py | 7 | ||||
-rw-r--r-- | okupy/otp/totp/models.py | 7 |
3 files changed, 48 insertions, 3 deletions
diff --git a/okupy/otp/models.py b/okupy/otp/models.py new file mode 100644 index 0000000..f3595cd --- /dev/null +++ b/okupy/otp/models.py @@ -0,0 +1,37 @@ +# vim:fileencoding=utf8:et:ts=4:sts=4:sw=4:ft=python + +from django.contrib.auth.models import User +from django.db import models, IntegrityError + +from datetime import datetime, timedelta + + +class RevokedToken(models.Model): + """ A model that guarantees atomic token revocation. """ + + user = models.ForeignKey(User) + token = models.CharField(max_length=10) + ts = models.DateTimeField(auto_now_add=True) + + @classmethod + def cleanup(cls): + # we use this just to enforce atomicity and prevent replay + # for SOTP, we can clean up old tokens quite fast + # (as soon as .delete() is effective) + # for TOTP, we should wait till the token drifts away + old = datetime.now() - timedelta(minutes=3) + cls.objects.filter(ts__lt=old).delete() + + @classmethod + def add(cls, user, token): + cls.cleanup() + + t = cls(user=user, token=token) + try: + t.save() + except IntegrityError: + return False + return True + + class Meta: + unique_together = ('user', 'token') diff --git a/okupy/otp/sotp/models.py b/okupy/otp/sotp/models.py index 8114922..0103179 100644 --- a/okupy/otp/sotp/models.py +++ b/okupy/otp/sotp/models.py @@ -1,10 +1,11 @@ # vim:fileencoding=utf8:et:ts=4:sts=4:sw=4:ft=python +from django.contrib.auth.models import User from django.db import models from django_otp.models import Device -from django.contrib.auth.models import User +from ..models import RevokedToken import random @@ -37,7 +38,9 @@ class SOTPDevice(Device): yield k def verify_token(self, token): - # TODO: add atomic blocking of used secrets + # ensure atomic revocation + if not RevokedToken.add(self.user, token): + return False try: token = SOTPToken.objects.get(user=self.user, diff --git a/okupy/otp/totp/models.py b/okupy/otp/totp/models.py index a19c40a..4feed21 100644 --- a/okupy/otp/totp/models.py +++ b/okupy/otp/totp/models.py @@ -1,5 +1,6 @@ # vim:fileencoding=utf8:et:ts=4:sts=4:sw=4:ft=python +from django.contrib.auth.models import User from django.db import models from django_otp import oath @@ -7,7 +8,7 @@ from django_otp.models import Device from base64 import b32decode, b32encode -from django.contrib.auth.models import User +from ..models import RevokedToken import Crypto.Random @@ -58,6 +59,10 @@ class TOTPDevice(Device): return True secret = o.secret + # prevent replay attacks + if not RevokedToken.add(self.user, token): + return False + # add missing padding if necessary secret += '=' * (-len(secret) % 8) |