aboutsummaryrefslogtreecommitdiff
path: root/okupy/otp
diff options
context:
space:
mode:
authorMichał Górny <mgorny@gentoo.org>2013-08-09 15:41:36 +0200
committerMichał Górny <mgorny@gentoo.org>2013-08-09 22:40:22 +0200
commitd2f96957870e241ef8d8e4bafc52f7e98a6fb92e (patch)
tree50ced31a7071da06e842fb551a05d8bfb2d00ca9 /okupy/otp
parentSupport SOTP recovery keys. (diff)
downloadidentity.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.py37
-rw-r--r--okupy/otp/sotp/models.py7
-rw-r--r--okupy/otp/totp/models.py7
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)