diff options
| -rw-r--r-- | keymanager/src/leap/keymanager/keys.py | 311 | ||||
| -rw-r--r-- | keymanager/src/leap/keymanager/openpgp.py | 120 | ||||
| -rw-r--r-- | keymanager/src/leap/keymanager/tests/test_openpgp.py | 2 | 
3 files changed, 143 insertions, 290 deletions
| diff --git a/keymanager/src/leap/keymanager/keys.py b/keymanager/src/leap/keymanager/keys.py index 1955d54..f2a12a9 100644 --- a/keymanager/src/leap/keymanager/keys.py +++ b/keymanager/src/leap/keymanager/keys.py @@ -28,10 +28,9 @@ except ImportError:  import logging  import re  import time -import traceback -from abc import ABCMeta, abstractmethod +from abc import ABCMeta  from datetime import datetime  from leap.common.check import leap_assert  from twisted.internet import defer @@ -296,290 +295,32 @@ class EncryptionKey(object):              "priv" if self.private else "publ") -# -# Encryption schemes -# - -class EncryptionScheme(object): +def init_indexes(soledad):      """ -    Abstract class for Encryption Schemes. - -    A wrapper for a certain encryption schemes should know how to get and put -    keys in local storage using Soledad, how to generate new keys and how to -    find out about possibly encrypted content. +    Initialize the database indexes.      """ +    leap_assert(soledad is not None, +                "Cannot init indexes with null soledad") -    __metaclass__ = ABCMeta - -    def __init__(self, soledad): -        """ -        Initialize this Encryption Scheme. - -        :param soledad: A Soledad instance for local storage of keys. -        :type soledad: leap.soledad.Soledad -        """ -        self._soledad = soledad -        self.deferred_init = self._init_indexes() -        self.deferred_init.addCallback(self._migrate_documents_schema) - -    def _init_indexes(self): -        """ -        Initialize the database indexes. -        """ -        leap_assert(self._soledad is not None, -                    "Cannot init indexes with null soledad") - -        def init_idexes(indexes): -            deferreds = [] -            db_indexes = dict(indexes) -            # Loop through the indexes we expect to find. -            for name, expression in INDEXES.items(): -                if name not in db_indexes: -                    # The index does not yet exist. -                    d = self._soledad.create_index(name, *expression) -                    deferreds.append(d) -                elif expression != db_indexes[name]: -                    # The index exists but the definition is not what expected, -                    # so we delete it and add the proper index expression. -                    d = self._soledad.delete_index(name) -                    d.addCallback( -                        lambda _: -                            self._soledad.create_index(name, *expression)) -                    deferreds.append(d) -            return defer.gatherResults(deferreds, consumeErrors=True) - -        d = self._soledad.list_indexes() -        d.addCallback(init_idexes) -        return d - -    def _migrate_documents_schema(self, _): -        from leap.keymanager.migrator import KeyDocumentsMigrator -        migrator = KeyDocumentsMigrator(self._soledad) -        return migrator.migrate() - -    def _wait_indexes(self, *methods): -        """ -        Methods that need to wait for the indexes to be ready. - -        Heavily based on -        http://blogs.fluidinfo.com/terry/2009/05/11/a-mixin-class-allowing-python-__init__-methods-to-work-with-twisted-deferreds/ - -        :param methods: methods that need to wait for the indexes to be ready -        :type methods: tuple(str) -        """ -        self.waiting = [] -        self.stored = {} - -        def restore(_): -            for method in self.stored: -                setattr(self, method, self.stored[method]) -            for d in self.waiting: -                d.callback(None) - -        def makeWrapper(method): -            def wrapper(*args, **kw): -                d = defer.Deferred() -                d.addCallback(lambda _: self.stored[method](*args, **kw)) -                self.waiting.append(d) -                return d -            return wrapper - -        for method in methods: -            self.stored[method] = getattr(self, method) -            setattr(self, method, makeWrapper(method)) - -        self.deferred_init.addCallback(restore) - -    @abstractmethod -    def get_key(self, address, private=False): -        """ -        Get key from local storage. - -        :param address: The address bound to the key. -        :type address: str -        :param private: Look for a private key instead of a public one? -        :type private: bool - -        :return: A Deferred which fires with the EncryptionKey bound to -                 address, or which fails with KeyNotFound if the key was not -                 found on local storage. -        :rtype: Deferred -        """ -        pass - -    @abstractmethod -    def put_key(self, key): -        """ -        Put a key in local storage. - -        :param key: The key to be stored. -        :type key: EncryptionKey - -        :return: A Deferred which fires when the key is in the storage. -        :rtype: Deferred -        """ -        pass - -    @abstractmethod -    def gen_key(self, address): -        """ -        Generate a new key. - -        :param address: The address bound to the key. -        :type address: str - -        :return: The key bound to C{address}. -        :rtype: EncryptionKey -        """ -        pass - -    @abstractmethod -    def delete_key(self, key): -        """ -        Remove C{key} from storage. - -        :param key: The key to be removed. -        :type key: EncryptionKey - -        :return: A Deferred which fires when the key is deleted, or which -                 fails with KeyNotFound if the key was not found on local -                 storage. -        :rtype: Deferred -        """ -        pass - -    @abstractmethod -    def encrypt(self, data, pubkey, passphrase=None, sign=None): -        """ -        Encrypt C{data} using public @{pubkey} and sign with C{sign} key. - -        :param data: The data to be encrypted. -        :type data: str -        :param pubkey: The key used to encrypt. -        :type pubkey: EncryptionKey -        :param sign: The key used for signing. -        :type sign: EncryptionKey - -        :return: The encrypted data. -        :rtype: str -        """ -        pass - -    @abstractmethod -    def decrypt(self, data, privkey, passphrase=None, verify=None): -        """ -        Decrypt C{data} using private @{privkey} and verify with C{verify} key. - -        :param data: The data to be decrypted. -        :type data: str -        :param privkey: The key used to decrypt. -        :type privkey: OpenPGPKey -        :param verify: The key used to verify a signature. -        :type verify: OpenPGPKey - -        :return: The decrypted data and if signature verifies -        :rtype: (unicode, bool) - -        :raise DecryptError: Raised if failed decrypting for some reason. -        """ -        pass - -    @abstractmethod -    def sign(self, data, privkey): -        """ -        Sign C{data} with C{privkey}. - -        :param data: The data to be signed. -        :type data: str - -        :param privkey: The private key to be used to sign. -        :type privkey: EncryptionKey - -        :return: The signed data. -        :rtype: str -        """ -        pass - -    @abstractmethod -    def verify(self, data, pubkey, detached_sig=None): -        """ -        Verify signed C{data} with C{pubkey}, eventually using -        C{detached_sig}. - -        :param data: The data to be verified. -        :type data: str -        :param pubkey: The public key to be used on verification. -        :type pubkey: EncryptionKey -        :param detached_sig: A detached signature. If given, C{data} is -                             verified against this sdetached signature. -        :type detached_sig: str - -        :return: signature matches -        :rtype: bool -        """ -        pass - -    def _repair_key_docs(self, doclist): -        """ -        If there is more than one key for a key id try to self-repair it - -        :return: a Deferred that will be fired with the valid key doc once all -                 the deletions are completed -        :rtype: Deferred -        """ -        def log_key_doc(doc): -            logger.error("\t%s: %s" % (doc.content[KEY_UIDS_KEY], -                                       doc.content[KEY_FINGERPRINT_KEY])) - -        def cmp_key(d1, d2): -            return cmp(d1.content[KEY_REFRESHED_AT_KEY], -                       d2.content[KEY_REFRESHED_AT_KEY]) - -        return self._repair_docs(doclist, cmp_key, log_key_doc) - -    def _repair_active_docs(self, doclist): -        """ -        If there is more than one active doc for an address try to self-repair -        it - -        :return: a Deferred that will be fired with the valid active doc once -                 all the deletions are completed -        :rtype: Deferred -        """ -        def log_active_doc(doc): -            logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], -                                       doc.content[KEY_FINGERPRINT_KEY])) - -        def cmp_active(d1, d2): -            res = cmp(d1.content[KEY_LAST_AUDITED_AT_KEY], -                      d2.content[KEY_LAST_AUDITED_AT_KEY]) -            if res != 0: -                return res - -            used1 = (d1.content[KEY_SIGN_USED_KEY] + -                     d1.content[KEY_ENCR_USED_KEY]) -            used2 = (d2.content[KEY_SIGN_USED_KEY] + -                     d2.content[KEY_ENCR_USED_KEY]) -            return cmp(used1, used2) - -        return self._repair_docs(doclist, cmp_active, log_active_doc) - -    def _repair_docs(self, doclist, cmp_func, log_func): -        logger.error("BUG ---------------------------------------------------") -        logger.error("There is more than one doc of type %s:" -                     % (doclist[0].content[KEY_TYPE_KEY],)) - -        doclist.sort(cmp=cmp_func, reverse=True) -        log_func(doclist[0]) +    def create_idexes(indexes):          deferreds = [] -        for doc in doclist[1:]: -            log_func(doc) -            d = self._soledad.delete_doc(doc) -            deferreds.append(d) - -        logger.error("") -        logger.error(traceback.extract_stack()) -        logger.error("BUG (please report above info) ------------------------") -        d = defer.gatherResults(deferreds, consumeErrors=True) -        d.addCallback(lambda _: doclist[0]) -        return d +        db_indexes = dict(indexes) +        # Loop through the indexes we expect to find. +        for name, expression in INDEXES.items(): +            if name not in db_indexes: +                # The index does not yet exist. +                d = soledad.create_index(name, *expression) +                deferreds.append(d) +            elif expression != db_indexes[name]: +                # The index exists but the definition is not what expected, +                # so we delete it and add the proper index expression. +                d = soledad.delete_index(name) +                d.addCallback( +                    lambda _: +                        soledad.create_index(name, *expression)) +                deferreds.append(d) +        return defer.gatherResults(deferreds, consumeErrors=True) + +    d = soledad.list_indexes() +    d.addCallback(create_idexes) +    return d diff --git a/keymanager/src/leap/keymanager/openpgp.py b/keymanager/src/leap/keymanager/openpgp.py index 82050cc..697ee8a 100644 --- a/keymanager/src/leap/keymanager/openpgp.py +++ b/keymanager/src/leap/keymanager/openpgp.py @@ -22,6 +22,7 @@ import os  import re  import shutil  import tempfile +import traceback  import io @@ -36,13 +37,19 @@ from leap.common.check import leap_assert, leap_assert_type, leap_check  from leap.keymanager import errors  from leap.keymanager.keys import (      EncryptionKey, -    EncryptionScheme, +    init_indexes,      is_address,      build_key_from_dict,      TYPE_FINGERPRINT_PRIVATE_INDEX,      TYPE_ADDRESS_PRIVATE_INDEX,      KEY_UIDS_KEY,      KEY_FINGERPRINT_KEY, +    KEY_REFRESHED_AT_KEY, +    KEY_LAST_AUDITED_AT_KEY, +    KEY_SIGN_USED_KEY, +    KEY_ENCR_USED_KEY, +    KEY_ADDRESS_KEY, +    KEY_TYPE_KEY,      KEYMANAGER_ACTIVE_TYPE,  ) @@ -256,7 +263,7 @@ class OpenPGPKey(EncryptionKey):          self.refreshed_at = datetime.now() -class OpenPGPScheme(EncryptionScheme): +class OpenPGPScheme(object):      """      A wrapper for OpenPGP keys management and use (encryption, decyption,      signing and verification). @@ -275,9 +282,49 @@ class OpenPGPScheme(EncryptionScheme):          :param gpgbinary: Name for GnuPG binary executable.          :type gpgbinary: C{str}          """ -        EncryptionScheme.__init__(self, soledad) -        self._wait_indexes("get_key", "put_key") +        self._soledad = soledad          self._gpgbinary = gpgbinary +        self.deferred_init = init_indexes(soledad) +        self.deferred_init.addCallback(self._migrate_documents_schema) +        self._wait_indexes("get_key", "put_key") + +    def _migrate_documents_schema(self, _): +        from leap.keymanager.migrator import KeyDocumentsMigrator +        migrator = KeyDocumentsMigrator(self._soledad) +        return migrator.migrate() + +    def _wait_indexes(self, *methods): +        """ +        Methods that need to wait for the indexes to be ready. + +        Heavily based on +        http://blogs.fluidinfo.com/terry/2009/05/11/a-mixin-class-allowing-python-__init__-methods-to-work-with-twisted-deferreds/ + +        :param methods: methods that need to wait for the indexes to be ready +        :type methods: tuple(str) +        """ +        self.waiting = [] +        self.stored = {} + +        def restore(_): +            for method in self.stored: +                setattr(self, method, self.stored[method]) +            for d in self.waiting: +                d.callback(None) + +        def makeWrapper(method): +            def wrapper(*args, **kw): +                d = defer.Deferred() +                d.addCallback(lambda _: self.stored[method](*args, **kw)) +                self.waiting.append(d) +                return d +            return wrapper + +        for method in methods: +            self.stored[method] = getattr(self, method) +            setattr(self, method, makeWrapper(method)) + +        self.deferred_init.addCallback(restore)      #      # Keys management @@ -849,6 +896,71 @@ class OpenPGPScheme(EncryptionScheme):              return repair_func(doclist)          return doclist[0] +    def _repair_key_docs(self, doclist): +        """ +        If there is more than one key for a key id try to self-repair it + +        :return: a Deferred that will be fired with the valid key doc once all +                 the deletions are completed +        :rtype: Deferred +        """ +        def log_key_doc(doc): +            logger.error("\t%s: %s" % (doc.content[KEY_UIDS_KEY], +                                       doc.content[KEY_FINGERPRINT_KEY])) + +        def cmp_key(d1, d2): +            return cmp(d1.content[KEY_REFRESHED_AT_KEY], +                       d2.content[KEY_REFRESHED_AT_KEY]) + +        return self._repair_docs(doclist, cmp_key, log_key_doc) + +    def _repair_active_docs(self, doclist): +        """ +        If there is more than one active doc for an address try to self-repair +        it + +        :return: a Deferred that will be fired with the valid active doc once +                 all the deletions are completed +        :rtype: Deferred +        """ +        def log_active_doc(doc): +            logger.error("\t%s: %s" % (doc.content[KEY_ADDRESS_KEY], +                                       doc.content[KEY_FINGERPRINT_KEY])) + +        def cmp_active(d1, d2): +            res = cmp(d1.content[KEY_LAST_AUDITED_AT_KEY], +                      d2.content[KEY_LAST_AUDITED_AT_KEY]) +            if res != 0: +                return res + +            used1 = (d1.content[KEY_SIGN_USED_KEY] + +                     d1.content[KEY_ENCR_USED_KEY]) +            used2 = (d2.content[KEY_SIGN_USED_KEY] + +                     d2.content[KEY_ENCR_USED_KEY]) +            return cmp(used1, used2) + +        return self._repair_docs(doclist, cmp_active, log_active_doc) + +    def _repair_docs(self, doclist, cmp_func, log_func): +        logger.error("BUG ---------------------------------------------------") +        logger.error("There is more than one doc of type %s:" +                     % (doclist[0].content[KEY_TYPE_KEY],)) + +        doclist.sort(cmp=cmp_func, reverse=True) +        log_func(doclist[0]) +        deferreds = [] +        for doc in doclist[1:]: +            log_func(doc) +            d = self._soledad.delete_doc(doc) +            deferreds.append(d) + +        logger.error("") +        logger.error(traceback.extract_stack()) +        logger.error("BUG (please report above info) ------------------------") +        d = defer.gatherResults(deferreds, consumeErrors=True) +        d.addCallback(lambda _: doclist[0]) +        return d +  def process_key(key_data, gpgbinary, secret=False):      with TempGPGWrapper(gpgbinary=gpgbinary) as gpg: diff --git a/keymanager/src/leap/keymanager/tests/test_openpgp.py b/keymanager/src/leap/keymanager/tests/test_openpgp.py index 68fb4e0..acb2c1c 100644 --- a/keymanager/src/leap/keymanager/tests/test_openpgp.py +++ b/keymanager/src/leap/keymanager/tests/test_openpgp.py @@ -305,8 +305,8 @@ class OpenPGPCryptoTestCase(KeyManagerWithSoledadTestCase):          pgp = openpgp.OpenPGPScheme(              self._soledad, gpgbinary=self.gpg_binary_path)          yield pgp.put_raw_key(PUBLIC_KEY, ADDRESS) -        self.assertEqual(self.count, 2)          self._soledad.delete_doc = delete_doc +        self.assertEqual(self.count, 2)      @inlineCallbacks      def test_self_repair_five_active_docs(self): | 
