summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Kahn Gillmor <dkg@fifthhorseman.net>2016-11-30 02:30:44 -0500
committerDylan Baker <dylan@pnwbakers.com>2017-08-14 09:30:34 -0700
commitb0e2f322aa571a5e1999c069779f589e282a566c (patch)
treed5b905ff596a045ef4c8e5c9540772218b026230
parentc377ee5bd6e2b64be8bbdd5df72ac3ca50373134 (diff)
convert from pygpgme to the python "gpg" module
This converts from the now abandoned pygpgme project for wrapping gpgme, to the upstream gpgme python bindings (which are descended from the pyme project, before they became official). Largely this change should not be user visible, but there are a couple cases where the new bindings provide slightly more detailed error messages, and alot directly presents those messages to users. This patch has been significantly revised and updated by Dylan Baker, but was originally authored by Daniel Kahn Gillmor. Fixes #1069
-rw-r--r--.travis.yml2
-rw-r--r--NEWS1
-rw-r--r--alot/commands/utils.py2
-rw-r--r--alot/crypto.py236
-rw-r--r--alot/db/envelope.py9
-rw-r--r--alot/db/utils.py2
-rw-r--r--alot/errors.py2
-rw-r--r--alot/utils/configobj.py2
-rw-r--r--docs/source/conf.py2
-rw-r--r--docs/source/installation.rst6
-rwxr-xr-xsetup.py2
-rw-r--r--tests/crypto_test.py88
-rw-r--r--tests/utilities.py6
13 files changed, 190 insertions, 170 deletions
diff --git a/.travis.yml b/.travis.yml
index cb863a5d..1436aec8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -123,7 +123,7 @@ install:
# alot when rebuilding the documentation. Notmuch would have to be
# installed by hand in order to get a recent enough version on travis.
printf '%s = None\n' NotmuchError NullPointerError > notmuch.py
- touch gpgme.py
+ touch gpg.py
# install sphinx for building the html docs
pip install sphinx
else
diff --git a/NEWS b/NEWS
index a919a4ed..ffd6b09e 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,7 @@ next:
* bug fix: GPG signatures are acutally verified
* feature: option to use linewise focussing in thread mode
* feature: add support to move to next or previous message matching a notmuch query in a thread buffer
+* feature: Convert from deprecated pygppme module to upstream gpg wrappers
0.5:
* save command prompt, recipient and sender history across program restarts
diff --git a/alot/commands/utils.py b/alot/commands/utils.py
index f8e5067d..ddfeec41 100644
--- a/alot/commands/utils.py
+++ b/alot/commands/utils.py
@@ -66,7 +66,7 @@ def _get_keys(ui, encrypt_keyids, block_error=False, signed_only=False):
to the key)
:type signed_only: bool
:returns: the available keys indexed by their key hash
- :rtype: dict(str->gpgme.Key)
+ :rtype: dict(str->gpg key object)
"""
keys = {}
diff --git a/alot/crypto.py b/alot/crypto.py
index 4da32eea..3700aeef 100644
--- a/alot/crypto.py
+++ b/alot/crypto.py
@@ -1,43 +1,13 @@
+# encoding=utf-8
# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
+# Copyright © 2017 Dylan Baker <dylan@pnwbakers.com>
# This file is released under the GNU GPL, version 3 or a later revision.
# For further details see the COPYING file
from __future__ import absolute_import
-import os
-from cStringIO import StringIO
-import gpgme
-from .errors import GPGProblem, GPGCode
-
-
-def _hash_algo_name(hash_algo):
- """
- Re-implements GPGME's hash_algo_name as long as pygpgme doesn't wrap that
- function.
+import gpg
- :param hash_algo: GPGME hash_algo
- :rtype: str
- """
- mapping = {
- gpgme.MD_MD5: "MD5",
- gpgme.MD_SHA1: "SHA1",
- gpgme.MD_RMD160: "RIPEMD160",
- gpgme.MD_MD2: "MD2",
- gpgme.MD_TIGER: "TIGER192",
- gpgme.MD_HAVAL: "HAVAL",
- gpgme.MD_SHA256: "SHA256",
- gpgme.MD_SHA384: "SHA384",
- gpgme.MD_SHA512: "SHA512",
- gpgme.MD_MD4: "MD4",
- gpgme.MD_CRC32: "CRC32",
- gpgme.MD_CRC32_RFC1510: "CRC32RFC1510",
- gpgme.MD_CRC24_RFC2440: "CRC24RFC2440",
- }
- if hash_algo in mapping:
- return mapping[hash_algo]
- else:
- raise GPGProblem(("Invalid hash_algo passed to hash_algo_name."
- " Please report this as a bug in alot."),
- code=GPGCode.INVALID_HASH)
+from .errors import GPGProblem, GPGCode
def RFC3156_micalg_from_algo(hash_algo):
@@ -47,12 +17,16 @@ def RFC3156_micalg_from_algo(hash_algo):
GPGME returns hash algorithm names such as "SHA256", but RFC3156 says that
programs need to use names such as "pgp-sha256" instead.
- :param hash_algo: GPGME hash_algo
+ :param str hash_algo: GPGME hash_algo
+ :returns: the lowercase name of of the algorithm with "pgp-" prepended
:rtype: str
"""
# hash_algo will be something like SHA256, but we need pgp-sha256.
- hash_algo = _hash_algo_name(hash_algo)
- return 'pgp-' + hash_algo.lower()
+ algo = gpg.core.hash_algo_name(hash_algo)
+ if algo is None:
+ raise GPGProblem('Unknown hash algorithm {}'.format(algo),
+ code=GPGCode.INVALID_HASH_ALGORITHM)
+ return 'pgp-' + algo.lower()
def get_key(keyid, validate=False, encrypt=False, sign=False,
@@ -81,69 +55,94 @@ def get_key(keyid, validate=False, encrypt=False, sign=False,
:param signed_only: only return keys whose uid is signed (trusted to
belong to the key)
:type signed_only: bool
- :rtype: gpgme.Key
+ :returns: A gpg key matching the given parameters
+ :rtype: gpg.gpgme._gpgme_key
+ :raises ~alot.errors.GPGProblem: if the keyid is ambiguous
+ :raises ~alot.errors.GPGProblem: if there is no key that matches the
+ parameters
+ :raises ~alot.errors.GPGProblem: if a key is found, but signed_only is true
+ and the key is unused
"""
- ctx = gpgme.Context()
+ ctx = gpg.core.Context()
try:
key = ctx.get_key(keyid)
if validate:
validate_key(key, encrypt=encrypt, sign=sign)
- except gpgme.GpgmeError as e:
- if e.code == gpgme.ERR_AMBIGUOUS_NAME:
+ except gpg.errors.KeyNotFound:
+ raise GPGProblem('Cannot find key for "{}".'.format(keyid),
+ code=GPGCode.NOT_FOUND)
+ except gpg.errors.GPGMEError as e:
+ if e.getcode() == gpg.errors.AMBIGUOUS_NAME:
# When we get here it means there were multiple keys returned by
# gpg for given keyid. Unfortunately gpgme returns invalid and
# expired keys together with valid keys. If only one key is valid
# for given operation maybe we can still return it instead of
# raising exception
- keys = list_keys(hint=keyid)
+
valid_key = None
- for k in keys:
- try:
- validate_key(k, encrypt=encrypt, sign=sign)
- except GPGProblem:
- # if the key is invalid for given action skip it
- continue
-
- if valid_key:
- # we have already found one valid key and now we find
- # another? We really received an ambiguous keyid
+
+ # Catching exceptions for list_keys
+ try:
+ for k in list_keys(hint=keyid):
+ try:
+ validate_key(k, encrypt=encrypt, sign=sign)
+ except GPGProblem:
+ # if the key is invalid for given action skip it
+ continue
+
+ if valid_key:
+ # we have already found one valid key and now we find
+ # another? We really received an ambiguous keyid
+ raise GPGProblem(
+ "More than one key found matching this filter. "
+ "Please be more specific "
+ "(use a key ID like 4AC8EE1D).",
+ code=GPGCode.AMBIGUOUS_NAME)
+ valid_key = k
+ except gpg.errors.GPGMEError as e:
+ # This if will be triggered if there is no key matching at all.
+ if e.getcode() == gpg.errors.AMBIGUOUS_NAME:
raise GPGProblem(
- "More than one key found matching this filter. Please "
- "be more specific (use a key ID like 4AC8EE1D).",
- code=GPGCode.AMBIGUOUS_NAME)
- valid_key = k
+ 'Can not find any key for "{}".'.format(keyid),
+ code=GPGCode.NOT_FOUND)
+ raise
if not valid_key:
# there were multiple keys found but none of them are valid for
# given action (we don't have private key, they are expired
- # etc)
+ # etc), or there was no key at all
raise GPGProblem(
'Can not find usable key for "{}".'.format(keyid),
code=GPGCode.NOT_FOUND)
return valid_key
- elif e.code == gpgme.ERR_INV_VALUE or e.code == gpgme.ERR_EOF:
- raise GPGProblem('Can not find key for "{}".'.format(keyid),
- code=GPGCode.NOT_FOUND)
+ elif e.getcode() == gpg.errors.INV_VALUE:
+ raise GPGProblem(
+ 'Can not find usable key for "{}".'.format(keyid),
+ code=GPGCode.NOT_FOUND)
else:
raise e
if signed_only and not check_uid_validity(key, keyid):
- raise GPGProblem('Can not find a trusworthy key for "{}".'.format(keyid),
+ raise GPGProblem('Cannot find a trusworthy key for "{}".'.format(keyid),
code=GPGCode.NOT_FOUND)
return key
def list_keys(hint=None, private=False):
"""
- Returns a iterator of all keys containing the fingerprint, or all keys if
+ Returns a generator of all keys containing the fingerprint, or all keys if
hint is None.
+ The generator may raise exceptions of :class:gpg.errors.GPGMEError, and it
+ is the caller's responsibility to handle them.
+
:param hint: Part of a fingerprint to usee to search
:type hint: str or None
:param private: Whether to return public keys or secret keys
:type private: bool
- :rtype: :class:`gpgme.KeyIter`
+ :returns: A generator that yields keys.
+ :rtype: Generator[gpg.gpgme.gpgme_key_t, None, None]
"""
- ctx = gpgme.Context()
+ ctx = gpg.core.Context()
return ctx.keylist(hint, private)
@@ -154,81 +153,69 @@ def detached_signature_for(plaintext_str, key=None):
A detached signature in GPG speak is a separate blob of data containing
a signature for the specified plaintext.
- :param plaintext_str: text to sign
- :param key: gpgme_key_t object representing the key to use
- :rtype: tuple of gpgme.NewSignature array and str
+ :param str plaintext_str: text to sign
+ :param key: key to sign with
+ :type key: gpg.gpgme._gpgme_key
+ :returns: A list of signature and the signed blob of data
+ :rtype: tuple[list[gpg.results.NewSignature], str]
"""
- ctx = gpgme.Context()
- ctx.armor = True
+ ctx = gpg.core.Context(armor=True)
if key is not None:
ctx.signers = [key]
- plaintext_data = StringIO(plaintext_str)
- signature_data = StringIO()
- sigs = ctx.sign(plaintext_data, signature_data, gpgme.SIG_MODE_DETACH)
- signature_data.seek(0, os.SEEK_SET)
- signature = signature_data.read()
- return sigs, signature
+ (sigblob, sign_result) = ctx.sign(plaintext_str, mode=gpg.constants.SIG_MODE_DETACH)
+ return sign_result.signatures, sigblob
def encrypt(plaintext_str, keys=None):
- """
- Encrypts the given plaintext string and returns a PGP/MIME compatible
- string
+ """Encrypt data and return the encrypted form.
- :param plaintext_str: the mail to encrypt
- :param key: gpgme_key_t object representing the key to use
- :rtype: a string holding the encrypted mail
+ :param str plaintext_str: the mail to encrypt
+ :param key: optionally, a list of keys to encrypt with
+ :type key: list[gpg.gpgme.gpgme_key_t] or None
+ :returns: encrypted mail
+ :rtype: str
"""
- plaintext_data = StringIO(plaintext_str)
- encrypted_data = StringIO()
- ctx = gpgme.Context()
- ctx.armor = True
- ctx.encrypt(keys, gpgme.ENCRYPT_ALWAYS_TRUST, plaintext_data,
- encrypted_data)
- encrypted_data.seek(0, os.SEEK_SET)
- encrypted = encrypted_data.read()
- return encrypted
+ ctx = gpg.core.Context(armor=True)
+ out = ctx.encrypt(plaintext_str, recipients=keys, always_trust=True)[0]
+ return out
def verify_detached(message, signature):
- '''Verifies whether the message is authentic by checking the
- signature.
+ """Verifies whether the message is authentic by checking the signature.
- :param message: the message as `str`
- :param signature: a `str` containing an OpenPGP signature
- :returns: a list of :class:`gpgme.Signature`
+ :param str message: The message to be verified, in canonical form.
+ :param str signature: the OpenPGP signature to verify
+ :returns: a list of signatures
+ :rtype: list[gpg.results.Signature]
:raises: :class:`~alot.errors.GPGProblem` if the verification fails
- '''
- message_data = StringIO(message)
- signature_data = StringIO(signature)
- ctx = gpgme.Context()
-
- status = ctx.verify(signature_data, message_data, None)
- if isinstance(status[0].status, gpgme.GpgmeError):
- raise GPGProblem(status[0].status.message, code=status[0].status.code)
- return status
+ """
+ ctx = gpg.core.Context()
+ try:
+ verify_results = ctx.verify(message, signature)[1]
+ return verify_results.signatures
+ except gpg.errors.BadSignatures as e:
+ raise GPGProblem(str(e), code=GPGCode.BAD_SIGNATURE)
+ except gpg.errors.GPGMEError as e:
+ raise GPGProblem(e.message, code=e.getcode())
def decrypt_verify(encrypted):
- '''Decrypts the given ciphertext string and returns both the
+ """Decrypts the given ciphertext string and returns both the
signatures (if any) and the plaintext.
- :param encrypted: the mail to decrypt
- :returns: a tuple (sigs, plaintext) with sigs being a list of a
- :class:`gpgme.Signature` and plaintext is a `str` holding
- the decrypted mail
+ :param str encrypted: the mail to decrypt
+ :returns: the signatures and decrypted plaintext data
+ :rtype: tuple[list[gpg.resuit.Signature], str]
:raises: :class:`~alot.errors.GPGProblem` if the decryption fails
- '''
- encrypted_data = StringIO(encrypted)
- plaintext_data = StringIO()
- ctx = gpgme.Context()
+ """
+ ctx = gpg.core.Context()
try:
- sigs = ctx.decrypt_verify(encrypted_data, plaintext_data)
- except gpgme.GpgmeError as e:
- raise GPGProblem(e.message, code=e.code)
+ (plaintext, _, verify_result) = ctx.decrypt(encrypted, verify=True)
+ except gpg.errors.GPGMEError as e:
+ raise GPGProblem(e.message, code=e.getcode())
+ # what if the signature is bad?
- plaintext_data.seek(0, os.SEEK_SET)
- return sigs, plaintext_data.read()
+ return verify_result.signatures, plaintext
def hash_key(key):
@@ -253,12 +240,16 @@ def validate_key(key, sign=False, encrypt=False):
signing or encrypting. Raise GPGProblem otherwise.
:param key: the GPG key to check
- :type key: gpgme.Key
+ :type key: gpg.gpgme._gpgme_key
:param sign: whether the key should be able to sign
:type sign: bool
:param encrypt: whether the key should be able to encrypt
:type encrypt: bool
-
+ :raises ~alot.errors.GPGProblem: If the key is revoked, expired, or invalid
+ :raises ~alot.errors.GPGProblem: If encrypt is true and the key cannot be
+ used to encrypt
+ :raises ~alot.errors.GPGProblem: If sign is true and th key cannot be used
+ to encrypt
"""
if key.revoked:
raise GPGProblem('The key "{}" is revoked.'.format(key.uids[0].uid),
@@ -285,16 +276,15 @@ def check_uid_validity(key, email):
email is assumed to belong to the key.
:param key: the GPG key to which the email should belong
- :type key: gpgme.Key
+ :type key: gpg.gpgme._gpgme_key
:param email: the email address that should belong to the key
:type email: str
:returns: whether the key can be assumed to belong to the given email
:rtype: bool
-
"""
for key_uid in key.uids:
if email == key_uid.email and not key_uid.revoked and \
not key_uid.invalid and \
- key_uid.validity >= gpgme.VALIDITY_FULL:
+ key_uid.validity >= gpg.constants.validity.FULL:
return True
return False
diff --git a/alot/db/envelope.py b/alot/db/envelope.py
index 0a6d4f58..3dc12278 100644
--- a/alot/db/envelope.py
+++ b/alot/db/envelope.py
@@ -13,8 +13,7 @@ from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
import email.charset as charset
-
-import gpgme
+import gpg
from .attachment import Attachment
from .utils import encode_header
@@ -197,8 +196,8 @@ class Envelope(object):
raise GPGProblem("Could not sign message (GPGME "
"did not return a signature)",
code=GPGCode.KEY_CANNOT_SIGN)
- except gpgme.GpgmeError as e:
- if e.code == gpgme.ERR_BAD_PASSPHRASE:
+ except gpg.errors.GPGMEError as e:
+ if e.getcode() == gpg.errors.BAD_PASSPHRASE:
# If GPG_AGENT_INFO is unset or empty, the user just does
# not have gpg-agent running (properly).
if os.environ.get('GPG_AGENT_INFO', '').strip() == '':
@@ -237,7 +236,7 @@ class Envelope(object):
try:
encrypted_str = crypto.encrypt(plaintext,
self.encrypt_keys.values())
- except gpgme.GpgmeError as e:
+ except gpg.errors.GPGMEError as e:
raise GPGProblem(str(e), code=GPGCode.KEY_CANNOT_ENCRYPT)
outer_msg = MIMEMultipart('encrypted',
diff --git a/alot/db/utils.py b/alot/db/utils.py
index 3cff108a..994e85b2 100644
--- a/alot/db/utils.py
+++ b/alot/db/utils.py
@@ -35,7 +35,7 @@ def add_signature_headers(mail, sigs, error_msg):
verification was successful.
:param mail: :class:`email.message.Message` the message to entitle
- :param sigs: list of :class:`gpgme.Signature`
+ :param sigs: list of :class:`gpg.results.Signature`
:param error_msg: `str` containing an error message, the empty
string indicating no error
'''
diff --git a/alot/errors.py b/alot/errors.py
index 9860f411..c312e674 100644
--- a/alot/errors.py
+++ b/alot/errors.py
@@ -13,6 +13,8 @@ class GPGCode(object):
KEY_CANNOT_ENCRYPT = 7
KEY_CANNOT_SIGN = 8
INVALID_HASH = 9
+ INVALID_HASH_ALGORITHM = 10
+ BAD_SIGNATURE = 11
class GPGProblem(Exception):
diff --git a/alot/utils/configobj.py b/alot/utils/configobj.py
index aba61c3d..5e04409a 100644
--- a/alot/utils/configobj.py
+++ b/alot/utils/configobj.py
@@ -136,7 +136,7 @@ def force_list(value, min=None, max=None):
def gpg_key(value):
"""
test if value points to a known gpg key
- and return that key as :class:`pyme.pygpgme._gpgme_key`.
+ and return that key as a gpg key object.
"""
try:
return crypto.get_key(value)
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 5ebab550..e096cc89 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -33,7 +33,7 @@ MOCK_MODULES = ['twisted', 'twisted.internet',
'urwid',
'urwidtrees',
'magic',
- 'gpgme',
+ 'gpg',
'configobj',
'validate',
'argparse']
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index 2110315b..170c384b 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -13,7 +13,7 @@ A full list of dependencies is below:
* `libnotmuch <http://notmuchmail.org/>`_ and it's python bindings, ≥ `0.13`
* `urwid <http://excess.org/urwid/>`_ toolkit, ≥ `1.1.0`
* `urwidtrees <https://github.com/pazz/urwidtrees>`_, ≥ `1.0`
-* `PyGPGME <https://launchpad.net/pygpgme>`_ ≥ `0.2`
+* `gpg <https://pypi.python.org/pypi/gpg>`_
.. note:: urwidtrees was only recently detached from alot and is not widely
available as a separate package. You can install it e.g., via
@@ -26,11 +26,11 @@ A full list of dependencies is below:
On debian/ubuntu the rest are packaged as::
- python-setuptools python-magic python-configobj python-twisted python-notmuch python-urwid python-gpgme
+ python-setuptools python-magic python-configobj python-twisted python-notmuch python-urwid python-gpg
On fedora/redhat these are packaged as::
- python-setuptools python-magic python-configobj python-twisted python-notmuch python-urwid pygpgme
+ python-setuptools python-magic python-configobj python-twisted python-notmuch python-urwid python-gpg
Alot uses `mailcap <http://en.wikipedia.org/wiki/Mailcap>`_ to look up mime-handler for inline
rendering and opening of attachments. For a full description of the maicap protocol consider the
diff --git a/setup.py b/setup.py
index ecfb33af..7df797f2 100755
--- a/setup.py
+++ b/setup.py
@@ -32,7 +32,7 @@ setup(name='alot',
'twisted>=10.2.0',
'python-magic',
'configobj>=4.7.0',
- 'pygpgme>=0.2'],
+ 'gpg'],
tests_require=[
'mock',
],
diff --git a/tests/crypto_test.py b/tests/crypto_test.py
index e51adaeb..1ee5404f 100644
--- a/tests/crypto_test.py
+++ b/tests/crypto_test.py
@@ -10,7 +10,7 @@ import subprocess
import tempfile
import unittest
-import gpgme
+import gpg
import mock
from alot import crypto
@@ -43,15 +43,13 @@ def setUpModule():
mock_home.start()
MOD_CLEAN.add_cleanup(mock_home.stop)
- ctx = gpgme.Context()
- ctx.armor = True
-
- # Add the public and private keys. They have no password
- search_dir = os.path.join(os.path.dirname(__file__), 'static/gpg-keys')
- for each in os.listdir(search_dir):
- if os.path.splitext(each)[1] == '.gpg':
- with open(os.path.join(search_dir, each)) as f:
- ctx.import_(f)
+ with gpg.core.Context(armor=True) as ctx:
+ # Add the public and private keys. They have no password
+ search_dir = os.path.join(os.path.dirname(__file__), 'static/gpg-keys')
+ for each in os.listdir(search_dir):
+ if os.path.splitext(each)[1] == '.gpg':
+ with open(os.path.join(search_dir, each)) as f:
+ ctx.op_import(f)
@MOD_CLEAN.wrap_teardown
@@ -66,29 +64,54 @@ def tearDownModule():
os.kill(int(pid), signal.SIGKILL)
+def make_key(revoked=False, expired=False, invalid=False, can_encrypt=True,
+ can_sign=True):
+ # This is ugly
+ mock_key = mock.create_autospec(gpg._gpgme._gpgme_key)
+ mock_key.uids = [mock.Mock(uid=u'mocked')]
+ mock_key.revoked = revoked
+ mock_key.expired = expired
+ mock_key.invalid = invalid
+ mock_key.can_encrypt = can_encrypt
+ mock_key.can_sign = can_sign
+
+ return mock_key
+
+
+def make_uid(email, revoked=False, invalid=False,
+ validity=gpg.constants.validity.FULL):
+ uid = mock.Mock()
+ uid.email = email
+ uid.revoked = revoked
+ uid.invalid = invalid
+ uid.validity = validity
+
+ return uid
+
+
class TestHashAlgorithmHelper(unittest.TestCase):
"""Test cases for the helper function RFC3156_canonicalize."""
def test_returned_string_starts_with_pgp(self):
- result = crypto.RFC3156_micalg_from_algo(gpgme.MD_MD5)
+ result = crypto.RFC3156_micalg_from_algo(gpg.constants.md.MD5)
self.assertTrue(result.startswith('pgp-'))
def test_returned_string_is_lower_case(self):
- result = crypto.RFC3156_micalg_from_algo(gpgme.MD_MD5)
+ result = crypto.RFC3156_micalg_from_algo(gpg.constants.md.MD5)
self.assertTrue(result.islower())
def test_raises_for_unknown_hash_name(self):
with self.assertRaises(GPGProblem):
- crypto.RFC3156_micalg_from_algo(gpgme.MD_NONE)
+ crypto.RFC3156_micalg_from_algo(gpg.constants.md.NONE)
class TestDetachedSignatureFor(unittest.TestCase):
def test_valid_signature_generated(self):
- ctx = gpgme.Context()
to_sign = "this is some text.\nit is more than nothing.\n"
- _, detached = crypto.detached_signature_for(to_sign, ctx.get_key(FPR))
+ with gpg.core.Context() as ctx:
+ _, detached = crypto.detached_signature_for(to_sign, ctx.get_key(FPR))
with tempfile.NamedTemporaryFile(delete=False) as f:
f.write(detached)
@@ -108,9 +131,9 @@ class TestDetachedSignatureFor(unittest.TestCase):
class TestVerifyDetached(unittest.TestCase):
def test_verify_signature_good(self):
- ctx = gpgme.Context()
to_sign = "this is some text.\nIt's something\n."
- _, detached = crypto.detached_signature_for(to_sign, ctx.get_key(FPR))
+ with gpg.core.Context() as ctx:
+ _, detached = crypto.detached_signature_for(to_sign, ctx.get_key(FPR))
try:
crypto.verify_detached(to_sign, detached)
@@ -118,10 +141,10 @@ class TestVerifyDetached(unittest.TestCase):
raise AssertionError
def test_verify_signature_bad(self):
- ctx = gpgme.Context()
to_sign = "this is some text.\nIt's something\n."
similar = "this is some text.\r\n.It's something\r\n."
- _, detached = crypto.detached_signature_for(to_sign, ctx.get_key(FPR))
+ with gpg.core.Context() as ctx:
+ _, detached = crypto.detached_signature_for(to_sign, ctx.get_key(FPR))
with self.assertRaises(GPGProblem):
crypto.verify_detached(similar, detached)
@@ -218,7 +241,7 @@ class TestCheckUIDValidity(unittest.TestCase):
key = utilities.make_key()
key.uids[0] = utilities.make_uid(
mock.sentinel.EMAIL,
- validity=gpgme.VALIDITY_UNDEFINED)
+ validity=gpg.constants.validity.UNDEFINED)
ret = crypto.check_uid_validity(key, mock.sentinel.EMAIL)
self.assertFalse(ret)
@@ -251,16 +274,16 @@ class TestGetKey(unittest.TestCase):
def test_plain(self):
# Test the uid of the only identity attached to the key we generated.
- ctx = gpgme.Context()
- expected = ctx.get_key(FPR).uids[0].uid
+ with gpg.core.Context() as ctx:
+ expected = ctx.get_key(FPR).uids[0].uid
actual = crypto.get_key(FPR).uids[0].uid
self.assertEqual(expected, actual)
def test_validate(self):
# Since we already test validation we're only going to test validate
# once.
- ctx = gpgme.Context()
- expected = ctx.get_key(FPR).uids[0].uid
+ with gpg.core.Context() as ctx:
+ expected = ctx.get_key(FPR).uids[0].uid
actual = crypto.get_key(FPR, validate=True, encrypt=True, sign=True).uids[0].uid
self.assertEqual(expected, actual)
@@ -289,10 +312,15 @@ class TestGetKey(unittest.TestCase):
@staticmethod
def _context_mock():
- error = gpgme.GpgmeError()
- error.code = gpgme.ERR_AMBIGUOUS_NAME
+ class CustomError(gpg.errors.GPGMEError):
+ """A custom GPGMEError class that always has an errors code of
+ AMBIGUOUS_NAME.
+ """
+ def getcode(self):
+ return gpg.errors.AMBIGUOUS_NAME
+
context_mock = mock.Mock()
- context_mock.get_key = mock.Mock(side_effect=error)
+ context_mock.get_key = mock.Mock(side_effect=CustomError)
return context_mock
@@ -300,7 +328,7 @@ class TestGetKey(unittest.TestCase):
invalid_key = utilities.make_key(invalid=True)
valid_key = utilities.make_key()
- with mock.patch('alot.crypto.gpgme.Context',
+ with mock.patch('alot.crypto.gpg.core.Context',
mock.Mock(return_value=self._context_mock())), \
mock.patch('alot.crypto.list_keys',
mock.Mock(return_value=[valid_key, invalid_key])):
@@ -308,7 +336,7 @@ class TestGetKey(unittest.TestCase):
self.assertIs(key, valid_key)
def test_ambiguous_two_valid(self):
- with mock.patch('alot.crypto.gpgme.Context',
+ with mock.patch('alot.crypto.gpg.core.Context',
mock.Mock(return_value=self._context_mock())), \
mock.patch('alot.crypto.list_keys',
mock.Mock(return_value=[utilities.make_key(),
@@ -318,7 +346,7 @@ class TestGetKey(unittest.TestCase):
self.assertEqual(cm.exception.code, GPGCode.AMBIGUOUS_NAME)
def test_ambiguous_no_valid(self):
- with mock.patch('alot.crypto.gpgme.Context',
+ with mock.patch('alot.crypto.gpg.core.Context',
mock.Mock(return_value=self._context_mock())), \
mock.patch('alot.crypto.list_keys',
mock.Mock(return_value=[
diff --git a/tests/utilities.py b/tests/utilities.py
index 45433c99..402feb38 100644
--- a/tests/utilities.py
+++ b/tests/utilities.py
@@ -21,7 +21,7 @@ from __future__ import absolute_import
import functools
import unittest
-import gpgme
+import gpg
import mock
@@ -149,7 +149,7 @@ class ModuleCleanup(object):
def make_uid(email, uid=u'mocked', revoked=False, invalid=False,
- validity=gpgme.VALIDITY_FULL):
+ validity=gpg.constants.validity.FULL):
uid_ = mock.Mock()
uid_.email = email
uid_.uid = uid
@@ -162,7 +162,7 @@ def make_uid(email, uid=u'mocked', revoked=False, invalid=False,
def make_key(revoked=False, expired=False, invalid=False, can_encrypt=True,
can_sign=True):
- mock_key = mock.create_autospec(gpgme.Key)
+ mock_key = mock.Mock()
mock_key.uids = [make_uid(u'foo@example.com')]
mock_key.revoked = revoked
mock_key.expired = expired