From 006d753c391d82baa634f112e5d8d06b61eeaaeb Mon Sep 17 00:00:00 2001 From: Bruno Wagner Date: Mon, 8 Jun 2015 20:34:29 -0300 Subject: Heavy rework on certs, removed most of it, simplified the logic --- service/pixelated/bitmask_libraries/certs.py | 66 ++-------------------- service/pixelated/bitmask_libraries/config.py | 48 ++-------------- service/pixelated/bitmask_libraries/nicknym.py | 2 +- service/pixelated/bitmask_libraries/provider.py | 6 +- service/pixelated/bitmask_libraries/session.py | 13 ----- service/pixelated/bitmask_libraries/smtp.py | 2 +- service/pixelated/bitmask_libraries/soledad.py | 2 +- service/pixelated/config/arguments.py | 4 +- service/pixelated/config/leap.py | 21 ++++--- service/pixelated/register.py | 2 +- service/test/unit/bitmask_libraries/test_certs.py | 30 ++++------ .../test/unit/bitmask_libraries/test_nicknym.py | 21 ++++--- .../test/unit/bitmask_libraries/test_provider.py | 18 +++--- 13 files changed, 66 insertions(+), 169 deletions(-) diff --git a/service/pixelated/bitmask_libraries/certs.py b/service/pixelated/bitmask_libraries/certs.py index 2535b747..ed6233c1 100644 --- a/service/pixelated/bitmask_libraries/certs.py +++ b/service/pixelated/bitmask_libraries/certs.py @@ -16,9 +16,6 @@ import os import requests import json -from leap.common import ca_bundle - -from .config import AUTO_DETECT_CA_BUNDLE class LeapCertificate(object): @@ -40,70 +37,17 @@ class LeapCertificate(object): LeapCertificate.LEAP_FINGERPRINT = cert_fingerprint LeapCertificate.LEAP_CERT = False - def auto_detect_bootstrap_ca_bundle(self): - if self.LEAP_CERT is not None: - return self.LEAP_CERT - - if self._config.bootstrap_ca_cert_bundle == AUTO_DETECT_CA_BUNDLE: - local_cert = self._local_bootstrap_server_cert() - if local_cert: - return local_cert - else: - return ca_bundle.where() - else: - return self._config.bootstrap_ca_cert_bundle - + @property def api_ca_bundle(self): - if self._provider.config.ca_cert_bundle: - return self._provider.config.ca_cert_bundle - - cert_file = self._api_cert_file() + return os.path.join(self._provider.config.leap_home, 'providers', self._server_name, 'keys', 'client', 'api.pem') - if not os.path.isfile(cert_file): - self._download_server_cert(cert_file) - - return cert_file - - def refresh_ca_bundle(self): - cert_file = self._api_cert_file() - self._download_server_cert(cert_file) - - def _api_cert_file(self): - certs_root = self._api_certs_root_path() - return os.path.join(certs_root, 'api.pem') - - def _api_certs_root_path(self): + def setup_ca_bundle(self): path = os.path.join(self._provider.config.leap_home, 'providers', self._server_name, 'keys', 'client') if not os.path.isdir(path): os.makedirs(path, 0700) - return path - - def _local_bootstrap_server_cert(self): - cert_file = self._bootstrap_certs_cert_file() - if os.path.isfile(cert_file): - return cert_file + self._download_cert(self.api_ca_bundle) - response = requests.get('https://%s/provider.json' % self._server_name) - provider_data = json.loads(response.content) - ca_cert_uri = str(provider_data['ca_cert_uri']) - - response = requests.get(ca_cert_uri) - with open(cert_file, 'w') as file: - file.write(response.content) - - return cert_file - - def _bootstrap_certs_cert_file(self): - path = os.path.join(self._provider.config.leap_home, 'providers', self._server_name) - if not os.path.isdir(path): - os.makedirs(path, 0700) - - file_path = os.path.join(path, '%s.ca.crt' % self._server_name) - - return file_path - - def _download_server_cert(self, cert_file_name): + def _download_cert(self, cert_file_name): cert = self._provider.fetch_valid_certificate() - with open(cert_file_name, 'w') as file: file.write(cert) diff --git a/service/pixelated/bitmask_libraries/config.py b/service/pixelated/bitmask_libraries/config.py index 8c862d0a..efb43411 100644 --- a/service/pixelated/bitmask_libraries/config.py +++ b/service/pixelated/bitmask_libraries/config.py @@ -13,10 +13,9 @@ # # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see . -from distutils.spawn import find_executable import os -from os.path import expanduser +from distutils.spawn import find_executable def discover_gpg_binary(): @@ -30,54 +29,19 @@ def discover_gpg_binary(): return path -DEFAULT_LEAP_HOME = os.path.join(expanduser("~"), '.leap') - SYSTEM_CA_BUNDLE = True -AUTO_DETECT_CA_BUNDLE = None class LeapConfig(object): - """ - LEAP client configuration - """ - - def __init__(self, leap_home=DEFAULT_LEAP_HOME, bootstrap_ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, - ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, verify_ssl=True, + def __init__(self, + leap_home=None, fetch_interval_in_s=30, - timeout_in_s=15, start_background_jobs=False, gpg_binary=discover_gpg_binary()): - """ - Constructor. - - :param server_name: The LEAP server name, e.g. demo.leap.se - :type server_name: str - - :param user_name: The LEAP account user name, normally the first part of your email, e.g. foobar for foobar@demo.leap.se - :type user_name: str - - :param user_password: The LEAP account password - :type user_password: str - - :param db_passphrase: The passphrase used to encrypt the local soledad database - :type db_passphrase: str - - :param verify_ssl: Set to false to disable strict SSL certificate validation - :type verify_ssl: bool - - :param fetch_interval_in_s: Polling interval for fetching incoming mail from LEAP server - :type fetch_interval_in_s: int - - :param timeout_in_s: Timeout for network operations, e.g. HTTP calls - :type timeout_in_s: int - - :param gpg_binary: Path to the GPG binary (must not be a symlink) - :type gpg_binary: str + timeout_in_s=15, + start_background_jobs=False, + gpg_binary=discover_gpg_binary()): - """ self.leap_home = leap_home - self.bootstrap_ca_cert_bundle = bootstrap_ca_cert_bundle - self.ca_cert_bundle = ca_cert_bundle - self.verify_ssl = verify_ssl self.timeout_in_s = timeout_in_s self.start_background_jobs = start_background_jobs self.gpg_binary = gpg_binary diff --git a/service/pixelated/bitmask_libraries/nicknym.py b/service/pixelated/bitmask_libraries/nicknym.py index d7c9c7af..8220d006 100644 --- a/service/pixelated/bitmask_libraries/nicknym.py +++ b/service/pixelated/bitmask_libraries/nicknym.py @@ -23,7 +23,7 @@ class NickNym(object): self._email = '%s@%s' % (username, provider.domain) self.keymanager = KeyManager('%s@%s' % (username, provider.domain), nicknym_url, soledad_session.soledad, - token, LeapCertificate(provider).api_ca_bundle(), provider.api_uri, + token, LeapCertificate(provider).api_ca_bundle, provider.api_uri, provider.api_version, uuid, config.gpg_binary) diff --git a/service/pixelated/bitmask_libraries/provider.py b/service/pixelated/bitmask_libraries/provider.py index 38df504e..0129480c 100644 --- a/service/pixelated/bitmask_libraries/provider.py +++ b/service/pixelated/bitmask_libraries/provider.py @@ -100,7 +100,7 @@ class LeapProvider(object): session = requests.session() try: session.mount('https://', EnforceTLSv1Adapter(assert_fingerprint=LeapCertificate.LEAP_FINGERPRINT)) - response = session.get(url, verify=LeapCertificate(self).auto_detect_bootstrap_ca_bundle(), timeout=self.config.timeout_in_s) + response = session.get(url, verify=LeapCertificate.LEAP_CERT, timeout=self.config.timeout_in_s) response.raise_for_status() return response finally: @@ -115,14 +115,14 @@ class LeapProvider(object): def fetch_soledad_json(self): service_url = "%s/%s/config/soledad-service.json" % ( self.api_uri, self.api_version) - response = requests.get(service_url, verify=LeapCertificate(self).api_ca_bundle(), timeout=self.config.timeout_in_s) + response = requests.get(service_url, verify=LeapCertificate(self).api_ca_bundle, timeout=self.config.timeout_in_s) response.raise_for_status() return json.loads(response.content) def fetch_smtp_json(self): service_url = '%s/%s/config/smtp-service.json' % ( self.api_uri, self.api_version) - response = requests.get(service_url, verify=LeapCertificate(self).api_ca_bundle(), timeout=self.config.timeout_in_s) + response = requests.get(service_url, verify=LeapCertificate(self).api_ca_bundle, timeout=self.config.timeout_in_s) response.raise_for_status() return json.loads(response.content) diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py index 09bf277d..ad01d495 100644 --- a/service/pixelated/bitmask_libraries/session.py +++ b/service/pixelated/bitmask_libraries/session.py @@ -22,29 +22,16 @@ from leap.mail.imap.fetch import LeapIncomingMail from leap.mail.imap.account import SoledadBackedAccount from leap.mail.imap.memorystore import MemoryStore from leap.mail.imap.soledadstore import SoledadStore -from pixelated.bitmask_libraries.config import LeapConfig -from pixelated.bitmask_libraries.provider import LeapProvider -from pixelated.bitmask_libraries.certs import LeapCertificate from twisted.internet import reactor from .nicknym import NickNym from leap.auth import SRPAuth from .soledad import SoledadSessionFactory from .smtp import LeapSmtp -from .config import DEFAULT_LEAP_HOME SESSIONS = {} -def open_leap_session(username, password, server_name, leap_home=DEFAULT_LEAP_HOME): - config = LeapConfig(leap_home=leap_home) - provider = LeapProvider(server_name, config) - LeapCertificate(provider).refresh_ca_bundle() - session = LeapSessionFactory(provider).create(username, password) - - return session - - class LeapSession(object): """ A LEAP session. diff --git a/service/pixelated/bitmask_libraries/smtp.py b/service/pixelated/bitmask_libraries/smtp.py index 4b6ec719..745d88ef 100644 --- a/service/pixelated/bitmask_libraries/smtp.py +++ b/service/pixelated/bitmask_libraries/smtp.py @@ -61,7 +61,7 @@ class LeapSmtp(object): response = requests.get( cert_url, - verify=LeapCertificate(self._provider).api_ca_bundle(), + verify=LeapCertificate(self._provider).api_ca_bundle, cookies=cookies, timeout=self._provider.config.timeout_in_s) response.raise_for_status() diff --git a/service/pixelated/bitmask_libraries/soledad.py b/service/pixelated/bitmask_libraries/soledad.py index 207b3e73..2e0219da 100644 --- a/service/pixelated/bitmask_libraries/soledad.py +++ b/service/pixelated/bitmask_libraries/soledad.py @@ -67,7 +67,7 @@ class SoledadSession(object): local_db = self._local_db_path() return Soledad(self.user_uuid, unicode(encryption_passphrase), secrets, - local_db, server_url, LeapCertificate(self.provider).api_ca_bundle(), self.user_token, defer_encryption=False) + local_db, server_url, LeapCertificate(self.provider).api_ca_bundle, self.user_token, defer_encryption=False) except (WrongMac, UnknownMacMethod), e: raise SoledadWrongPassphraseException(e) diff --git a/service/pixelated/config/arguments.py b/service/pixelated/config/arguments.py index 8899f9a8..72ae6141 100644 --- a/service/pixelated/config/arguments.py +++ b/service/pixelated/config/arguments.py @@ -14,8 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see . +import os import argparse -from pixelated.bitmask_libraries.config import DEFAULT_LEAP_HOME def parse_user_agent_args(): @@ -57,6 +57,6 @@ def parser_add_default_arguments(parser): parser.add_argument('--debug', action='store_true', help='DEBUG mode.') parser.add_argument('--organization-mode', help='Runs the user agent in organization mode, the credentials will be received from the stdin', default=False, action='store_true', dest='organization_mode') parser.add_argument('-c', '--config', dest='credentials_file', metavar='', default=None, help='use specified file for credentials (for test purposes only)') - parser.add_argument('--leap-home', help='The folder where the user agent stores its data. Defaults to ~/.leap', dest='leap_home', default=DEFAULT_LEAP_HOME) + parser.add_argument('--leap-home', help='The folder where the user agent stores its data. Defaults to ~/.leap', dest='leap_home', default=os.path.join(os.path.expanduser("~"), '.leap')) parser.add_argument('-lc', '--leap-provider-cert', metavar='', default=None, help='use specified file for LEAP provider cert authority certificate (url https:///ca.crt)') parser.add_argument('-lf', '--leap-provider-cert-fingerprint', metavar='', default=None, help='use specified fingerprint to validate connection with LEAP provider', dest='leap_provider_cert_fingerprint') diff --git a/service/pixelated/config/leap.py b/service/pixelated/config/leap.py index 7a383b17..f2c025ba 100644 --- a/service/pixelated/config/leap.py +++ b/service/pixelated/config/leap.py @@ -2,8 +2,10 @@ from __future__ import absolute_import import random from pixelated.config import credentials from leap.common.events import server as events_server +from pixelated.bitmask_libraries.config import LeapConfig from pixelated.bitmask_libraries.certs import LeapCertificate -from pixelated.bitmask_libraries.session import open_leap_session +from pixelated.bitmask_libraries.provider import LeapProvider +from pixelated.bitmask_libraries.session import LeapSessionFactory def initialize_leap(leap_provider_cert, @@ -12,21 +14,18 @@ def initialize_leap(leap_provider_cert, organization_mode, leap_home): init_monkeypatches() - provider, user, password = credentials.read(organization_mode, credentials_file) - LeapCertificate.set_cert_and_fingerprint(leap_provider_cert, leap_provider_cert_fingerprint) events_server.ensure_server(random.randrange(8000, 11999)) - leap_session = create_leap_session(provider, user, password, leap_home) - leap_session.start_background_jobs() - return leap_session + provider, username, password = credentials.read(organization_mode, credentials_file) + LeapCertificate.set_cert_and_fingerprint(leap_provider_cert, leap_provider_cert_fingerprint) + config = LeapConfig(leap_home=leap_home) + provider = LeapProvider(provider, config) + LeapCertificate(provider).setup_ca_bundle() + leap_session = LeapSessionFactory(provider).create(username, password) -def create_leap_session(provider, username, password, leap_home): - leap_session = open_leap_session(username, - password, - provider, - leap_home) leap_session.soledad_session.soledad.sync(defer_decryption=False) leap_session.nicknym.generate_openpgp_key() + leap_session.start_background_jobs() return leap_session diff --git a/service/pixelated/register.py b/service/pixelated/register.py index 576c069d..9fa98137 100644 --- a/service/pixelated/register.py +++ b/service/pixelated/register.py @@ -37,7 +37,7 @@ def register(server_name, username): config = LeapConfig() provider = LeapProvider(server_name, config) password = getpass.getpass('Please enter password for %s: ' % username) - srp_auth = SRPAuth(provider.api_uri, LeapCertificate(provider).api_ca_bundle()) + srp_auth = SRPAuth(provider.api_uri, LeapCertificate(provider).api_ca_bundle) if srp_auth.register(username, password): session = leap_session.open_leap_session(username, password, server_name) diff --git a/service/test/unit/bitmask_libraries/test_certs.py b/service/test/unit/bitmask_libraries/test_certs.py index 814f083f..150a1f14 100644 --- a/service/test/unit/bitmask_libraries/test_certs.py +++ b/service/test/unit/bitmask_libraries/test_certs.py @@ -1,33 +1,27 @@ import unittest from pixelated.bitmask_libraries.certs import LeapCertificate -from pixelated.bitmask_libraries.config import AUTO_DETECT_CA_BUNDLE from mock import MagicMock, patch class CertsTest(unittest.TestCase): - @patch('pixelated.bitmask_libraries.certs.os.path.isfile') - @patch('pixelated.bitmask_libraries.certs.os.path.isdir') - def test_that_which_bootstrap_cert_bundle_returns_string(self, mock_isdir, mock_isfile): - mock_isfile.return_value = True - mock_isdir.return_value = True - config = MagicMock(bootstrap_ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, leap_home='/leap/home') - provider = MagicMock(server_name=u'test.leap.net', config=config) + def test_set_cert_and_fingerprint_sets_cert(self): + LeapCertificate.set_cert_and_fingerprint('some cert', None) - bundle = LeapCertificate(provider).auto_detect_bootstrap_ca_bundle() + self.assertIsNone(LeapCertificate.LEAP_FINGERPRINT) + self.assertEqual('some cert', LeapCertificate.LEAP_CERT) - self.assertEqual('/leap/home/providers/test.leap.net/test.leap.net.ca.crt', bundle) + def test_set_cert_and_fingerprint_sets_fingerprint(self): + LeapCertificate.set_cert_and_fingerprint(None, 'fingerprint') - @patch('pixelated.bitmask_libraries.certs.os.path.isfile') - @patch('pixelated.bitmask_libraries.certs.os.path.isdir') - def test_that_which_bundle_returns_string(self, mock_isdir, mock_isfile): - mock_isfile.return_value = True - mock_isdir.return_value = True + self.assertEqual('fingerprint', LeapCertificate.LEAP_FINGERPRINT) + self.assertFalse(LeapCertificate.LEAP_CERT) - config = MagicMock(bootstrap_ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, ca_cert_bundle=None, leap_home='/some/leap/home') + def test_api_ca_bundle(self): + config = MagicMock(leap_home='/some/leap/home') provider = MagicMock(server_name=u'test.leap.net', config=config) - bundle = LeapCertificate(provider).api_ca_bundle() + cert = LeapCertificate(provider).api_ca_bundle - self.assertEqual('/some/leap/home/providers/test.leap.net/keys/client/api.pem', bundle) + self.assertEqual('/some/leap/home/providers/test.leap.net/keys/client/api.pem', cert) diff --git a/service/test/unit/bitmask_libraries/test_nicknym.py b/service/test/unit/bitmask_libraries/test_nicknym.py index b892c22c..4e683494 100644 --- a/service/test/unit/bitmask_libraries/test_nicknym.py +++ b/service/test/unit/bitmask_libraries/test_nicknym.py @@ -15,16 +15,17 @@ # along with Pixelated. If not, see . from mock import patch +from test_abstract_leap import AbstractLeapTest from leap.keymanager import openpgp, KeyNotFound from pixelated.bitmask_libraries.nicknym import NickNym -from test_abstract_leap import AbstractLeapTest +from pixelated.bitmask_libraries.certs import LeapCertificate class NickNymTest(AbstractLeapTest): @patch('pixelated.bitmask_libraries.nicknym.KeyManager.__init__', return_value=None) - def test_that_keymanager_is_created(self, init_mock): + def test_that_keymanager_is_created(self, keymanager_init_mock): # given - + LeapCertificate.api_ca_bundle = '/some/path/to/provider_ca_cert' # when NickNym(self.provider, self.config, @@ -34,10 +35,16 @@ class NickNymTest(AbstractLeapTest): self.auth.uuid) # then - init_mock.assert_called_with('test_user@some-server.test', 'https://nicknym.some-server.test:6425/', - self.soledad, self.auth.token, '/some/path/to/provider_ca_cert', - 'https://api.some-server.test:4430', '1', self.auth.uuid, - '/path/to/gpg') + keymanager_init_mock.assert_called_with( + 'test_user@some-server.test', + 'https://nicknym.some-server.test:6425/', + self.soledad, + self.auth.token, + '/some/path/to/provider_ca_cert', + 'https://api.some-server.test:4430', + '1', + self.auth.uuid, + '/path/to/gpg') @patch('pixelated.bitmask_libraries.nicknym.KeyManager') def test_gen_key(self, keymanager_mock): diff --git a/service/test/unit/bitmask_libraries/test_provider.py b/service/test/unit/bitmask_libraries/test_provider.py index 49627565..320fece2 100644 --- a/service/test/unit/bitmask_libraries/test_provider.py +++ b/service/test/unit/bitmask_libraries/test_provider.py @@ -20,6 +20,7 @@ from httmock import all_requests, HTTMock, urlmatch from requests import HTTPError from pixelated.bitmask_libraries.config import LeapConfig from pixelated.bitmask_libraries.provider import LeapProvider +from pixelated.bitmask_libraries.certs import LeapCertificate from test_abstract_leap import AbstractLeapTest from requests import Session import requests @@ -139,7 +140,8 @@ BOOTSTRAP_CA_CERT = '/tmp/bootstrap-ca.crt' class LeapProviderTest(AbstractLeapTest): def setUp(self): - self.config = LeapConfig(verify_ssl=False, leap_home='/tmp/foobar', bootstrap_ca_cert_bundle=BOOTSTRAP_CA_CERT, ca_cert_bundle=CA_CERT) + self.config = LeapConfig(leap_home='/tmp/foobar') + LeapCertificate.set_cert_and_fingerprint(BOOTSTRAP_CA_CERT, None) def test_provider_fetches_provider_json(self): with HTTMock(provider_json_mock): @@ -195,6 +197,7 @@ class LeapProviderTest(AbstractLeapTest): session = MagicMock(wraps=requests.session()) session_func = MagicMock(return_value=session) get_func = MagicMock(wraps=requests.get) + LeapCertificate.LEAP_CERT = BOOTSTRAP_CA_CERT with patch('pixelated.bitmask_libraries.provider.requests.session', new=session_func): with patch('pixelated.bitmask_libraries.provider.requests.get', new=get_func): @@ -207,24 +210,23 @@ class LeapProviderTest(AbstractLeapTest): def test_that_provider_cert_is_used_to_fetch_soledad_json(self): get_func = MagicMock(wraps=requests.get) + LeapCertificate.api_ca_bundle = CA_CERT with patch('pixelated.bitmask_libraries.provider.requests.get', new=get_func): with HTTMock(provider_json_mock, soledad_json_mock, not_found_mock): provider = LeapProvider('some-provider.test', self.config) provider.fetch_soledad_json() - get_func.assert_called_with('https://api.some-provider.test:4430/1/config/soledad-service.json', verify=CA_CERT, timeout=15) def test_that_leap_fingerprint_is_validated(self): session = MagicMock(wraps=requests.session()) session_func = MagicMock(return_value=session) + LeapCertificate.set_cert_and_fingerprint(None, 'some fingerprint') - with patch('pixelated.bitmask_libraries.certs.LeapCertificate.LEAP_FINGERPRINT', return_value='some fingerprint'): - with patch('pixelated.bitmask_libraries.provider.requests.session', new=session_func): - with patch('pixelated.bitmask_libraries.certs.LeapCertificate.auto_detect_bootstrap_ca_bundle', return_value=False): - with HTTMock(provider_json_mock, ca_cert_mock, not_found_mock): - provider = LeapProvider('some-provider.test', self.config) - provider.fetch_valid_certificate() + with patch('pixelated.bitmask_libraries.provider.requests.session', new=session_func): + with HTTMock(provider_json_mock, ca_cert_mock, not_found_mock): + provider = LeapProvider('some-provider.test', self.config) + provider.fetch_valid_certificate() session.get.assert_any_call('https://some-provider.test/ca.crt', verify=False, timeout=15) session.mount.assert_called_with('https://', ANY) -- cgit v1.2.3