diff options
| -rw-r--r-- | src/leap/bitmask/backend.py | 204 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/advanced_key_management.py | 237 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 12 | 
3 files changed, 342 insertions, 111 deletions
| diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index e8bf0482..5e22a8c4 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -56,6 +56,9 @@ from leap.bitmask.services.soledad.soledadbootstrapper import \  from leap.common import certs as leap_certs +from leap.keymanager import openpgp +from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch +  from leap.soledad.client import NoStorageSecret, PassphraseTooShort  # Frontend side @@ -745,6 +748,125 @@ class Soledad(object):          d.addErrback(self._change_password_error) +class Keymanager(object): +    """ +    Interfaces with KeyManager. +    """ +    zope.interface.implements(ILEAPComponent) + +    def __init__(self, keymanager_proxy, signaler=None): +        """ +        Constructor for the Keymanager component. + +        :param keymanager_proxy: proxy to pass around a Keymanager object. +        :type keymanager_proxy: zope.ProxyBase +        :param signaler: Object in charge of handling communication +                         back to the frontend +        :type signaler: Signaler +        """ +        self.key = "keymanager" +        self._keymanager_proxy = keymanager_proxy +        self._signaler = signaler + +    def import_keys(self, username, filename): +        """ +        Imports the username's key pair. +        Those keys need to be ascii armored. + +        :param username: the user that will have the imported pair of keys. +        :type username: str +        :param filename: the name of the file where the key pair is stored. +        :type filename: str +        """ +        # NOTE: This feature is disabled right now since is dangerous +        return + +        new_key = '' +        signal = None +        try: +            with open(filename, 'r') as keys_file: +                new_key = keys_file.read() +        except IOError as e: +            logger.error("IOError importing key. {0!r}".format(e)) +            signal = self._signaler.KEYMANAGER_IMPORT_IOERROR +            self._signaler.signal(signal) +            return + +        keymanager = self._keymanager_proxy +        try: +            public_key, private_key = keymanager.parse_openpgp_ascii_key( +                new_key) +        except (KeyAddressMismatch, KeyFingerprintMismatch) as e: +            logger.error(repr(e)) +            signal = self._signaler.KEYMANAGER_IMPORT_DATAMISMATCH +            self._signaler.signal(signal) +            return + +        if public_key is None or private_key is None: +            signal = self._signaler.KEYMANAGER_IMPORT_MISSINGKEY +            self._signaler.signal(signal) +            return + +        current_public_key = keymanager.get_key(username, openpgp.OpenPGPKey) +        if public_key.address != current_public_key.address: +            logger.error("The key does not match the ID") +            signal = self._signaler.KEYMANAGER_IMPORT_ADDRESSMISMATCH +            self._signaler.signal(signal) +            return + +        keymanager.delete_key(self._key) +        keymanager.delete_key(self._key_priv) +        keymanager.put_key(public_key) +        keymanager.put_key(private_key) +        keymanager.send_key(openpgp.OpenPGPKey) + +        logger.debug('Import ok') +        signal = self._signaler.KEYMANAGER_IMPORT_OK + +        self._signaler.signal(signal) + +    def export_keys(self, username, filename): +        """ +        Export the given username's keys to a file. + +        :param username: the username whos keys we need to export. +        :type username: str +        :param filename: the name of the file where we want to save the keys. +        :type filename: str +        """ +        keymanager = self._keymanager_proxy + +        public_key = keymanager.get_key(username, openpgp.OpenPGPKey) +        private_key = keymanager.get_key(username, openpgp.OpenPGPKey, +                                         private=True) +        try: +            with open(filename, 'w') as keys_file: +                keys_file.write(public_key.key_data) +                keys_file.write(private_key.key_data) + +            logger.debug('Export ok') +            self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_OK) +        except IOError as e: +            logger.error("IOError exporting key. {0!r}".format(e)) +            self._signaler.signal(self._signaler.KEYMANAGER_EXPORT_ERROR) + +    def list_keys(self): +        """ +        List all the keys stored in the local DB. +        """ +        keys = self._keymanager_proxy.get_all_keys_in_local_db() +        self._signaler.signal(self._signaler.KEYMANAGER_KEYS_LIST, keys) + +    def get_key_details(self, username): +        """ +        List all the keys stored in the local DB. +        """ +        public_key = self._keymanager_proxy.get_key(username, +                                                    openpgp.OpenPGPKey) +        details = (public_key.key_id, public_key.fingerprint) +        self._signaler.signal(self._signaler.KEYMANAGER_KEY_DETAILS, details) + +  class Mail(object):      """      Interfaces with setup and launch of Mail. @@ -1047,6 +1169,19 @@ class Signaler(QtCore.QObject):      soledad_password_change_ok = QtCore.Signal(object)      soledad_password_change_error = QtCore.Signal(object) +    # Keymanager signals +    keymanager_export_ok = QtCore.Signal(object) +    keymanager_export_error = QtCore.Signal(object) +    keymanager_keys_list = QtCore.Signal(object) + +    keymanager_import_ioerror = QtCore.Signal(object) +    keymanager_import_datamismatch = QtCore.Signal(object) +    keymanager_import_missingkey = QtCore.Signal(object) +    keymanager_import_addressmismatch = QtCore.Signal(object) +    keymanager_import_ok = QtCore.Signal(object) + +    keymanager_key_details = QtCore.Signal(object) +      # mail related signals      imap_stopped = QtCore.Signal(object) @@ -1135,6 +1270,17 @@ class Signaler(QtCore.QObject):      SOLEDAD_CANCELLED_BOOTSTRAP = "soledad_cancelled_bootstrap" +    KEYMANAGER_EXPORT_OK = "keymanager_export_ok" +    KEYMANAGER_EXPORT_ERROR = "keymanager_export_error" +    KEYMANAGER_KEYS_LIST = "keymanager_keys_list" + +    KEYMANAGER_IMPORT_IOERROR = "keymanager_import_ioerror" +    KEYMANAGER_IMPORT_DATAMISMATCH = "keymanager_import_datamismatch" +    KEYMANAGER_IMPORT_MISSINGKEY = "keymanager_import_missingkey" +    KEYMANAGER_IMPORT_ADDRESSMISMATCH = "keymanager_import_addressmismatch" +    KEYMANAGER_IMPORT_OK = "keymanager_import_ok" +    KEYMANAGER_KEY_DETAILS = "keymanager_key_details" +      IMAP_STOPPED = "imap_stopped"      BACKEND_BAD_CALL = "backend_bad_call" @@ -1223,6 +1369,17 @@ class Signaler(QtCore.QObject):              self.SOLEDAD_PASSWORD_CHANGE_OK,              self.SOLEDAD_PASSWORD_CHANGE_ERROR, +            self.KEYMANAGER_EXPORT_OK, +            self.KEYMANAGER_EXPORT_ERROR, +            self.KEYMANAGER_KEYS_LIST, + +            self.KEYMANAGER_IMPORT_IOERROR, +            self.KEYMANAGER_IMPORT_DATAMISMATCH, +            self.KEYMANAGER_IMPORT_MISSINGKEY, +            self.KEYMANAGER_IMPORT_ADDRESSMISMATCH, +            self.KEYMANAGER_IMPORT_OK, +            self.KEYMANAGER_KEY_DETAILS, +              self.IMAP_STOPPED,              self.BACKEND_BAD_CALL, @@ -1292,6 +1449,8 @@ class Backend(object):          self._register(Soledad(self._soledad_proxy,                                 self._keymanager_proxy,                                 self._signaler)) +        self._register(Keymanager(self._keymanager_proxy, +                                  self._signaler))          self._register(Mail(self._soledad_proxy,                              self._keymanager_proxy,                              self._signaler)) @@ -1749,6 +1908,43 @@ class Backend(object):          """          self._call_queue.put(("soledad", "close", None)) +    def keymanager_list_keys(self): +        """ +        Signal a list of public keys locally stored. + +        Signals: +            keymanager_keys_list -> list +        """ +        self._call_queue.put(("keymanager", "list_keys", None)) + +    def keymanager_export_keys(self, username, filename): +        """ +        Export the given username's keys to a file. + +        :param username: the username whos keys we need to export. +        :type username: str +        :param filename: the name of the file where we want to save the keys. +        :type filename: str + +        Signals: +            keymanager_export_ok +            keymanager_export_error +        """ +        self._call_queue.put(("keymanager", "export_keys", None, +                              username, filename)) + +    def keymanager_get_key_details(self, username): +        """ +        Signal the given username's key details. + +        :param username: the username whos keys we need to get details. +        :type username: str + +        Signals: +            keymanager_key_details +        """ +        self._call_queue.put(("keymanager", "get_key_details", None, username)) +      def smtp_start_service(self, full_user_id, download_if_needed=False):          """          Start the SMTP service. @@ -1788,11 +1984,3 @@ class Backend(object):              imap_stopped          """          self._call_queue.put(("mail", "stop_imap_service", None)) - -    ########################################################################### -    # XXX HACK: this section is meant to be a place to hold methods and -    # variables needed in the meantime while we migrate all to the backend. - -    def get_keymanager(self): -        km = self._components["soledad"]._soledad_bootstrapper._keymanager -        return km diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index 1681caca..b3a4ed8e 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -19,10 +19,8 @@ Advanced Key Management  """  import logging -from PySide import QtGui +from PySide import QtCore, QtGui -from leap.keymanager import openpgp -from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch  from leap.bitmask.services import get_service_display_name, MX_SERVICE  from ui_advanced_key_management import Ui_AdvancedKeyManagement @@ -33,7 +31,7 @@ class AdvancedKeyManagement(QtGui.QDialog):      """      Advanced Key Management      """ -    def __init__(self, parent, has_mx, user, keymanager, soledad_started): +    def __init__(self, parent, has_mx, user, backend, soledad_started):          """          :param parent: parent object of AdvancedKeyManagement.          :parent type: QWidget @@ -42,8 +40,8 @@ class AdvancedKeyManagement(QtGui.QDialog):          :type has_mx: bool          :param user: the current logged in user.          :type user: unicode -        :param keymanager: the existing keymanager instance -        :type keymanager: KeyManager +        :param backend: Backend being used +        :type backend: Backend          :param soledad_started: whether soledad has started or not          :type soledad_started: bool          """ @@ -75,16 +73,12 @@ class AdvancedKeyManagement(QtGui.QDialog):          #         "existing e-mails.")          #     self.ui.lblStatus.setText(msg) -        self._keymanager = keymanager - -        self._key = keymanager.get_key(user, openpgp.OpenPGPKey) -        self._key_priv = keymanager.get_key( -            user, openpgp.OpenPGPKey, private=True) +        self._user = user +        self._backend = backend +        self._backend_connect()          # show current key information          self.ui.leUser.setText(user) -        self.ui.leKeyID.setText(self._key.key_id) -        self.ui.leFingerprint.setText(self._key.fingerprint)          # set up connections          self.ui.pbImportKeys.clicked.connect(self._import_keys) @@ -94,7 +88,15 @@ class AdvancedKeyManagement(QtGui.QDialog):          self.ui.twPublicKeys.horizontalHeader().setResizeMode(              0, QtGui.QHeaderView.Stretch) -        self._list_keys() +        self._backend.keymanager_get_key_details(user) +        self._backend.keymanager_list_keys() + +    def _keymanager_key_details(self, details): +        """ +        Set the current user's key details into the gui. +        """ +        self.ui.leKeyID.setText(details[0]) +        self.ui.leFingerprint.setText(details[1])      def _disable_ui(self, msg):          """ @@ -113,53 +115,11 @@ class AdvancedKeyManagement(QtGui.QDialog):          Imports the user's key pair.          Those keys need to be ascii armored.          """ -        fileName, filtr = QtGui.QFileDialog.getOpenFileName( +        file_name, filtr = QtGui.QFileDialog.getOpenFileName(              self, self.tr("Open keys file"),              options=QtGui.QFileDialog.DontUseNativeDialog) -        if fileName: -            new_key = '' -            try: -                with open(fileName, 'r') as keys_file: -                    new_key = keys_file.read() -            except IOError as e: -                logger.error("IOError importing key. {0!r}".format(e)) -                QtGui.QMessageBox.critical( -                    self, self.tr("Input/Output error"), -                    self.tr("There was an error accessing the file.\n" -                            "Import canceled.")) -                return - -            keymanager = self._keymanager -            try: -                public_key, private_key = keymanager.parse_openpgp_ascii_key( -                    new_key) -            except (KeyAddressMismatch, KeyFingerprintMismatch) as e: -                logger.error(repr(e)) -                QtGui.QMessageBox.warning( -                    self, self.tr("Data mismatch"), -                    self.tr("The public and private key should have the " -                            "same address and fingerprint.\n" -                            "Import canceled.")) -                return - -            if public_key is None or private_key is None: -                QtGui.QMessageBox.warning( -                    self, self.tr("Missing key"), -                    self.tr("You need to provide the public AND private " -                            "key in the same file.\n" -                            "Import canceled.")) -                return - -            if public_key.address != self._key.address: -                logger.error("The key does not match the ID") -                QtGui.QMessageBox.warning( -                    self, self.tr("Address mismatch"), -                    self.tr("The identity for the key needs to be the same " -                            "as your user address.\n" -                            "Import canceled.")) -                return - +        if file_name:              question = self.tr("Are you sure that you want to replace "                                 "the current key pair whith the imported?")              res = QtGui.QMessageBox.question( @@ -167,61 +127,152 @@ class AdvancedKeyManagement(QtGui.QDialog):                  QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,                  QtGui.QMessageBox.No)  # default No -            if res == QtGui.QMessageBox.No: -                return +            if res == QtGui.QMessageBox.Yes: +                self._backend.keymanager_import_keys(self._user, file_name) +        else: +            logger.debug('Import canceled by the user.') -            keymanager.delete_key(self._key) -            keymanager.delete_key(self._key_priv) -            keymanager.put_key(public_key) -            keymanager.put_key(private_key) -            keymanager.send_key(openpgp.OpenPGPKey) +    @QtCore.Slot() +    def _keymanager_import_ok(self): +        """ +        TRIGGERS: +            Signaler.keymanager_import_ok -            logger.debug('Import ok') +        Notify the user that the key import went OK. +        """ +        QtGui.QMessageBox.information( +            self, self.tr("Import Successful"), +            self.tr("The key pair was imported successfully.")) -            QtGui.QMessageBox.information( -                self, self.tr("Import Successful"), -                self.tr("The key pair was imported successfully.")) -        else: -            logger.debug('Import canceled by the user.') +    @QtCore.Slot() +    def _import_ioerror(self): +        """ +        TRIGGERS: +            Signaler.keymanager_import_ioerror + +        Notify the user that the key import had an IOError problem. +        """ +        QtGui.QMessageBox.critical( +            self, self.tr("Input/Output error"), +            self.tr("There was an error accessing the file.\n" +                    "Import canceled.")) + +    @QtCore.Slot() +    def _import_datamismatch(self): +        """ +        TRIGGERS: +            Signaler.keymanager_import_datamismatch + +        Notify the user that the key import had an data mismatch problem. +        """ +        QtGui.QMessageBox.warning( +            self, self.tr("Data mismatch"), +            self.tr("The public and private key should have the " +                    "same address and fingerprint.\n" +                    "Import canceled.")) + +    @QtCore.Slot() +    def _import_missingkey(self): +        """ +        TRIGGERS: +            Signaler.keymanager_import_missingkey + +        Notify the user that the key import failed due a missing key. +        """ +        QtGui.QMessageBox.warning( +            self, self.tr("Missing key"), +            self.tr("You need to provide the public AND private " +                    "key in the same file.\n" +                    "Import canceled.")) + +    @QtCore.Slot() +    def _import_addressmismatch(self): +        """ +        TRIGGERS: +            Signaler.keymanager_import_addressmismatch + +        Notify the user that the key import failed due an address mismatch. +        """ +        QtGui.QMessageBox.warning( +            self, self.tr("Address mismatch"), +            self.tr("The identity for the key needs to be the same " +                    "as your user address.\n" +                    "Import canceled."))      def _export_keys(self):          """          Exports the user's key pair.          """ -        fileName, filtr = QtGui.QFileDialog.getSaveFileName( +        file_name, filtr = QtGui.QFileDialog.getSaveFileName(              self, self.tr("Save keys file"),              options=QtGui.QFileDialog.DontUseNativeDialog) -        if fileName: -            try: -                with open(fileName, 'w') as keys_file: -                    keys_file.write(self._key.key_data) -                    keys_file.write(self._key_priv.key_data) - -                logger.debug('Export ok') -                QtGui.QMessageBox.information( -                    self, self.tr("Export Successful"), -                    self.tr("The key pair was exported successfully.\n" -                            "Please, store your private key in a safe place.")) -            except IOError as e: -                logger.error("IOError exporting key. {0!r}".format(e)) -                QtGui.QMessageBox.critical( -                    self, self.tr("Input/Output error"), -                    self.tr("There was an error accessing the file.\n" -                            "Export canceled.")) -                return +        if file_name: +            self._backend.keymanager_export_keys(self._user, file_name)          else:              logger.debug('Export canceled by the user.') -    def _list_keys(self): +    @QtCore.Slot() +    def _keymanager_export_ok(self): +        """ +        TRIGGERS: +            Signaler.keymanager_export_ok + +        Notify the user that the key export went OK.          """ -        Loads all the public keys stored in the local db to the keys table. +        QtGui.QMessageBox.information( +            self, self.tr("Export Successful"), +            self.tr("The key pair was exported successfully.\n" +                    "Please, store your private key in a safe place.")) + +    @QtCore.Slot() +    def _keymanager_export_error(self): +        """ +        TRIGGERS: +            Signaler.keymanager_export_error + +        Notify the user that the key export didn't go well. +        """ +        QtGui.QMessageBox.critical( +            self, self.tr("Input/Output error"), +            self.tr("There was an error accessing the file.\n" +                    "Export canceled.")) + +    @QtCore.Slot() +    def _keymanager_keys_list(self, keys):          """ -        keys = self._keymanager.get_all_keys_in_local_db() +        TRIGGERS: +            Signaler.keymanager_keys_list +        Load the keys given as parameter in the table. + +        :param keys: the list of keys to load. +        :type keys: list +        """          keys_table = self.ui.twPublicKeys +          for key in keys:              row = keys_table.rowCount()              keys_table.insertRow(row)              keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address))              keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.key_id)) + +    def _backend_connect(self): +        """ +        Connect to backend signals. +        """ +        sig = self._backend.signaler + +        sig.keymanager_export_ok.connect(self._keymanager_export_ok) +        sig.keymanager_export_error.connect(self._keymanager_export_error) +        sig.keymanager_keys_list.connect(self._keymanager_keys_list) + +        sig.keymanager_key_details.connect(self._keymanager_key_details) + +        sig.keymanager_import_ok.connect(self._keymanager_import_ok) + +        sig.keymanager_import_ioerror.connect(self._import_ioerror) +        sig.keymanager_import_datamismatch.connect(self._import_datamismatch) +        sig.keymanager_import_missingkey.connect(self._import_missingkey) +        sig.keymanager_import_addressmismatch.connect( +            self._import_addressmismatch) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 4d79305e..cf6614be 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -23,7 +23,6 @@ import socket  from datetime import datetime  from PySide import QtCore, QtGui -from zope.proxy import ProxyBase, setProxiedObject  from twisted.internet import reactor, threads  from leap.bitmask import __version__ as VERSION @@ -269,8 +268,6 @@ class MainWindow(QtGui.QMainWindow):          self._bypass_checks = bypass_checks          self._start_hidden = start_hidden -        self._keymanager = ProxyBase(None) -          self._mail_conductor = mail_conductor.MailConductor(self._backend)          self._mail_conductor.connect_mail_signals(self._mail_status) @@ -598,11 +595,11 @@ class MainWindow(QtGui.QMainWindow):          details = self._provider_details          mx_provided = False          if details is not None: -            mx_provided = MX_SERVICE in details +            mx_provided = MX_SERVICE in details.services          # XXX: handle differently not logged in user?          akm = AdvancedKeyManagement(self, mx_provided, logged_user, -                                    self._keymanager, self._soledad_started) +                                    self._backend, self._soledad_started)          akm.show()      @QtCore.Slot() @@ -1437,12 +1434,7 @@ class MainWindow(QtGui.QMainWindow):          """          logger.debug("Done bootstrapping Soledad") -        # Update the proxy objects to point to the initialized instances. -        # setProxiedObject(self._soledad, self._backend.get_soledad()) -        setProxiedObject(self._keymanager, self._backend.get_keymanager()) -          self._soledad_started = True -          self.soledad_ready.emit()      ################################################################### | 
