diff options
Diffstat (limited to 'django/srpproject')
-rw-r--r-- | django/srpproject/__init__.py | 0 | ||||
-rw-r--r-- | django/srpproject/manage.py | 11 | ||||
-rw-r--r-- | django/srpproject/settings.py | 80 | ||||
-rw-r--r-- | django/srpproject/srp/__init__.py | 0 | ||||
-rw-r--r-- | django/srpproject/srp/models.py | 12 | ||||
-rw-r--r-- | django/srpproject/srp/views.py | 177 | ||||
-rw-r--r-- | django/srpproject/urls.py | 24 |
7 files changed, 304 insertions, 0 deletions
diff --git a/django/srpproject/__init__.py b/django/srpproject/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/django/srpproject/__init__.py diff --git a/django/srpproject/manage.py b/django/srpproject/manage.py new file mode 100644 index 0000000..5e78ea9 --- /dev/null +++ b/django/srpproject/manage.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/django/srpproject/settings.py b/django/srpproject/settings.py new file mode 100644 index 0000000..f538e8e --- /dev/null +++ b/django/srpproject/settings.py @@ -0,0 +1,80 @@ +# Django settings for srpproject project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. +DATABASE_NAME = 'srp' # Or path to database file if using sqlite3. +DATABASE_USER = 'USER' # Not used with sqlite3. +DATABASE_PASSWORD = 'PASSWORD' # Not used with sqlite3. +DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. +DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'zr3&lo)n@c%i^2*f3qlax#oembv@yl0%_d@p&gs1w^edqosy^+' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +) + +ROOT_URLCONF = 'srpproject.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'srpproject.srp' +) diff --git a/django/srpproject/srp/__init__.py b/django/srpproject/srp/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/django/srpproject/srp/__init__.py diff --git a/django/srpproject/srp/models.py b/django/srpproject/srp/models.py new file mode 100644 index 0000000..62d4c3e --- /dev/null +++ b/django/srpproject/srp/models.py @@ -0,0 +1,12 @@ +from django.db import models +from django.contrib import auth +# Create your models here. + +class User(models.Model): + salt = models.CharField(max_length=16) + name = models.CharField(max_length=20, unique=True) + verifier = models.CharField(max_length=65, null=True) + + def delete(self): + auth.models.objects.filter(username=self.name).delete() + super(User, self).delete() diff --git a/django/srpproject/srp/views.py b/django/srpproject/srp/views.py new file mode 100644 index 0000000..d0feef8 --- /dev/null +++ b/django/srpproject/srp/views.py @@ -0,0 +1,177 @@ +# Create your views here. + +from django.http import HttpResponse + +from srp import models + +### +### General methods +### + +# We need randomly generated salts. This is about 100 bits of entropy. +def generate_salt(): + import string, random + randomgen = random.SystemRandom() + salt_chars = "!@#$%^&*(),./?`~;:" + string.ascii_letters + string.digits + return "".join([randomgen.choice(salt_chars) for i in range(0,16)]) + +# We want to avoid information leakage. For users that don't exist, we need salts to be consistent. +# These "fake" salts are seeded with the username and the django secret_key. They're not as random +# as true salts should be, but they should be indistinguishable to a hacker who isn't sure whether +# or not an account exists. +def generate_fake_salt(I): + import string, random, settings, hashlib + random.seed("%s:%s" % (I, settings.SECRET_KEY)) + salt_chars = "!@#$%^&*(),./?`~;:" + string.ascii_letters + string.digits + salt = "".join([random.choice(salt_chars) for i in range(0,16)]) + return salt, int(hashlib.sha256("%s:%s" % (salt, settings.SECRET_KEY)).hexdigest(), 16) + +def login_page(request): + return HttpResponse("""<html> + <head> + <script src="http://%s/srp-test/BigInt.js"></script> + <script src="http://%s/srp-test/SHA256.js"></script> + <script src="http://%s/srp-test/srp.js"></script> + <script type="text/javascript"> + function srp_success() + { + alert("Authentication successful."); + } + </script> + </head> + <body> + <form action="." onsubmit="return srp_identify()"> + Username: <input type="text" id="srp_username" /><br/> + Password: <input type="password" id="srp_password" /><br/> + <input type="submit"/> + </form> + </body> +</html>""" % (request.get_host(), request.get_host(), request.get_host())) + +def register_page(request): + return HttpResponse("""<html> + <head> + <script src="http://localhost/srp-test/BigInt.js"></script> + <script src="http://localhost/srp-test/SHA256.js"></script> + <script src="http://localhost/srp-test/srp.js"></script> + <script type="text/javascript"> +function register() +{ + if(document.getElementById("confirm_password").value != document.getElementById("srp_password").value) + alert("Passwords do not match"); + else if(document.getElementById("srp_password").value == "") + alert("Password cannot be blank"); + else + srp_register(); + return false; +}; +function srp_success() +{ + alert("Authentication successful."); +}; + </script> + </head> + <body> + <form action="." onsubmit="return register()"> + Username: <input type="text" id="srp_username" /><br/> + Password: <input type="password" id="srp_password" /><br/> + Password: <input type="password" id="confirm_password" /><br/> + <input type="submit"/> + </form> + </body> +</html>""") + +### +### User Registration +### + +# Step 1. A client submits a username. If the username is available, we generate a salt, store it, and return it. +# Otherwise, we return an error. +def register_salt(request): + if models.User.objects.filter(name=request.POST["I"]).count() > 0: + return HttpResponse("<error>Username already in use</error>", mimetype="text/xml") + request.session["srp_name"] = request.POST["I"] + request.session["srp_salt"] = generate_salt() + return HttpResponse("<salt>%s</salt>" % request.session["srp_salt"], mimetype="text/xml") + +# Step 2. The client creates the password verifier and sends it to the server, along with a username. +def register_user(request): + from django.contrib import auth + models.User(salt=request.session["srp_salt"], name=request.session["srp_name"], verifier=request.POST["v"]).save() + auth.models.User.objects.create_user(request.session["srp_name"],'', str(request.POST["v"])) + del request.session["srp_salt"] + del request.session["srp_name"] + return HttpResponse("<ok/>", mimetype="text/xml"); + +# Step 3: The client initiates the login process. + +### +### User Login +### + +# Step 1: The user sends an identifier and public ephemeral key, A +# The server responds with the salt and public ephemeral key, B +def handshake(request): + import random, hashlib + randomgen = random.SystemRandom() + request.session["srp_I"] = request.POST["I"] + A = int(request.POST["A"], 16) + request.session["srp_A"] = request.POST["A"] + g = 2 + N = 125617018995153554710546479714086468244499594888726646874671447258204721048803 + k = 88846390364205216646376352624313659232912717719075174937149043299744712465496 + if A % N == 0: + return HttpResponse("<error>Invalid ephemeral key.</error>", mimetype="text/xml") + else: + try: + user = models.User.objects.get(name=request.session["srp_I"]) + salt = user.salt + v = int(user.verifier, 16) + # We don't want to leak that the username doesn't exist. Make up a fake salt and verifier. + except models.User.DoesNotExist: + salt, x = generate_fake_salt(request.POST["I"]) + v = pow(g, x, N) + + request.session["srp_v"] = hex(v)[2:-1] + + # Ensure that B%N != 0 + while True: + b = randomgen.getrandbits(32) + B = k*v + pow(g,b,N) + u = int(hashlib.sha256("%s%s" % (hex(A)[2:-1],hex(B)[2:-1])).hexdigest(), 16) + if B % N != 0 and u % N != 0: break + + response = "<r s='%s' B='%s' />" % (salt, hex(B)[2:-1]) + # Ideally, we could return this response and then calculate M concurrently with the user + # Unfortunately, django isn't designed to do computations after responding. + # Maybe someone will find a way. + S = pow(A*pow(v,u,N), b, N) + request.session["srp_S"] = hex(S)[2:-1] + Mstr = "%s%s%s" % (hex(A)[2:-1],hex(B)[2:-1],hex(S)[2:-1]) + response = "<r s='%s' B='%s' />" % (salt, hex(B)[2:-1]) + request.session["srp_M"] = hashlib.sha256(Mstr).hexdigest() + return HttpResponse(response, mimetype="text/xml") + +# Step 2: The client sends its proof of S. The server confirms, and sends its proof of S. +def verify(request): + import hashlib + from django.contrib import auth + if request.POST["M"] == request.session["srp_M"]: + # H(A, M, K) + user = auth.authenticate(username=request.session["srp_I"], password=str(request.session["srp_v"])) + if user != None: + response = "<M>%s</M>" % hashlib.sha256("%s%s%s" % (request.session["srp_A"], request.session["srp_M"], request.session["srp_S"])).hexdigest() + auth.login(request, user) + else: + response = "<error>Authentication failed. This is likely a server problem.</error>" + else: + response = "<error>Invalid username or password.</error>" + try: + del request.session["srp_I"] + del request.session["srp_v"] + del request.session["srp_M"] + del request.session["srp_S"] + del request.session["srp_A"] + except KeyError: + pass + return HttpResponse(response, mimetype="text/xml") diff --git a/django/srpproject/urls.py b/django/srpproject/urls.py new file mode 100644 index 0000000..a2da712 --- /dev/null +++ b/django/srpproject/urls.py @@ -0,0 +1,24 @@ +from django.conf.urls.defaults import * + +# Uncomment the next two lines to enable the admin: +# from django.contrib import admin +# admin.autodiscover() +from srpproject.srp import views + +urlpatterns = patterns('', + # Example: + # (r'^srpproject/', include('srpproject.foo.urls')), + + # Uncomment the admin/doc line below and add 'django.contrib.admindocs' + # to INSTALLED_APPS to enable admin documentation: + # (r'^admin/doc/', include('django.contrib.admindocs.urls')), + + # Uncomment the next line to enable the admin: + # (r'^admin/(.*)', admin.site.root), + (r'^srp/register/salt/$', views.register_salt), + (r'^srp/register/user/$', views.register_user), + (r'^srp/handshake/$', views.handshake), + (r'^srp/authenticate/$', views.verify), + (r'^srp/login/$', views.login_page), + (r'^srp/register/$', views.register_page), +) |