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