Exemplo n.º 1
0
class NickNym(object):
    def __init__(self, provider, config, soledad, email_address, token, uuid):
        nicknym_url = _discover_nicknym_server(provider)
        self._email = email_address
        self.keymanager = KeyManager(self._email, nicknym_url,
                                     soledad,
                                     token=token, ca_cert_path=LeapCertificate(provider).provider_api_cert, api_uri=provider.api_uri,
                                     api_version=provider.api_version,
                                     uid=uuid, gpgbinary=config.gpg_binary)

    @defer.inlineCallbacks
    def generate_openpgp_key(self):
        key_present = yield self._key_exists(self._email)
        if not key_present:
            logger.info("Generating keys - this could take a while...")
            yield self._gen_key()
        yield self._send_key_to_leap()

    @defer.inlineCallbacks
    def _key_exists(self, email):
        try:
            yield self.fetch_key(email, private=True, fetch_remote=False)
            defer.returnValue(True)
        except KeyNotFound:
            defer.returnValue(False)

    def fetch_key(self, email, private=False, fetch_remote=True):
        return self.keymanager.get_key(email, private=private, fetch_remote=fetch_remote)

    def _gen_key(self):
        return self.keymanager.gen_key()

    def _send_key_to_leap(self):
        return self.keymanager.send_key()
Exemplo n.º 2
0
class NickNym(object):
    def __init__(self, provider, config, soledad, email_address, token, uuid):
        nicknym_url = _discover_nicknym_server(provider)
        self._email = email_address
        self.keymanager = KeyManager(self._email, nicknym_url,
                                     soledad,
                                     token=token, ca_cert_path=LeapCertificate(provider).provider_api_cert, api_uri=provider.api_uri,
                                     api_version=provider.api_version,
                                     uid=uuid, gpgbinary=config.gpg_binary)

    @defer.inlineCallbacks
    def generate_openpgp_key(self):
        key_present = yield self._key_exists(self._email)
        if not key_present:
            print "Generating keys - this could take a while..."
            yield self._gen_key()
        # Sending it anyway for now. TODO: This can be better with real checking (downloading pubkey from nicknym)
        yield self._send_key_to_leap()

    @defer.inlineCallbacks
    def _key_exists(self, email):
        try:
            yield self.fetch_key(email, private=True, fetch_remote=False)
            defer.returnValue(True)
        except KeyNotFound:
            defer.returnValue(False)

    def fetch_key(self, email, private=False, fetch_remote=True):
        return self.keymanager.get_key(email, openpgp.OpenPGPKey, private=private, fetch_remote=fetch_remote)

    def _gen_key(self):
        return self.keymanager.gen_key(openpgp.OpenPGPKey)

    def _send_key_to_leap(self):
        return self.keymanager.send_key(openpgp.OpenPGPKey)
Exemplo n.º 3
0
 def __init__(self, provider, config, soledad_session, srp_session):
     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, soledad_session.soledad, srp_session.token,
         which_bundle(provider), provider.api_uri, provider.api_version,
         srp_session.uuid, config.gpg_binary)
Exemplo n.º 4
0
 def __init__(self, provider, config, soledad, email_address, token, uuid):
     nicknym_url = _discover_nicknym_server(provider)
     self._email = email_address
     self.keymanager = KeyManager(self._email, nicknym_url,
                                  soledad,
                                  token=token, ca_cert_path=LeapCertificate(provider).provider_api_cert, api_uri=provider.api_uri,
                                  api_version=provider.api_version,
                                  uid=uuid, gpgbinary=config.gpg_binary)
    def test_retrieves_key(self, requests_mock):
        nickserver_url = 'http://some/nickserver/uri'
        soledad = MagicMock()
        soledad.get_from_index.side_effect = [[], [TestDoc(sample_key)]]

        km = KeyManager('*****@*****.**', nickserver_url, soledad, ca_cert_path='some path')

        result = km.get_key('*****@*****.**', OpenPGPKey)

        self.assertEqual(str(OpenPGPKey('*****@*****.**', key_id='key_id')), str(result))
Exemplo n.º 6
0
    def _init_keymanager(self, address, token):
        """
        Initialize the keymanager.

        :param address: the address to initialize the keymanager with.
        :type address: str
        :param token: the auth token for accessing webapp.
        :type token: str
        :rtype: Deferred
        """
        logger.debug('initializing keymanager...')

        if flags.OFFLINE:
            nickserver_uri = "https://localhost"
            kwargs = {
                "ca_cert_path": "",
                "api_uri": "",
                "api_version": "",
                "uid": self._uuid,
                "gpgbinary": self._get_gpg_bin_path()
            }
        else:
            nickserver_uri = "https://nicknym.%s:6425" % (
                self._provider_config.get_domain(), )
            kwargs = {
                "token": token,
                "ca_cert_path": self._provider_config.get_ca_cert_path(),
                "api_uri": self._provider_config.get_api_uri(),
                "api_version": self._provider_config.get_api_version(),
                "uid": self.srpauth.get_uuid(),
                "gpgbinary": self._get_gpg_bin_path()
            }
        self._keymanager = KeyManager(address, nickserver_uri, self._soledad,
                                      **kwargs)

        if flags.OFFLINE is False:
            # make sure key is in server
            logger.debug('Trying to send key to server...')

            def send_errback(failure):
                if failure.check(KeyNotFound):
                    logger.debug(
                        'No key found for %s, it might be because soledad not '
                        'synced yet or it will generate it soon.' % address)
                else:
                    logger.error("Error sending key to server.")
                    logger.exception(failure.value)
                    # but we do not raise

            d = self._keymanager.send_key(openpgp.OpenPGPKey)
            d.addErrback(send_errback)
            return d
        else:
            return defer.succeed(None)
Exemplo n.º 7
0
    def _init_keymanager(self, address, token):
        """
        Initialize the keymanager.

        :param address: the address to initialize the keymanager with.
        :type address: str
        :param token: the auth token for accessing webapp.
        :type token: str
        """
        srp_auth = self.srpauth
        logger.debug('initializing keymanager...')

        if flags.OFFLINE is True:
            args = (address, "https://localhost", self._soledad)
            kwargs = {
                "ca_cert_path": "",
                "api_uri": "",
                "api_version": "",
                "uid": self._uuid,
                "gpgbinary": self._get_gpg_bin_path()
            }
        else:
            args = (address, "https://nicknym.%s:6425" %
                    (self._provider_config.get_domain(), ), self._soledad)
            kwargs = {
                "token": token,
                "ca_cert_path": self._provider_config.get_ca_cert_path(),
                "api_uri": self._provider_config.get_api_uri(),
                "api_version": self._provider_config.get_api_version(),
                "uid": srp_auth.get_uuid(),
                "gpgbinary": self._get_gpg_bin_path()
            }
        try:
            self._keymanager = KeyManager(*args, **kwargs)
        except KeyNotFound:
            logger.debug('key for %s not found.' % address)
        except Exception as exc:
            logger.exception(exc)
            raise

        if flags.OFFLINE is False:
            # make sure key is in server
            logger.debug('Trying to send key to server...')
            try:
                self._keymanager.send_key(openpgp.OpenPGPKey)
            except KeyNotFound:
                logger.debug('No key found for %s, will generate soon.' %
                             address)
            except Exception as exc:
                logger.error("Error sending key to server.")
                logger.exception(exc)
Exemplo n.º 8
0
    def _setup_keymanager(self, address):
        """
        Set up Key Manager and return a Deferred that will be fired when done.
        """
        self._config = {
            'host': 'https://provider/',
            'port': 25,
            'username': address,
            'password': '******',
            'encrypted_only': True,
            'cert': u'src/leap/mail/smtp/tests/cert/server.crt',
            'key': u'src/leap/mail/smtp/tests/cert/server.key',
        }

        class Response(object):
            status_code = 200
            headers = {'content-type': 'application/json'}

            def json(self):
                return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2}

            def raise_for_status(self):
                pass

        nickserver_url = ''  # the url of the nickserver
        self._km = KeyManager(address, nickserver_url, self._soledad,
                              gpgbinary=self.GPG_BINARY_PATH)
        self._km._fetcher.put = Mock()
        self._km._fetcher.get = Mock(return_value=Response())

        d1 = self._km.put_raw_key(PRIVATE_KEY, OpenPGPKey, ADDRESS)
        d2 = self._km.put_raw_key(PRIVATE_KEY_2, OpenPGPKey, ADDRESS_2)
        return gatherResults([d1, d2])
    def _init_keymanager(self, address):
        """
        Initializes the keymanager.
        :param address: the address to initialize the keymanager with.
        :type address: str
        """
        srp_auth = self.srpauth
        logger.debug('initializing keymanager...')
        try:
            self._keymanager = KeyManager(
                address,
                "https://nicknym.%s:6425" % (
                    self._provider_config.get_domain(),),
                self._soledad,
                #token=srp_auth.get_token(),  # TODO: enable token usage
                session_id=srp_auth.get_session_id(),
                ca_cert_path=self._provider_config.get_ca_cert_path(),
                api_uri=self._provider_config.get_api_uri(),
                api_version=self._provider_config.get_api_version(),
                uid=srp_auth.get_uid(),
                gpgbinary=self._get_gpg_bin_path())
        except Exception as exc:
            logger.exception(exc)
            raise

        logger.debug('sending key to server...')

        # make sure key is in server
        try:
            self._keymanager.send_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("Error sending key to server.")
            logger.exception(exc)
Exemplo n.º 10
0
    def _setup_keymanager(self, address):
        """
        Set up Key Manager and return a Deferred that will be fired when done.
        """
        self._config = {
            "host": "https://provider/",
            "port": 25,
            "username": address,
            "password": "******",
            "encrypted_only": True,
            "cert": u"src/leap/mail/smtp/tests/cert/server.crt",
            "key": u"src/leap/mail/smtp/tests/cert/server.key",
        }

        class Response(object):
            status_code = 200
            headers = {"content-type": "application/json"}

            def json(self):
                return {"address": ADDRESS_2, "openpgp": PUBLIC_KEY_2}

            def raise_for_status(self):
                pass

        nickserver_url = ""  # the url of the nickserver
        self._km = KeyManager(address, nickserver_url, self._soledad, ca_cert_path="", gpgbinary=self.GPG_BINARY_PATH)
        self._km._fetcher.put = Mock()
        self._km._fetcher.get = Mock(return_value=Response())

        d1 = self._km.put_raw_key(PRIVATE_KEY, OpenPGPKey, ADDRESS)
        d2 = self._km.put_raw_key(PRIVATE_KEY_2, OpenPGPKey, ADDRESS_2)
        return gatherResults([d1, d2])
Exemplo n.º 11
0
 def __init__(self, soledad, leap_session, nicknym_url):
     provider = leap_session.provider
     self.keymanager = KeyManager(
         leap_session.account_email(), nicknym_url, soledad,
         leap_session.session_id, leap_session.leap_home + '/ca.crt',
         provider.api_uri, leap_session.api_version, leap_session.uuid,
         leap_session.leap_config.gpg_binary)
Exemplo n.º 12
0
 def __init__(self, provider, config, soledad_session, srp_session):
     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,
                                  soledad_session.soledad,
                                  srp_session.token, which_bundle(provider), provider.api_uri,
                                  provider.api_version,
                                  srp_session.uuid, config.gpg_binary)
Exemplo n.º 13
0
def get_imap_server(soledad, uuid, address, token):
    log("Starting imap... ", line_break=False)

    keymanager = KeyManager(address, '', soledad, token=token, uid=uuid)
    with open(
            os.path.join(
                os.path.dirname(__file__),
                'keys/5447A9AD50E3075ECCE432711B450E665FE63573.sec'), 'r') as f:
        pubkey, privkey = keymanager.parse_openpgp_ascii_key(f.read())
        keymanager.put_key(privkey)
    
    imap_service, imap_port, imap_factory = imap.run_service(
        soledad, keymanager, userid=address, offline=False)

    imap_service.start_loop()
    log("started.")
    return imap_service
Exemplo n.º 14
0
 def __init__(self, provider, config, soledad_session, email_address, token, uuid):
     nicknym_url = _discover_nicknym_server(provider)
     self._email = email_address
     self.keymanager = KeyManager(self._email, nicknym_url,
                                  soledad_session.soledad,
                                  token, LeapCertificate(provider).provider_api_cert, provider.api_uri,
                                  provider.api_version,
                                  uuid, config.gpg_binary)
Exemplo n.º 15
0
 def __init__(self, provider, config, soledad, email_address, token, uuid):
     nicknym_url = _discover_nicknym_server(provider)
     self._email = email_address
     self.keymanager = KeyManager(self._email, nicknym_url,
                                  soledad,
                                  token=token, ca_cert_path=LeapCertificate(provider).provider_api_cert, api_uri=provider.api_uri,
                                  api_version=provider.api_version,
                                  uid=uuid, gpgbinary=config.gpg_binary)
    def _init_keymanager(self, address, token):
        """
        Initialize the keymanager.

        :param address: the address to initialize the keymanager with.
        :type address: str
        :param token: the auth token for accessing webapp.
        :type token: str
        """
        srp_auth = self.srpauth
        logger.debug('initializing keymanager...')

        if flags.OFFLINE is True:
            args = (address, "https://localhost", self._soledad)
            kwargs = {
                "ca_cert_path": "",
                "api_uri": "",
                "api_version": "",
                "uid": self._uuid,
                "gpgbinary": self._get_gpg_bin_path()
            }
        else:
            args = (
                address,
                "https://nicknym.%s:6425" % (
                    self._provider_config.get_domain(),),
                self._soledad
            )
            kwargs = {
                "token": token,
                "ca_cert_path": self._provider_config.get_ca_cert_path(),
                "api_uri": self._provider_config.get_api_uri(),
                "api_version": self._provider_config.get_api_version(),
                "uid": srp_auth.get_uuid(),
                "gpgbinary": self._get_gpg_bin_path()
            }
        try:
            self._keymanager = KeyManager(*args, **kwargs)
        except KeyNotFound:
            logger.debug('key for %s not found.' % address)
        except Exception as exc:
            logger.exception(exc)
            raise

        if flags.OFFLINE is False:
            # make sure key is in server
            logger.debug('Trying to send key to server...')
            try:
                self._keymanager.send_key(openpgp.OpenPGPKey)
            except KeyNotFound:
                logger.debug('No key found for %s, will generate soon.'
                             % address)
            except Exception as exc:
                logger.error("Error sending key to server.")
                logger.exception(exc)
    def _init_keymanager(self, address, token):
        """
        Initialize the keymanager.

        :param address: the address to initialize the keymanager with.
        :type address: str
        :param token: the auth token for accessing webapp.
        :type token: str
        :rtype: Deferred
        """
        logger.debug('initializing keymanager...')

        if flags.OFFLINE:
            nickserver_uri = "https://localhost"
            kwargs = {
                "ca_cert_path": "",
                "api_uri": "",
                "api_version": "",
                "uid": self._uuid,
                "gpgbinary": self._get_gpg_bin_path()
            }
        else:
            nickserver_uri = "https://nicknym.%s:6425" % (
                self._provider_config.get_domain(),)
            kwargs = {
                "token": token,
                "ca_cert_path": self._provider_config.get_ca_cert_path(),
                "api_uri": self._provider_config.get_api_uri(),
                "api_version": self._provider_config.get_api_version(),
                "uid": self.srpauth.get_uuid(),
                "gpgbinary": self._get_gpg_bin_path()
            }
        self._keymanager = KeyManager(address, nickserver_uri, self._soledad,
                                      **kwargs)

        if flags.OFFLINE is False:
            # make sure key is in server
            logger.debug('Trying to send key to server...')

            def send_errback(failure):
                if failure.check(KeyNotFound):
                    logger.debug(
                        'No key found for %s, it might be because soledad not '
                        'synced yet or it will generate it soon.' % address)
                else:
                    logger.error("Error sending key to server.")
                    logger.exception(failure.value)
                    # but we do not raise

            d = self._keymanager.send_key(openpgp.OpenPGPKey)
            d.addErrback(send_errback)
            return d
        else:
            return defer.succeed(None)
Exemplo n.º 18
0
class NickNym(object):
    def __init__(self, provider, config, soledad_session, email_address, token, uuid):
        nicknym_url = _discover_nicknym_server(provider)
        self._email = email_address
        self.keymanager = KeyManager(self._email, nicknym_url,
                                     soledad_session.soledad,
                                     token, LeapCertificate(provider).provider_api_cert, provider.api_uri,
                                     provider.api_version,
                                     uuid, config.gpg_binary)

    def generate_openpgp_key(self):
        if not self._key_exists(self._email):
            print "Generating keys - this could take a while..."
            self._gen_key()
            self._send_key_to_leap()

    def _key_exists(self, email):
        try:
            self.keymanager.get_key(email, openpgp.OpenPGPKey, private=True, fetch_remote=False)
            return True
        except KeyNotFound:
            return False

    def _gen_key(self):
        self.keymanager.gen_key(openpgp.OpenPGPKey)

    def _send_key_to_leap(self):
        self.keymanager.send_key(openpgp.OpenPGPKey)
Exemplo n.º 19
0
class NickNym(object):
    def __init__(self, provider, config, soledad_session, username, token,
                 uuid):
        nicknym_url = _discover_nicknym_server(provider)
        self._email = '%s@%s' % (username, provider.domain)
        self.keymanager = KeyManager('%s@%s' % (username, provider.domain),
                                     nicknym_url, soledad_session.soledad,
                                     token, which_api_CA_bundle(provider),
                                     provider.api_uri, provider.api_version,
                                     uuid, config.gpg_binary)

    def generate_openpgp_key(self):
        if not self._key_exists(self._email):
            print "Generating keys - this could take a while..."
            self._gen_key()
            self._send_key_to_leap()

    def _key_exists(self, email):
        try:
            self.keymanager.get_key(email,
                                    openpgp.OpenPGPKey,
                                    private=True,
                                    fetch_remote=False)
            return True
        except KeyNotFound:
            return False

    def _gen_key(self):
        self.keymanager.gen_key(openpgp.OpenPGPKey)

    def _send_key_to_leap(self):
        self.keymanager.send_key(openpgp.OpenPGPKey)
Exemplo n.º 20
0
class NickNym(object):
    def __init__(self, provider, config, soledad_session, srp_session):
        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,
                                     soledad_session.soledad,
                                     srp_session.token, which_bundle(provider), provider.api_uri,
                                     provider.api_version,
                                     srp_session.uuid, config.gpg_binary)

    def generate_openpgp_key(self):
        if not self._key_exists(self._email):
            self._gen_key()
            self._send_key_to_leap()

    def _key_exists(self, email):
        try:
            self.keymanager.get_key(email, openpgp.OpenPGPKey, private=True, fetch_remote=False)
            return True
        except KeyNotFound:
            return False

    def _gen_key(self):
        self.keymanager.gen_key(openpgp.OpenPGPKey)

    def _send_key_to_leap(self):
        self.keymanager.send_key(openpgp.OpenPGPKey)
Exemplo n.º 21
0
    def _setup_keymanager(self, address):
        """
        Set up Key Manager and return a Deferred that will be fired when done.
        """
        self._config = {
            'host': 'https://provider/',
            'port': 25,
            'username': address,
            'password': '******',
            'encrypted_only': True,
            'cert': u'src/leap/mail/smtp/tests/cert/server.crt',
            'key': u'src/leap/mail/smtp/tests/cert/server.key',
        }

        class Response(object):
            status_code = 200
            headers = {'content-type': 'application/json'}

            def json(self):
                return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2}

            def raise_for_status(self):
                pass

        nickserver_url = ''  # the url of the nickserver
        self._km = KeyManager(address,
                              nickserver_url,
                              self._soledad,
                              ca_cert_path='',
                              gpgbinary=self.GPG_BINARY_PATH)
        self._km._fetcher.put = Mock()
        self._km._fetcher.get = Mock(return_value=Response())

        d1 = self._km.put_raw_key(PRIVATE_KEY, OpenPGPKey, ADDRESS)
        d2 = self._km.put_raw_key(PRIVATE_KEY_2, OpenPGPKey, ADDRESS_2)
        return gatherResults([d1, d2])
    def test_http_error_500(self, requests_mock):
        def do_request(one, data=None, verify=None):
            response = MagicMock()
            response.raise_for_status = MagicMock()
            response.raise_for_status.side_effect = HTTPError
            return response

        nickserver_url = 'http://some/nickserver/uri'
        soledad = MagicMock()
        soledad.get_from_index.side_effect = [[], []]
        requests_mock.get.side_effect = do_request

        km = KeyManager('*****@*****.**', nickserver_url, soledad, ca_cert_path='some path')

        self.assertRaises(KeyNotFound, km.get_key, '*****@*****.**', OpenPGPKey)
Exemplo n.º 23
0
def _get_keymanager_instance(username,
                             provider,
                             soledad,
                             token,
                             ca_cert_path=None,
                             api_uri=None,
                             api_version=None,
                             uid=None,
                             gpgbinary=None):
    return KeyManager("{username}@{provider}".format(username=username,
                                                     provider=provider),
                      "http://uri",
                      soledad,
                      token=token,
                      ca_cert_path=ca_cert_path,
                      api_uri=api_uri,
                      api_version=api_version,
                      uid=uid,
                      gpgbinary=gpgbinary)
Exemplo n.º 24
0
 def _init_keymanager(self, address):
     """
     Initializes the keymanager.
     :param address: the address to initialize the keymanager with.
     :type address: str
     """
     srp_auth = self.srpauth
     logger.debug('initializing keymanager...')
     self._keymanager = KeyManager(
         address,
         "https://nicknym.%s:6425" % (self._provider_config.get_domain(),),
         self._soledad,
         #token=srp_auth.get_token(),  # TODO: enable token usage
         session_id=srp_auth.get_session_id(),
         ca_cert_path=self._provider_config.get_ca_cert_path(),
         api_uri=self._provider_config.get_api_uri(),
         api_version=self._provider_config.get_api_version(),
         uid=srp_auth.get_uid(),
         gpgbinary=self._get_gpg_bin_path())
    def _gen_key(self, _):
        """
        Generates the key pair if needed, uploads it to the webapp and
        nickserver
        """
        leap_assert(self._provider_config,
                    "We need a provider configuration!")

        address = "%s@%s" % (self._user, self._provider_config.get_domain())

        logger.debug("Retrieving key for %s" % (address,))

        srp_auth = self.srpauth

        # TODO: use which implementation with known paths
        # TODO: Fix for Windows
        gpgbin = "/usr/bin/gpg"

        if self._standalone:
            gpgbin = os.path.join(self._provider_config.get_path_prefix(),
                                  "..", "apps", "mail", "gpg")

        self._keymanager = KeyManager(
            address,
            "https://nicknym.%s:6425" % (self._provider_config.get_domain(),),
            self._soledad,
            #token=srp_auth.get_token(),  # TODO: enable token usage
            session_id=srp_auth.get_session_id(),
            ca_cert_path=self._provider_config.get_ca_cert_path(),
            api_uri=self._provider_config.get_api_uri(),
            api_version=self._provider_config.get_api_version(),
            uid=srp_auth.get_uid(),
            gpgbinary=gpgbin)
        try:
            self._keymanager.get_key(address, openpgp.OpenPGPKey,
                                     private=True, fetch_remote=False)
        except KeyNotFound:
            logger.debug("Key not found. Generating key for %s" % (address,))
            self._keymanager.gen_key(openpgp.OpenPGPKey)
            self._keymanager.send_key(openpgp.OpenPGPKey)
            logger.debug("Key generated successfully.")
Exemplo n.º 26
0
    def _keymanager_instance(self, address):
        """
        Return a Key Manager instance for tests.
        """
        self._config = {
            'host': 'http://provider/',
            'port': 25,
            'username': address,
            'password': '******',
            'encrypted_only': True,
            'cert': u'src/leap/mail/smtp/tests/cert/server.crt',
            'key': u'src/leap/mail/smtp/tests/cert/server.key',
        }

        class Response(object):
            status_code = 200
            headers = {'content-type': 'application/json'}

            def json(self):
                return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2}

            def raise_for_status(self):
                pass

        nickserver_url = ''  # the url of the nickserver
        km = KeyManager(address,
                        nickserver_url,
                        self._soledad,
                        ca_cert_path='',
                        gpgbinary=self.GPG_BINARY_PATH)
        km._fetcher.put = Mock()
        km._fetcher.get = Mock(return_value=Response())

        # insert test keys in key manager.
        pgp = openpgp.OpenPGPScheme(self._soledad,
                                    gpgbinary=self.GPG_BINARY_PATH)
        pgp.put_ascii_key(PRIVATE_KEY)
        pgp.put_ascii_key(PRIVATE_KEY_2)

        return km
Exemplo n.º 27
0
class TestCaseWithKeyManager(unittest.TestCase, BaseLeapTest):

    GPG_BINARY_PATH = _find_gpg()

    def setUp(self):
        self.setUpEnv()

        # setup our own stuff
        address = "*****@*****.**"  # user's address in the form user@provider
        uuid = "*****@*****.**"
        passphrase = u"123"
        secrets_path = os.path.join(self.tempdir, "secret.gpg")
        local_db_path = os.path.join(self.tempdir, "soledad.u1db")
        server_url = "http://provider/"
        cert_file = ""

        self._soledad = Soledad(
            uuid,
            passphrase,
            secrets_path=secrets_path,
            local_db_path=local_db_path,
            server_url=server_url,
            cert_file=cert_file,
            syncable=False,
        )
        return self._setup_keymanager(address)

    def _setup_keymanager(self, address):
        """
        Set up Key Manager and return a Deferred that will be fired when done.
        """
        self._config = {
            "host": "https://provider/",
            "port": 25,
            "username": address,
            "password": "******",
            "encrypted_only": True,
            "cert": u"src/leap/mail/smtp/tests/cert/server.crt",
            "key": u"src/leap/mail/smtp/tests/cert/server.key",
        }

        class Response(object):
            status_code = 200
            headers = {"content-type": "application/json"}

            def json(self):
                return {"address": ADDRESS_2, "openpgp": PUBLIC_KEY_2}

            def raise_for_status(self):
                pass

        nickserver_url = ""  # the url of the nickserver
        self._km = KeyManager(address, nickserver_url, self._soledad, ca_cert_path="", gpgbinary=self.GPG_BINARY_PATH)
        self._km._fetcher.put = Mock()
        self._km._fetcher.get = Mock(return_value=Response())

        d1 = self._km.put_raw_key(PRIVATE_KEY, OpenPGPKey, ADDRESS)
        d2 = self._km.put_raw_key(PRIVATE_KEY_2, OpenPGPKey, ADDRESS_2)
        return gatherResults([d1, d2])

    def tearDown(self):
        self.tearDownEnv()
class SoledadBootstrapper(AbstractBootstrapper):
    """
    Soledad init procedure.
    """
    SOLEDAD_KEY = "soledad"
    KEYMANAGER_KEY = "keymanager"

    PUBKEY_KEY = "user[public_key]"

    MAX_INIT_RETRIES = 10

    def __init__(self, signaler=None):
        AbstractBootstrapper.__init__(self, signaler)

        if signaler is not None:
            self._cancel_signal = signaler.soledad_cancelled_bootstrap

        self._provider_config = None
        self._soledad_config = None
        self._download_if_needed = False

        self._user = ""
        self._password = u""
        self._address = ""
        self._uuid = ""

        self._srpauth = None

        self._soledad = None
        self._keymanager = None

    @property
    def srpauth(self):
        if flags.OFFLINE is True:
            return None
        if self._srpauth is None:
            leap_assert(self._provider_config is not None,
                        "We need a provider config")
            self._srpauth = SRPAuth(self._provider_config)
        return self._srpauth

    @property
    def soledad(self):
        return self._soledad

    @property
    def keymanager(self):
        return self._keymanager

    # initialization

    def load_offline_soledad(self, username, password, uuid):
        """
        Instantiate Soledad for offline use.

        :param username: full user id (user@provider)
        :type username: str or unicode
        :param password: the soledad passphrase
        :type password: unicode
        :param uuid: the user uuid
        :type uuid: str or unicode
        """
        self._address = username
        self._password = password
        self._uuid = uuid

        def error(failure):
            # TODO: we should handle more specific exceptions in here
            logger.exception(failure.value)
            self._signaler.signal(self._signaler.soledad_offline_failed)

        d = self.load_and_sync_soledad(uuid, offline=True)
        d.addCallback(
            lambda _: self._signaler.signal(
                self._signaler.soledad_offline_finished))
        d.addErrback(error)
        return d

    def _get_soledad_local_params(self, uuid, offline=False):
        """
        Return the locals parameters needed for the soledad initialization.

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :return: secrets_path, local_db_path, token
        :rtype: tuple
        """
        # in the future, when we want to be able to switch to
        # online mode, this should be a proxy object too.
        # Same for server_url below.

        if offline is False:
            token = self.srpauth.get_token()
        else:
            token = ""

        secrets_path, local_db_path = get_db_paths(uuid)

        logger.debug('secrets_path:%s' % (secrets_path,))
        logger.debug('local_db:%s' % (local_db_path,))
        return (secrets_path, local_db_path, token)

    def _get_soledad_server_params(self, uuid, offline):
        """
        Return the remote parameters needed for the soledad initialization.

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :return: server_url, cert_file
        :rtype: tuple
        """
        if offline is True:
            server_url = "http://localhost:9999/"
            cert_file = ""
        else:
            if uuid is None:
                uuid = self.srpauth.get_uuid()
            server_url = self._pick_server(uuid)
            cert_file = self._provider_config.get_ca_cert_path()

        return server_url, cert_file

    def _do_soledad_init(self, uuid, secrets_path, local_db_path,
                         server_url, cert_file, token, syncable):
        """
        Initialize soledad, retry if necessary and raise an exception if we
        can't succeed.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        init_tries = 1
        while init_tries <= self.MAX_INIT_RETRIES:
            try:
                logger.debug("Trying to init soledad....")
                self._try_soledad_init(
                    uuid, secrets_path, local_db_path,
                    server_url, cert_file, token, syncable)
                logger.debug("Soledad has been initialized.")
                return
            except Exception as exc:
                init_tries += 1
                msg = "Init failed, retrying... (retry {0} of {1})".format(
                    init_tries, self.MAX_INIT_RETRIES)
                logger.warning(msg)
                continue

        self._signaler.signal(self._signaler.soledad_bootstrap_failed)
        logger.exception(exc)
        raise SoledadInitError()

    def load_and_sync_soledad(self, uuid=u"", offline=False):
        """
        Once everthing is in the right place, we instantiate and sync
        Soledad

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode.
        :param offline: whether to instantiate soledad for offline use.
        :type offline: bool

        :return: A Deferred which fires when soledad is sync, or which fails
                 with SoledadInitError or SoledadSyncError
        :rtype: defer.Deferred
        """
        local_param = self._get_soledad_local_params(uuid, offline)
        remote_param = self._get_soledad_server_params(uuid, offline)

        secrets_path, local_db_path, token = local_param
        server_url, cert_file = remote_param

        if offline:
            return self._load_soledad_nosync(
                uuid, secrets_path, local_db_path, cert_file, token)

        else:
            return self._load_soledad_online(uuid, secrets_path, local_db_path,
                                             server_url, cert_file, token)

    def _load_soledad_online(self, uuid, secrets_path, local_db_path,
                             server_url, cert_file, token):
        syncable = True
        try:
            self._do_soledad_init(uuid, secrets_path, local_db_path,
                                  server_url, cert_file, token, syncable)
        except SoledadInitError as e:
            # re-raise the exceptions from try_init,
            # we're currently handling the retries from the
            # soledad-launcher in the gui.
            return defer.fail(e)

        leap_assert(not sameProxiedObjects(self._soledad, None),
                    "Null soledad, error while initializing")

        address = make_address(
            self._user, self._provider_config.get_domain())
        syncer = Syncer(self._soledad, self._signaler)

        d = self._init_keymanager(address, token)
        d.addCallback(lambda _: syncer.sync())
        d.addErrback(self._soledad_sync_errback)
        return d

    def _load_soledad_nosync(self, uuid, secrets_path, local_db_path,
                             cert_file, token):
        syncable = False
        self._do_soledad_init(uuid, secrets_path, local_db_path,
                              "", cert_file, token, syncable)
        d = self._init_keymanager(self._address, token)
        return d

    def _soledad_sync_errback(self, failure):
        failure.trap(InvalidAuthTokenError)
        # in the case of an invalid token we have already turned off mail and
        # warned the user

    def _pick_server(self, uuid):
        """
        Choose a soledad server to sync against.

        :param uuid: the uuid for the user.
        :type uuid: unicode
        :returns: the server url
        :rtype: unicode
        """
        if not self._soledad_config:
            self._soledad_config = SoledadConfig()

        # TODO: Select server based on timezone (issue #3308)
        server_dict = self._soledad_config.get_hosts()

        if not server_dict.keys():
            # XXX raise more specific exception, and catch it properly!
            raise Exception("No soledad server found")

        selected_server = server_dict[first(server_dict.keys())]
        server_url = "https://%s:%s/user-%s" % (
            selected_server["hostname"],
            selected_server["port"],
            uuid)
        logger.debug("Using soledad server url: %s" % (server_url,))
        return server_url

    def _try_soledad_init(self, uuid, secrets_path, local_db_path,
                          server_url, cert_file, auth_token, syncable):
        """
        Try to initialize soledad.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        # TODO: If selected server fails, retry with another host
        # (issue #3309)
        encoding = sys.getfilesystemencoding()

        try:
            self._soledad = Soledad(
                uuid,
                self._password,
                secrets_path=secrets_path.encode(encoding),
                local_db_path=local_db_path.encode(encoding),
                server_url=server_url,
                cert_file=cert_file.encode(encoding),
                auth_token=auth_token,
                defer_encryption=True,
                syncable=syncable)

        # XXX All these errors should be handled by soledad itself,
        # and return a subclass of SoledadInitializationFailed

        # recoverable, will guarantee retries
        except (socket.timeout, socket.error, BootstrapSequenceError):
            logger.warning("Error while initializing Soledad")
            raise

        # unrecoverable
        except (u1db_errors.Unauthorized, u1db_errors.HTTPError):
            logger.error("Error while initializing Soledad (u1db error).")
            raise
        except Exception as exc:
            logger.exception("Unhandled error while initializating "
                             "Soledad: %r" % (exc,))
            raise

    def _download_config(self):
        """
        Download the Soledad config for the given provider
        """
        leap_assert(self._provider_config,
                    "We need a provider configuration!")
        logger.debug("Downloading Soledad config for %s" %
                     (self._provider_config.get_domain(),))

        self._soledad_config = SoledadConfig()
        download_service_config(
            self._provider_config,
            self._soledad_config,
            self._session,
            self._download_if_needed)

    def _get_gpg_bin_path(self):
        """
        Return the path to gpg binary.

        :returns: the gpg binary path
        :rtype: str
        """
        gpgbin = None
        if flags.STANDALONE:
            gpgbin = os.path.join(
                get_path_prefix(), "..", "apps", "mail", "gpg")
            if IS_WIN:
                gpgbin += ".exe"
        else:
            try:
                gpgbin_options = which("gpg")
                # gnupg checks that the path to the binary is not a
                # symlink, so we need to filter those and come up with
                # just one option.
                for opt in gpgbin_options:
                    if not os.path.islink(opt):
                        gpgbin = opt
                        break
            except IndexError as e:
                logger.debug("Couldn't find the gpg binary!")
                logger.exception(e)
        if IS_MAC:
            gpgbin = os.path.abspath(
                os.path.join(here(), "apps", "mail", "gpg"))

        # During the transition towards gpg2, we can look for /usr/bin/gpg1
        # binary, in case it was renamed using dpkg-divert or manually.
        # We could just pick gpg2, but we need to solve #7564 first.
        if gpgbin is None:
            try:
                gpgbin_options = which("gpg1")
                for opt in gpgbin_options:
                    if not os.path.islink(opt):
                        gpgbin = opt
                        break
            except IndexError as e:
                logger.debug("Couldn't find the gpg1 binary!")
                logger.exception(e)
        leap_check(gpgbin is not None, "Could not find gpg1 binary")
        return gpgbin

    def _init_keymanager(self, address, token):
        """
        Initialize the keymanager.

        :param address: the address to initialize the keymanager with.
        :type address: str
        :param token: the auth token for accessing webapp.
        :type token: str
        :rtype: Deferred
        """
        logger.debug('initializing keymanager...')

        if flags.OFFLINE:
            nickserver_uri = "https://localhost"
            kwargs = {
                "ca_cert_path": "",
                "api_uri": "",
                "api_version": "",
                "uid": self._uuid,
                "gpgbinary": self._get_gpg_bin_path()
            }
        else:
            nickserver_uri = "https://nicknym.%s:6425" % (
                self._provider_config.get_domain(),)
            kwargs = {
                "token": token,
                "ca_cert_path": self._provider_config.get_ca_cert_path(),
                "api_uri": self._provider_config.get_api_uri(),
                "api_version": self._provider_config.get_api_version(),
                "uid": self.srpauth.get_uuid(),
                "gpgbinary": self._get_gpg_bin_path()
            }
        self._keymanager = KeyManager(address, nickserver_uri, self._soledad,
                                      **kwargs)

        if flags.OFFLINE is False:
            # make sure key is in server
            logger.debug('Trying to send key to server...')

            def send_errback(failure):
                if failure.check(KeyNotFound):
                    logger.debug(
                        'No key found for %s, it might be because soledad not '
                        'synced yet or it will generate it soon.' % address)
                else:
                    logger.error("Error sending key to server.")
                    logger.exception(failure.value)
                    # but we do not raise

            d = self._keymanager.send_key(openpgp.OpenPGPKey)
            d.addErrback(send_errback)
            return d
        else:
            return defer.succeed(None)

    def _gen_key(self):
        """
        Generates the key pair if needed, uploads it to the webapp and
        nickserver
        :rtype: Deferred
        """
        leap_assert(self._provider_config is not None,
                    "We need a provider configuration!")
        leap_assert(self._soledad is not None,
                    "We need a non-null soledad to generate keys")

        address = make_address(
            self._user, self._provider_config.get_domain())
        logger.debug("Retrieving key for %s" % (address,))

        def if_not_found_generate(failure):
            failure.trap(KeyNotFound)
            logger.debug("Key not found. Generating key for %s"
                         % (address,))
            d = self._keymanager.gen_key(openpgp.OpenPGPKey)
            d.addCallbacks(send_key, log_key_error("generating"))
            return d

        def send_key(_):
            d = self._keymanager.send_key(openpgp.OpenPGPKey)
            d.addCallbacks(
                lambda _: logger.debug("Key generated successfully."),
                log_key_error("sending"))

        def log_key_error(step):
            def log_err(failure):
                logger.error("Error while %s key!", (step,))
                logger.exception(failure.value)
                return failure
            return log_err

        d = self._keymanager.get_key(
            address, openpgp.OpenPGPKey, private=True, fetch_remote=False)
        d.addErrback(if_not_found_generate)
        return d

    def run_soledad_setup_checks(self, provider_config, user, password,
                                 download_if_needed=False):
        """
        Starts the checks needed for a new soledad setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        :param user: User's login
        :type user: unicode
        :param password: User's password
        :type password: unicode
        :param download_if_needed: If True, it will only download
                                   files if the have changed since the
                                   time it was previously downloaded.
        :type download_if_needed: bool

        :return: Deferred
        """
        leap_assert_type(provider_config, ProviderConfig)

        # XXX we should provider a method for setting provider_config
        self._provider_config = provider_config
        self._download_if_needed = download_if_needed
        self._user = user
        self._password = password

        if flags.OFFLINE:
            signal_finished = self._signaler.soledad_offline_finished
            self._signaler.signal(signal_finished)
            return defer.succeed(True)

        signal_finished = self._signaler.soledad_bootstrap_finished
        signal_failed = self._signaler.soledad_bootstrap_failed

        try:
            # XXX FIXME make this async too! (use txrequests)
            # Also, why the f**k would we want to download it *every time*?
            # We should be fine by using last-time config, or at least
            # trying it.
            self._download_config()
            uuid = self.srpauth.get_uuid()
        except Exception as e:
            # TODO: we should handle more specific exceptions in here
            self._soledad = None
            self._keymanager = None
            logger.exception("Error while bootstrapping Soledad: %r" % (e,))
            self._signaler.signal(signal_failed)
            return defer.succeed(None)

        # soledad config is ok, let's proceed to load and sync soledad
        d = self.load_and_sync_soledad(uuid)
        d.addCallback(lambda _: self._gen_key())
        d.addCallback(lambda _: self._signaler.signal(signal_finished))
        return d
class SoledadBootstrapper(AbstractBootstrapper):
    """
    Soledad init procedure
    """

    SOLEDAD_KEY = "soledad"
    KEYMANAGER_KEY = "keymanager"

    PUBKEY_KEY = "user[public_key]"

    MAX_INIT_RETRIES = 10

    # All dicts returned are of the form
    # {"passed": bool, "error": str}
    download_config = QtCore.Signal(dict)
    gen_key = QtCore.Signal(dict)
    soledad_timeout = QtCore.Signal()
    soledad_failed = QtCore.Signal()

    def __init__(self):
        AbstractBootstrapper.__init__(self)

        self._provider_config = None
        self._soledad_config = None
        self._keymanager = None
        self._download_if_needed = False
        self._user = ""
        self._password = ""
        self._srpauth = None
        self._soledad = None

        self._soledad_retries = 0

    @property
    def keymanager(self):
        return self._keymanager

    @property
    def soledad(self):
        return self._soledad

    @property
    def srpauth(self):
        leap_assert(self._provider_config is not None,
                    "We need a provider config")
        return SRPAuth(self._provider_config)

    # retries

    def cancel_bootstrap(self):
        self._soledad_retries = self.MAX_INIT_RETRIES

    def should_retry_initialization(self):
        """
        Returns True if we should retry the initialization.
        """
        logger.debug("current retries: %s, max retries: %s" % (
            self._soledad_retries,
            self.MAX_INIT_RETRIES))
        return self._soledad_retries < self.MAX_INIT_RETRIES

    def increment_retries_count(self):
        """
        Increments the count of initialization retries.
        """
        self._soledad_retries += 1

    # initialization

    def load_and_sync_soledad(self):
        """
        Once everthing is in the right place, we instantiate and sync
        Soledad

        :param srp_auth: SRPAuth object used
        :type srp_auth: SRPAuth
        """
        srp_auth = self.srpauth
        uuid = srp_auth.get_uid()

        prefix = os.path.join(self._soledad_config.get_path_prefix(),
                              "leap", "soledad")
        secrets_path = "%s/%s.secret" % (prefix, uuid)
        local_db_path = "%s/%s.db" % (prefix, uuid)

        # TODO: Select server based on timezone (issue #3308)
        server_dict = self._soledad_config.get_hosts()

        if server_dict.keys():
            selected_server = server_dict[server_dict.keys()[0]]
            server_url = "https://%s:%s/user-%s" % (
                selected_server["hostname"],
                selected_server["port"],
                uuid)

            logger.debug("Using soledad server url: %s" % (server_url,))

            cert_file = self._provider_config.get_ca_cert_path()

            # TODO: If selected server fails, retry with another host
            # (issue #3309)
            try:
                self._soledad = Soledad(
                    uuid,
                    self._password.encode("utf-8"),
                    secrets_path=secrets_path,
                    local_db_path=local_db_path,
                    server_url=server_url,
                    cert_file=cert_file,
                    auth_token=srp_auth.get_token())
                self._soledad.sync()

            # XXX All these errors should be handled by soledad itself,
            # and return a subclass of SoledadInitializationFailed
            except socket.timeout:
                logger.debug("SOLEDAD TIMED OUT...")
                self.soledad_timeout.emit()
            except socket.error as exc:
                logger.error("Socket error while initializing soledad")
                self.soledad_failed.emit()
            except u1db_errors.Unauthorized:
                logger.error("Error while initializing soledad "
                             "(unauthorized).")
                self.soledad_failed.emit()
            except Exception as exc:
                logger.error("Unhandled error while initializating "
                             "soledad: %r" % (exc,))
                raise
        else:
            raise Exception("No soledad server found")

    def _download_config(self):
        """
        Downloads the Soledad config for the given provider
        """

        leap_assert(self._provider_config,
                    "We need a provider configuration!")

        logger.debug("Downloading Soledad config for %s" %
                     (self._provider_config.get_domain(),))

        self._soledad_config = SoledadConfig()

        headers = {}
        mtime = get_mtime(
            os.path.join(
                self._soledad_config.get_path_prefix(),
                "leap", "providers",
                self._provider_config.get_domain(),
                "soledad-service.json"))

        if self._download_if_needed and mtime:
            headers['if-modified-since'] = mtime

        api_version = self._provider_config.get_api_version()

        # there is some confusion with this uri,
        config_uri = "%s/%s/config/soledad-service.json" % (
            self._provider_config.get_api_uri(),
            api_version)
        logger.debug('Downloading soledad config from: %s' % config_uri)

        # TODO factor out this srpauth protected get (make decorator)
        srp_auth = self.srpauth
        session_id = srp_auth.get_session_id()
        cookies = None
        if session_id:
            cookies = {"_session_id": session_id}

        res = self._session.get(config_uri,
                                verify=self._provider_config
                                .get_ca_cert_path(),
                                headers=headers,
                                cookies=cookies)
        res.raise_for_status()

        self._soledad_config.set_api_version(api_version)

        # Not modified
        if res.status_code == 304:
            logger.debug("Soledad definition has not been modified")
            self._soledad_config.load(
                os.path.join(
                    "leap", "providers",
                    self._provider_config.get_domain(),
                    "soledad-service.json"))
        else:
            soledad_definition, mtime = get_content(res)

            self._soledad_config.load(data=soledad_definition, mtime=mtime)
            self._soledad_config.save(["leap",
                                       "providers",
                                       self._provider_config.get_domain(),
                                       "soledad-service.json"])

        self.load_and_sync_soledad()

    def _gen_key(self, _):
        """
        Generates the key pair if needed, uploads it to the webapp and
        nickserver
        """
        leap_assert(self._provider_config,
                    "We need a provider configuration!")

        address = "%s@%s" % (self._user, self._provider_config.get_domain())

        logger.debug("Retrieving key for %s" % (address,))

        srp_auth = self.srpauth

        # TODO: use which implementation with known paths
        # TODO: Fix for Windows
        gpgbin = "/usr/bin/gpg"

        if self._standalone:
            gpgbin = os.path.join(self._provider_config.get_path_prefix(),
                                  "..", "apps", "mail", "gpg")

        self._keymanager = KeyManager(
            address,
            "https://nicknym.%s:6425" % (self._provider_config.get_domain(),),
            self._soledad,
            #token=srp_auth.get_token(),  # TODO: enable token usage
            session_id=srp_auth.get_session_id(),
            ca_cert_path=self._provider_config.get_ca_cert_path(),
            api_uri=self._provider_config.get_api_uri(),
            api_version=self._provider_config.get_api_version(),
            uid=srp_auth.get_uid(),
            gpgbinary=gpgbin)
        try:
            self._keymanager.get_key(address, openpgp.OpenPGPKey,
                                     private=True, fetch_remote=False)
        except KeyNotFound:
            logger.debug("Key not found. Generating key for %s" % (address,))
            self._keymanager.gen_key(openpgp.OpenPGPKey)
            self._keymanager.send_key(openpgp.OpenPGPKey)
            logger.debug("Key generated successfully.")

    def run_soledad_setup_checks(self,
                                 provider_config,
                                 user,
                                 password,
                                 download_if_needed=False,
                                 standalone=False):
        """
        Starts the checks needed for a new soledad setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        :param user: User's login
        :type user: str
        :param password: User's password
        :type password: str
        :param download_if_needed: If True, it will only download
                                   files if the have changed since the
                                   time it was previously downloaded.
        :type download_if_needed: bool
        :param standalone: If True, it'll look for paths inside the
                           bundle (like for gpg)
        :type standalone: bool
        """
        leap_assert_type(provider_config, ProviderConfig)

        # XXX we should provider a method for setting provider_config
        self._provider_config = provider_config
        self._download_if_needed = download_if_needed
        self._user = user
        self._password = password
        self._standalone = standalone

        cb_chain = [
            (self._download_config, self.download_config),
            (self._gen_key, self.gen_key)
        ]

        self.addCallbackChain(cb_chain)
Exemplo n.º 30
0
class SoledadBootstrapper(AbstractBootstrapper):
    """
    Soledad init procedure.
    """
    SOLEDAD_KEY = "soledad"
    KEYMANAGER_KEY = "keymanager"

    PUBKEY_KEY = "user[public_key]"

    MAX_INIT_RETRIES = 10

    def __init__(self, signaler=None):
        AbstractBootstrapper.__init__(self, signaler)

        if signaler is not None:
            self._cancel_signal = signaler.soledad_cancelled_bootstrap

        self._provider_config = None
        self._soledad_config = None
        self._download_if_needed = False

        self._user = ""
        self._password = u""
        self._address = ""
        self._uuid = ""

        self._srpauth = None

        self._soledad = None
        self._keymanager = None

    @property
    def srpauth(self):
        if flags.OFFLINE is True:
            return None
        if self._srpauth is None:
            leap_assert(self._provider_config is not None,
                        "We need a provider config")
            self._srpauth = SRPAuth(self._provider_config)
        return self._srpauth

    @property
    def soledad(self):
        return self._soledad

    @property
    def keymanager(self):
        return self._keymanager

    # initialization

    def load_offline_soledad(self, username, password, uuid):
        """
        Instantiate Soledad for offline use.

        :param username: full user id (user@provider)
        :type username: str or unicode
        :param password: the soledad passphrase
        :type password: unicode
        :param uuid: the user uuid
        :type uuid: str or unicode
        """
        self._address = username
        self._password = password
        self._uuid = uuid

        def error(failure):
            # TODO: we should handle more specific exceptions in here
            logger.exception(failure.value)
            self._signaler.signal(self._signaler.soledad_offline_failed)

        d = self.load_and_sync_soledad(uuid, offline=True)
        d.addCallback(lambda _: self._signaler.signal(
            self._signaler.soledad_offline_finished))
        d.addErrback(error)
        return d

    def _get_soledad_local_params(self, uuid, offline=False):
        """
        Return the locals parameters needed for the soledad initialization.

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :return: secrets_path, local_db_path, token
        :rtype: tuple
        """
        # in the future, when we want to be able to switch to
        # online mode, this should be a proxy object too.
        # Same for server_url below.

        if offline is False:
            token = self.srpauth.get_token()
        else:
            token = ""

        secrets_path, local_db_path = get_db_paths(uuid)

        logger.debug('secrets_path:%s' % (secrets_path, ))
        logger.debug('local_db:%s' % (local_db_path, ))
        return (secrets_path, local_db_path, token)

    def _get_soledad_server_params(self, uuid, offline):
        """
        Return the remote parameters needed for the soledad initialization.

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :return: server_url, cert_file
        :rtype: tuple
        """
        if offline is True:
            server_url = "http://localhost:9999/"
            cert_file = ""
        else:
            if uuid is None:
                uuid = self.srpauth.get_uuid()
            server_url = self._pick_server(uuid)
            cert_file = self._provider_config.get_ca_cert_path()

        return server_url, cert_file

    def _do_soledad_init(self, uuid, secrets_path, local_db_path, server_url,
                         cert_file, token, syncable):
        """
        Initialize soledad, retry if necessary and raise an exception if we
        can't succeed.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        init_tries = 1
        while init_tries <= self.MAX_INIT_RETRIES:
            try:
                logger.debug("Trying to init soledad....")
                self._try_soledad_init(uuid, secrets_path, local_db_path,
                                       server_url, cert_file, token, syncable)
                logger.debug("Soledad has been initialized.")
                return
            except Exception as exc:
                init_tries += 1
                msg = "Init failed, retrying... (retry {0} of {1})".format(
                    init_tries, self.MAX_INIT_RETRIES)
                logger.warning(msg)
                continue

        self._signaler.signal(self._signaler.soledad_bootstrap_failed)
        logger.exception(exc)
        raise SoledadInitError()

    def load_and_sync_soledad(self, uuid=u"", offline=False):
        """
        Once everthing is in the right place, we instantiate and sync
        Soledad

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode.
        :param offline: whether to instantiate soledad for offline use.
        :type offline: bool

        :return: A Deferred which fires when soledad is sync, or which fails
                 with SoledadInitError or SoledadSyncError
        :rtype: defer.Deferred
        """
        local_param = self._get_soledad_local_params(uuid, offline)
        remote_param = self._get_soledad_server_params(uuid, offline)

        secrets_path, local_db_path, token = local_param
        server_url, cert_file = remote_param

        if offline:
            return self._load_soledad_nosync(uuid, secrets_path, local_db_path,
                                             cert_file, token)

        else:
            return self._load_soledad_online(uuid, secrets_path, local_db_path,
                                             server_url, cert_file, token)

    def _load_soledad_online(self, uuid, secrets_path, local_db_path,
                             server_url, cert_file, token):
        syncable = True
        try:
            self._do_soledad_init(uuid, secrets_path, local_db_path,
                                  server_url, cert_file, token, syncable)
        except SoledadInitError as e:
            # re-raise the exceptions from try_init,
            # we're currently handling the retries from the
            # soledad-launcher in the gui.
            return defer.fail(e)

        leap_assert(not sameProxiedObjects(self._soledad, None),
                    "Null soledad, error while initializing")

        address = make_address(self._user, self._provider_config.get_domain())
        syncer = Syncer(self._soledad, self._signaler)

        d = self._init_keymanager(address, token)
        d.addCallback(lambda _: syncer.sync())
        d.addErrback(self._soledad_sync_errback)
        return d

    def _load_soledad_nosync(self, uuid, secrets_path, local_db_path,
                             cert_file, token):
        syncable = False
        self._do_soledad_init(uuid, secrets_path, local_db_path, "", cert_file,
                              token, syncable)
        d = self._init_keymanager(self._address, token)
        return d

    def _soledad_sync_errback(self, failure):
        failure.trap(InvalidAuthTokenError)
        # in the case of an invalid token we have already turned off mail and
        # warned the user

    def _pick_server(self, uuid):
        """
        Choose a soledad server to sync against.

        :param uuid: the uuid for the user.
        :type uuid: unicode
        :returns: the server url
        :rtype: unicode
        """
        if not self._soledad_config:
            self._soledad_config = SoledadConfig()

        # TODO: Select server based on timezone (issue #3308)
        server_dict = self._soledad_config.get_hosts()

        if not server_dict.keys():
            # XXX raise more specific exception, and catch it properly!
            raise Exception("No soledad server found")

        selected_server = server_dict[first(server_dict.keys())]
        server_url = "https://%s:%s/user-%s" % (selected_server["hostname"],
                                                selected_server["port"], uuid)
        logger.debug("Using soledad server url: %s" % (server_url, ))
        return server_url

    def _try_soledad_init(self, uuid, secrets_path, local_db_path, server_url,
                          cert_file, auth_token, syncable):
        """
        Try to initialize soledad.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        # TODO: If selected server fails, retry with another host
        # (issue #3309)
        encoding = sys.getfilesystemencoding()

        try:
            self._soledad = Soledad(
                uuid,
                self._password,
                secrets_path=secrets_path.encode(encoding),
                local_db_path=local_db_path.encode(encoding),
                server_url=server_url,
                cert_file=cert_file.encode(encoding),
                auth_token=auth_token,
                defer_encryption=True,
                syncable=syncable)

        # XXX All these errors should be handled by soledad itself,
        # and return a subclass of SoledadInitializationFailed

        # recoverable, will guarantee retries
        except (socket.timeout, socket.error, BootstrapSequenceError):
            logger.warning("Error while initializing Soledad")
            raise

        # unrecoverable
        except (u1db_errors.Unauthorized, u1db_errors.HTTPError):
            logger.error("Error while initializing Soledad (u1db error).")
            raise
        except Exception as exc:
            logger.exception("Unhandled error while initializating "
                             "Soledad: %r" % (exc, ))
            raise

    def _download_config(self):
        """
        Download the Soledad config for the given provider
        """
        leap_assert(self._provider_config, "We need a provider configuration!")
        logger.debug("Downloading Soledad config for %s" %
                     (self._provider_config.get_domain(), ))

        self._soledad_config = SoledadConfig()
        download_service_config(self._provider_config, self._soledad_config,
                                self._session, self._download_if_needed)

    def _get_gpg_bin_path(self):
        """
        Return the path to gpg binary.

        :returns: the gpg binary path
        :rtype: str
        """
        gpgbin = None
        if flags.STANDALONE:
            gpgbin = os.path.join(get_path_prefix(), "..", "apps", "mail",
                                  "gpg")
            if IS_WIN:
                gpgbin += ".exe"
        else:
            try:
                gpgbin_options = which("gpg")
                # gnupg checks that the path to the binary is not a
                # symlink, so we need to filter those and come up with
                # just one option.
                for opt in gpgbin_options:
                    if not os.path.islink(opt):
                        gpgbin = opt
                        break
            except IndexError as e:
                logger.debug("Couldn't find the gpg binary!")
                logger.exception(e)
        if IS_MAC:
            gpgbin = os.path.abspath(
                os.path.join(here(), "apps", "mail", "gpg"))

        # During the transition towards gpg2, we can look for /usr/bin/gpg1
        # binary, in case it was renamed using dpkg-divert or manually.
        # We could just pick gpg2, but we need to solve #7564 first.
        if gpgbin is None:
            try:
                gpgbin_options = which("gpg1")
                for opt in gpgbin_options:
                    if not os.path.islink(opt):
                        gpgbin = opt
                        break
            except IndexError as e:
                logger.debug("Couldn't find the gpg1 binary!")
                logger.exception(e)
        leap_check(gpgbin is not None, "Could not find gpg1 binary")
        return gpgbin

    def _init_keymanager(self, address, token):
        """
        Initialize the keymanager.

        :param address: the address to initialize the keymanager with.
        :type address: str
        :param token: the auth token for accessing webapp.
        :type token: str
        :rtype: Deferred
        """
        logger.debug('initializing keymanager...')

        if flags.OFFLINE:
            nickserver_uri = "https://localhost"
            kwargs = {
                "ca_cert_path": "",
                "api_uri": "",
                "api_version": "",
                "uid": self._uuid,
                "gpgbinary": self._get_gpg_bin_path()
            }
        else:
            nickserver_uri = "https://nicknym.%s:6425" % (
                self._provider_config.get_domain(), )
            kwargs = {
                "token": token,
                "ca_cert_path": self._provider_config.get_ca_cert_path(),
                "api_uri": self._provider_config.get_api_uri(),
                "api_version": self._provider_config.get_api_version(),
                "uid": self.srpauth.get_uuid(),
                "gpgbinary": self._get_gpg_bin_path()
            }
        self._keymanager = KeyManager(address, nickserver_uri, self._soledad,
                                      **kwargs)

        if flags.OFFLINE is False:
            # make sure key is in server
            logger.debug('Trying to send key to server...')

            def send_errback(failure):
                if failure.check(KeyNotFound):
                    logger.debug(
                        'No key found for %s, it might be because soledad not '
                        'synced yet or it will generate it soon.' % address)
                else:
                    logger.error("Error sending key to server.")
                    logger.exception(failure.value)
                    # but we do not raise

            d = self._keymanager.send_key(openpgp.OpenPGPKey)
            d.addErrback(send_errback)
            return d
        else:
            return defer.succeed(None)

    def _gen_key(self):
        """
        Generates the key pair if needed, uploads it to the webapp and
        nickserver
        :rtype: Deferred
        """
        leap_assert(self._provider_config is not None,
                    "We need a provider configuration!")
        leap_assert(self._soledad is not None,
                    "We need a non-null soledad to generate keys")

        address = make_address(self._user, self._provider_config.get_domain())
        logger.debug("Retrieving key for %s" % (address, ))

        def if_not_found_generate(failure):
            failure.trap(KeyNotFound)
            logger.debug("Key not found. Generating key for %s" % (address, ))
            d = self._keymanager.gen_key(openpgp.OpenPGPKey)
            d.addCallbacks(send_key, log_key_error("generating"))
            return d

        def send_key(_):
            d = self._keymanager.send_key(openpgp.OpenPGPKey)
            d.addCallbacks(
                lambda _: logger.debug("Key generated successfully."),
                log_key_error("sending"))

        def log_key_error(step):
            def log_err(failure):
                logger.error("Error while %s key!", (step, ))
                logger.exception(failure.value)
                return failure

            return log_err

        d = self._keymanager.get_key(address,
                                     openpgp.OpenPGPKey,
                                     private=True,
                                     fetch_remote=False)
        d.addErrback(if_not_found_generate)
        return d

    def run_soledad_setup_checks(self,
                                 provider_config,
                                 user,
                                 password,
                                 download_if_needed=False):
        """
        Starts the checks needed for a new soledad setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        :param user: User's login
        :type user: unicode
        :param password: User's password
        :type password: unicode
        :param download_if_needed: If True, it will only download
                                   files if the have changed since the
                                   time it was previously downloaded.
        :type download_if_needed: bool

        :return: Deferred
        """
        leap_assert_type(provider_config, ProviderConfig)

        # XXX we should provider a method for setting provider_config
        self._provider_config = provider_config
        self._download_if_needed = download_if_needed
        self._user = user
        self._password = password

        if flags.OFFLINE:
            signal_finished = self._signaler.soledad_offline_finished
            self._signaler.signal(signal_finished)
            return defer.succeed(True)

        signal_finished = self._signaler.soledad_bootstrap_finished
        signal_failed = self._signaler.soledad_bootstrap_failed

        try:
            # XXX FIXME make this async too! (use txrequests)
            # Also, why the f**k would we want to download it *every time*?
            # We should be fine by using last-time config, or at least
            # trying it.
            self._download_config()
            uuid = self.srpauth.get_uuid()
        except Exception as e:
            # TODO: we should handle more specific exceptions in here
            self._soledad = None
            self._keymanager = None
            logger.exception("Error while bootstrapping Soledad: %r" % (e, ))
            self._signaler.signal(signal_failed)
            return defer.succeed(None)

        # soledad config is ok, let's proceed to load and sync soledad
        d = self.load_and_sync_soledad(uuid)
        d.addCallback(lambda _: self._gen_key())
        d.addCallback(lambda _: self._signaler.signal(signal_finished))
        return d
Exemplo n.º 31
0
class SoledadBootstrapper(AbstractBootstrapper):
    """
    Soledad init procedure.
    """

    SOLEDAD_KEY = "soledad"
    KEYMANAGER_KEY = "keymanager"

    PUBKEY_KEY = "user[public_key]"

    MAX_INIT_RETRIES = 10
    MAX_SYNC_RETRIES = 10
    WAIT_MAX_SECONDS = 600
    # WAIT_STEP_SECONDS = 1
    WAIT_STEP_SECONDS = 5

    def __init__(self, signaler=None):
        AbstractBootstrapper.__init__(self, signaler)

        if signaler is not None:
            self._cancel_signal = signaler.soledad_cancelled_bootstrap

        self._provider_config = None
        self._soledad_config = None
        self._keymanager = None
        self._download_if_needed = False

        self._user = ""
        self._password = ""
        self._address = ""
        self._uuid = ""

        self._srpauth = None
        self._soledad = None

    @property
    def keymanager(self):
        return self._keymanager

    @property
    def soledad(self):
        return self._soledad

    @property
    def srpauth(self):
        if flags.OFFLINE is True:
            return None
        leap_assert(self._provider_config is not None, "We need a provider config")
        return SRPAuth(self._provider_config)

    # initialization

    def load_offline_soledad(self, username, password, uuid):
        """
        Instantiate Soledad for offline use.

        :param username: full user id (user@provider)
        :type username: str or unicode
        :param password: the soledad passphrase
        :type password: unicode
        :param uuid: the user uuid
        :type uuid: str or unicode
        """
        self._address = username
        self._password = password
        self._uuid = uuid
        try:
            self.load_and_sync_soledad(uuid, offline=True)
            self._signaler.signal(self._signaler.soledad_offline_finished)
        except Exception as e:
            # TODO: we should handle more specific exceptions in here
            logger.exception(e)
            self._signaler.signal(self._signaler.soledad_offline_failed)

    def _get_soledad_local_params(self, uuid, offline=False):
        """
        Return the locals parameters needed for the soledad initialization.

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :return: secrets_path, local_db_path, token
        :rtype: tuple
        """
        # in the future, when we want to be able to switch to
        # online mode, this should be a proxy object too.
        # Same for server_url below.

        if offline is False:
            token = self.srpauth.get_token()
        else:
            token = ""

        secrets_path, local_db_path = get_db_paths(uuid)

        logger.debug("secrets_path:%s" % (secrets_path,))
        logger.debug("local_db:%s" % (local_db_path,))
        return (secrets_path, local_db_path, token)

    def _get_soledad_server_params(self, uuid, offline):
        """
        Return the remote parameters needed for the soledad initialization.

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :return: server_url, cert_file
        :rtype: tuple
        """
        if uuid is None:
            uuid = self.srpauth.get_uuid()

        if offline is True:
            server_url = "http://localhost:9999/"
            cert_file = ""
        else:
            server_url = self._pick_server(uuid)
            cert_file = self._provider_config.get_ca_cert_path()

        return server_url, cert_file

    def _soledad_sync_errback(self, failure):
        failure.trap(InvalidAuthTokenError)
        # in the case of an invalid token we have already turned off mail and
        # warned the user in _do_soledad_sync()

    def _do_soledad_init(self, uuid, secrets_path, local_db_path, server_url, cert_file, token):
        """
        Initialize soledad, retry if necessary and raise an exception if we
        can't succeed.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        init_tries = 1
        while init_tries <= self.MAX_INIT_RETRIES:
            try:
                logger.debug("Trying to init soledad....")
                self._try_soledad_init(uuid, secrets_path, local_db_path, server_url, cert_file, token)
                logger.debug("Soledad has been initialized.")
                return
            except Exception as exc:
                init_tries += 1
                msg = "Init failed, retrying... (retry {0} of {1})".format(init_tries, self.MAX_INIT_RETRIES)
                logger.warning(msg)
                continue

        logger.exception(exc)
        raise SoledadInitError()

    def load_and_sync_soledad(self, uuid=None, offline=False):
        """
        Once everthing is in the right place, we instantiate and sync
        Soledad

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :param offline: whether to instantiate soledad for offline use.
        :type offline: bool
        """
        local_param = self._get_soledad_local_params(uuid, offline)
        remote_param = self._get_soledad_server_params(uuid, offline)

        secrets_path, local_db_path, token = local_param
        server_url, cert_file = remote_param

        try:
            self._do_soledad_init(uuid, secrets_path, local_db_path, server_url, cert_file, token)
        except SoledadInitError:
            # re-raise the exceptions from try_init,
            # we're currently handling the retries from the
            # soledad-launcher in the gui.
            raise

        leap_assert(not sameProxiedObjects(self._soledad, None), "Null soledad, error while initializing")

        if flags.OFFLINE:
            self._init_keymanager(self._address, token)
        else:
            try:
                address = make_address(self._user, self._provider_config.get_domain())
                self._init_keymanager(address, token)
                self._keymanager.get_key(address, openpgp.OpenPGPKey, private=True, fetch_remote=False)
                d = threads.deferToThread(self._do_soledad_sync)
                d.addErrback(self._soledad_sync_errback)
            except KeyNotFound:
                logger.debug("Key not found. Generating key for %s" % (address,))
                self._do_soledad_sync()

    def _pick_server(self, uuid):
        """
        Choose a soledad server to sync against.

        :param uuid: the uuid for the user.
        :type uuid: unicode
        :returns: the server url
        :rtype: unicode
        """
        # TODO: Select server based on timezone (issue #3308)
        server_dict = self._soledad_config.get_hosts()

        if not server_dict.keys():
            # XXX raise more specific exception, and catch it properly!
            raise Exception("No soledad server found")

        selected_server = server_dict[first(server_dict.keys())]
        server_url = "https://%s:%s/user-%s" % (selected_server["hostname"], selected_server["port"], uuid)
        logger.debug("Using soledad server url: %s" % (server_url,))
        return server_url

    def _do_soledad_sync(self):
        """
        Do several retries to get an initial soledad sync.
        """
        # and now, let's sync
        sync_tries = self.MAX_SYNC_RETRIES
        step = self.WAIT_STEP_SECONDS
        max_wait = self.WAIT_MAX_SECONDS
        while sync_tries > 0:
            wait = 0
            try:
                logger.debug("Trying to sync soledad....")
                self._try_soledad_sync()
                while self.soledad.syncing:
                    time.sleep(step)
                    wait += step
                    if wait >= max_wait:
                        raise SoledadSyncError("timeout!")
                logger.debug("Soledad has been synced!")
                # so long, and thanks for all the fish
                return
            except SoledadSyncError:
                # maybe it's my connection, but I'm getting
                # ssl handshake timeouts and read errors quite often.
                # A particularly big sync is a disaster.
                # This deserves further investigation, maybe the
                # retry strategy can be pushed to u1db, or at least
                # it's something worthy to talk about with the
                # ubuntu folks.
                sync_tries += 1
                msg = "Sync failed, retrying... (retry {0} of {1})".format(sync_tries, self.MAX_SYNC_RETRIES)
                logger.warning(msg)
                continue
            except InvalidAuthTokenError:
                self._signaler.signal(self._signaler.soledad_invalid_auth_token)
                raise
            except Exception as e:
                # XXX release syncing lock
                logger.exception("Unhandled error while syncing " "soledad: %r" % (e,))
                break

        raise SoledadSyncError()

    def _try_soledad_init(self, uuid, secrets_path, local_db_path, server_url, cert_file, auth_token):
        """
        Try to initialize soledad.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        # TODO: If selected server fails, retry with another host
        # (issue #3309)
        encoding = sys.getfilesystemencoding()

        # XXX We should get a flag in soledad itself
        if flags.OFFLINE is True:
            Soledad._shared_db = MockSharedDB()
        try:
            self._soledad = Soledad(
                uuid,
                self._password,
                secrets_path=secrets_path.encode(encoding),
                local_db_path=local_db_path.encode(encoding),
                server_url=server_url,
                cert_file=cert_file.encode(encoding),
                auth_token=auth_token,
                defer_encryption=True,
            )

        # XXX All these errors should be handled by soledad itself,
        # and return a subclass of SoledadInitializationFailed

        # recoverable, will guarantee retries
        except (socket.timeout, socket.error, BootstrapSequenceError):
            logger.warning("Error while initializing Soledad")
            raise

        # unrecoverable
        except (u1db_errors.Unauthorized, u1db_errors.HTTPError):
            logger.error("Error while initializing Soledad (u1db error).")
            raise
        except Exception as exc:
            logger.exception("Unhandled error while initializating " "Soledad: %r" % (exc,))
            raise

    def _try_soledad_sync(self):
        """
        Try to sync soledad.
        Raises SoledadSyncError if not successful.
        """
        try:
            logger.debug("BOOTSTRAPPER: trying to sync Soledad....")
            # pass defer_decryption=False to get inline decryption
            # for debugging.
            self._soledad.sync(defer_decryption=True)
        except SSLError as exc:
            logger.error("%r" % (exc,))
            raise SoledadSyncError("Failed to sync soledad")
        except u1db_errors.InvalidGeneration as exc:
            logger.error("%r" % (exc,))
            raise SoledadSyncError("u1db: InvalidGeneration")
        except (sqlite_ProgrammingError, sqlcipher_ProgrammingError) as e:
            logger.exception("%r" % (e,))
            raise
        except InvalidAuthTokenError:
            # token is invalid, probably expired
            logger.error("Invalid auth token while trying to sync Soledad")
            raise
        except Exception as exc:
            logger.exception("Unhandled error while syncing " "soledad: %r" % (exc,))
            raise SoledadSyncError("Failed to sync soledad")

    def _download_config(self):
        """
        Download the Soledad config for the given provider
        """
        leap_assert(self._provider_config, "We need a provider configuration!")
        logger.debug("Downloading Soledad config for %s" % (self._provider_config.get_domain(),))

        self._soledad_config = SoledadConfig()
        download_service_config(self._provider_config, self._soledad_config, self._session, self._download_if_needed)

    def _get_gpg_bin_path(self):
        """
        Return the path to gpg binary.

        :returns: the gpg binary path
        :rtype: str
        """
        gpgbin = None
        if flags.STANDALONE:
            gpgbin = os.path.join(get_path_prefix(), "..", "apps", "mail", "gpg")
            if IS_WIN:
                gpgbin += ".exe"
        else:
            try:
                gpgbin_options = which("gpg")
                # gnupg checks that the path to the binary is not a
                # symlink, so we need to filter those and come up with
                # just one option.
                for opt in gpgbin_options:
                    if not os.path.islink(opt):
                        gpgbin = opt
                        break
            except IndexError as e:
                logger.debug("Couldn't find the gpg binary!")
                logger.exception(e)
        leap_check(gpgbin is not None, "Could not find gpg binary")
        return gpgbin

    def _init_keymanager(self, address, token):
        """
        Initialize the keymanager.

        :param address: the address to initialize the keymanager with.
        :type address: str
        :param token: the auth token for accessing webapp.
        :type token: str
        """
        srp_auth = self.srpauth
        logger.debug("initializing keymanager...")

        if flags.OFFLINE is True:
            args = (address, "https://localhost", self._soledad)
            kwargs = {
                "ca_cert_path": "",
                "api_uri": "",
                "api_version": "",
                "uid": self._uuid,
                "gpgbinary": self._get_gpg_bin_path(),
            }
        else:
            args = (address, "https://nicknym.%s:6425" % (self._provider_config.get_domain(),), self._soledad)
            kwargs = {
                "token": token,
                "ca_cert_path": self._provider_config.get_ca_cert_path(),
                "api_uri": self._provider_config.get_api_uri(),
                "api_version": self._provider_config.get_api_version(),
                "uid": srp_auth.get_uuid(),
                "gpgbinary": self._get_gpg_bin_path(),
            }
        try:
            self._keymanager = KeyManager(*args, **kwargs)
        except KeyNotFound:
            logger.debug("key for %s not found." % address)
        except Exception as exc:
            logger.exception(exc)
            raise

        if flags.OFFLINE is False:
            # make sure key is in server
            logger.debug("Trying to send key to server...")
            try:
                self._keymanager.send_key(openpgp.OpenPGPKey)
            except KeyNotFound:
                logger.debug("No key found for %s, will generate soon." % address)
            except Exception as exc:
                logger.error("Error sending key to server.")
                logger.exception(exc)
                # but we do not raise

    def _gen_key(self):
        """
        Generates the key pair if needed, uploads it to the webapp and
        nickserver
        """
        leap_assert(self._provider_config is not None, "We need a provider configuration!")
        leap_assert(self._soledad is not None, "We need a non-null soledad to generate keys")

        address = make_address(self._user, self._provider_config.get_domain())
        logger.debug("Retrieving key for %s" % (address,))

        try:
            self._keymanager.get_key(address, openpgp.OpenPGPKey, private=True, fetch_remote=False)
            return
        except KeyNotFound:
            logger.debug("Key not found. Generating key for %s" % (address,))

        # generate key
        try:
            self._keymanager.gen_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("Error while generating key!")
            logger.exception(exc)
            raise

        # send key
        try:
            self._keymanager.send_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("Error while sending key!")
            logger.exception(exc)
            raise

        logger.debug("Key generated successfully.")

    def run_soledad_setup_checks(self, provider_config, user, password, download_if_needed=False):
        """
        Starts the checks needed for a new soledad setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        :param user: User's login
        :type user: unicode
        :param password: User's password
        :type password: unicode
        :param download_if_needed: If True, it will only download
                                   files if the have changed since the
                                   time it was previously downloaded.
        :type download_if_needed: bool
        """
        leap_assert_type(provider_config, ProviderConfig)

        # XXX we should provider a method for setting provider_config
        self._provider_config = provider_config
        self._download_if_needed = download_if_needed
        self._user = user
        self._password = password

        if flags.OFFLINE:
            signal_finished = self._signaler.soledad_offline_finished
            signal_failed = self._signaler.soledad_offline_failed
        else:
            signal_finished = self._signaler.soledad_bootstrap_finished
            signal_failed = self._signaler.soledad_bootstrap_failed

        try:
            self._download_config()

            # soledad config is ok, let's proceed to load and sync soledad
            uuid = self.srpauth.get_uuid()
            self.load_and_sync_soledad(uuid)

            if not flags.OFFLINE:
                self._gen_key()

            self._signaler.signal(signal_finished)
        except Exception as e:
            # TODO: we should handle more specific exceptions in here
            self._soledad = None
            self._keymanager = None
            logger.exception("Error while bootstrapping Soledad: %r" % (e,))
            self._signaler.signal(signal_failed)
Exemplo n.º 32
0
class TestCaseWithKeyManager(unittest.TestCase, BaseLeapTest):

    GPG_BINARY_PATH = _find_gpg()

    def setUp(self):
        self.setUpEnv()

        # setup our own stuff
        address = '*****@*****.**'  # user's address in the form user@provider
        uuid = '*****@*****.**'
        passphrase = u'123'
        secrets_path = os.path.join(self.tempdir, 'secret.gpg')
        local_db_path = os.path.join(self.tempdir, 'soledad.u1db')
        server_url = 'http://provider/'
        cert_file = ''

        self._soledad = Soledad(
            uuid,
            passphrase,
            secrets_path=secrets_path,
            local_db_path=local_db_path,
            server_url=server_url,
            cert_file=cert_file,
            syncable=False
        )
        return self._setup_keymanager(address)

    def _setup_keymanager(self, address):
        """
        Set up Key Manager and return a Deferred that will be fired when done.
        """
        self._config = {
            'host': 'https://provider/',
            'port': 25,
            'username': address,
            'password': '******',
            'encrypted_only': True,
            'cert': u'src/leap/mail/smtp/tests/cert/server.crt',
            'key': u'src/leap/mail/smtp/tests/cert/server.key',
        }

        class Response(object):
            status_code = 200
            headers = {'content-type': 'application/json'}

            def json(self):
                return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2}

            def raise_for_status(self):
                pass

        nickserver_url = ''  # the url of the nickserver
        self._km = KeyManager(address, nickserver_url, self._soledad,
                              gpgbinary=self.GPG_BINARY_PATH)
        self._km._fetcher.put = Mock()
        self._km._fetcher.get = Mock(return_value=Response())

        d1 = self._km.put_raw_key(PRIVATE_KEY, OpenPGPKey, ADDRESS)
        d2 = self._km.put_raw_key(PRIVATE_KEY_2, OpenPGPKey, ADDRESS_2)
        return gatherResults([d1, d2])

    def tearDown(self):
        self.tearDownEnv()
Exemplo n.º 33
0
class TestCaseWithKeyManager(unittest.TestCase, BaseLeapTest):

    GPG_BINARY_PATH = _find_gpg()

    def setUp(self):
        self.setUpEnv()

        # setup our own stuff
        address = '*****@*****.**'  # user's address in the form user@provider
        uuid = '*****@*****.**'
        passphrase = u'123'
        secrets_path = os.path.join(self.tempdir, 'secret.gpg')
        local_db_path = os.path.join(self.tempdir, 'soledad.u1db')
        server_url = 'http://provider/'
        cert_file = ''

        self._soledad = Soledad(uuid,
                                passphrase,
                                secrets_path=secrets_path,
                                local_db_path=local_db_path,
                                server_url=server_url,
                                cert_file=cert_file,
                                syncable=False)
        return self._setup_keymanager(address)

    def _setup_keymanager(self, address):
        """
        Set up Key Manager and return a Deferred that will be fired when done.
        """
        self._config = {
            'host': 'https://provider/',
            'port': 25,
            'username': address,
            'password': '******',
            'encrypted_only': True,
            'cert': u'src/leap/mail/smtp/tests/cert/server.crt',
            'key': u'src/leap/mail/smtp/tests/cert/server.key',
        }

        class Response(object):
            status_code = 200
            headers = {'content-type': 'application/json'}

            def json(self):
                return {'address': ADDRESS_2, 'openpgp': PUBLIC_KEY_2}

            def raise_for_status(self):
                pass

        nickserver_url = ''  # the url of the nickserver
        self._km = KeyManager(address,
                              nickserver_url,
                              self._soledad,
                              ca_cert_path='',
                              gpgbinary=self.GPG_BINARY_PATH)
        self._km._fetcher.put = Mock()
        self._km._fetcher.get = Mock(return_value=Response())

        d1 = self._km.put_raw_key(PRIVATE_KEY, OpenPGPKey, ADDRESS)
        d2 = self._km.put_raw_key(PRIVATE_KEY_2, OpenPGPKey, ADDRESS_2)
        return gatherResults([d1, d2])

    def tearDown(self):
        self.tearDownEnv()
Exemplo n.º 34
0
class SoledadBootstrapper(AbstractBootstrapper):
    """
    Soledad init procedure.
    """
    SOLEDAD_KEY = "soledad"
    KEYMANAGER_KEY = "keymanager"

    PUBKEY_KEY = "user[public_key]"

    MAX_INIT_RETRIES = 10
    MAX_SYNC_RETRIES = 10
    WAIT_MAX_SECONDS = 600
    # WAIT_STEP_SECONDS = 1
    WAIT_STEP_SECONDS = 5

    def __init__(self, signaler=None):
        AbstractBootstrapper.__init__(self, signaler)

        if signaler is not None:
            self._cancel_signal = signaler.soledad_cancelled_bootstrap

        self._provider_config = None
        self._soledad_config = None
        self._keymanager = None
        self._download_if_needed = False

        self._user = ""
        self._password = ""
        self._address = ""
        self._uuid = ""

        self._srpauth = None
        self._soledad = None

    @property
    def keymanager(self):
        return self._keymanager

    @property
    def soledad(self):
        return self._soledad

    @property
    def srpauth(self):
        if flags.OFFLINE is True:
            return None
        leap_assert(self._provider_config is not None,
                    "We need a provider config")
        return SRPAuth(self._provider_config)

    # initialization

    def load_offline_soledad(self, username, password, uuid):
        """
        Instantiate Soledad for offline use.

        :param username: full user id (user@provider)
        :type username: str or unicode
        :param password: the soledad passphrase
        :type password: unicode
        :param uuid: the user uuid
        :type uuid: str or unicode
        """
        self._address = username
        self._password = password
        self._uuid = uuid
        try:
            self.load_and_sync_soledad(uuid, offline=True)
            self._signaler.signal(self._signaler.soledad_offline_finished)
        except Exception as e:
            # TODO: we should handle more specific exceptions in here
            logger.exception(e)
            self._signaler.signal(self._signaler.soledad_offline_failed)

    def _get_soledad_local_params(self, uuid, offline=False):
        """
        Return the locals parameters needed for the soledad initialization.

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :return: secrets_path, local_db_path, token
        :rtype: tuple
        """
        # in the future, when we want to be able to switch to
        # online mode, this should be a proxy object too.
        # Same for server_url below.

        if offline is False:
            token = self.srpauth.get_token()
        else:
            token = ""

        secrets_path, local_db_path = get_db_paths(uuid)

        logger.debug('secrets_path:%s' % (secrets_path, ))
        logger.debug('local_db:%s' % (local_db_path, ))
        return (secrets_path, local_db_path, token)

    def _get_soledad_server_params(self, uuid, offline):
        """
        Return the remote parameters needed for the soledad initialization.

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :return: server_url, cert_file
        :rtype: tuple
        """
        if uuid is None:
            uuid = self.srpauth.get_uuid()

        if offline is True:
            server_url = "http://localhost:9999/"
            cert_file = ""
        else:
            server_url = self._pick_server(uuid)
            cert_file = self._provider_config.get_ca_cert_path()

        return server_url, cert_file

    def _soledad_sync_errback(self, failure):
        failure.trap(InvalidAuthTokenError)
        # in the case of an invalid token we have already turned off mail and
        # warned the user in _do_soledad_sync()

    def _do_soledad_init(self, uuid, secrets_path, local_db_path, server_url,
                         cert_file, token):
        """
        Initialize soledad, retry if necessary and raise an exception if we
        can't succeed.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        init_tries = 1
        while init_tries <= self.MAX_INIT_RETRIES:
            try:
                logger.debug("Trying to init soledad....")
                self._try_soledad_init(uuid, secrets_path, local_db_path,
                                       server_url, cert_file, token)
                logger.debug("Soledad has been initialized.")
                return
            except Exception as exc:
                init_tries += 1
                msg = "Init failed, retrying... (retry {0} of {1})".format(
                    init_tries, self.MAX_INIT_RETRIES)
                logger.warning(msg)
                continue

        logger.exception(exc)
        raise SoledadInitError()

    def load_and_sync_soledad(self, uuid=None, offline=False):
        """
        Once everthing is in the right place, we instantiate and sync
        Soledad

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :param offline: whether to instantiate soledad for offline use.
        :type offline: bool
        """
        local_param = self._get_soledad_local_params(uuid, offline)
        remote_param = self._get_soledad_server_params(uuid, offline)

        secrets_path, local_db_path, token = local_param
        server_url, cert_file = remote_param

        try:
            self._do_soledad_init(uuid, secrets_path, local_db_path,
                                  server_url, cert_file, token)
        except SoledadInitError:
            # re-raise the exceptions from try_init,
            # we're currently handling the retries from the
            # soledad-launcher in the gui.
            raise

        leap_assert(not sameProxiedObjects(self._soledad, None),
                    "Null soledad, error while initializing")

        if flags.OFFLINE:
            self._init_keymanager(self._address, token)
        else:
            try:
                address = make_address(self._user,
                                       self._provider_config.get_domain())
                self._init_keymanager(address, token)
                self._keymanager.get_key(address,
                                         openpgp.OpenPGPKey,
                                         private=True,
                                         fetch_remote=False)
                d = threads.deferToThread(self._do_soledad_sync)
                d.addErrback(self._soledad_sync_errback)
            except KeyNotFound:
                logger.debug("Key not found. Generating key for %s" %
                             (address, ))
                self._do_soledad_sync()

    def _pick_server(self, uuid):
        """
        Choose a soledad server to sync against.

        :param uuid: the uuid for the user.
        :type uuid: unicode
        :returns: the server url
        :rtype: unicode
        """
        # TODO: Select server based on timezone (issue #3308)
        server_dict = self._soledad_config.get_hosts()

        if not server_dict.keys():
            # XXX raise more specific exception, and catch it properly!
            raise Exception("No soledad server found")

        selected_server = server_dict[first(server_dict.keys())]
        server_url = "https://%s:%s/user-%s" % (selected_server["hostname"],
                                                selected_server["port"], uuid)
        logger.debug("Using soledad server url: %s" % (server_url, ))
        return server_url

    def _do_soledad_sync(self):
        """
        Do several retries to get an initial soledad sync.
        """
        # and now, let's sync
        sync_tries = self.MAX_SYNC_RETRIES
        step = self.WAIT_STEP_SECONDS
        max_wait = self.WAIT_MAX_SECONDS
        while sync_tries > 0:
            wait = 0
            try:
                logger.debug("Trying to sync soledad....")
                self._try_soledad_sync()
                while self.soledad.syncing:
                    time.sleep(step)
                    wait += step
                    if wait >= max_wait:
                        raise SoledadSyncError("timeout!")
                logger.debug("Soledad has been synced!")
                # so long, and thanks for all the fish
                return
            except SoledadSyncError:
                # maybe it's my connection, but I'm getting
                # ssl handshake timeouts and read errors quite often.
                # A particularly big sync is a disaster.
                # This deserves further investigation, maybe the
                # retry strategy can be pushed to u1db, or at least
                # it's something worthy to talk about with the
                # ubuntu folks.
                sync_tries += 1
                msg = "Sync failed, retrying... (retry {0} of {1})".format(
                    sync_tries, self.MAX_SYNC_RETRIES)
                logger.warning(msg)
                continue
            except InvalidAuthTokenError:
                self._signaler.signal(
                    self._signaler.soledad_invalid_auth_token)
                raise
            except Exception as e:
                # XXX release syncing lock
                logger.exception("Unhandled error while syncing "
                                 "soledad: %r" % (e, ))
                break

        raise SoledadSyncError()

    def _try_soledad_init(self, uuid, secrets_path, local_db_path, server_url,
                          cert_file, auth_token):
        """
        Try to initialize soledad.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        # TODO: If selected server fails, retry with another host
        # (issue #3309)
        encoding = sys.getfilesystemencoding()

        # XXX We should get a flag in soledad itself
        if flags.OFFLINE is True:
            Soledad._shared_db = MockSharedDB()
        try:
            self._soledad = Soledad(
                uuid,
                self._password,
                secrets_path=secrets_path.encode(encoding),
                local_db_path=local_db_path.encode(encoding),
                server_url=server_url,
                cert_file=cert_file.encode(encoding),
                auth_token=auth_token,
                defer_encryption=True)

        # XXX All these errors should be handled by soledad itself,
        # and return a subclass of SoledadInitializationFailed

        # recoverable, will guarantee retries
        except (socket.timeout, socket.error, BootstrapSequenceError):
            logger.warning("Error while initializing Soledad")
            raise

        # unrecoverable
        except (u1db_errors.Unauthorized, u1db_errors.HTTPError):
            logger.error("Error while initializing Soledad (u1db error).")
            raise
        except Exception as exc:
            logger.exception("Unhandled error while initializating "
                             "Soledad: %r" % (exc, ))
            raise

    def _try_soledad_sync(self):
        """
        Try to sync soledad.
        Raises SoledadSyncError if not successful.
        """
        try:
            logger.debug("BOOTSTRAPPER: trying to sync Soledad....")
            # pass defer_decryption=False to get inline decryption
            # for debugging.
            self._soledad.sync(defer_decryption=True)
        except SSLError as exc:
            logger.error("%r" % (exc, ))
            raise SoledadSyncError("Failed to sync soledad")
        except u1db_errors.InvalidGeneration as exc:
            logger.error("%r" % (exc, ))
            raise SoledadSyncError("u1db: InvalidGeneration")
        except (sqlite_ProgrammingError, sqlcipher_ProgrammingError) as e:
            logger.exception("%r" % (e, ))
            raise
        except InvalidAuthTokenError:
            # token is invalid, probably expired
            logger.error('Invalid auth token while trying to sync Soledad')
            raise
        except Exception as exc:
            logger.exception("Unhandled error while syncing "
                             "soledad: %r" % (exc, ))
            raise SoledadSyncError("Failed to sync soledad")

    def _download_config(self):
        """
        Download the Soledad config for the given provider
        """
        leap_assert(self._provider_config, "We need a provider configuration!")
        logger.debug("Downloading Soledad config for %s" %
                     (self._provider_config.get_domain(), ))

        self._soledad_config = SoledadConfig()
        download_service_config(self._provider_config, self._soledad_config,
                                self._session, self._download_if_needed)

    def _get_gpg_bin_path(self):
        """
        Return the path to gpg binary.

        :returns: the gpg binary path
        :rtype: str
        """
        gpgbin = None
        if flags.STANDALONE:
            gpgbin = os.path.join(get_path_prefix(), "..", "apps", "mail",
                                  "gpg")
            if IS_WIN:
                gpgbin += ".exe"
        else:
            try:
                gpgbin_options = which("gpg")
                # gnupg checks that the path to the binary is not a
                # symlink, so we need to filter those and come up with
                # just one option.
                for opt in gpgbin_options:
                    if not os.path.islink(opt):
                        gpgbin = opt
                        break
            except IndexError as e:
                logger.debug("Couldn't find the gpg binary!")
                logger.exception(e)
        leap_check(gpgbin is not None, "Could not find gpg binary")
        return gpgbin

    def _init_keymanager(self, address, token):
        """
        Initialize the keymanager.

        :param address: the address to initialize the keymanager with.
        :type address: str
        :param token: the auth token for accessing webapp.
        :type token: str
        """
        srp_auth = self.srpauth
        logger.debug('initializing keymanager...')

        if flags.OFFLINE is True:
            args = (address, "https://localhost", self._soledad)
            kwargs = {
                "ca_cert_path": "",
                "api_uri": "",
                "api_version": "",
                "uid": self._uuid,
                "gpgbinary": self._get_gpg_bin_path()
            }
        else:
            args = (address, "https://nicknym.%s:6425" %
                    (self._provider_config.get_domain(), ), self._soledad)
            kwargs = {
                "token": token,
                "ca_cert_path": self._provider_config.get_ca_cert_path(),
                "api_uri": self._provider_config.get_api_uri(),
                "api_version": self._provider_config.get_api_version(),
                "uid": srp_auth.get_uuid(),
                "gpgbinary": self._get_gpg_bin_path()
            }
        try:
            self._keymanager = KeyManager(*args, **kwargs)
        except KeyNotFound:
            logger.debug('key for %s not found.' % address)
        except Exception as exc:
            logger.exception(exc)
            raise

        if flags.OFFLINE is False:
            # make sure key is in server
            logger.debug('Trying to send key to server...')
            try:
                self._keymanager.send_key(openpgp.OpenPGPKey)
            except KeyNotFound:
                logger.debug('No key found for %s, will generate soon.' %
                             address)
            except Exception as exc:
                logger.error("Error sending key to server.")
                logger.exception(exc)
                # but we do not raise

    def _gen_key(self):
        """
        Generates the key pair if needed, uploads it to the webapp and
        nickserver
        """
        leap_assert(self._provider_config is not None,
                    "We need a provider configuration!")
        leap_assert(self._soledad is not None,
                    "We need a non-null soledad to generate keys")

        address = make_address(self._user, self._provider_config.get_domain())
        logger.debug("Retrieving key for %s" % (address, ))

        try:
            self._keymanager.get_key(address,
                                     openpgp.OpenPGPKey,
                                     private=True,
                                     fetch_remote=False)
            return
        except KeyNotFound:
            logger.debug("Key not found. Generating key for %s" % (address, ))

        # generate key
        try:
            self._keymanager.gen_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("Error while generating key!")
            logger.exception(exc)
            raise

        # send key
        try:
            self._keymanager.send_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("Error while sending key!")
            logger.exception(exc)
            raise

        logger.debug("Key generated successfully.")

    def run_soledad_setup_checks(self,
                                 provider_config,
                                 user,
                                 password,
                                 download_if_needed=False):
        """
        Starts the checks needed for a new soledad setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        :param user: User's login
        :type user: unicode
        :param password: User's password
        :type password: unicode
        :param download_if_needed: If True, it will only download
                                   files if the have changed since the
                                   time it was previously downloaded.
        :type download_if_needed: bool
        """
        leap_assert_type(provider_config, ProviderConfig)

        # XXX we should provider a method for setting provider_config
        self._provider_config = provider_config
        self._download_if_needed = download_if_needed
        self._user = user
        self._password = password

        if flags.OFFLINE:
            signal_finished = self._signaler.soledad_offline_finished
            signal_failed = self._signaler.soledad_offline_failed
        else:
            signal_finished = self._signaler.soledad_bootstrap_finished
            signal_failed = self._signaler.soledad_bootstrap_failed

        try:
            self._download_config()

            # soledad config is ok, let's proceed to load and sync soledad
            uuid = self.srpauth.get_uuid()
            self.load_and_sync_soledad(uuid)

            if not flags.OFFLINE:
                self._gen_key()

            self._signaler.signal(signal_finished)
        except Exception as e:
            # TODO: we should handle more specific exceptions in here
            self._soledad = None
            self._keymanager = None
            logger.exception("Error while bootstrapping Soledad: %r" % (e, ))
            self._signaler.signal(signal_failed)
Exemplo n.º 35
0
class SoledadBootstrapper(AbstractBootstrapper):
    """
    Soledad init procedure
    """
    SOLEDAD_KEY = "soledad"
    KEYMANAGER_KEY = "keymanager"

    PUBKEY_KEY = "user[public_key]"

    MAX_INIT_RETRIES = 10
    MAX_SYNC_RETRIES = 10

    # All dicts returned are of the form
    # {"passed": bool, "error": str}
    download_config = QtCore.Signal(dict)
    gen_key = QtCore.Signal(dict)
    soledad_timeout = QtCore.Signal()
    soledad_failed = QtCore.Signal()

    def __init__(self):
        AbstractBootstrapper.__init__(self)

        self._provider_config = None
        self._soledad_config = None
        self._keymanager = None
        self._download_if_needed = False

        self._user = ""
        self._password = ""
        self._srpauth = None
        self._soledad = None

        self._soledad_retries = 0

    @property
    def keymanager(self):
        return self._keymanager

    @property
    def soledad(self):
        return self._soledad

    @property
    def srpauth(self):
        leap_assert(self._provider_config is not None,
                    "We need a provider config")
        return SRPAuth(self._provider_config)

    # retries

    def cancel_bootstrap(self):
        self._soledad_retries = self.MAX_INIT_RETRIES

    def should_retry_initialization(self):
        """
        Returns True if we should retry the initialization.
        """
        logger.debug("current retries: %s, max retries: %s" % (
            self._soledad_retries,
            self.MAX_INIT_RETRIES))
        return self._soledad_retries < self.MAX_INIT_RETRIES

    def increment_retries_count(self):
        """
        Increments the count of initialization retries.
        """
        self._soledad_retries += 1

    def _get_db_paths(self, uuid):
        """
        Returns the secrets and local db paths needed for soledad
        initialization

        :param uuid: uuid for user
        :type uuid: str

        :return: a tuple with secrets, local_db paths
        :rtype: tuple
        """
        prefix = os.path.join(get_path_prefix(), "leap", "soledad")
        secrets = "%s/%s.secret" % (prefix, uuid)
        local_db = "%s/%s.db" % (prefix, uuid)

        # We remove an empty file if found to avoid complains
        # about the db not being properly initialized
        if is_file(local_db) and is_empty_file(local_db):
            try:
                os.remove(local_db)
            except OSError:
                logger.warning("Could not remove empty file %s"
                               % local_db)
        return secrets, local_db

    # initialization

    def load_and_sync_soledad(self):
        """
        Once everthing is in the right place, we instantiate and sync
        Soledad
        """
        # TODO this method is still too large
        uuid = self.srpauth.get_uid()
        token = self.srpauth.get_token()

        secrets_path, local_db_path = self._get_db_paths(uuid)

        # TODO: Select server based on timezone (issue #3308)
        server_dict = self._soledad_config.get_hosts()

        if not server_dict.keys():
            # XXX raise more specific exception, and catch it properly!
            raise Exception("No soledad server found")

        selected_server = server_dict[server_dict.keys()[0]]
        server_url = "https://%s:%s/user-%s" % (
            selected_server["hostname"],
            selected_server["port"],
            uuid)
        logger.debug("Using soledad server url: %s" % (server_url,))

        cert_file = self._provider_config.get_ca_cert_path()

        logger.debug('local_db:%s' % (local_db_path,))
        logger.debug('secrets_path:%s' % (secrets_path,))

        try:
            self._try_soledad_init(
                uuid, secrets_path, local_db_path,
                server_url, cert_file, token)
        except:
            # re-raise the exceptions from try_init,
            # we're currently handling the retries from the
            # soledad-launcher in the gui.
            raise

        leap_check(self._soledad is not None,
                   "Null soledad, error while initializing")

        # and now, let's sync
        sync_tries = self.MAX_SYNC_RETRIES
        while sync_tries > 0:
            try:
                self._try_soledad_sync()

                # at this point, sometimes the client
                # gets stuck and does not progress to
                # the _gen_key step. XXX investigate.
                logger.debug("Soledad has been synced.")
                # so long, and thanks for all the fish
                return
            except SoledadSyncError:
                # maybe it's my connection, but I'm getting
                # ssl handshake timeouts and read errors quite often.
                # A particularly big sync is a disaster.
                # This deserves further investigation, maybe the
                # retry strategy can be pushed to u1db, or at least
                # it's something worthy to talk about with the
                # ubuntu folks.
                sync_tries -= 1
                continue

        # reached bottom, failed to sync
        # and there's nothing we can do...
        self.soledad_failed.emit()
        raise SoledadSyncError()

    def _try_soledad_init(self, uuid, secrets_path, local_db_path,
                          server_url, cert_file, auth_token):
        """
        Tries to initialize soledad.

        :param uuid: user identifier
        :param secrets_path: path to secrets file
        :param local_db_path: path to local db file
        :param server_url: soledad server uri
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        # TODO: If selected server fails, retry with another host
        # (issue #3309)
        try:
            self._soledad = Soledad(
                uuid,
                self._password.encode("utf-8"),
                secrets_path=secrets_path,
                local_db_path=local_db_path,
                server_url=server_url,
                cert_file=cert_file,
                auth_token=auth_token)

        # XXX All these errors should be handled by soledad itself,
        # and return a subclass of SoledadInitializationFailed

        # recoverable, will guarantee retries
        except socket.timeout:
            logger.debug("SOLEDAD initialization TIMED OUT...")
            self.soledad_timeout.emit()
        except socket.error as exc:
            logger.error("Socket error while initializing soledad")
            self.soledad_timeout.emit()

        # unrecoverable
        except u1db_errors.Unauthorized:
            logger.error("Error while initializing soledad "
                         "(unauthorized).")
            self.soledad_failed.emit()
        except Exception as exc:
            logger.exception("Unhandled error while initializating "
                             "soledad: %r" % (exc,))
            self.soledad_failed.emit()

    def _try_soledad_sync(self):
        """
        Tries to sync soledad.
        Raises SoledadSyncError if not successful.
        """
        try:
            logger.error("trying to sync soledad....")
            self._soledad.sync()
        except SSLError as exc:
            logger.error("%r" % (exc,))
            raise SoledadSyncError("Failed to sync soledad")
        except Exception as exc:
            logger.exception("Unhandled error while syncing"
                             "soledad: %r" % (exc,))
            self.soledad_failed.emit()
            raise SoledadSyncError("Failed to sync soledad")

    def _download_config(self):
        """
        Downloads the Soledad config for the given provider
        """

        leap_assert(self._provider_config,
                    "We need a provider configuration!")
        logger.debug("Downloading Soledad config for %s" %
                     (self._provider_config.get_domain(),))

        self._soledad_config = SoledadConfig()
        download_service_config(
            self._provider_config,
            self._soledad_config,
            self._session,
            self._download_if_needed)

        # soledad config is ok, let's proceed to load and sync soledad
        # XXX but honestly, this is a pretty strange entry point for that.
        # it feels like it should be the other way around:
        # load_and_sync, and from there, if needed, call download_config
        self.load_and_sync_soledad()

    def _get_gpg_bin_path(self):
        """
        Returns the path to gpg binary.
        :returns: the gpg binary path
        :rtype: str
        """
        # TODO: Fix for Windows
        gpgbin = None
        if flags.STANDALONE:
            gpgbin = os.path.join(
                get_path_prefix(), "..", "apps", "mail", "gpg")
        else:
            try:
                gpgbin_options = which("gpg")
                # gnupg checks that the path to the binary is not a
                # symlink, so we need to filter those and come up with
                # just one option.
                for opt in gpgbin_options:
                    if not os.path.islink(opt):
                        gpgbin = opt
                        break
            except IndexError as e:
                logger.debug("Couldn't find the gpg binary!")
                logger.exception(e)
        leap_check(gpgbin is not None, "Could not find gpg binary")
        return gpgbin

    def _init_keymanager(self, address):
        """
        Initializes the keymanager.
        :param address: the address to initialize the keymanager with.
        :type address: str
        """
        srp_auth = self.srpauth
        logger.debug('initializing keymanager...')
        self._keymanager = KeyManager(
            address,
            "https://nicknym.%s:6425" % (self._provider_config.get_domain(),),
            self._soledad,
            #token=srp_auth.get_token(),  # TODO: enable token usage
            session_id=srp_auth.get_session_id(),
            ca_cert_path=self._provider_config.get_ca_cert_path(),
            api_uri=self._provider_config.get_api_uri(),
            api_version=self._provider_config.get_api_version(),
            uid=srp_auth.get_uid(),
            gpgbinary=self._get_gpg_bin_path())

    def _gen_key(self, _):
        """
        Generates the key pair if needed, uploads it to the webapp and
        nickserver
        """
        leap_assert(self._provider_config is not None,
                    "We need a provider configuration!")
        leap_assert(self._soledad is not None,
                    "We need a non-null soledad to generate keys")

        address = "%s@%s" % (self._user, self._provider_config.get_domain())
        self._init_keymanager(address)
        logger.debug("Retrieving key for %s" % (address,))

        try:
            self._keymanager.get_key(
                address, openpgp.OpenPGPKey, private=True, fetch_remote=False)
            return
        except KeyNotFound:
            logger.debug("Key not found. Generating key for %s" % (address,))

        # generate key
        try:
            self._keymanager.gen_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("error while generating key!")
            logger.exception(exc)
            raise

        # send key
        try:
            self._keymanager.send_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("error while sending key!")
            logger.exception(exc)
            raise

        logger.debug("Key generated successfully.")

    def run_soledad_setup_checks(self,
                                 provider_config,
                                 user,
                                 password,
                                 download_if_needed=False):
        """
        Starts the checks needed for a new soledad setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        :param user: User's login
        :type user: str
        :param password: User's password
        :type password: str
        :param download_if_needed: If True, it will only download
                                   files if the have changed since the
                                   time it was previously downloaded.
        :type download_if_needed: bool
        """
        leap_assert_type(provider_config, ProviderConfig)

        # XXX we should provider a method for setting provider_config
        self._provider_config = provider_config
        self._download_if_needed = download_if_needed
        self._user = user
        self._password = password

        cb_chain = [
            (self._download_config, self.download_config),
            (self._gen_key, self.gen_key)
        ]

        self.addCallbackChain(cb_chain)
Exemplo n.º 36
0
 def _key_manager(self, user=ADDRESS, url='', token=None):
     return KeyManager(user,
                       url,
                       self._soledad,
                       token=token,
                       gpgbinary=self.gpg_binary_path)