diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/soledad/__init__.py | 44 | ||||
| -rw-r--r-- | src/leap/soledad/server.py | 1 | ||||
| -rw-r--r-- | src/leap/soledad/shared_db.py | 29 | ||||
| -rw-r--r-- | src/leap/soledad/tests/test_leap_backend.py | 75 | ||||
| -rw-r--r-- | src/leap/soledad/tests/u1db_tests/test_https.py | 10 | 
5 files changed, 101 insertions, 58 deletions
| diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py index 4373c53b..b41987cf 100644 --- a/src/leap/soledad/__init__.py +++ b/src/leap/soledad/__init__.py @@ -33,10 +33,18 @@ import logging  import urlparse  import simplejson as json  import scrypt +import httplib +import socket +import ssl  from xdg import BaseDirectory  from hashlib import sha256 +from u1db.remote import http_client +from u1db.remote.ssl_match_hostname import (  # noqa +    CertificateError, +    match_hostname, +)  from leap.common import events @@ -58,6 +66,13 @@ from leap.soledad.crypto import SoledadCrypto  logger = logging.getLogger(name=__name__) +SOLEDAD_CERT = None +""" +Path to the certificate file used to certify the SSL connection between +Soledad client and server. +""" + +  #  # Exceptions  # @@ -100,7 +115,6 @@ def base64_encode(data):  # Soledad: local encrypted storage and remote encrypted sync.  # -  class Soledad(object):      """      Soledad provides encrypted data storage and sync. @@ -204,7 +218,7 @@ class Soledad(object):          self._init_config(secrets_path, local_db_path, server_url)          self._set_token(auth_token)          # configure SSL certificate -        shared_db.SOLEDAD_CERT = cert_file +        SOLEDAD_CERT = cert_file          # initiate bootstrap sequence          self._bootstrap() @@ -959,3 +973,29 @@ class Soledad(object):      server_url = property(          _get_server_url,          doc='The URL of the Soledad server.') + + +#----------------------------------------------------------------------------- +# Monkey patching u1db to be able to provide a custom SSL cert +#----------------------------------------------------------------------------- + +class VerifiedHTTPSConnection(httplib.HTTPSConnection): +    """HTTPSConnection verifying server side certificates.""" +    # derived from httplib.py + +    def connect(self): +        "Connect to a host on a given (SSL) port." +        sock = socket.create_connection((self.host, self.port), +                                        self.timeout, self.source_address) +        if self._tunnel_host: +            self.sock = sock +            self._tunnel() +        self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, +                                    ssl_version=ssl.PROTOCOL_SSLv3, +                                    cert_reqs=ssl.CERT_REQUIRED, +                                    ca_certs=SOLEDAD_CERT) +        match_hostname(self.sock.getpeercert(), self.host) + + +old__VerifiedHTTPSConnection = http_client._VerifiedHTTPSConnection +http_client._VerifiedHTTPSConnection = VerifiedHTTPSConnection diff --git a/src/leap/soledad/server.py b/src/leap/soledad/server.py index b39b90f8..e7b55a3e 100644 --- a/src/leap/soledad/server.py +++ b/src/leap/soledad/server.py @@ -121,7 +121,6 @@ class SoledadAuthMiddleware(object):          error message otherwise.          @rtype: list          """ -          unauth_err = lambda msg: self._error(start_response,                                               401,                                               "unauthorized", diff --git a/src/leap/soledad/shared_db.py b/src/leap/soledad/shared_db.py index 3929e828..c954dbea 100644 --- a/src/leap/soledad/shared_db.py +++ b/src/leap/soledad/shared_db.py @@ -26,38 +26,11 @@ except ImportError:      import json  # noqa -from u1db.remote import http_database, http_client +from u1db.remote import http_database  from leap.soledad.auth import TokenBasedAuth -SOLEDAD_CERT = None - -#----------------------------------------------------------------------------- -# Monkey patching u1db to be able to provide a custom SSL cert -#----------------------------------------------------------------------------- - -import httplib -import socket -import ssl - -class VerifiedHTTPSConnection(httplib.HTTPSConnection): -    """HTTPSConnection verifying server side certificates.""" -    # derived from httplib.py - -    def connect(self): -        "Connect to a host on a given (SSL) port." -        sock = socket.create_connection((self.host, self.port), -                                        self.timeout, self.source_address) -        if self._tunnel_host: -            self.sock = sock -            self._tunnel() -        self.sock = ssl.wrap_socket(sock, self.key_file, SOLEDAD_CERT, -                                    ssl_version=ssl.PROTOCOL_SSLv3, -                                    cert_reqs=ssl.CERT_REQUIRED, -                                    ca_certs=SOLEDAD_CERT) - -http_client._VerifiedHTTPSConnection = VerifiedHTTPSConnection  #-----------------------------------------------------------------------------  # Soledad shared database diff --git a/src/leap/soledad/tests/test_leap_backend.py b/src/leap/soledad/tests/test_leap_backend.py index b58a5473..dbebadb5 100644 --- a/src/leap/soledad/tests/test_leap_backend.py +++ b/src/leap/soledad/tests/test_leap_backend.py @@ -22,6 +22,7 @@ Test Leap backend bits.  import u1db  import os +import ssl  try:      import simplejson as json  except ImportError: @@ -35,7 +36,7 @@ from u1db.remote import (      http_target,  ) - +from leap import soledad  from leap.soledad.backends import leap_backend  from leap.soledad.server import (      SoledadApp, @@ -509,39 +510,63 @@ class TestLeapSyncTarget(  # The following tests come from `u1db.tests.test_https`.  #----------------------------------------------------------------------------- -def oauth_https_sync_target(test, host, path): -    _, port = test.server.server_address -    st = leap_backend.LeapSyncTarget( -        'https://%s:%d/~/%s' % (host, port, path), -        crypto=test._soledad._crypto) -    st.set_oauth_credentials(tests.consumer1.key, tests.consumer1.secret, -                             tests.token1.key, tests.token1.secret) -    return st -  def token_leap_https_sync_target(test, host, path):      _, port = test.server.server_address      st = leap_backend.LeapSyncTarget( -        'https://%s:%d/~/%s' % (host, port, path), +        'https://%s:%d/%s' % (host, port, path),          crypto=test._soledad._crypto)      st.set_token_credentials('user-uuid', 'auth-token')      return st -#class TestLeapSyncTargetHttpsSupport(test_https.TestHttpSyncTargetHttpsSupport, -#                                     BaseSoledadTest): -# -#    scenarios = [ -#        ('oauth_https', {'server_def': test_https.https_server_def, -#                         'make_app_with_state': make_oauth_http_app, -#                         'make_document_for_test': make_leap_document_for_test, -#                         'sync_target': oauth_https_sync_target, -#                         }), -#        ('token_soledad_https', {'server_def': test_https.https_server_def, -#                        'make_app_with_state': make_token_soledad_app, -#                        'make_document_for_test': make_leap_document_for_test, -#                        'sync_target': token_leap_https_sync_target}), -#    ] +class TestLeapSyncTargetHttpsSupport(test_https.TestHttpSyncTargetHttpsSupport, +                                     BaseSoledadTest): + +    scenarios = [ +        ('token_soledad_https', {'server_def': test_https.https_server_def, +                        'make_app_with_state': make_token_soledad_app, +                        'make_document_for_test': make_leap_document_for_test, +                        'sync_target': token_leap_https_sync_target}), +    ] +    def setUp(self): +        # the parent constructor undoes our SSL monkey patch to ensure tests +        # run smoothly with standard u1db. +        test_https.TestHttpSyncTargetHttpsSupport.setUp(self) +        # so here monkey patch again to test our functionality. +        http_client._VerifiedHTTPSConnection = soledad.VerifiedHTTPSConnection +        soledad.SOLEDAD_CERT = http_client.CA_CERTS + +    def test_working(self): +        """ +        Test that SSL connections work well. + +        This test was adapted to patch Soledad's HTTPS connection custom class +        with the intended CA certificates. +        """ +        self.startServer() +        db = self.request_state._create_database('test') +        self.patch(soledad, 'SOLEDAD_CERT', self.cacert_pem) +        remote_target = self.getSyncTarget('localhost', 'test') +        remote_target.record_sync_info('other-id', 2, 'T-id') +        self.assertEqual( +            (2, 'T-id'), db._get_replica_gen_and_trans_id('other-id')) + +    def test_host_mismatch(self): +        """ +        Test that SSL connections to a hostname different than the one in the +        certificate raise CertificateError. + +        This test was adapted to patch Soledad's HTTPS connection custom class +        with the intended CA certificates. +        """ +        self.startServer() +        self.request_state._create_database('test') +        self.patch(soledad, 'SOLEDAD_CERT', self.cacert_pem) +        remote_target = self.getSyncTarget('127.0.0.1', 'test') +        self.assertRaises( +            http_client.CertificateError, remote_target.record_sync_info, +            'other-id', 2, 'T-id')  #-----------------------------------------------------------------------------  # The following tests come from `u1db.tests.test_http_database`. diff --git a/src/leap/soledad/tests/u1db_tests/test_https.py b/src/leap/soledad/tests/u1db_tests/test_https.py index 3f8797d8..b4b14722 100644 --- a/src/leap/soledad/tests/u1db_tests/test_https.py +++ b/src/leap/soledad/tests/u1db_tests/test_https.py @@ -6,13 +6,13 @@ import sys  from paste import httpserver -from leap.soledad.tests import u1db_tests as tests -  from u1db.remote import (      http_client,      http_target,  ) +from leap import soledad +from leap.soledad.tests import u1db_tests as tests  from leap.soledad.tests.u1db_tests.test_remote_sync_target import (      make_oauth_http_app,  ) @@ -69,6 +69,12 @@ class TestHttpSyncTargetHttpsSupport(tests.TestCaseWithServer):              self.skipTest("Requires pyOpenSSL")          self.cacert_pem = os.path.join(os.path.dirname(__file__),                                         'testing-certs', 'cacert.pem') +        # The default u1db http_client class for doing HTTPS only does HTTPS +        # if the platform is linux. Because of this, soledad replaces that +        # class with one that will do HTTPS independent of the platform. In +        # order to maintain the compatibility with u1db default tests, we undo +        # that replacement here. +        http_client._VerifiedHTTPSConnection = soledad.old__VerifiedHTTPSConnection          super(TestHttpSyncTargetHttpsSupport, self).setUp()      def getSyncTarget(self, host, path=None): | 
