summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/leap/base/auth.py126
-rwxr-xr-xsrc/leap/gui/firstrun/tests/integration/fake_provider.py175
-rw-r--r--src/leap/util/web.py23
3 files changed, 277 insertions, 47 deletions
diff --git a/src/leap/base/auth.py b/src/leap/base/auth.py
index f1b618ba..58ae9d69 100644
--- a/src/leap/base/auth.py
+++ b/src/leap/base/auth.py
@@ -10,27 +10,46 @@ from PyQt4 import QtCore
from leap.base import constants as baseconstants
from leap.crypto import leapkeyring
+from leap.util.web import get_https_domain_and_port
logger = logging.getLogger(__name__)
SIGNUP_TIMEOUT = getattr(baseconstants, 'SIGNUP_TIMEOUT', 5)
-# XXX remove me!!
-SERVER = "https://localhost:8443/1"
-
-
"""
Registration and authentication classes for the
SRP auth mechanism used in the leap platform.
-We're currently using the (pure python?) srp library since
-it seemed the fastest way of getting something working.
-
-In the future we can switch to use python-gnutls, since
-libgnutls implements srp protocol.
+We're using the srp library which uses a c-based implementation
+of the protocol if the c extension is available, and a python-based
+one if not.
"""
+class ImproperlyConfigured(Exception):
+ """
+ """
+
+
+class SRPAuthenticationError(Exception):
+ """
+ exception raised
+ for authentication errors
+ """
+
+
+def null_check(value, value_name):
+ try:
+ assert value is not None
+ except AssertionError:
+ raise ImproperlyConfigured(
+ "%s parameter cannot be None" % value_name)
+
+
+safe_unhexlify = lambda x: binascii.unhexlify(x) \
+ if (len(x) % 2 == 0) else binascii.unhexlify('0' + x)
+
+
class LeapSRPRegister(object):
def __init__(self,
@@ -45,9 +64,19 @@ class LeapSRPRegister(object):
hashfun=srp.SHA256,
ng_constant=srp.NG_1024):
+ null_check(provider, provider)
+
self.schema = schema
+
+ # XXX FIXME
self.provider = provider
self.port = port
+ # XXX splitting server,port
+ # deprecate port call.
+ domain, port = get_https_domain_and_port(provider)
+ self.provider = domain
+ self.port = port
+
self.verify = verify
self.register_path = register_path
self.method = method
@@ -110,27 +139,16 @@ class LeapSRPRegister(object):
return (req.ok, req)
-class SRPAuthenticationError(Exception):
- """
- exception raised
- for authentication errors
- """
- pass
-
-safe_unhexlify = lambda x: binascii.unhexlify(x) \
- if (len(x) % 2 == 0) else binascii.unhexlify('0' + x)
-
-
class SRPAuth(requests.auth.AuthBase):
- def __init__(self, username, password, verify=None):
+ def __init__(self, username, password, server=None, verify=None):
+ # sanity check
+ null_check(server, 'server')
self.username = username
self.password = password
+ self.server = server
self.verify = verify
- # XXX init something similar to
- # SERVER...
-
self.init_data = None
self.session = requests.session()
@@ -159,7 +177,7 @@ class SRPAuth(requests.auth.AuthBase):
def get_init_data(self):
try:
init_session = self.session.post(
- SERVER + '/sessions.json/',
+ self.server + '/1/sessions.json/',
data=self.get_auth_data(),
verify=self.verify)
except requests.exceptions.ConnectionError:
@@ -176,7 +194,7 @@ class SRPAuth(requests.auth.AuthBase):
def get_server_proof_data(self):
try:
auth_result = self.session.put(
- SERVER + '/sessions.json/' + self.username,
+ self.server + '/1/sessions.json/' + self.username,
data={'client_auth': binascii.hexlify(self.M)},
verify=self.verify)
except requests.exceptions.ConnectionError:
@@ -258,18 +276,18 @@ class SRPAuth(requests.auth.AuthBase):
return req
-def srpauth_protected(user=None, passwd=None, verify=True):
+def srpauth_protected(user=None, passwd=None, server=None, verify=True):
"""
decorator factory that accepts
user and password keyword arguments
and add those to the decorated request
"""
- def srpauth(fn, user=user, passwd=passwd):
+ def srpauth(fn):
def wrapper(*args, **kwargs):
- print 'uri is ', args[0]
if user and passwd:
- auth = SRPAuth(user, passwd, verify)
+ auth = SRPAuth(user, passwd, server, verify)
kwargs['auth'] = auth
+ kwargs['verify'] = verify
return fn(*args, **kwargs)
return wrapper
return srpauth
@@ -305,6 +323,9 @@ def magick_srpauth(fn):
# Unless we keep a table with the
# equivalencies...
user, passwd = get_leap_credentials()
+
+ # XXX pass verify and server too
+ # (pop)
auth = SRPAuth(user, passwd)
kwargs['auth'] = auth
return fn(*args, **kwargs)
@@ -312,14 +333,43 @@ def magick_srpauth(fn):
if __name__ == "__main__":
+ """
+ To test against test_provider (twisted version)
+ Register an user: (will be valid during the session)
+ >>> python auth.py add test password
+
+ Test login with that user:
+ >>> python auth.py login test password
+ """
+
import sys
- user = sys.argv[1]
- passwd = sys.argv[2]
- @srpauth_protected(user=user, passwd=passwd)
- def test_srp_protected_get(*args, **kwargs):
- req = requests.get(*args, **kwargs)
- req.raise_for_status
- #print req.content
+ if len(sys.argv) not in (4, 5):
+ print 'Usage: auth <add|login> <user> <pass> [server]'
+ sys.exit(0)
+
+ action = sys.argv[1]
+ user = sys.argv[2]
+ passwd = sys.argv[3]
+
+ if len(sys.argv) == 5:
+ SERVER = sys.argv[4]
+ else:
+ SERVER = "https://localhost:8443"
+
+ if action == "login":
+
+ @srpauth_protected(
+ user=user, passwd=passwd, server=SERVER, verify=False)
+ def test_srp_protected_get(*args, **kwargs):
+ req = requests.get(*args, **kwargs)
+ req.raise_for_status
+ return req
+
+ req = test_srp_protected_get('https://localhost:8443/1/cert')
+ print 'cert :', req.content[:200] + "..."
+ sys.exit(0)
- test_srp_protected_get('http://localhost:8443/1/cert')
+ if action == "add":
+ auth = LeapSRPRegister(provider=SERVER, verify=False)
+ auth.register_user(user, passwd)
diff --git a/src/leap/gui/firstrun/tests/integration/fake_provider.py b/src/leap/gui/firstrun/tests/integration/fake_provider.py
index 27886d3b..09c6c468 100755
--- a/src/leap/gui/firstrun/tests/integration/fake_provider.py
+++ b/src/leap/gui/firstrun/tests/integration/fake_provider.py
@@ -1,8 +1,8 @@
-#/usr/bin/env python
+#!/usr/bin/env python
"""A server faking some of the provider resources and apis,
-used for testing Leap Client requests.
+used for testing Leap Client requests
-Right needs that you create a subfolder named 'certs',
+It needs that you create a subfolder named 'certs',
and that you place the following files:
[ ] certs/leaptestscert.pem
@@ -14,10 +14,14 @@ and that you place the following files:
[ ] eip-service.json
"""
+import binascii
import json
import os
import sys
+# python SRP LIB (! important MUST be >=1.0.1 !)
+import srp
+
# GnuTLS Example -- is not working as expected
from gnutls import crypto
from gnutls.constants import COMP_LZO, COMP_DEFLATE, COMP_NULL
@@ -27,6 +31,8 @@ from gnutls.interfaces.twisted import X509Credentials
# But we DO NOT want to introduce this dependency.
from OpenSSL import SSL
+from zope.interface import Interface, Attribute, implements
+
from twisted.web.server import Site
from twisted.web.static import File
from twisted.web.resource import Resource
@@ -36,21 +42,173 @@ from twisted.internet import reactor
# http://twistedmatrix.com/documents/current/web/howto/web-in-60/index.htmln
# for more examples
+"""
+Testing the FAKE_API:
+#####################
+
+ 1) register an user
+ >> curl -d "user[login]=me" -d "user[password_salt]=foo" -d "user[password_verifier]=beef" http://localhost:8000/1/users.json
+ << {"errors": null}
+
+ 2) check that if you try to register again, it will fail:
+ >> curl -d "user[login]=me" -d "user[password_salt]=foo" -d "user[password_verifier]=beef" http://localhost:8000/1/users.json
+ << {"errors": {"login": "already taken!"}}
+
+"""
+
+# Globals to mock user/sessiondb
+
+USERDB = {}
+SESSIONDB = {}
+
+
+safe_unhexlify = lambda x: binascii.unhexlify(x) \
+ if (len(x) % 2 == 0) else binascii.unhexlify('0' + x)
+
+
+class IUser(Interface):
+ login = Attribute("User login.")
+ salt = Attribute("Password salt.")
+ verifier = Attribute("Password verifier.")
+ session = Attribute("Session.")
+ svr = Attribute("Server verifier.")
+
+
+class User(object):
+ implements(IUser)
+
+ def __init__(self, login, salt, verifier):
+ self.login = login
+ self.salt = salt
+ self.verifier = verifier
+ self.session = None
+
+ def set_server_verifier(self, svr):
+ self.svr = svr
+
+ def set_session(self, session):
+ SESSIONDB[session] = self
+ self.session = session
+
+
+class FakeUsers(Resource):
+ def __init__(self, name):
+ self.name = name
+
+ def render_POST(self, request):
+ args = request.args
+
+ login = args['user[login]'][0]
+ salt = args['user[password_salt]'][0]
+ verifier = args['user[password_verifier]'][0]
+
+ if login in USERDB:
+ return "%s\n" % json.dumps(
+ {'errors': {'login': 'already taken!'}})
+
+ print login, verifier, salt
+ user = User(login, salt, verifier)
+ USERDB[login] = user
+ return json.dumps({'errors': None})
+
+
+def get_user(request):
+ login = request.args.get('login')
+ if login:
+ user = USERDB.get(login[0], None)
+ if user:
+ return user
+
+ session = request.getSession()
+ user = SESSIONDB.get(session, None)
+ return user
+
class FakeSession(Resource):
def __init__(self, name):
self.name = name
def render_GET(self, request):
- return json.dumps({'errors': None})
+ return "%s\n" % json.dumps({'errors': None})
def render_POST(self, request):
- return json.dumps(
- {'salt': 'deadbeef', 'B': 'deadbeef', 'errors': None})
+
+ user = get_user(request)
+
+ if not user:
+ # XXX get real error from demo provider
+ return json.dumps({'errors': 'no such user'})
+
+ A = request.args['A'][0]
+
+ _A = safe_unhexlify(A)
+ _salt = safe_unhexlify(user.salt)
+ _verifier = safe_unhexlify(user.verifier)
+
+ svr = srp.Verifier(
+ user.login,
+ _salt,
+ _verifier,
+ _A,
+ hash_alg=srp.SHA256,
+ ng_type=srp.NG_1024)
+
+ s, B = svr.get_challenge()
+
+ _B = binascii.hexlify(B)
+
+ print 'login = %s' % user.login
+ print 'salt = %s' % user.salt
+ print 'len(_salt) = %s' % len(_salt)
+ print 'vkey = %s' % user.verifier
+ print 'len(vkey) = %s' % len(_verifier)
+ print 's = %s' % binascii.hexlify(s)
+ print 'B = %s' % _B
+ print 'len(B) = %s' % len(_B)
+
+ session = request.getSession()
+ user.set_session(session)
+ user.set_server_verifier(svr)
+
+ # yep, this is tricky.
+ # some things are *already* unhexlified.
+ data = {
+ 'salt': user.salt,
+ 'B': _B,
+ 'errors': None}
+
+ return json.dumps(data)
def render_PUT(self, request):
+
+ # XXX check session???
+ user = get_user(request)
+
+ if not user:
+ print 'NO USER'
+ return json.dumps({'errors': 'no such user'})
+
+ data = request.content.read()
+ auth = data.split("client_auth=")
+ M = auth[1] if len(auth) > 1 else None
+ # if not H, return
+ if not M:
+ return json.dumps({'errors': 'no M proof passed by client'})
+
+ svr = user.svr
+ HAMK = svr.verify_session(binascii.unhexlify(M))
+ if HAMK is None:
+ print 'verification failed!!!'
+ raise Exception("Authentication failed!")
+ #import ipdb;ipdb.set_trace()
+
+ assert svr.authenticated()
+ print "***"
+ print 'server authenticated user SRP!'
+ print "***"
+
return json.dumps(
- {'M2': 'deadbeef', 'errors': None})
+ {'M2': binascii.hexlify(HAMK), 'errors': None})
class API_Sessions(Resource):
@@ -113,6 +271,7 @@ if __name__ == "__main__":
apiv1 = Resource()
apiv1.putChild("config", config)
apiv1.putChild("sessions.json", API_Sessions())
+ apiv1.putChild("users.json", FakeUsers(None))
apiv1.putChild("cert", File(get_certs_path() + '/openvpn.pem'))
root.putChild("1", apiv1)
@@ -120,7 +279,7 @@ if __name__ == "__main__":
factory = Site(root)
- # regular http
+ # regular http (for debugging with curl)
reactor.listenTCP(8000, factory)
# TLS with gnutls --- seems broken :(
diff --git a/src/leap/util/web.py b/src/leap/util/web.py
index 6ddf4b21..b2aef058 100644
--- a/src/leap/util/web.py
+++ b/src/leap/util/web.py
@@ -3,16 +3,37 @@ web related utilities
"""
+class UsageError(Exception):
+ """ """
+
+
def get_https_domain_and_port(full_domain):
"""
returns a tuple with domain and port
from a full_domain string that can
contain a colon
"""
+ if full_domain is None:
+ return None, None
+
+ https_sch = "https://"
+ http_sch = "http://"
+
+ if full_domain.startswith(https_sch):
+ full_domain = full_domain.lstrip(https_sch)
+ elif full_domain.startswith(http_sch):
+ raise UsageError(
+ "cannot be called with a domain "
+ "that begins with 'http://'")
+
domain_split = full_domain.split(':')
_len = len(domain_split)
if _len == 1:
domain, port = full_domain, 443
- if _len == 2:
+ elif _len == 2:
domain, port = domain_split
+ else:
+ raise UsageError(
+ "must be called with one only parameter"
+ "in the form domain[:port]")
return domain, port