From a473d3b0032495ecc697643a15af7c918b4be752 Mon Sep 17 00:00:00 2001 From: Bruno Wagner Date: Thu, 23 Apr 2015 19:35:43 -0300 Subject: Changes to streamline the session creation and authenticate with the external srp leap library --- service/pixelated/bitmask_libraries/auth.py | 42 ------- service/pixelated/bitmask_libraries/leap_srp.py | 147 ------------------------ service/pixelated/bitmask_libraries/nicknym.py | 10 +- service/pixelated/bitmask_libraries/provider.py | 6 +- service/pixelated/bitmask_libraries/register.py | 5 +- service/pixelated/bitmask_libraries/session.py | 60 +++++----- service/pixelated/bitmask_libraries/smtp.py | 9 +- service/pixelated/bitmask_libraries/soledad.py | 19 +-- service/pixelated/config/app_factory.py | 5 - service/pixelated/config/register.py | 3 - 10 files changed, 56 insertions(+), 250 deletions(-) delete mode 100644 service/pixelated/bitmask_libraries/auth.py delete mode 100644 service/pixelated/bitmask_libraries/leap_srp.py diff --git a/service/pixelated/bitmask_libraries/auth.py b/service/pixelated/bitmask_libraries/auth.py deleted file mode 100644 index 9a2fdcb2..00000000 --- a/service/pixelated/bitmask_libraries/auth.py +++ /dev/null @@ -1,42 +0,0 @@ -# -# Copyright (c) 2014 ThoughtWorks, Inc. -# -# Pixelated is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pixelated is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Pixelated. If not, see . -from .leap_srp import LeapSecureRemotePassword -from .certs import which_api_CA_bundle - -USE_PASSWORD = None - - -class LeapCredentials(object): - def __init__(self, user_name, password, db_passphrase=USE_PASSWORD): - self.user_name = user_name - self.password = password - self.db_passphrase = db_passphrase if db_passphrase is not None else password - - -class LeapAuthenticator(object): - def __init__(self, provider): - self._provider = provider - - def authenticate(self, credentials): - config = self._provider.config - srp = LeapSecureRemotePassword(ca_bundle=which_api_CA_bundle(self._provider), timeout_in_s=config.timeout_in_s) - srp_session = srp.authenticate(self._provider.api_uri, credentials.user_name, credentials.password) - return srp_session - - def register(self, credentials): - config = self._provider.config - srp = LeapSecureRemotePassword(ca_bundle=which_api_CA_bundle(self._provider), timeout_in_s=config.timeout_in_s) - srp.register(self._provider.api_uri, credentials.user_name, credentials.password) diff --git a/service/pixelated/bitmask_libraries/leap_srp.py b/service/pixelated/bitmask_libraries/leap_srp.py deleted file mode 100644 index 7a627c1d..00000000 --- a/service/pixelated/bitmask_libraries/leap_srp.py +++ /dev/null @@ -1,147 +0,0 @@ -# -# Copyright (c) 2014 ThoughtWorks, Inc. -# -# Pixelated is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pixelated is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Pixelated. If not, see . -import binascii -import json - -import requests -from requests import Session -from srp import User, srp, create_salted_verification_key -from requests.exceptions import HTTPError, SSLError, Timeout -from config import SYSTEM_CA_BUNDLE - - -REGISTER_USER_LOGIN_KEY = 'user[login]' -REGISTER_USER_VERIFIER_KEY = 'user[password_verifier]' -REGISTER_USER_SALT_KEY = 'user[password_salt]' - - -class LeapAuthException(Exception): - def __init__(self, *args, **kwargs): - super(LeapAuthException, self).__init__(*args, **kwargs) - - -class LeapSRPSession(object): - def __init__(self, user_name, api_server_name, uuid, token, session_id, api_version='1'): - self.user_name = user_name - self.api_server_name = api_server_name - self.uuid = uuid - self.token = token - self.session_id = session_id - self.api_version = api_version - - def __str__(self): - return 'LeapSRPSession(%s, %s, %s, %s, %s, %s)' % (self.user_name, self.api_server_name, self.uuid, self.token, self.session_id, self.api_version) - - -class LeapSecureRemotePassword(object): - def __init__(self, hash_alg=srp.SHA256, ng_type=srp.NG_1024, ca_bundle=SYSTEM_CA_BUNDLE, timeout_in_s=15, - leap_api_version='1'): - - self.hash_alg = hash_alg - self.ng_type = ng_type - self.timeout_in_s = timeout_in_s - self.ca_bundle = ca_bundle - self.leap_api_version = leap_api_version - - def authenticate(self, api_uri, username, password): - session = Session() - try: - return self._authenticate_with_session(session, api_uri, username, password) - except Timeout, e: - raise LeapAuthException(e) - finally: - session.close() - - def _authenticate_with_session(self, http_session, api_uri, username, password): - try: - srp_user = User(username.encode('utf-8'), password.encode('utf-8'), self.hash_alg, self.ng_type) - - salt, B_challenge = self._begin_authentication(srp_user, http_session, api_uri) - M2_verfication_code, leap_session = self._process_challenge(srp_user, http_session, api_uri, salt, - B_challenge) - self._verify_session(srp_user, M2_verfication_code) - - return leap_session - except (HTTPError, SSLError), e: - raise LeapAuthException(e) - - def _begin_authentication(self, user, session, api_uri): - _, A = user.start_authentication() - - auth_data = { - "login": user.get_username(), - "A": binascii.hexlify(A) - } - session_url = '%s/%s/sessions' % (api_uri, self.leap_api_version) - response = session.post(session_url, data=auth_data, verify=self.ca_bundle, timeout=self.timeout_in_s) - response.raise_for_status() - json_content = json.loads(response.content) - - salt = _safe_unhexlify(json_content.get('salt')) - B = _safe_unhexlify(json_content.get('B')) - - return salt, B - - def _process_challenge(self, user, session, api_uri, salt, B): - M = user.process_challenge(salt, B) - - auth_data = { - "client_auth": binascii.hexlify(M) - } - - auth_url = '%s/%s/sessions/%s' % (api_uri, self.leap_api_version, user.get_username()) - response = session.put(auth_url, data=auth_data, verify=self.ca_bundle, timeout=self.timeout_in_s) - response.raise_for_status() - auth_json = json.loads(response.content) - - M2 = _safe_unhexlify(auth_json.get('M2')) - uuid = auth_json.get('id') - token = auth_json.get('token') - session_id = response.cookies.get('_session_id') - - return M2, LeapSRPSession(user.get_username(), api_uri, uuid, token, session_id) - - def _verify_session(self, user, M2): - user.verify_session(M2) - if not user.authenticated(): - raise LeapAuthException() - - def register(self, api_uri, username, password): - try: - salt, verifier = create_salted_verification_key(username, password, self.hash_alg, self.ng_type) - return self._post_registration_data(api_uri, username, salt, verifier) - except (HTTPError, SSLError, Timeout), e: - raise LeapAuthException(e) - - def _post_registration_data(self, api_uri, username, salt, verifier): - users_url = '%s/%s/users' % (api_uri, self.leap_api_version) - - user_data = { - REGISTER_USER_LOGIN_KEY: username, - REGISTER_USER_SALT_KEY: binascii.hexlify(salt), - REGISTER_USER_VERIFIER_KEY: binascii.hexlify(verifier) - } - - response = requests.post(users_url, data=user_data, verify=self.ca_bundle, timeout=self.timeout_in_s) - response.raise_for_status() - reg_json = json.loads(response.content) - - return reg_json['ok'] - - -def _safe_unhexlify(hex_str): - return binascii.unhexlify(hex_str) \ - if (len(hex_str) % 2 == 0) else binascii.unhexlify('0' + hex_str) diff --git a/service/pixelated/bitmask_libraries/nicknym.py b/service/pixelated/bitmask_libraries/nicknym.py index ef846bba..bee90897 100644 --- a/service/pixelated/bitmask_libraries/nicknym.py +++ b/service/pixelated/bitmask_libraries/nicknym.py @@ -18,14 +18,14 @@ from .certs import which_api_CA_bundle class NickNym(object): - def __init__(self, provider, config, soledad_session, srp_session): + def __init__(self, provider, config, soledad_session, username, token, uuid): nicknym_url = _discover_nicknym_server(provider) - self._email = '%s@%s' % (srp_session.user_name, provider.domain) - self.keymanager = KeyManager('%s@%s' % (srp_session.user_name, provider.domain), nicknym_url, + self._email = '%s@%s' % (username, provider.domain) + self.keymanager = KeyManager('%s@%s' % (username, provider.domain), nicknym_url, soledad_session.soledad, - srp_session.token, which_api_CA_bundle(provider), provider.api_uri, + token, which_api_CA_bundle(provider), provider.api_uri, provider.api_version, - srp_session.uuid, config.gpg_binary) + uuid, config.gpg_binary) def generate_openpgp_key(self): if not self._key_exists(self._email): diff --git a/service/pixelated/bitmask_libraries/provider.py b/service/pixelated/bitmask_libraries/provider.py index a0bf4843..1564c974 100644 --- a/service/pixelated/bitmask_libraries/provider.py +++ b/service/pixelated/bitmask_libraries/provider.py @@ -25,6 +25,7 @@ class LeapProvider(object): def __init__(self, server_name, config): self.server_name = server_name self.config = config + self.local_ca_crt = '%s/ca.crt' % self.config.leap_home self.provider_json = self.fetch_provider_json() @@ -62,12 +63,13 @@ class LeapProvider(object): if 'mx' not in self.services: raise Exception - def download_certificate_to(self, filename): + def download_certificate(self, filename=None): """ Downloads the server certificate, validates it against the provided fingerprint and stores it to file """ + path = filename or self.local_ca_crt cert = self.fetch_valid_certificate() - with open(filename, 'w') as out: + with open(path, 'w') as out: out.write(cert) def fetch_valid_certificate(self): diff --git a/service/pixelated/bitmask_libraries/register.py b/service/pixelated/bitmask_libraries/register.py index bb54477f..add58acc 100644 --- a/service/pixelated/bitmask_libraries/register.py +++ b/service/pixelated/bitmask_libraries/register.py @@ -21,14 +21,15 @@ import os.path import pixelated.bitmask_libraries.session as LeapSession from pixelated.bitmask_libraries.config import LeapConfig from pixelated.bitmask_libraries.provider import LeapProvider -from pixelated.bitmask_libraries.auth import LeapAuthenticator, LeapCredentials +from leap.srp_auth import SRPAuth def register_new_user(username, server_name): config = LeapConfig() provider = LeapProvider(server_name, config) password = getpass.getpass('Please enter password for %s: ' % username) - LeapAuthenticator(provider).register(LeapCredentials(username, password)) + srp_auth = SRPAuth(provider, provider.local_ca_crt) + srp_auth.register(username, password) session = LeapSession.open(username, password, server_name) session.nicknym.generate_openpgp_key() diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py index eb6f39d7..f8e2b03e 100644 --- a/service/pixelated/bitmask_libraries/session.py +++ b/service/pixelated/bitmask_libraries/session.py @@ -27,7 +27,8 @@ from pixelated.bitmask_libraries.provider import LeapProvider from pixelated.bitmask_libraries.certs import refresh_ca_bundle from twisted.internet import reactor from .nicknym import NickNym -from .auth import LeapAuthenticator, LeapCredentials +from leap.srp_auth import SRPAuth +# from .auth import LeapAuthenticator, LeapCredentials from .soledad import SoledadSessionFactory, SoledadSession from .smtp import LeapSmtp from .config import DEFAULT_LEAP_HOME @@ -40,7 +41,7 @@ def open(username, password, server_name, leap_home=DEFAULT_LEAP_HOME): config = LeapConfig(leap_home=leap_home) provider = LeapProvider(server_name, config) refresh_ca_bundle(provider) - session = LeapSessionFactory(provider).create(LeapCredentials(username, password)) + session = LeapSessionFactory(provider).create(username, password) return session @@ -65,7 +66,7 @@ class LeapSession(object): - ``incoming_mail_fetcher`` Background job for fetching incoming mails from LEAP server (LeapIncomingMail) """ - def __init__(self, provider, srp_session, soledad_session, nicknym, soledad_account, incoming_mail_fetcher, smtp): + def __init__(self, provider, user_auth, soledad_session, nicknym, soledad_account, incoming_mail_fetcher, smtp): """ Constructor. @@ -76,7 +77,7 @@ class LeapSession(object): self.smtp = smtp self.config = provider.config self.provider = provider - self.srp_session = srp_session + self.user_auth = user_auth self.soledad_session = soledad_session self.nicknym = nicknym self.account = soledad_account @@ -87,7 +88,7 @@ class LeapSession(object): def account_email(self): domain = self.provider.domain - name = self.srp_session.user_name + name = self.user_auth.username return '%s@%s' % (name, domain) def close(self): @@ -112,28 +113,29 @@ class LeapSessionFactory(object): self._provider = provider self._config = provider.config - def create(self, credentials): - key = self._session_key(credentials) + def create(self, username, password): + key = self._session_key(username) session = self._lookup_session(key) if not session: - session = self._create_new_session(credentials) + session = self._create_new_session(username, password) self._remember_session(key, session) return session - def _create_new_session(self, credentials): + def _create_new_session(self, username, password): self._create_dir(self._provider.config.leap_home) - self._provider.download_certificate_to('%s/ca.crt' % self._provider.config.leap_home) + self._provider.download_certificate() - auth = LeapAuthenticator(self._provider).authenticate(credentials) - soledad = SoledadSessionFactory.create(self._provider, auth, credentials.db_passphrase) + srp_auth = SRPAuth(self._provider.api_uri, self._provider.local_ca_crt) + auth = srp_auth.authenticate(username, password) - nicknym = self._create_nicknym(auth, soledad) - account = self._create_account(auth, soledad) - incoming_mail_fetcher = self._create_incoming_mail_fetcher(nicknym, soledad, - account, auth) + soledad = SoledadSessionFactory.create(self._provider, auth.token, auth.uuid, password) - smtp = LeapSmtp(self._provider, nicknym.keymanager, auth) + nicknym = self._create_nicknym(auth.username, auth.token, auth.uuid, soledad) + account = self._create_account(auth.uuid, soledad) + incoming_mail_fetcher = self._create_incoming_mail_fetcher(nicknym, soledad, account, auth.username) + + smtp = LeapSmtp(self._provider, auth.username, auth.session_id, nicknym.keymanager) smtp.ensure_running() @@ -150,8 +152,8 @@ class LeapSessionFactory(object): global SESSIONS SESSIONS[key] = session - def _session_key(self, credentials): - return hash((self._provider, credentials.user_name)) + def _session_key(self, username): + return hash((self._provider, username)) def _create_dir(self, path): try: @@ -162,21 +164,17 @@ class LeapSessionFactory(object): else: raise - def _create_soledad_session(self, srp_session, db_passphrase): - return SoledadSession(self._provider, db_passphrase, srp_session) - - def _create_nicknym(self, srp_session, soledad_session): - return NickNym(self._provider, self._config, soledad_session, srp_session) + def _create_nicknym(self, username, token, uuid, soledad_session): + return NickNym(self._provider, self._config, soledad_session, username, token, uuid) - def _create_account(self, srp_session, soledad_session): + def _create_account(self, uuid, soledad_session): memstore = MemoryStore(permanent_store=SoledadStore(soledad_session.soledad)) - return SoledadBackedAccount(srp_session.uuid, soledad_session.soledad, memstore) + return SoledadBackedAccount(uuid, soledad_session.soledad, memstore) - def _create_incoming_mail_fetcher(self, nicknym, soledad_session, account, auth): + def _create_incoming_mail_fetcher(self, nicknym, soledad_session, account, username): return LeapIncomingMail(nicknym.keymanager, soledad_session.soledad, account, - self._config.fetch_interval_in_s, self._account_email(auth)) + self._config.fetch_interval_in_s, self._account_email(username)) - def _account_email(self, auth): + def _account_email(self, username): domain = self._provider.domain - name = auth.user_name - return '%s@%s' % (name, domain) + return '%s@%s' % (username, domain) diff --git a/service/pixelated/bitmask_libraries/smtp.py b/service/pixelated/bitmask_libraries/smtp.py index ba5e7102..a0f9c6e4 100644 --- a/service/pixelated/bitmask_libraries/smtp.py +++ b/service/pixelated/bitmask_libraries/smtp.py @@ -27,10 +27,11 @@ class LeapSmtp(object): TWISTED_PORT = 4650 - def __init__(self, provider, keymanager=None, leap_srp_session=None): + def __init__(self, provider, username, session_id, keymanager=None): self._provider = provider + self.username = username + self.session_id = session_id self._keymanager = keymanager - self._srp_session = leap_srp_session self._hostname, self._port = self._discover_smtp_server() self._smtp_port = None self._smtp_service = None @@ -56,7 +57,7 @@ class LeapSmtp(object): os.makedirs(os.path.dirname(cert_path)) cert_url = '%s/%s/cert' % (self._provider.api_uri, self._provider.api_version) - cookies = {"_session_id": self._srp_session.session_id} + cookies = {"_session_id": self.session_id} response = requests.get(cert_url, verify=which_api_CA_bundle(self._provider), cookies=cookies, timeout=self._provider.config.timeout_in_s) response.raise_for_status() @@ -76,7 +77,7 @@ class LeapSmtp(object): def start(self): self._download_client_certificates() cert_path = self._client_cert_path() - email = '%s@%s' % (self._srp_session.user_name, self._provider.domain) + email = '%s@%s' % (self.username, self._provider.domain) self._smtp_service, self._smtp_port = setup_smtp_gateway( port=self.TWISTED_PORT, diff --git a/service/pixelated/bitmask_libraries/soledad.py b/service/pixelated/bitmask_libraries/soledad.py index 83a8caa9..f3fca95a 100644 --- a/service/pixelated/bitmask_libraries/soledad.py +++ b/service/pixelated/bitmask_libraries/soledad.py @@ -46,15 +46,16 @@ class LeapKeyManager(object): class SoledadSessionFactory(object): @classmethod - def create(cls, provider, srp_session, encryption_passphrase): - return SoledadSession(provider, encryption_passphrase, srp_session) + def create(cls, provider, user_token, user_uuid, encryption_passphrase): + return SoledadSession(provider, encryption_passphrase, user_token, user_uuid) class SoledadSession(object): - def __init__(self, provider, encryption_passphrase, leap_srp_session): + def __init__(self, provider, encryption_passphrase, user_token, user_uuid): self.provider = provider self.config = provider.config - self.leap_srp_session = leap_srp_session + self.user_uuid = user_uuid + self.user_token = user_token self.soledad = self._init_soledad(encryption_passphrase) @@ -66,8 +67,8 @@ class SoledadSession(object): secrets = self._secrets_path() local_db = self._local_db_path() - return Soledad(self.leap_srp_session.uuid, unicode(encryption_passphrase), secrets, - local_db, server_url, which_api_CA_bundle(self.provider), self.leap_srp_session.token, defer_encryption=False) + return Soledad(self.user_uuid, unicode(encryption_passphrase), secrets, + local_db, server_url, which_api_CA_bundle(self.provider), self.user_token, defer_encryption=False) except (WrongMac, UnknownMacMethod), e: raise SoledadWrongPassphraseException(e) @@ -76,10 +77,10 @@ class SoledadSession(object): return "%s/soledad" % self.config.leap_home def _secrets_path(self): - return "%s/%s.secret" % (self._leap_path(), self.leap_srp_session.uuid) + return "%s/%s.secret" % (self._leap_path(), self.user_uuid) def _local_db_path(self): - return "%s/%s.db" % (self._leap_path(), self.leap_srp_session.uuid) + return "%s/%s.db" % (self._leap_path(), self.user_uuid) def _create_database_dir(self): try: @@ -102,7 +103,7 @@ class SoledadSession(object): host = hosts.keys()[0] server_url = 'https://%s:%d/user-%s' % \ (hosts[host]['hostname'], hosts[host]['port'], - self.leap_srp_session.uuid) + self.user_uuid) return server_url except Exception, e: raise SoledadDiscoverException(e) diff --git a/service/pixelated/config/app_factory.py b/service/pixelated/config/app_factory.py index 51f76741..96412386 100644 --- a/service/pixelated/config/app_factory.py +++ b/service/pixelated/config/app_factory.py @@ -31,7 +31,6 @@ from pixelated.adapter.search import SearchEngine from pixelated.adapter.services.draft_service import DraftService from pixelated.adapter.listeners.mailbox_indexer_listener import MailboxIndexerListener import pixelated.bitmask_libraries.session as LeapSession -from pixelated.bitmask_libraries.leap_srp import LeapAuthException from requests.exceptions import ConnectionError from leap.common.events import ( register, @@ -70,10 +69,6 @@ def init_leap_session(app, leap_home): print("Can't connect to the requested provider", error) reactor.stop() sys.exit(1) - except LeapAuthException, e: - print("Couldn't authenticate with the credentials provided %s" % e.message) - reactor.stop() - sys.exit(1) return leap_session diff --git a/service/pixelated/config/register.py b/service/pixelated/config/register.py index d54b10ff..8a7caa8b 100644 --- a/service/pixelated/config/register.py +++ b/service/pixelated/config/register.py @@ -15,7 +15,6 @@ # along with Pixelated. If not, see . import re -from pixelated.bitmask_libraries.leap_srp import LeapAuthException from pixelated.bitmask_libraries.register import register_new_user @@ -23,8 +22,6 @@ def register(username, server_name): try: validate_username(username) register_new_user(username, server_name) - except LeapAuthException: - print('User already exists') except ValueError: print('Only lowercase letters, digits, . - and _ allowed.') -- cgit v1.2.3