def signContent(self, content, key, password='', mode=None): """See IGPGHandler.""" if not isinstance(content, str): raise TypeError('Content should be a string.') if mode is None: mode = gpgme.SIG_MODE_CLEAR # Find the key and make it the only one allowed to sign content # during this session. context = get_gpgme_context() context.signers = [removeSecurityProxy(key.key)] # Set up containers. plaintext = StringIO(content) signature = StringIO() # Make sure that gpg-agent doesn't interfere. if 'GPG_AGENT_INFO' in os.environ: del os.environ['GPG_AGENT_INFO'] def passphrase_cb(uid_hint, passphrase_info, prev_was_bad, fd): os.write(fd, '%s\n' % password) context.passphrase_cb = passphrase_cb # Sign the text. try: with gpgme_timeline("sign", key.fingerprint): context.sign(plaintext, signature, mode) except gpgme.GpgmeError: return None return signature.getvalue()
def decrypt_content(content, password): """Return the decrypted content or None if failed content and password must be traditional strings. It's up to the caller to encode or decode properly. :content: encrypted data content :password: unicode password to unlock the secret key in question """ if isinstance(password, unicode): raise TypeError('Password cannot be Unicode.') if isinstance(content, unicode): raise TypeError('Content cannot be Unicode.') ctx = get_gpgme_context() # setup containers cipher = StringIO(content) plain = StringIO() def passphrase_cb(uid_hint, passphrase_info, prev_was_bad, fd): os.write(fd, '%s\n' % password) ctx.passphrase_cb = passphrase_cb # Do the decryption. try: ctx.decrypt(cipher, plain) except gpgme.GpgmeError: return None return plain.getvalue()
def generateKey(self, name): """See `IGPGHandler`.""" context = get_gpgme_context() # Make sure that gpg-agent doesn't interfere. if 'GPG_AGENT_INFO' in os.environ: del os.environ['GPG_AGENT_INFO'] # Only 'utf-8' encoding is supported by gpgme. # See more information at: # http://pyme.sourceforge.net/doc/gpgme/Generating-Keys.html with gpgme_timeline("genkey", name): result = context.genkey(signing_only_param % {'name': name.encode('utf-8')}) # Right, it might seem paranoid to have this many assertions, # but we have to take key generation very seriously. assert result.primary, 'Secret key generation failed.' assert not result.sub, ( 'Only sign-only RSA keys are safe to be generated') secret_keys = list(self.localKeys(result.fpr, secret=True)) assert len(secret_keys) == 1, 'Found %d secret GPG keys for %s' % ( len(secret_keys), result.fpr) key = secret_keys[0] assert key.fingerprint == result.fpr, ( 'The key in the local keyring does not match the one generated.') assert key.exists_in_local_keyring, ( 'The key does not seem to exist in the local keyring.') return key
def importSecretKey(self, content): """See `IGPGHandler`.""" assert isinstance(content, str) # Make sure that gpg-agent doesn't interfere. if 'GPG_AGENT_INFO' in os.environ: del os.environ['GPG_AGENT_INFO'] context = get_gpgme_context() newkey = StringIO(content) with gpgme_timeline("import", "new secret key"): import_result = context.import_(newkey) secret_imports = [ fingerprint for fingerprint, result, status in import_result.imports if status & gpgme.IMPORT_SECRET ] if len(secret_imports) != 1: raise MoreThanOneGPGKeyFound( 'Found %d secret GPG keys when importing %s' % (len(secret_imports), content)) fingerprint, result, status = import_result.imports[0] try: key = context.get_key(fingerprint, True) except gpgme.GpgmeError: return None key = PymeKey.newFromGpgmeKey(key) assert key.exists_in_local_keyring return key
def importPublicKey(self, content): """See IGPGHandler.""" assert isinstance(content, str) context = get_gpgme_context() newkey = StringIO(content) with gpgme_timeline("import", "new public key"): result = context.import_(newkey) if len(result.imports) == 0: raise GPGKeyNotFoundError(content) # Check the status of all imported keys to see if any of them is # a secret key. We can't rely on result.secret_imported here # because if there's a secret key which is already imported, # result.secret_imported will be 0. for fingerprint, res, status in result.imports: if status & gpgme.IMPORT_SECRET != 0: raise SecretGPGKeyImportDetected( "GPG key '%s' is a secret key." % fingerprint) if len(result.imports) > 1: raise MoreThanOneGPGKeyFound( 'Found %d GPG keys when importing %s' % (len(result.imports), content)) fingerprint, res, status = result.imports[0] key = PymeKey(fingerprint) assert key.exists_in_local_keyring return key
def _buildFromFingerprint(self, fingerprint): """Build key information from a fingerprint.""" context = get_gpgme_context() # retrive additional key information try: with gpgme_timeline("get-key", fingerprint): key = context.get_key(fingerprint, False) except gpgme.GpgmeError: key = None if key and valid_fingerprint(key.subkeys[0].fpr): self._buildFromGpgmeKey(key)
def localKeys(self, filter=None, secret=False): """Get an iterator of the keys this gpg handler already knows about. """ ctx = get_gpgme_context() # XXX michaeln 2010-05-07 bug=576405 # Currently gpgme.Context().keylist fails if passed a unicode # string even though that's what is returned for fingerprints. if type(filter) == unicode: filter = filter.encode('utf-8') with gpgme_timeline("keylist", "filter: %r, secret: %r" % (filter, secret)): for key in ctx.keylist(filter, secret): yield PymeKey.newFromGpgmeKey(key)
def getVerifiedSignature(self, content, signature=None): """See IGPGHandler.""" assert not isinstance(content, unicode) assert not isinstance(signature, unicode) ctx = get_gpgme_context() # We may not yet have the public key, so find out the fingerprint we # need to fetch. _, sig = self._rawVerifySignature(ctx, content, signature=signature) # Fetch the full key from the keyserver now that we know its # fingerprint, and then verify the signature again. (This also lets # us support subkeys by using the master key fingerprint.) # XXX cjwatson 2019-03-12: Before GnuPG 2.2.7 and GPGME 1.11.0, # sig.fpr is a 64-bit key ID in the case where the key isn't in the # local keyring yet. I haven't yet heard of 64-bit key ID # collisions in the wild, but even if they happen here, # importPublicKey will raise MoreThanOneGPGKeyFound, so the worst # consequence is a denial of service for the owner of an affected # key. If we do run into this, then the correct fix is to upgrade # GnuPG and GPGME. key = self.retrieveKey(sig.fpr) plain, sig = self._rawVerifySignature(ctx, content, signature=signature) expired = False # sig.status == 0 means "Ok" if sig.status is not None: if sig.status.code == gpgme.ERR_KEY_EXPIRED: expired = True else: raise GPGVerificationError(sig.status.args) if expired: # This should already be set, but let's make sure. key.expired = True raise GPGKeyExpired(key) # return the signature container return PymeSignature(fingerprint=key.fingerprint, plain_data=plain.getvalue(), timestamp=sig.timestamp)
def export(self): """See `IPymeKey`.""" if self.secret: # XXX cprov 20081014: gpgme_op_export() only supports public keys. # See http://www.fifi.org/cgi-bin/info2www?(gpgme)Exporting+Keys p = subprocess.Popen([ get_gpg_path(), '--export-secret-keys', '-a', self.fingerprint ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) return p.stdout.read() context = get_gpgme_context() keydata = StringIO() with gpgme_timeline("export", self.fingerprint): context.export(self.fingerprint.encode('ascii'), keydata) return keydata.getvalue()
def retrieveKey(self, fingerprint): """See IGPGHandler.""" # XXX cprov 2005-07-05: # Integrate it with the furure proposal related # synchronization of the local key ring with the # global one. It should basically consists of be # aware of a revoked flag coming from the global # key ring, but it needs "specing" key = PymeKey(fingerprint.encode('ascii')) if not key.exists_in_local_keyring: pubkey = self._getPubKey(fingerprint) key = self.importPublicKey(pubkey) if not key.matches(fingerprint): ctx = get_gpgme_context() with gpgme_timeline("delete", key.fingerprint): ctx.delete(key.key) raise GPGKeyMismatchOnServer(fingerprint, key.fingerprint) return key
def encryptContent(self, content, key): """See IGPGHandler.""" if isinstance(content, unicode): raise TypeError('Content cannot be Unicode.') ctx = get_gpgme_context() # setup containers plain = StringIO(content) cipher = StringIO() if key.key is None: return None if not key.can_encrypt: raise ValueError('key %s can not be used for encryption' % key.fingerprint) # encrypt content with gpgme_timeline("encrypt", key.fingerprint): ctx.encrypt([removeSecurityProxy(key.key)], gpgme.ENCRYPT_ALWAYS_TRUST, plain, cipher) return cipher.getvalue()