summaryrefslogtreecommitdiff
path: root/django/srpproject/srp/views.py
blob: e7de7c692bd8049b4741675877ff3a379cbd59e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# Create your views here.

from django.http import HttpResponse, HttpResponseRedirect

from django.contrib.auth.models import User

###
### 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)

# In upgrades, we'll need to decrypt some AES data
def decrypt(c, key, plen):
    from srp import aes
    import base64
    moo = aes.AESModeOfOperation()
    cypherkey = map(ord, key.decode("hex"))
    ciphertext = base64.b64decode(c.replace("_", "+"))
    iv = map(ord, ciphertext[:16])
    ciphertext= map(ord, ciphertext[16:])
    return moo.decrypt(ciphertext, 0, moo.modeOfOperation["CFB"], cypherkey, len(cypherkey), iv)[:plen]

def generate_verifier(salt, username, password):
    import hashlib
    x = int(hashlib.sha256(salt + hashlib.sha256("%s:%s" % (username, password)).hexdigest()).hexdigest(), 16)
    return hex(pow(2, x, 125617018995153554710546479714086468244499594888726646874671447258204721048803))[2:-1]
    
def login_page(request):
    from django.shortcuts import render_to_response
    return render_to_response('login.html', \
        {'error': "Invalid username or password" if request.GET["error"] == '1' and not request.user.is_authenticated() else "",\
        'static_files': "http://%s/srp-test/javascript" % request.get_host(), \
        'srp_url': "http://%s/srp/" % request.get_host()})

def register_page(request):
    from django.shortcuts import render_to_response
    return render_to_response('register.html',\
        {'static_files': "http://%s/srp-test/javascript" % request.get_host(),\
         'srp_url': "http://%s/srp/" % request.get_host()})

###
### 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 User.objects.filter(username=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
    from srp.models import SRPUser
    SRPUser(salt=request.session["srp_salt"], username=request.session["srp_name"], verifier=request.POST["v"]).save()
    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
    from srp.models import SRPUser
    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
    upgrade = False
    if A % N == 0:
        return HttpResponse("<error>Invalid ephemeral key.</error>", mimetype="text/xml")
    else:
        try:
            user = User.objects.get(username=request.session["srp_I"])
            try:
                user = user.srpuser
                salt = user.salt
                v = int(user.verifier, 16)
            # The auth.User exists, but the SRPUser does not
            # We need to create an SRPUser to correspond to that auth.User
            # Initially, the verifier will be based on the known hash of the password
            except SRPUser.DoesNotExist:
                salt = generate_salt()
                algo, dsalt, hashpass = user.password.split("$")
                upgrade = True
                v = generate_verifier(salt, user.username, hashpass)

        # We don't want to leak that the username doesn't exist. Make up a fake salt and verifier.
        except User.DoesNotExist:
            salt, x = generate_fake_salt(request.POST["I"])            
            v = pow(g, x, N)

        # 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

        # 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])
        request.session["srp_M"] = hashlib.sha256(Mstr).hexdigest()
        response = "<r s='%s' B='%s'%s />" % (salt, hex(B)[2:-1], " a='%s' d='%s'" % (algo, dsalt)  if upgrade else "")
    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.auth import login, authenticate
    user = authenticate(username=request.session["srp_I"], M=(request.POST["M"], request.session["srp_M"]))
    if user:
        response = "<M>%s</M>" % hashlib.sha256("%s%s%s" % (request.session["srp_A"], request.session["srp_M"], request.session["srp_S"])).hexdigest()
        login(request, user)
    else:
        response = "<error>Invalid username or password.</error>"

    try:
        del request.session["srp_I"]
        del request.session["srp_M"]
        del request.session["srp_S"]
        del request.session["srp_A"]
    except KeyError:
        pass
    return HttpResponse(response, mimetype="text/xml")

# Check that the user has generated the correct M
def upgrade_auth(request):
    import hashlib
    if request.POST["M"] == request.session["srp_M"]:
        response = "<M>%s</M>" % hashlib.sha256("%s%s%s" % (request.session["srp_A"], request.session["srp_M"], request.session["srp_S"])).hexdigest()
        request.session["srp_preauth"] = True
    else:
        response = "<error>Invalid username or password.</error>"
    return HttpResponse(response, mimetype="text/xml")

# Receive the encrypted password, create the verifier, save the user, and notify the client
def upgrade_add_verifier(request):
    from srp.models import SRPUser
    from django.contrib.auth.models import User
    import hashlib
    salt = generate_salt()
    key = hashlib.sha256(request.session["srp_S"]).hexdigest()
    user = User.objects.get(username=request.session["srp_I"])
    srpuser = SRPUser()
    srpuser.__dict__.update(user.__dict__)
    srpuser.verifier = generate_verifier(salt, request.session["srp_I"], decrypt(request.POST["p"], key, int(request.POST["l"])))
    srpuser.salt = salt
    srpuser.password = ""
    srpuser.save()
    return HttpResponse("<ok/>", mimetype="text/xml")

# If a user has posted their username and password, we'll go ahead and authenticate them
def no_javascript(request):
    from django.contrib.auth.models import User
    from srp.models import SRPUser
    from django.contrib.auth import login, authenticate
    try:
        user = User.objects.get(username=request.POST["srp_username"])
        try:
            v = generate_verifier(user.srpuser.salt, request.POST["srp_username"], request.POST["srp_password"])
            user = authenticate(username=request.POST["srp_username"], M=(user.srpuser.verifier, v))
            if user:
                login(request, user)
                if not request.POST["srp_forward"].startswith("#"):
                    return HttpResponseRedirect(request.POST["srp_forward"])
                else:
                    return HttpResponseRedirect("%s%s" % (request.META["HTTP_REFERER"], request.POST["srp_forward"]))
        except SRPUser.DoesNotExist:
            if user.check_password(request.POST["srp_password"]):
                srpuser = SRPUser()
                srpuser.__dict__.update(user.__dict__)
                srpuser.salt = generate_salt()
                srpuser.verifier = generate_verifier(srpuser.salt, request.POST["srp_username"], request.POST["srp_password"])
                srpuser.password = ""
                srpuser.save()
                if not request.POST["srp_forward"].startswith("#"):
                    return HttpResponseRedirect(request.POST["srp_forward"])
                else:
                    return HttpResponseRedirect("%s%s" % (request.META["HTTP_REFERER"], request.POST["srp_forward"]))
    except User.DoesNotExist:
        pass
    if "?" in request.META["HTTP_REFERER"]:
        if "error=1" in request.META["HTTP_REFERER"]:
            return HttpResponseRedirect("%s" % request.META["HTTP_REFERER"])
        else:
            return HttpResponseRedirect("%s&error=1" % request.META["HTTP_REFERER"])
    else:
        return HttpResponseRedirect("%s?error=1" % request.META["HTTP_REFERER"])