Example #1
0
class GPGCrypt(object):
    """
    Wrapper around GPGME GPG encrypting/decrypting.
    """
    def __init__(self):
        # Gnome keyring already takes care of the key access if you're running
        # X. If you cancel the GPG popup, you get this callback instead.
        self.password_cb = self.default_password_cb
        # Init a GPG context.
        self.context = Context()
        self.context.passphrase_cb = self._password_cb  # wrap the other cb
        # No ascii armor stuff. We'll juggle some base64 around ourselves.
        self.context.armor = False

    def set_password_cb(self, password_cb):
        # Set password callback. The callback must return a (password,
        # allow_caching) tuple.
        self.password_cb = password_cb

    def generate_key(self, **kwargs):
        # <GnupgKeyParms format="internal">
        # (Sub)Key-Type: RSA
        # (Sub)Key-Length: 2048 (or 4096)
        # Name+Comment+Email "Alex Boonstra (TEST) <*****@*****.**>"
        # Expire-Date: None (or in a couple of years)
        # Passphrase: xxx
        # |
        # v
        # self.context.op_genkey(parms.encode('utf-8'), None, None)
        # result = self.context.op_genkey(parms.encode('utf-8'), None, None)
        msg = ('This takes too long. Generate they keys yourself. Look in '
               'docs/examples for an example')
        raise NotImplementedError(msg)

    def get_key(self, **kwargs):
        """
        Get the appropriate key to work with.

        Takes either an id= or an email= argument.

        TODO: if the key is not found, get it from server?
        """
        if (len(kwargs) != 1 or
            any(i not in ('id', 'email') for i in kwargs.keys())):
            raise TypeError('get_key takes either id= or email=')

        # Lookup by id.
        if 'id' in kwargs:
            # "If [PARAM2] is 1, only private keys will be returned."
            key = self.context.get_key(kwargs['id'], 0)
            # Make sure we have an e-mail to go by.
            email = unicode(key.uids[0].email.lower())

        # Lookup by email. E-mail returned by gpgme is in unicode. (Even
        # though it probably won't contain any non-ascii.)
        else:
            email = unicode(kwargs['email']).lower()
            found = []

            for key in self.context.keylist():
                for uid in key.uids:
                    if uid.email.lower() == email:
                        found.append(key)
                        if len(found) > 1:
                            msg = 'Cannot cope with more than one recipient'
                            raise NotImplementedError(msg)
            if not found:
                return None

            key = found[0]

        # Do checks on the key.
        if key.expired:
            raise CryptBadPubKey('%s key is expired' % (email,))
        elif key.invalid or key.revoked or key.disabled:
            raise CryptBadPubKey('%s key is invalid/revoked/diabled' %
                                 (email,))
        elif not key.can_encrypt:  # also set to false if e.g. expired
            raise CryptBadPubKey('%s key is unusable' % (email,))

        # Find right subkey and check if expiry is nigh.
        for subkey in key.subkeys:
            if not (subkey.expired or subkey.invalid or subkey.revoked or
                    subkey.disabled or not subkey.can_encrypt):
                break
        if subkey.expires and float(subkey.expires - time()) / 86400.0 < 200:
            # Send out a warning that this key is about to expires. I'm not
            # sure what the implications of expired keys are, but let's prepare
            # for the worst and warn the user at an early stage.
            print >>stderr, ('WARNING: (sub)key %s for %s will expire in '
                             '%.1f days' % (subkey.keyid, key.uids[0].email,
                             float(subkey.expires - time()) / 86400.0))

        # Ok. All is good.
        return key

    def import_key(self, key):
        to_import = BytesIO(str(key))  # ensure bytestring!
        res = self.context.import_(to_import)
        if (res.imported + res.unchanged) != 1:
            raise CryptError('import failed (imported=%d, unchanged=%d). '
                             'Try setting os.environ["GNUPGHOME"] to an '
                             '*existing* writable path in settings; '
                             'e.g. "/tmp"' % (res.imported, res.unchanged))

        try:
            key_id = get_pubkey_id_from_ascii(key)
            key = self.get_key(id=unicode(key_id))
        except Exception, e:
            raise CryptError('GPG quick hack failed; no key found', e)

        return key