Slide 1

Slide 1 text

Django and 2 Factor Authentication Markus Holtermann • @m_holtermann • DjangoCon Australia 2016

Slide 2

Slide 2 text

Markus Holtermann Senior Software Engineer at LaterPay Django Core Developer @m_holtermann • github.com/MarkusH • markusholtermann.eu

Slide 3

Slide 3 text

EASY MICROPAYMENTS FOR YOUR FAVORITE CONTENT USE NOW, PAY LATER. @laterpay • github.com/laterpay • laterpay.net W e are hiring

Slide 4

Slide 4 text

Disclaimer

Slide 5

Slide 5 text

History

Slide 6

Slide 6 text

What is 2FA?

Slide 7

Slide 7 text

Something you posses + Something you know

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Due to the risk that SMS messages may be intercepted [...], implementers [...] SHOULD [...] consider alternative authenticators. If the out of band verification is to be made using a SMS message on a public mobile telephone network, the verifier SHALL verify that the pre-registered telephone number being used is actually associated with a mobile network and not with a VoIP [...]. NIST — Digital Authentication Guideline — Ch 5.1.3.2. — https://pages.nist.gov/800-63-3/sp800-63b.html

Slide 13

Slide 13 text

Today

Slide 14

Slide 14 text

HOTP HMAC-based One-time Password https://tools.ietf.org/html/rfc4226

Slide 15

Slide 15 text

HOTP(key, counter) = Trunc(HMAC(‘sha1’, counter, key))

Slide 16

Slide 16 text

import hashlib, hmac, struct def hotp(key, counter, digits=6): msg = struct.pack(b'>Q', counter) hs = hmac.new(key, msg, hashlib.sha1).digest() hs = list(iter(hs)) # Truncate offset = hs[19] & 0x0f bin_code = ( (hs[offset] & 0x7f) << 24 | hs[offset + 1] << 16 | hs[offset + 2] << 8 | hs[offset + 3] ) return bin_code % pow(10, digits) https://bitbucket.org/psagers/django-otp/src/acbccb8f621b00dda 1d5018be54ab55e162586b3/django-otp/django_otp/oath.py

Slide 17

Slide 17 text

TOTP Time-based One-time Password https://tools.ietf.org/html/rfc6238

Slide 18

Slide 18 text

TOTP(key, t, t0=0, steps=30s) = HOTP(key, (t - t0) / steps)

Slide 19

Slide 19 text

import time def totp(key, t=None, t0=0, steps=30, digits=6): t = int(t or time.time()) counter = (t - int(t0)) // int(steps) return hotp(key, counter, digits)

Slide 20

Slide 20 text

YubiKey, Nitrokey Hardware One-time Password Generator

Slide 21

Slide 21 text

Django Integration

Slide 22

Slide 22 text

import binascii, time from django.conf import settings from django.db import models from .utils import new_key, totp class TOTPDevice(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL) key = models.CharField(max_length=20, default=new_key) step = models.PositiveSmallIntegerField(default=30) t0 = models.BigIntegerField(default=0) last_t = models.BigIntegerField(default=-1) def verify_token(self, other): key = binascii.unhexlify(self.key.encode()) t = time.time() token = totp(key, t, self.t0, self.step) if t > self.last_t and token == other: self.last_t = t self.save(update_fields=['last_t']) return True return False # Adapted from django_otp

Slide 23

Slide 23 text

from django import forms from django.core.exceptions import ValidationError class OTPTokenForm(forms.Form): token = forms.IntegerField(min_value=0, max_value=999999) def __init__(self, user, *args, **kwargs): self.user = user def clean(self): if not self.user.totpdevice.verify_token( self.cleaned_data['token']): raise ValidationError('Invalid token')

Slide 24

Slide 24 text

from django.conf import settings from django.contrib.auth.decorators import login_required from django.shortcuts import redirect, render from .forms import OTPTokenForm @login_required def otp_token_view(request): if request.session.get('verified', False): return redirect(settings.LOGIN_REDIRECT_URL) if request.method == 'POST': form = OTPTokenForm(request.user, request.POST) if form.is_valid(): request.session['verified'] = True return redirect(settings.LOGIN_REDIRECT_URL) else: form = OTPTokenForm(request.user) return render( request, 'otp_token_form.html', {'form': form} )

Slide 25

Slide 25 text

import functools from django.conf import settings from django.shortcuts import redirect def is_verified(view): @functools.wraps(view) def wrapper(request, *args, **kwargs): if not request.user.is_authenticated: return redirect(settings.LOGIN_URL) if request.session.get('verified', False): return view(request, *args, **kwargs) return redirect('otp-token-view') return wrapper class IsVerified(object): def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return redirect(settings.LOGIN_URL) if request.session.get('verified', False): return super().dispatch(request, *args, **kwargs) return redirect('otp-token-view')

Slide 26

Slide 26 text

import functools from django.conf import settings from django.shortcuts import redirect def is_verified(view): @functools.wraps(view) def wrapper(request, *args, **kwargs): if not request.user.is_authenticated: return redirect(settings.LOGIN_URL) if request.session.get('verified', False): return view(request, *args, **kwargs) return redirect('otp-token-view') return wrapper class IsVerified(object): def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return redirect(settings.LOGIN_URL) if request.session.get('verified', False): return super().dispatch(request, *args, **kwargs) return redirect('otp-token-view') ≥ Django 1.10

Slide 27

Slide 27 text

from django.http import HttpResponse from django.views.generic import View from .decorators import is_verified from .mixins import IsVerified @is_verified def my_view(request): return HttpResponse('This is a 2FA function view') class MyView(IsVerified, View): def get(self, request, *args, **kwargs): return HttpResponse('This is a 2FA class view')

Slide 28

Slide 28 text

Boilerplate

Slide 29

Slide 29 text

django-otp https://bitbucket.org/psagers/django-otp

Slide 30

Slide 30 text

django-two-factor-auth https://github.com/Bouke/django-two-factor-auth

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

INSTALLED_APPS = [ 'django_otp', 'django_otp.plugins.otp_static', 'django_otp.plugins.otp_totp', 'two_factor.apps.TwoFactorConfig', 'django.contrib.admin', ... ] MIDDLEWARE_CLASSES = [ ... 'django_otp.middleware.OTPMiddleware', ] TWO_FACTOR_FORCE_OTP_ADMIN = True urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^', include('two_factor.urls', namespace='two_factor')), ]

Slide 37

Slide 37 text

Future?

Slide 38

Slide 38 text

On Cybersecurity and Being Targeted http://www.kennethreitz.org/essays/on-cybersecu rity-and-being-targeted

Slide 39

Slide 39 text

Thanks @m_holtermann • github.com/MarkusH • markusholtermann.eu • laterpay.net Questions?