Ejemplo n.º 1
0
    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: OpenPGPKey

        :return: The ascii-armored signed data.
        :rtype: str
        """
        leap_assert_type(privkey, OpenPGPKey)
        leap_assert(privkey.private is True)

        # result.fingerprint - contains the fingerprint of the key used to
        #                      sign.
        with self._temporary_gpgwrapper(privkey) as gpg:
            result = gpg.sign(data, keyid=privkey.key_id)
            rfprint = privkey.fingerprint
            privkey = gpg.list_keys(secret=True).pop()
            kfprint = privkey['fingerprint']
            if result.fingerprint is None:
                raise errors.SignFailed(
                    'Failed to sign with key %s: %s' %
                    (privkey['keyid'], stderr))
            leap_assert(
                result.fingerprint == kfprint,
                'Signature and private key fingerprints mismatch: '
                '%s != %s' % (rfprint, kfprint))
        return result.data
Ejemplo n.º 2
0
    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: OpenPGPKey
        :param sign: The key used for signing.
        :type sign: OpenPGPKey

        :return: The encrypted data.
        :rtype: str
        """
        leap_assert_type(pubkey, OpenPGPKey)
        leap_assert(pubkey.private is False, 'Key is not public.')
        keys = [pubkey]
        if sign is not None:
            leap_assert_type(sign, OpenPGPKey)
            leap_assert(sign.private is True)
            keys.append(sign)
        with self._temporary_gpgwrapper(keys) as gpg:
            result = gpg.encrypt(
                data, pubkey.fingerprint,
                sign=sign.key_id if sign else None,
                passphrase=passphrase, symmetric=False)
            # Here we cannot assert for correctness of sig because the sig is
            # in the ciphertext.
            # result.ok    - (bool) indicates if the operation succeeded
            # result.data  - (bool) contains the result of the operation
            self._assert_gpg_result_ok(result)
            return result.data
Ejemplo n.º 3
0
    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.
        :rtype: str

        @raise InvalidSignature: Raised if unable to verify the signature with
            C{verify} key.
        """
        leap_assert(privkey.private is True, 'Key is not private.')
        keys = [privkey]
        if verify is not None:
            leap_assert_type(verify, OpenPGPKey)
            leap_assert(verify.private is False)
            keys.append(verify)
        with self._temporary_gpgwrapper(keys) as gpg:
            result = gpg.decrypt(data, passphrase=passphrase)
            self._assert_gpg_result_ok(result)
            # verify signature
            if (verify is not None):
                if result.valid is False or \
                        verify.fingerprint != result.pubkey_fingerprint:
                    raise errors.InvalidSignature(
                        'Failed to verify signature with key %s: %s' %
                        (verify.key_id, stderr))
            return result.data
Ejemplo n.º 4
0
    def load_user_from_keyring(self, saved_user):
        """
        Try to load a user from the keyring.

        :param saved_user: the saved username as user@domain
        :type saved_user: unicode

        :return: True if the user was loaded successfully, False otherwise.
        :rtype: bool
        """
        leap_assert_type(saved_user, unicode)

        try:
            username, domain = saved_user.split('@')
        except ValueError as e:
            # if the saved_user does not contain an '@'
            logger.error('Username@provider malformed. %r' % (e, ))
            return False

        self.set_user(username)
        self.set_remember(True)

        saved_password = None
        try:
            keyring = get_keyring()
            u_user = saved_user.encode("utf8")
            saved_password = keyring.get_password(self.KEYRING_KEY, u_user)
        except ValueError as e:
            logger.debug("Incorrect Password. %r." % (e,))

        if saved_password is not None:
            self.set_password(saved_password)
            return True

        return False
Ejemplo n.º 5
0
def encrypt_sym(data, passphrase, sign=None):
    """
    Encrypt C{data} with C{passphrase} and sign with C{sign} private key.

    @param data: The data to be encrypted.
    @type data: str
    @param passphrase: The passphrase used to encrypt C{data}.
    @type passphrase: str
    @param sign: The private key used for signing.
    @type sign: OpenPGPKey

    @return: The encrypted data.
    @rtype: str
    """
    leap_assert_type(passphrase, str)
    if sign is not None:
        leap_assert_type(sign, OpenPGPKey)
        leap_assert(sign.private is True)

    def _encrypt_cb(gpg):
        result = gpg.encrypt(data,
                             None,
                             sign=sign.key_id if sign else None,
                             passphrase=passphrase,
                             symmetric=True)
        # Here we cannot assert for correctness of sig because the sig is in
        # the ciphertext.
        # result.ok    - (bool) indicates if the operation succeeded
        # result.data  - (bool) contains the result of the operation
        if result.ok is False:
            raise EncryptionFailed('Failed to encrypt: %s' % result.stderr)
        return result.data

    return _safe_call(_encrypt_cb, [sign])
Ejemplo n.º 6
0
def sign(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: OpenPGPKey

    @return: The ascii-armored signed data.
    @rtype: str
    """
    leap_assert_type(privkey, OpenPGPKey)
    leap_assert(privkey.private is True)

    def _sign_cb(gpg):
        result = gpg.sign(data, keyid=privkey.key_id)
        # result.fingerprint - contains the fingerprint of the key used to
        #                      sign.
        if result.fingerprint is None:
            raise SignFailed('Failed to sign with key %s: %s' %
                             (privkey.key_id, result.stderr))
        leap_assert(
            result.fingerprint == privkey.fingerprint,
            'Signature and private key fingerprints mismatch: %s != %s' %
            (result.fingerprint, privkey.fingerprint))
        return result.data

    return _safe_call(_sign_cb, [privkey])
Ejemplo n.º 7
0
    def verify(self, data, pubkey):
        """
        Verify signed C{data} with C{pubkey}.

        :param data: The data to be verified.
        :type data: str

        :param pubkey: The public key to be used on verification.
        :type pubkey: OpenPGPKey

        :return: The ascii-armored signed data.
        :rtype: str
        """
        leap_assert_type(pubkey, OpenPGPKey)
        leap_assert(pubkey.private is False)
        with self._temporary_gpgwrapper(pubkey) as gpg:
            result = gpg.verify(data)
            gpgpubkey = gpg.list_keys().pop()
            valid = result.valid
            rfprint = result.fingerprint
            kfprint = gpgpubkey['fingerprint']
            # raise in case sig is invalid
            if valid is False or rfprint != kfprint:
                raise errors.InvalidSignature(
                    'Failed to verify signature '
                    'with key %s.' % gpgpubkey['keyid'])
            return True
Ejemplo n.º 8
0
    def write_last_uid(self, mbox, value):
        """
        Write the `last_uid` integer to the proper mailbox document
        in Soledad.
        This is called from the deferred triggered by
        memorystore.increment_last_soledad_uid, which is expected to
        run in a separate thread.

        :param mbox: the mailbox
        :type mbox: str or unicode
        :param value: the value to set
        :type value: int
        """
        leap_assert_type(value, int)
        key = fields.LAST_UID_KEY

        # XXX use accumulator to reduce number of hits
        with mbox_doc_locks[mbox]:
            mbox_doc = self._get_mbox_document(mbox)
            old_val = mbox_doc.content[key]
            if value > old_val:
                mbox_doc.content[key] = value
                try:
                    self._soledad.put_doc(mbox_doc)
                except Exception as exc:
                    logger.error("Error while setting last_uid for %r"
                                 % (mbox,))
                    logger.exception(exc)
Ejemplo n.º 9
0
    def run_smtp_setup_checks(self,
                              provider_config,
                              smtp_config,
                              download_if_needed=False):
        """
        Starts the checks needed for a new smtp setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        :param smtp_config: SMTP configuration to populate
        :type smtp_config: SMTPConfig
        :param download_if_needed: True if it should check for mtime
                                   for the file
        :type download_if_needed: bool
        """
        leap_assert_type(provider_config, ProviderConfig)

        self._provider_config = provider_config
        self._smtp_config = smtp_config
        self._download_if_needed = download_if_needed

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

        self.addCallbackChain(cb_chain)
Ejemplo n.º 10
0
    def get_client_cert_path(self,
                             userid,
                             providerconfig=None,
                             about_to_download=False):
        """
        Returns the path to the certificate used by smtp
        :param userid: the user id, in user@provider form
        """

        leap_assert(userid, "Need an userid")
        leap_assert(providerconfig, "We need a provider")
        leap_assert_type(providerconfig, ProviderConfig)

        username = userid.split("@")[0]

        cert_path = os.path.join(get_path_prefix(),
                                 "leap", "providers",
                                 providerconfig.get_domain(),
                                 "keys", "client", "smtp_%s.pem" % username)

        if not about_to_download:
            leap_assert(os.path.exists(cert_path),
                        "You need to download the certificate first")
            logger.debug("Using SMTP cert %s" % (cert_path,))

        return cert_path
Ejemplo n.º 11
0
    def put_ascii_key(self, key_data, address):
        """
        Put key contained in ascii-armored C{key_data} in local storage.

        :param key_data: The key data to be stored.
        :type key_data: str or unicode
        :param address: address for which this key will be active
        :type address: str

        :return: A Deferred which fires when the OpenPGPKey is in the storage.
        :rtype: Deferred
        """
        leap_assert_type(key_data, (str, unicode))

        openpgp_privkey = None
        try:
            openpgp_pubkey, openpgp_privkey = self.parse_ascii_key(
                key_data, address)
        except (errors.KeyAddressMismatch, errors.KeyFingerprintMismatch) as e:
            return defer.fail(e)

        def put_key(_, key):
            return self.put_key(key)

        d = defer.succeed(None)
        if openpgp_pubkey is not None:
            d.addCallback(put_key, openpgp_pubkey)
        if openpgp_privkey is not None:
            d.addCallback(put_key, openpgp_privkey)
        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
        """
        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)
        ]

        return self.addCallbackChain(cb_chain)
Ejemplo n.º 13
0
def decrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR, **kwargs):
    """
    Decrypt C{data} with C{key} using C{method} encryption method.

    :param data: The data to be decrypted.
    :type data: str
    :param key: The key used to decrypt C{data} (must be 256 bits long).
    :type key: str
    :param method: The encryption method to use.
    :type method: str
    :param kwargs: Other parameters specific to each encryption method.
    :type kwargs: dict

    :return: The decrypted data.
    :rtype: str
    """
    leap_assert_type(key, str)

    # AES-256 in CTR mode
    if method == EncryptionMethods.AES_256_CTR:
        # assert params
        leap_assert(
            len(key) == 32,  # 32 x 8 = 256 bits.
            'Wrong key size: %s (must be 256 bits long).' % len(key))
        leap_assert('iv' in kwargs,
                    'AES-256-CTR needs an initial value given as.')
        ctr = Counter.new(64, prefix=binascii.a2b_base64(kwargs['iv']))
        cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr)
        return cipher.decrypt(data)

    # raise if method is unknown
    raise UnknownEncryptionMethod('Unkwnown method: %s' % method)
Ejemplo n.º 14
0
def encrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR):
    """
    Encrypt C{data} with C{key}, using C{method} encryption method.

    :param data: The data to be encrypted.
    :type data: str
    :param key: The key used to encrypt C{data} (must be 256 bits long).
    :type key: str
    :param method: The encryption method to use.
    :type method: str

    :return: A tuple with the initial value and the encrypted data.
    :rtype: (long, str)
    """
    leap_assert_type(key, str)

    # AES-256 in CTR mode
    if method == EncryptionMethods.AES_256_CTR:
        leap_assert(
            len(key) == 32,  # 32 x 8 = 256 bits.
            'Wrong key size: %s bits (must be 256 bits long).' %
            (len(key) * 8))
        iv = os.urandom(8)
        ctr = Counter.new(64, prefix=iv)
        cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr)
        return binascii.b2a_base64(iv), cipher.encrypt(data)

    # raise if method is unknown
    raise UnknownEncryptionMethod('Unkwnown method: %s' % method)
    def addCallbackChain(self, callbacks):
        """
        Creates a callback/errback chain on another thread using
        deferToThread and adds the _gui_errback to the end to notify
        the GUI on an error.

        :param callbacks: List of tuples of callbacks and the signal
                          associated to that callback
        :type callbacks: list(tuple(func, func))

        :returns: the defer with the callback chain
        :rtype: deferred
        """
        leap_assert_type(callbacks, list)

        self._signal_to_emit = None
        self._err_msg = None

        d = None
        for cb, sig in callbacks:
            if d is None:
                d = threads.deferToThread(cb)
            else:
                d.addCallback(partial(self._callback_threader, cb))
            d.addErrback(self._errback, signal=sig)
            d.addCallback(self._gui_notify, signal=sig)
        d.addErrback(self._gui_errback)
        return d
Ejemplo n.º 16
0
    def __init__(self, account_name, soledad=None):
        """
        Creates a SoledadAccountIndex that keeps track of the mailboxes
        and subscriptions handled by this account.

        :param acct_name: The name of the account (user id).
        :type acct_name: str

        :param soledad: a Soledad instance.
        :param soledad: Soledad
        """
        leap_assert(soledad, "Need a soledad instance to initialize")
        leap_assert_type(soledad, Soledad)

        # XXX SHOULD assert too that the name matches the user/uuid with which
        # soledad has been initialized.

        self._account_name = account_name.upper()
        self._soledad = soledad

        self.initialize_db()

        # every user should have the right to an inbox folder
        # at least, so let's make one!

        if not self.mailboxes:
            self.addMailbox(self.INBOX_NAME)
Ejemplo n.º 17
0
    def __init__(self, keymanager, soledad, imap_account,
                 check_period):

        """
        Initialize LeapIMAP.

        :param keymanager: a keymanager instance
        :type keymanager: keymanager.KeyManager

        :param soledad: a soledad instance
        :type soledad: Soledad

        :param imap_account: the account to fetch periodically
        :type imap_account: SoledadBackedAccount

        :param check_period: the period to fetch new mail, in seconds.
        :type check_period: int
        """

        leap_assert(keymanager, "need a keymanager to initialize")
        leap_assert_type(soledad, Soledad)
        leap_assert(check_period, "need a period to check incoming mail")
        leap_assert_type(check_period, int)

        self._keymanager = keymanager
        self._soledad = soledad
        self.imapAccount = imap_account
        self._inbox = self.imapAccount.getMailbox('inbox')

        self._pkey = self._keymanager.get_all_keys_in_local_db(
            private=True).pop()
        self._loop = None
        self._check_period = check_period

        self._create_soledad_indexes()
Ejemplo n.º 18
0
    def write_last_uid(self, mbox, value):
        """
        Write the `last_uid` integer to the proper mailbox document
        in Soledad.
        This is called from the deferred triggered by
        memorystore.increment_last_soledad_uid, which is expected to
        run in a separate thread.

        :param mbox: the mailbox
        :type mbox: str or unicode
        :param value: the value to set
        :type value: int
        """
        leap_assert_type(value, int)
        key = fields.LAST_UID_KEY

        # XXX use accumulator to reduce number of hits
        with mbox_doc_locks[mbox]:
            mbox_doc = self._get_mbox_document(mbox)
            old_val = mbox_doc.content[key]
            if value > old_val:
                mbox_doc.content[key] = value
                try:
                    self._soledad.put_doc(mbox_doc)
                except Exception as exc:
                    logger.error("Error while setting last_uid for %r" %
                                 (mbox, ))
                    logger.exception(exc)
Ejemplo n.º 19
0
    def run_provider_setup_checks(self,
                                  provider_config,
                                  download_if_needed=False):
        """
        Starts the checks needed for a new provider setup.

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig

        :param download_if_needed: if True, makes the checks do not
                                   overwrite already downloaded data.
        :type download_if_needed: bool
        """
        leap_assert(provider_config, "We need a provider config!")
        leap_assert_type(provider_config, ProviderConfig)

        self._provider_config = provider_config
        self._download_if_needed = download_if_needed

        download_ca_cert = None
        check_ca_fingerprint = None
        check_api_certificate = None
        if self._signaler is not None:
            download_ca_cert = self._signaler.prov_download_ca_cert
            check_ca_fingerprint = self._signaler.prov_check_ca_fingerprint
            check_api_certificate = self._signaler.prov_check_api_certificate

        cb_chain = [(self._download_ca_cert, download_ca_cert),
                    (self._check_ca_fingerprint, check_ca_fingerprint),
                    (self._check_api_certificate, check_api_certificate)]

        return self.addCallbackChain(cb_chain)
Ejemplo n.º 20
0
    def __init__(self, fromAddress, user, keymanager, host, port, cert, key):
        """
        Initialize the encrypted message.

        :param fromAddress: The address of the sender.
        :type fromAddress: twisted.mail.smtp.Address
        :param user: The recipient of this message.
        :type user: twisted.mail.smtp.User
        :param keymanager: A KeyManager for retrieving recipient's keys.
        :type keymanager: leap.common.keymanager.KeyManager
        :param host: The hostname of the remote SMTP server.
        :type host: str
        :param port: The port of the remote SMTP server.
        :type port: int
        :param cert: The client certificate for authentication.
        :type cert: str
        :param key: The client key for authentication.
        :type key: str
        """
        # assert params
        leap_assert_type(user, smtp.User)
        leap_assert_type(keymanager, KeyManager)
        # and store them
        self._fromAddress = fromAddress
        self._user = user
        self._km = keymanager
        self._host = host
        self._port = port
        self._cert = cert
        self._key = key
        # initialize list for message's lines
        self.lines = []
Ejemplo n.º 21
0
    def get_client_cert_path(self,
                             userid,
                             providerconfig=None,
                             about_to_download=False):
        """
        Returns the path to the certificate used by smtp
        :param userid: the user id, in user@provider form
        """

        leap_assert(userid, "Need an userid")
        leap_assert(providerconfig, "We need a provider")
        leap_assert_type(providerconfig, ProviderConfig)

        username = userid.split("@")[0]

        cert_path = os.path.join(get_path_prefix(), "leap", "providers",
                                 providerconfig.get_domain(), "keys", "client",
                                 "smtp_%s.pem" % username)

        if not about_to_download:
            leap_assert(os.path.exists(cert_path),
                        "You need to download the certificate first")
            logger.debug("Using SMTP cert %s" % (cert_path, ))

        return cert_path
Ejemplo n.º 22
0
    def __init__(self, fromAddress, user, keymanager, host, port, cert, key):
        """
        Initialize the encrypted message.

        :param fromAddress: The address of the sender.
        :type fromAddress: twisted.mail.smtp.Address
        :param user: The recipient of this message.
        :type user: twisted.mail.smtp.User
        :param keymanager: A KeyManager for retrieving recipient's keys.
        :type keymanager: leap.common.keymanager.KeyManager
        :param host: The hostname of the remote SMTP server.
        :type host: str
        :param port: The port of the remote SMTP server.
        :type port: int
        :param cert: The client certificate for authentication.
        :type cert: str
        :param key: The client key for authentication.
        :type key: str
        """
        # assert params
        leap_assert_type(user, smtp.User)
        leap_assert_type(keymanager, KeyManager)
        # and store them
        self._fromAddress = fromAddress
        self._user = user
        self._km = keymanager
        self._host = host
        self._port = port
        self._cert = cert
        self._key = key
        # initialize list for message's lines
        self.lines = []
Ejemplo n.º 23
0
    def __init__(self, fromAddress, user, keymanager, config):
        """
        Initialize the encrypted message.

        @param fromAddress: The address of the sender.
        @type fromAddress: twisted.mail.smtp.Address
        @param user: The recipient of this message.
        @type user: twisted.mail.smtp.User
        @param keymanager: A KeyManager for retrieving recipient's keys.
        @type keymanager: leap.common.keymanager.KeyManager
        @param config: A dictionary with smtp configuration. Should have
            the following structure:
                {
                    HOST_KEY: '<str>',
                    PORT_KEY: <int>,
                    USERNAME_KEY: '<str>',
                    PASSWORD_KEY: '<str>',
                    ENCRYPTED_ONLY_KEY: <bool>,
                }
        @type config: dict
        """
        # assert params
        leap_assert_type(user, smtp.User)
        leap_assert_type(keymanager, KeyManager)
        assert_config_structure(config)
        # and store them
        self._fromAddress = fromAddress
        self._user = user
        self._km = keymanager
        self._config = config
        # initialize list for message's lines
        self.lines = []
Ejemplo n.º 24
0
    def run_eip_setup_checks(self,
                             provider_config,
                             download_if_needed=False):
        """
        Starts the checks needed for a new eip setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        """
        leap_assert(provider_config, "We need a provider config!")
        leap_assert_type(provider_config, ProviderConfig)

        self._provider_config = provider_config
        self._download_if_needed = download_if_needed

        eip_config_ready = None
        eip_certificate_ready = None
        if self._signaler is not None:
            eip_config_ready = self._signaler.eip_config_ready
            eip_certificate_ready = self._signaler.eip_client_certificate_ready

        cb_chain = [
            (self._download_config, eip_config_ready),
            (self._download_client_certificates, eip_certificate_ready)
        ]

        return self.addCallbackChain(cb_chain)
Ejemplo n.º 25
0
    def __init__(self, account_name, soledad, memstore=None):
        """
        Creates a SoledadAccountIndex that keeps track of the mailboxes
        and subscriptions handled by this account.

        :param acct_name: The name of the account (user id).
        :type acct_name: str

        :param soledad: a Soledad instance.
        :type soledad: Soledad
        :param memstore: a MemoryStore instance.
        :type memstore: MemoryStore
        """
        leap_assert(soledad, "Need a soledad instance to initialize")
        leap_assert_type(soledad, Soledad)

        # XXX SHOULD assert too that the name matches the user/uuid with which
        # soledad has been initialized.

        # XXX ??? why is this parsing mailbox name??? it's account...
        # userid? homogenize.
        self._account_name = self._parse_mailbox_name(account_name)
        self._soledad = soledad
        self._memstore = memstore

        self.__mailboxes = set([])

        self.initialize_db()

        # every user should have the right to an inbox folder
        # at least, so let's make one!
        self._load_mailboxes()

        if not self.mailboxes:
            self.addMailbox(self.INBOX_NAME)
Ejemplo n.º 26
0
    def run_provider_setup_checks(self,
                                  provider_config,
                                  download_if_needed=False):
        """
        Starts the checks needed for a new provider setup.

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig

        :param download_if_needed: if True, makes the checks do not
                                   overwrite already downloaded data.
        :type download_if_needed: bool
        """
        leap_assert(provider_config, "We need a provider config!")
        leap_assert_type(provider_config, ProviderConfig)

        self._provider_config = provider_config
        self._download_if_needed = download_if_needed

        cb_chain = [
            (self._download_ca_cert, self._signaler.prov_download_ca_cert),
            (self._check_ca_fingerprint,
             self._signaler.prov_check_ca_fingerprint),
            (self._check_api_certificate,
             self._signaler.prov_check_api_certificate)
        ]

        return self.addCallbackChain(cb_chain)
Ejemplo n.º 27
0
def encrypt_asym(data, key, passphrase=None, sign=None):
    """
    Encrypt C{data} using public @{key} 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: OpenPGPKey
    :param sign: The key used for signing.
    :type sign: OpenPGPKey

    :return: The encrypted data.
    :rtype: str
    """
    leap_assert_type(key, OpenPGPKey)
    leap_assert(key.private is False, 'Key is not public.')
    if sign is not None:
        leap_assert_type(sign, OpenPGPKey)
        leap_assert(sign.private is True)

    # Here we cannot assert for correctness of sig because the sig is in
    # the ciphertext.
    # result.ok    - (bool) indicates if the operation succeeded
    # result.data  - (bool) contains the result of the operation

    return lambda gpg: gpg.encrypt(
        data, key.fingerprint,
        sign=sign.key_id if sign else None,
        passphrase=passphrase, symmetric=False)
Ejemplo n.º 28
0
    def __init__(self, account_name, soledad, memstore=None):
        """
        Creates a SoledadAccountIndex that keeps track of the mailboxes
        and subscriptions handled by this account.

        :param acct_name: The name of the account (user id).
        :type acct_name: str

        :param soledad: a Soledad instance.
        :type soledad: Soledad
        :param memstore: a MemoryStore instance.
        :type memstore: MemoryStore
        """
        leap_assert(soledad, "Need a soledad instance to initialize")
        leap_assert_type(soledad, Soledad)

        # XXX SHOULD assert too that the name matches the user/uuid with which
        # soledad has been initialized.

        # XXX ??? why is this parsing mailbox name??? it's account...
        # userid? homogenize.
        self._account_name = self._parse_mailbox_name(account_name)
        self._soledad = soledad
        self._memstore = memstore

        self.__mailboxes = set([])

        self.initialize_db()

        # every user should have the right to an inbox folder
        # at least, so let's make one!
        self._load_mailboxes()

        if not self.mailboxes:
            self.addMailbox(self.INBOX_NAME)
Ejemplo n.º 29
0
    def __init__(self, user_id, store, d=defer.Deferred()):
        """
        Keeps track of the mailboxes and subscriptions handled by this account.

        The account is not ready to be used, since the store needs to be
        initialized and we also need to do some initialization routines.
        You can either pass a deferred to this constructor, or use
        `callWhenReady` method.

        :param user_id: The name of the account (user id, in the form
                        user@provider).
        :type user_id: str

        :param store: a Soledad instance.
        :type store: Soledad

        :param d: a deferred that will be fired with this IMAPAccount instance
                  when the account is ready to be used.
        :type d: defer.Deferred
        """
        leap_assert(store, "Need a store instance to initialize")
        leap_assert_type(store, Soledad)

        # TODO assert too that the name matches the user/uuid with which
        # soledad has been initialized. Although afaik soledad doesn't know
        # about user_id, only the client backend.

        self.user_id = user_id
        self.account = Account(store, ready_cb=lambda: d.callback(self))
Ejemplo n.º 30
0
    def _wait_for_indexes(self):
        """
        Make the marked methods 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)
        """
        leap_assert_type(self.wait_for_indexes, list)
        methods = self.wait_for_indexes

        self.waiting = []
        self.stored = {}

        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))
Ejemplo n.º 31
0
    def __init__(self, handler):
        """
        Initialize the widget with the custom handler.

        :param handler: Custom handler that supports history and signal.
        :type handler: LeapLogHandler.
        """
        QtGui.QDialog.__init__(self)
        leap_assert(handler, "We need a handler for the logger window")
        leap_assert_type(handler, LeapLogHandler)

        # Load UI
        self.ui = Ui_LoggerWindow()
        self.ui.setupUi(self)

        # Make connections
        self.ui.btnSave.clicked.connect(self._save_log_to_file)
        self.ui.btnDebug.toggled.connect(self._load_history),
        self.ui.btnInfo.toggled.connect(self._load_history),
        self.ui.btnWarning.toggled.connect(self._load_history),
        self.ui.btnError.toggled.connect(self._load_history),
        self.ui.btnCritical.toggled.connect(self._load_history)
        self.ui.leFilterBy.textEdited.connect(self._filter_by)
        self.ui.cbCaseInsensitive.stateChanged.connect(self._load_history)

        self._current_filter = ""

        # Load logging history and connect logger with the widget
        self._logging_handler = handler
        self._connect_to_handler()
        self._load_history()
Ejemplo n.º 32
0
    def add_msg(self, raw, subject=None, flags=None, date=None, uid=None,
                notify_on_disk=False):
        """
        Creates a new message document.

        :param raw: the raw message
        :type raw: str

        :param subject: subject of the message.
        :type subject: str

        :param flags: flags
        :type flags: list

        :param date: the received date for the message
        :type date: str

        :param uid: the message uid for this mailbox
        :type uid: int

        :return: a deferred that will be fired with the message
                 uid when the adding succeed.
        :rtype: deferred
        """
        if flags is None:
            flags = tuple()
        leap_assert_type(flags, tuple)

        observer = defer.Deferred()
        d = self._do_parse(raw)
        d.addCallback(lambda result: self.reactor.callInThread(
            self._do_add_msg, result, flags, subject, date,
            notify_on_disk, observer))
        return observer
Ejemplo n.º 33
0
    def load_user_from_keyring(self, saved_user):
        """
        Try to load a user from the keyring.

        :param saved_user: the saved username as user@domain
        :type saved_user: unicode

        :return: True if the user was loaded successfully, False otherwise.
        :rtype: bool
        """
        leap_assert_type(saved_user, unicode)

        try:
            username, domain = saved_user.split('@')
        except ValueError as e:
            # if the saved_user does not contain an '@'
            logger.error('Username@provider malformed. %r' % (e, ))
            return False

        self.set_user(username)
        self.set_remember(True)

        saved_password = None
        try:
            keyring = get_keyring()
            u_user = saved_user.encode("utf8")
            saved_password = keyring.get_password(self.KEYRING_KEY, u_user)
        except ValueError as e:
            logger.debug("Incorrect Password. %r." % (e, ))

        if saved_password is not None:
            self.set_password(saved_password)
            return True

        return False
Ejemplo n.º 34
0
    def initialize_db(self):
        """
        Initialize the database.
        """
        leap_assert(self._soledad,
                    "Need a soledad attribute accesible in the instance")
        leap_assert_type(self.INDEXES, dict)

        # Ask the database for currently existing indexes.
        if not self._soledad:
            logger.debug("NO SOLEDAD ON IMAP INITIALIZATION")
            return
        db_indexes = dict()
        if self._soledad is not None:
            db_indexes = dict(self._soledad.list_indexes())
        for name, expression in SoledadBackedAccount.INDEXES.items():
            if name not in db_indexes:
                # The index does not yet exist.
                self._soledad.create_index(name, *expression)
                continue

            if expression == db_indexes[name]:
                # The index exists and is up to date.
                continue
            # The index exists but the definition is not what expected, so we
            # delete it and add the proper index expression.
            self._soledad.delete_index(name)
            self._soledad.create_index(name, *expression)
Ejemplo n.º 35
0
    def initialize_db(self):
        """
        Initialize the database.
        """
        leap_assert(self._soledad,
                    "Need a soledad attribute accesible in the instance")
        leap_assert_type(self.INDEXES, dict)

        # Ask the database for currently existing indexes.
        if not self._soledad:
            logger.debug("NO SOLEDAD ON IMAP INITIALIZATION")
            return
        db_indexes = dict()
        if self._soledad is not None:
            db_indexes = dict(self._soledad.list_indexes())
        for name, expression in fields.INDEXES.items():
            if name not in db_indexes:
                # The index does not yet exist.
                self._soledad.create_index(name, *expression)
                continue

            if expression == db_indexes[name]:
                # The index exists and is up to date.
                continue
            # The index exists but the definition is not what expected, so we
            # delete it and add the proper index expression.
            self._soledad.delete_index(name)
            self._soledad.create_index(name, *expression)
Ejemplo n.º 36
0
def encrypt_sym(data, key, method=EncryptionMethods.AES_256_CTR):
    """
    Encrypt C{data} with C{key}, using C{method} encryption method.

    @param data: The data to be encrypted.
    @type data: str
    @param key: The key used to encrypt C{data} (must be 256 bits long).
    @type key: str
    @param method: The encryption method to use.
    @type method: str

    @return: A tuple with the initial value and the encrypted data.
    @rtype: (long, str)
    """
    leap_assert_type(key, str)

    # AES-256 in CTR mode
    if method == EncryptionMethods.AES_256_CTR:
        leap_assert(
            len(key) == 32,  # 32 x 8 = 256 bits.
            'Wrong key size: %s bits (must be 256 bits long).' %
            (len(key) * 8))
        iv = random.getrandbits(256)
        ctr = Counter.new(128, initial_value=iv)
        cipher = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr)
        return iv, cipher.encrypt(data)

    # raise if method is unknown
    raise UnknownEncryptionMethod('Unkwnown method: %s' % method)
Ejemplo n.º 37
0
    def _init_indexes(self, store):
        """
        Initialize the database indexes.
        """
        leap_assert(store, "Cannot init indexes with null soledad")
        leap_assert_type(self.indexes, dict)

        def _create_index(name, expression):
            return store.create_index(name, *expression)

        def init_idexes(indexes):
            deferreds = []
            db_indexes = dict(indexes)
            # Loop through the indexes we expect to find.
            for name, expression in self.indexes.items():
                if name not in db_indexes:
                    # The index does not yet exist.
                    d = _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 = store.delete_index(name)
                    d.addCallback(lambda _: _create_index(name, *expression))
                    deferreds.append(d)
            return defer.gatherResults(deferreds, consumeErrors=True)

        def store_ready(whatever):
            self.store_ready = True
            return whatever

        self.deferred_indexes = store.list_indexes()
        self.deferred_indexes.addCallback(init_idexes)
        self.deferred_indexes.addCallback(store_ready)
        return self.deferred_indexes
Ejemplo n.º 38
0
def encrypt_asym(data, pubkey, sign=None):
    """
    Encrypt C{data} using public @{key} 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: OpenPGPKey
    @param sign: The key used for signing.
    @type sign: OpenPGPKey

    @return: The encrypted data.
    @rtype: str
    """
    leap_assert_type(pubkey, OpenPGPKey)
    leap_assert(pubkey.private is False, 'Key is not public.')
    if sign is not None:
        leap_assert_type(sign, OpenPGPKey)
        leap_assert(sign.private is True)

    def _encrypt_cb(gpg):
        result = gpg.encrypt(data,
                             pubkey.fingerprint,
                             sign=sign.key_id if sign else None,
                             symmetric=False)
        # Here we cannot assert for correctness of sig because the sig is in
        # the ciphertext.
        # result.ok    - (bool) indicates if the operation succeeded
        # result.data  - (bool) contains the result of the operation
        if result.ok is False:
            raise EncryptionFailed('Failed to encrypt with key %s: %s' %
                                   (pubkey.key_id, result.stderr))
        return result.data

    return _safe_call(_encrypt_cb, [pubkey, sign])
Ejemplo n.º 39
0
    def __init__(self, store, user_id, d=defer.Deferred()):
        """
        Keeps track of the mailboxes and subscriptions handled by this account.

        The account is not ready to be used, since the store needs to be
        initialized and we also need to do some initialization routines.
        You can either pass a deferred to this constructor, or use
        `callWhenReady` method.

        :param store: a Soledad instance.
        :type store: Soledad

        :param user_id: The identifier of the user this account belongs to
                        (user id, in the form user@provider).
        :type user_id: str


        :param d: a deferred that will be fired with this IMAPAccount instance
                  when the account is ready to be used.
        :type d: defer.Deferred
        """
        leap_assert(store, "Need a store instance to initialize")
        leap_assert_type(store, Soledad)

        # TODO assert too that the name matches the user/uuid with which
        # soledad has been initialized. Although afaik soledad doesn't know
        # about user_id, only the client backend.

        self.user_id = user_id
        self.account = Account(store, user_id, ready_cb=lambda: d.callback(self))
Ejemplo n.º 40
0
    def put_raw_key(self, key_data, address):
        """
        Put key contained in C{key_data} in local storage.

        :param key_data: The key data to be stored.
        :type key_data: str or unicode
        :param address: address for which this key will be active
        :type address: str

        :return: A Deferred which fires when the OpenPGPKey is in the storage.
        :rtype: Deferred
        """
        leap_assert_type(key_data, (str, unicode))

        openpgp_privkey = None
        try:
            openpgp_pubkey, openpgp_privkey = self.parse_key(key_data, address)
        except (errors.KeyAddressMismatch, errors.KeyFingerprintMismatch) as e:
            return defer.fail(e)

        def put_key(_, key):
            return self.put_key(key)

        d = defer.succeed(None)
        if openpgp_pubkey is not None:
            d.addCallback(put_key, openpgp_pubkey)
        if openpgp_privkey is not None:
            d.addCallback(put_key, openpgp_privkey)
        return d
Ejemplo n.º 41
0
    def addCallbackChain(self, callbacks):
        """
        Creates a callback/errback chain on another thread using
        deferToThread and adds the _gui_errback to the end to notify
        the GUI on an error.

        :param callbacks: List of tuples of callbacks and the signal
                          associated to that callback
        :type callbacks: list(tuple(func, func))

        :returns: the defer with the callback chain
        :rtype: deferred
        """
        leap_assert_type(callbacks, list)

        self._signal_to_emit = None
        self._err_msg = None

        d = None
        for cb, sig in callbacks:
            if d is None:
                d = threads.deferToThread(cb)
            else:
                d.addCallback(partial(self._callback_threader, cb))
            d.addErrback(self._errback, signal=sig)
            d.addCallback(self._gui_notify, signal=sig)
        d.addErrback(self._gui_errback)
        return d
Ejemplo n.º 42
0
    def _wait_for_indexes(self):
        """
        Make the marked methods 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)
        """
        leap_assert_type(self.wait_for_indexes, list)
        methods = self.wait_for_indexes

        self.waiting = []
        self.stored = {}

        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))
Ejemplo n.º 43
0
 def __init__(self, connection):
     """
     :param connection: an instance of a concrete LEAPConnection
                        we will be building a state machine for.
     :type connection: AbstractLEAPConnection
     """
     self._conn = connection
     leap_assert_type(self._conn, connections.AbstractLEAPConnection)
Ejemplo n.º 44
0
    def parse_ascii_key(self, key_data):
        """
        Parses an ascii armored key (or key pair) data and returns
        the OpenPGPKey keys.

        :param key_data: the key data to be parsed.
        :type key_data: str or unicode

        :returns: the public key and private key (if applies) for that data.
        :rtype: (public, private) -> tuple(OpenPGPKey, OpenPGPKey)
                the tuple may have one or both components None
        """
        leap_assert_type(key_data, (str, unicode))
        # TODO: add more checks for correct key data.
        leap_assert(key_data is not None, 'Data does not represent a key.')
        mail_regex = '.*<([\w.-]+@[\w.-]+)>.*'

        with self._temporary_gpgwrapper() as gpg:
            # TODO: inspect result, or use decorator
            gpg.import_keys(key_data)
            privkey = None
            pubkey = None

            try:
                privkey = gpg.list_keys(secret=True).pop()
            except IndexError:
                pass
            pubkey = gpg.list_keys(secret=False).pop()  # unitary keyring

            # extract adress from first uid on key
            match = re.match(mail_regex, pubkey['uids'].pop())
            leap_assert(match is not None, 'No user address in key data.')
            address = match.group(1)

            openpgp_privkey = None
            if privkey is not None:
                match = re.match(mail_regex, privkey['uids'].pop())
                leap_assert(match is not None, 'No user address in key data.')
                privaddress = match.group(1)

                # build private key
                openpgp_privkey = _build_key_from_gpg(
                    privaddress, privkey,
                    gpg.export_keys(privkey['fingerprint'], secret=True))

                leap_check(address == privaddress,
                           'Addresses in public and private key differ.',
                           errors.KeyAddressMismatch)
                leap_check(pubkey['fingerprint'] == privkey['fingerprint'],
                           'Fingerprints for public and private key differ.',
                           errors.KeyFingerprintMismatch)

            # build public key
            openpgp_pubkey = _build_key_from_gpg(
                address, pubkey,
                gpg.export_keys(pubkey['fingerprint'], secret=False))

            return (openpgp_pubkey, openpgp_privkey)
Ejemplo n.º 45
0
    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
Ejemplo n.º 46
0
    def set_action_mail_status(self, action_mail_status):
        """
        Sets the action_mail_status to use.

        :param action_mail_status: action_mail_status to be used
        :type action_mail_status: QtGui.QAction
        """
        leap_assert_type(action_mail_status, QtGui.QAction)
        self._action_mail_status = action_mail_status
Ejemplo n.º 47
0
    def set_action_mail_status(self, action_mail_status):
        """
        Sets the action_mail_status to use.

        :param action_mail_status: action_mail_status to be used
        :type action_mail_status: QtGui.QAction
        """
        leap_assert_type(action_mail_status, QtGui.QAction)
        self._action_mail_status = action_mail_status
Ejemplo n.º 48
0
    def set_autostart_eip(self, autostart):
        """
        Sets whether the app should autostart EIP.

        :param autostart: True if we should try to autostart EIP.
        :type autostart: bool
        """
        leap_assert_type(autostart, bool)
        self._settings.setValue(self.AUTOSTARTEIP_KEY, autostart)
Ejemplo n.º 49
0
    def set_eip_status_menu(self, eip_status_menu):
        """
        Sets the eip_status_menu to use.

        :param eip_status_menu: eip_status_menu to be used
        :type eip_status_menu: QtGui.QMenu
        """
        leap_assert_type(eip_status_menu, QtGui.QMenu)
        self._eip_status_menu = eip_status_menu
Ejemplo n.º 50
0
    def set_alert_missing_scripts(self, value):
        """
        Sets the setting for alerting of missing up/down scripts.

        :param value: the value to set
        :type value: bool
        """
        leap_assert_type(value, bool)
        self._settings.setValue(self.ALERTMISSING_KEY, value)
Ejemplo n.º 51
0
    def set_skip_first_run(self, skip):
        """
        Gets the setting for skip the first run wizard.

        :param skip: if the first run wizard should be skipped or not
        :type skip: bool
        """
        leap_assert_type(skip, bool)
        self._settings.setValue(self.SKIPFIRSTRUN_KEY, skip)
Ejemplo n.º 52
0
    def set_skip_first_run(self, skip):
        """
        Gets the setting for skip the first run wizard.

        :param skip: if the first run wizard should be skipped or not
        :type skip: bool
        """
        leap_assert_type(skip, bool)
        self._settings.setValue(self.SKIPFIRSTRUN_KEY, skip)
Ejemplo n.º 53
0
 def set_flags(self, flags):
     # TODO serialize the get + update
     if flags is None:
         flags = tuple()
     leap_assert_type(flags, tuple)
     self.fdoc.flags = list(flags)
     self.fdoc.deleted = "\\Deleted" in flags
     self.fdoc.seen = "\\Seen" in flags
     self.fdoc.recent = "\\Recent" in flags
Ejemplo n.º 54
0
    def set_eip_status_menu(self, eip_status_menu):
        """
        Sets the eip_status_menu to use.

        :param eip_status_menu: eip_status_menu to be used
        :type eip_status_menu: QtGui.QMenu
        """
        leap_assert_type(eip_status_menu, QtGui.QMenu)
        self._eip_status_menu = eip_status_menu
Ejemplo n.º 55
0
    def set_alert_missing_scripts(self, value):
        """
        Sets the setting for alerting of missing up/down scripts.

        :param value: the value to set
        :type value: bool
        """
        leap_assert_type(value, bool)
        self._settings.setValue(self.ALERTMISSING_KEY, value)
Ejemplo n.º 56
0
 def set_flags(self, flags):
     # TODO serialize the get + update
     if flags is None:
         flags = tuple()
     leap_assert_type(flags, tuple)
     self.fdoc.flags = list(flags)
     self.fdoc.deleted = "\\Deleted" in flags
     self.fdoc.seen = "\\Seen" in flags
     self.fdoc.recent = "\\Recent" in flags
Ejemplo n.º 57
0
    def set_remember(self, remember):
        """
        Sets wheter the app should remember username and password

        :param remember: True if the app should remember username and
            password, False otherwise
        :rtype: bool
        """
        leap_assert_type(remember, bool)
        self._settings.setValue(self.REMEMBER_KEY, remember)
Ejemplo n.º 58
0
    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
        """
        leap_assert_type(key, OpenPGPKey)

        def delete_docs(activedocs):
            deferreds = []
            for doc in activedocs:
                d = self._soledad.delete_doc(doc)
                deferreds.append(d)
            return defer.gatherResults(deferreds)

        def get_key_docs(_):
            return self._soledad.get_from_index(
                TYPE_FINGERPRINT_PRIVATE_INDEX,
                self.KEY_TYPE,
                key.fingerprint,
                '1' if key.private else '0')

        def delete_key(docs):
            if len(docs) == 0:
                raise errors.KeyNotFound(key)
            elif len(docs) > 1:
                logger.warn("There is more than one key for fingerprint %s"
                            % key.fingerprint)

            has_deleted = False
            deferreds = []
            for doc in docs:
                if doc.content['fingerprint'] == key.fingerprint:
                    d = self._soledad.delete_doc(doc)
                    deferreds.append(d)
                    has_deleted = True
            if not has_deleted:
                raise errors.KeyNotFound(key)
            return defer.gatherResults(deferreds)

        d = self._soledad.get_from_index(
            TYPE_FINGERPRINT_PRIVATE_INDEX,
            self.ACTIVE_TYPE,
            key.fingerprint,
            '1' if key.private else '0')
        d.addCallback(delete_docs)
        d.addCallback(get_key_docs)
        d.addCallback(delete_key)
        return d