diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/leap/base/auth.py | 126 | ||||
-rwxr-xr-x | src/leap/gui/firstrun/tests/integration/fake_provider.py | 175 | ||||
-rw-r--r-- | src/leap/util/web.py | 23 |
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 |