From 2ec7bcfd32c2151e2e42ae7b19631dcc4018f93e Mon Sep 17 00:00:00 2001 From: Folker Bernitt Date: Fri, 27 Mar 2015 11:14:14 +0100 Subject: Splitting certificate validation into provider and bootstrap certificate. - Issue #333 - Now a different certificate is used to communicate with the provider's HTTPS website than for all other connections, e.g. to the api --- service/pixelated/bitmask_libraries/certs.py | 45 ++++++++++++++++------ service/pixelated/bitmask_libraries/config.py | 4 +- service/pixelated/bitmask_libraries/provider.py | 6 +-- .../unit/bitmask_libraries/test_abstract_leap.py | 2 +- service/test/unit/bitmask_libraries/test_certs.py | 24 ++++++++++-- .../test/unit/bitmask_libraries/test_nicknym.py | 2 +- .../test/unit/bitmask_libraries/test_provider.py | 33 +++++++++++++++- 7 files changed, 93 insertions(+), 23 deletions(-) (limited to 'service') diff --git a/service/pixelated/bitmask_libraries/certs.py b/service/pixelated/bitmask_libraries/certs.py index 4ee28a19..bafc809d 100644 --- a/service/pixelated/bitmask_libraries/certs.py +++ b/service/pixelated/bitmask_libraries/certs.py @@ -27,7 +27,13 @@ LEAP_CERT = None def which_bundle(provider): if LEAP_CERT: return LEAP_CERT - return str(LeapCertificate(provider).auto_detect_ca_bundle()) + return str(LeapCertificate(provider).provider_ca_bundle()) + + +def which_bootstrap_bundle(provider): + if LEAP_CERT: + return LEAP_CERT + return str(LeapCertificate(provider).auto_detect_bootstrap_ca_bundle()) class LeapCertificate(object): @@ -35,18 +41,37 @@ class LeapCertificate(object): self._config = provider.config self._server_name = provider.server_name self._certs_home = self._config.certs_home + self._provider = provider - def auto_detect_ca_bundle(self): - if self._config.ca_cert_bundle == AUTO_DETECT_CA_BUNDLE: - local_cert = self._local_server_cert() + def auto_detect_bootstrap_ca_bundle(self): + 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.ca_cert_bundle + return self._config.bootstrap_ca_cert_bundle + + def provider_ca_bundle(self): + if self._provider.config.ca_cert_bundle: + return self._provider.config.ca_cert_bundle + + certs_root = self._provider_certs_root_path() + cert_file = os.path.join(certs_root, 'provider.pem') + + if not os.path.isfile(cert_file): + self._download_server_cert(cert_file) + + return cert_file + + def _provider_certs_root_path(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_server_cert(self): + def _local_bootstrap_server_cert(self): cert_file = os.path.join(self._certs_home, '%s.ca.crt' % self._server_name) if not os.path.isfile(cert_file): self._download_server_cert(cert_file) @@ -54,11 +79,7 @@ class LeapCertificate(object): return cert_file def _download_server_cert(self, cert_file_name): - 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']) + cert = self._provider.fetch_valid_certificate() - response = requests.get(ca_cert_uri) with open(cert_file_name, 'w') as file: - file.write(response.content) - file.close + file.write(cert) diff --git a/service/pixelated/bitmask_libraries/config.py b/service/pixelated/bitmask_libraries/config.py index db0df762..56f28706 100644 --- a/service/pixelated/bitmask_libraries/config.py +++ b/service/pixelated/bitmask_libraries/config.py @@ -42,7 +42,8 @@ class LeapConfig(object): """ - def __init__(self, leap_home=DEFAULT_LEAP_HOME, ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, verify_ssl=True, + 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, fetch_interval_in_s=30, timeout_in_s=15, start_background_jobs=False, gpg_binary=discover_gpg_binary(), certs_home=None): """ @@ -75,6 +76,7 @@ class LeapConfig(object): """ self.leap_home = leap_home self.certs_home = certs_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 diff --git a/service/pixelated/bitmask_libraries/provider.py b/service/pixelated/bitmask_libraries/provider.py index 4fe5f17d..5304e662 100644 --- a/service/pixelated/bitmask_libraries/provider.py +++ b/service/pixelated/bitmask_libraries/provider.py @@ -17,7 +17,7 @@ import json from leap.common.certs import get_digest import requests -from .certs import which_bundle +from .certs import which_bootstrap_bundle, which_bundle class LeapProvider(object): @@ -78,7 +78,7 @@ class LeapProvider(object): session = requests.session() try: cert_url = '%s/ca.crt' % self._provider_base_url() - response = session.get(cert_url, verify=which_bundle(self), timeout=self.config.timeout_in_s) + response = session.get(cert_url, verify=which_bootstrap_bundle(self), timeout=self.config.timeout_in_s) response.raise_for_status() cert_data = response.content @@ -101,7 +101,7 @@ class LeapProvider(object): def fetch_provider_json(self): url = '%s/provider.json' % self._provider_base_url() - response = requests.get(url, verify=which_bundle(self), timeout=self.config.timeout_in_s) + response = requests.get(url, verify=which_bootstrap_bundle(self), timeout=self.config.timeout_in_s) response.raise_for_status() json_data = json.loads(response.content) diff --git a/service/test/unit/bitmask_libraries/test_abstract_leap.py b/service/test/unit/bitmask_libraries/test_abstract_leap.py index 2634f330..c11c7ea9 100644 --- a/service/test/unit/bitmask_libraries/test_abstract_leap.py +++ b/service/test/unit/bitmask_libraries/test_abstract_leap.py @@ -28,7 +28,7 @@ class AbstractLeapTest(unittest.TestCase): leap_home = os.path.join(tempfile.mkdtemp(), 'leap') - config = Mock(leap_home=leap_home, ca_cert_bundle='/some/path/to/ca_cert', gpg_binary='/path/to/gpg') + config = Mock(leap_home=leap_home, bootstrap_ca_cert_bundle='/some/path/to/ca_cert', ca_cert_bundle='/some/path/to/provider_ca_cert', gpg_binary='/path/to/gpg') provider = Mock(config=config, server_name='some-server.test', domain='some-server.test', api_uri='https://api.some-server.test:4430', api_version='1') soledad = Mock() diff --git a/service/test/unit/bitmask_libraries/test_certs.py b/service/test/unit/bitmask_libraries/test_certs.py index 8caafe7e..3683f9ae 100644 --- a/service/test/unit/bitmask_libraries/test_certs.py +++ b/service/test/unit/bitmask_libraries/test_certs.py @@ -1,6 +1,6 @@ import unittest -from pixelated.bitmask_libraries.certs import which_bundle +from pixelated.bitmask_libraries.certs import which_bootstrap_bundle, which_bundle from pixelated.bitmask_libraries.config import AUTO_DETECT_CA_BUNDLE from mock import MagicMock, patch @@ -8,12 +8,28 @@ from mock import MagicMock, patch class CertsTest(unittest.TestCase): @patch('pixelated.bitmask_libraries.certs.os.path.isfile') - def test_that_which_bundle_returns_byte_string(self, mock_isfile): + @patch('pixelated.bitmask_libraries.certs.os.path.isdir') + def test_that_which_bootstrap_bundle_returns_byte_string(self, mock_isdir, mock_isfile): mock_isfile.return_value = True - config = MagicMock(ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, certs_home='/some/path') + mock_isdir.return_value = True + config = MagicMock(bootstrap_ca_cert_bundle=AUTO_DETECT_CA_BUNDLE, certs_home='/some/path') provider = MagicMock(server_name=u'test.leap.net', config=config) - bundle = which_bundle(provider) + bundle = which_bootstrap_bundle(provider) self.assertEqual('/some/path/test.leap.net.ca.crt', bundle) self.assertEqual(str, type(bundle)) + + @patch('pixelated.bitmask_libraries.certs.os.path.isfile') + @patch('pixelated.bitmask_libraries.certs.os.path.isdir') + def test_that_which_bundle_returns_byte_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, ca_cert_bundle=None, leap_home='/some/leap/home', certs_home='/some/path') + provider = MagicMock(server_name=u'test.leap.net', config=config) + + bundle = which_bundle(provider) + + self.assertEqual('/some/leap/home/providers/test.leap.net/keys/client/provider.pem', bundle) + self.assertEqual(str, type(bundle)) diff --git a/service/test/unit/bitmask_libraries/test_nicknym.py b/service/test/unit/bitmask_libraries/test_nicknym.py index 7dec4b2c..6e589ae9 100644 --- a/service/test/unit/bitmask_libraries/test_nicknym.py +++ b/service/test/unit/bitmask_libraries/test_nicknym.py @@ -30,7 +30,7 @@ class NickNymTest(AbstractLeapTest): # then init_mock.assert_called_with('test_user@some-server.test', 'https://nicknym.some-server.test:6425/', - self.soledad, self.token, '/some/path/to/ca_cert', + self.soledad, self.token, '/some/path/to/provider_ca_cert', 'https://api.some-server.test:4430', '1', self.uuid, '/path/to/gpg') diff --git a/service/test/unit/bitmask_libraries/test_provider.py b/service/test/unit/bitmask_libraries/test_provider.py index af8aa291..8c0cf97e 100644 --- a/service/test/unit/bitmask_libraries/test_provider.py +++ b/service/test/unit/bitmask_libraries/test_provider.py @@ -15,11 +15,14 @@ # along with Pixelated. If not, see . import json +from mock import patch, MagicMock 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 test_abstract_leap import AbstractLeapTest +from requests import Session +import requests @all_requests @@ -130,9 +133,13 @@ VeJ6 """ +CA_CERT = '/tmp/ca.crt' +BOOTSTRAP_CA_CERT = '/tmp/bootstrap-ca.crt' + + class LeapProviderTest(AbstractLeapTest): def setUp(self): - self.config = LeapConfig(verify_ssl=False, leap_home='/tmp/foobar', ca_cert_bundle='/tmp/ca.crt') + self.config = LeapConfig(verify_ssl=False, leap_home='/tmp/foobar', bootstrap_ca_cert_bundle=BOOTSTRAP_CA_CERT, ca_cert_bundle=CA_CERT) def test_provider_fetches_provider_json(self): with HTTMock(provider_json_mock): @@ -183,3 +190,27 @@ class LeapProviderTest(AbstractLeapTest): with HTTMock(provider_json_invalid_fingerprint_mock, ca_cert_mock, not_found_mock): provider = LeapProvider('some-provider.test', self.config) self.assertRaises(Exception, provider.fetch_valid_certificate) + + def test_that_bootstrap_cert_is_used_to_fetch_certificate(self): + session = MagicMock(wraps=requests.session()) + session_func = MagicMock(return_value=session) + get_func = MagicMock(wraps=requests.get) + + with patch('pixelated.bitmask_libraries.provider.requests.session', new=session_func): + with patch('pixelated.bitmask_libraries.provider.requests.get', new=get_func): + with HTTMock(provider_json_mock, ca_cert_mock, not_found_mock): + provider = LeapProvider('some-provider.test', self.config) + provider.fetch_valid_certificate() + + get_func.assert_called_once_with('https://some-provider.test/provider.json', verify=BOOTSTRAP_CA_CERT, timeout=15) + session.get.assert_called_once_with('https://some-provider.test/ca.crt', verify=BOOTSTRAP_CA_CERT, timeout=15) + + def test_that_provider_cert_is_used_to_fetch_soledad_json(self): + get_func = MagicMock(wraps=requests.get) + + 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) -- cgit v1.2.3