aboutsummaryrefslogtreecommitdiff
path: root/src/mailman_pgp
diff options
context:
space:
mode:
authorJ08nY2017-08-01 22:53:15 +0200
committerJ08nY2017-08-02 01:35:10 +0200
commitdff7befbc5860f2f78f63ab694ef88d21a53771f (patch)
tree458e8004705eab2ca024da0cd21057cc2f5f56f3 /src/mailman_pgp
parent7c0aaf28767e494323557b6cb8fb6500df5822e5 (diff)
downloadmailman-pgp-dff7befbc5860f2f78f63ab694ef88d21a53771f.tar.gz
mailman-pgp-dff7befbc5860f2f78f63ab694ef88d21a53771f.tar.zst
mailman-pgp-dff7befbc5860f2f78f63ab694ef88d21a53771f.zip
Diffstat (limited to 'src/mailman_pgp')
-rw-r--r--src/mailman_pgp/pgp/inline.py36
-rw-r--r--src/mailman_pgp/pgp/mime.py55
-rw-r--r--src/mailman_pgp/pgp/tests/data/messages/inline_revoc.eml19
-rw-r--r--src/mailman_pgp/pgp/tests/data/messages/inline_revoc_multipart.eml30
-rw-r--r--src/mailman_pgp/pgp/tests/data/messages/mime_revoc.eml35
-rw-r--r--src/mailman_pgp/pgp/tests/test_inline.py32
-rw-r--r--src/mailman_pgp/pgp/tests/test_mime.py26
-rw-r--r--src/mailman_pgp/pgp/tests/test_wrapper.py38
-rw-r--r--src/mailman_pgp/pgp/wrapper.py31
-rw-r--r--src/mailman_pgp/testing/pgp.py37
-rw-r--r--src/mailman_pgp/utils/pgp.py27
11 files changed, 356 insertions, 10 deletions
diff --git a/src/mailman_pgp/pgp/inline.py b/src/mailman_pgp/pgp/inline.py
index bb0971d..cf92ffc 100644
--- a/src/mailman_pgp/pgp/inline.py
+++ b/src/mailman_pgp/pgp/inline.py
@@ -23,7 +23,7 @@ from pgpy import PGPMessage
from pgpy.constants import SymmetricKeyAlgorithm
from public import public
-from mailman_pgp.utils.pgp import key_from_blob
+from mailman_pgp.utils.pgp import key_from_blob, revoc_from_blob
@public
@@ -95,7 +95,7 @@ class InlineWrapper:
:rtype: typing.Generator[pgpy.PGPMessage]
"""
for part in walk(self.msg):
- if not part.is_multipart() and self._is_signed(part):
+ if not part.is_multipart():
try:
msg = PGPMessage.from_blob(part.get_payload())
except:
@@ -135,7 +135,7 @@ class InlineWrapper:
:rtype: typing.Generator[pgpy.PGPMessage]
"""
for part in walk(self.msg):
- if not part.is_multipart() and self._is_encrypted(part):
+ if not part.is_multipart():
try:
msg = PGPMessage.from_blob(part.get_payload())
except:
@@ -176,13 +176,41 @@ class InlineWrapper:
:rtype: Generator[pgpy.PGPKey]
"""
for part in walk(self.msg):
- if not part.is_multipart() and self._has_keys(part):
+ if not part.is_multipart():
try:
key = key_from_blob(part.get_payload())
except:
continue
yield key
+ def _is_revoc(self, part):
+ try:
+ revoc_from_blob(part.get_payload())
+ except ValueError:
+ return False
+ return True
+
+ def is_revocs(self):
+ for part in walk(self.msg):
+ if (not part.is_multipart() and not self._is_revoc(part)):
+ return False
+ return True
+
+ def has_revocs(self):
+ for part in walk(self.msg):
+ if (not part.is_multipart() and self._is_revoc(part)):
+ return True
+ return False
+
+ def revocs(self):
+ for part in walk(self.msg):
+ if not part.is_multipart():
+ try:
+ revoc = revoc_from_blob(part.get_payload())
+ except:
+ continue
+ yield revoc
+
def verify(self, key):
"""
Verify the signatures of this message with key.
diff --git a/src/mailman_pgp/pgp/mime.py b/src/mailman_pgp/pgp/mime.py
index a1303c9..e40f581 100644
--- a/src/mailman_pgp/pgp/mime.py
+++ b/src/mailman_pgp/pgp/mime.py
@@ -29,7 +29,7 @@ from pgpy.constants import HashAlgorithm, SymmetricKeyAlgorithm
from public import public
from mailman_pgp.utils.email import copy_headers
-from mailman_pgp.utils.pgp import key_from_blob
+from mailman_pgp.utils.pgp import key_from_blob, revoc_from_blob
@public
@@ -208,6 +208,59 @@ class MIMEWrapper:
out.attach(key_part)
return out
+ def _is_revoc(self, part):
+ if part.get_content_type() != MIMEWrapper._keys_type:
+ return False
+ try:
+ revoc_from_blob(part.get_payload())
+ except ValueError:
+ return False
+ return True
+
+ def is_revocs(self):
+ for part in walk(self.msg):
+ if (not part.is_multipart() and not self._is_revoc(part)):
+ return False
+ return True
+
+ def has_revocs(self):
+ for part in walk(self.msg):
+ if (not part.is_multipart() and self._is_revoc(part)):
+ return True
+ return False
+
+ def revocs(self):
+ for part in walk(self.msg):
+ if (not part.is_multipart() # noqa
+ and part.get_content_type() == MIMEWrapper._keys_type):
+ try:
+ revoc = revoc_from_blob(part.get_payload())
+ except:
+ continue
+ yield revoc
+
+ def attach_revoc(self, key_revocation):
+ """
+ Attach a key revocation signature to the message, as a key subpart.
+
+ :param key_revocation: A key revocation signature to attach.
+ :type key_revocation: pgpy.PGPSignature
+ :return: The message with the signature attached.
+ :rtype: mailman.email.message.Message
+ """
+ filename = '0x' + key_revocation.signer + '.asc'
+ key_part = MIMEApplication(_data=str(key_revocation),
+ _subtype=MIMEWrapper._keys_subtype,
+ _encoder=encode_7or8bit,
+ name=filename)
+ key_part.add_header('Content-Description',
+ 'OpenPGP key')
+ key_part.add_header('Content-Disposition', 'attachment',
+ filename=filename)
+ out = copy.deepcopy(self.msg)
+ out.attach(key_part)
+ return out
+
def verify(self, key):
"""
Verify the signature of this message with key.
diff --git a/src/mailman_pgp/pgp/tests/data/messages/inline_revoc.eml b/src/mailman_pgp/pgp/tests/data/messages/inline_revoc.eml
new file mode 100644
index 0000000..f215777
--- /dev/null
+++ b/src/mailman_pgp/pgp/tests/data/messages/inline_revoc.eml
@@ -0,0 +1,19 @@
+To: nobody@example.org
+From: RSA 1024b example <RSA-1024b@example.org>
+Subject: Some subject.
+Message-ID: <76a591ed-bfc4-d08b-73d3-fc2489148fd7@example.org>
+Date: Wed, 21 Jun 2017 13:50:59 +0200
+User-Agent: Mutt/1.7.2 (2016-11-26)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: This is a revocation certificate
+
+iLYEIAEIACAWIQTUqUiGscoglqZFjlxH8QwIeIS3WAUCWYClvgIdAgAKCRBH8QwI
+eIS3WLpBBADCUtyYmI2Z8DCOnKUW4nRjHc3ZVMoZJlwceJCWhSybBrnjo6LWzvBy
+eKke4qHlh+jmSk3/Qyio6vYzvicOayDwhr0s/1X26MiYorthfiCQOg2WQ0YiRuMC
+/Ml8rukvBTRGvXikcIuBw5MFqCWsWI7ExPKaaresnHaCn37KF0A6hw==
+=4oku
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/mailman_pgp/pgp/tests/data/messages/inline_revoc_multipart.eml b/src/mailman_pgp/pgp/tests/data/messages/inline_revoc_multipart.eml
new file mode 100644
index 0000000..a1052d6
--- /dev/null
+++ b/src/mailman_pgp/pgp/tests/data/messages/inline_revoc_multipart.eml
@@ -0,0 +1,30 @@
+To: nobody@example.org
+From: RSA 1024b example <RSA-1024b@example.org>
+Subject: Some subject.
+Message-ID: <76a591ed-bfc4-d08b-73d3-fc2489148fd7@example.org>
+Date: Wed, 21 Jun 2017 13:50:59 +0200
+User-Agent: Mutt/1.7.2 (2016-11-26)
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="abjqkjsfwqsfa546qw2wfq6sdq2sqwr56qqs"
+
+--abjqkjsfwqsfa546qw2wfq6sdq2sqwr56qqs
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: This is a revocation certificate
+
+iLYEIAEIACAWIQTUqUiGscoglqZFjlxH8QwIeIS3WAUCWYClvgIdAgAKCRBH8QwI
+eIS3WLpBBADCUtyYmI2Z8DCOnKUW4nRjHc3ZVMoZJlwceJCWhSybBrnjo6LWzvBy
+eKke4qHlh+jmSk3/Qyio6vYzvicOayDwhr0s/1X26MiYorthfiCQOg2WQ0YiRuMC
+/Ml8rukvBTRGvXikcIuBw5MFqCWsWI7ExPKaaresnHaCn37KF0A6hw==
+=4oku
+-----END PGP PUBLIC KEY BLOCK-----
+
+--abjqkjsfwqsfa546qw2wfq6sdq2sqwr56qqs
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 8bit
+
+Some cleartext.
+
+--abjqkjsfwqsfa546qw2wfq6sdq2sqwr56qqs-- \ No newline at end of file
diff --git a/src/mailman_pgp/pgp/tests/data/messages/mime_revoc.eml b/src/mailman_pgp/pgp/tests/data/messages/mime_revoc.eml
new file mode 100644
index 0000000..e1055a3
--- /dev/null
+++ b/src/mailman_pgp/pgp/tests/data/messages/mime_revoc.eml
@@ -0,0 +1,35 @@
+To: nobody@example.org
+From: RSA 1024b example <RSA-1024b@example.org>
+Subject: Some subject.
+Message-ID: <76a591ed-bfc4-d08b-73d3-fc2489148fd7@example.org>
+Date: Wed, 21 Jun 2017 13:50:59 +0200
+User-Agent: Mutt/1.7.2 (2016-11-26)
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------A851F166D50529639139DD0B"
+
+This is a multi-part message in MIME format.
+--------------A851F166D50529639139DD0B
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 7bit
+
+Some other text.
+
+--------------A851F166D50529639139DD0B
+Content-Type: application/pgp-keys;
+ name="0x7884B758.asc"
+Content-Transfer-Encoding: 7bit
+Content-Disposition: attachment;
+ filename="0x7884B758.asc"
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: This is a revocation certificate
+
+iLYEIAEIACAWIQTUqUiGscoglqZFjlxH8QwIeIS3WAUCWYClvgIdAgAKCRBH8QwI
+eIS3WLpBBADCUtyYmI2Z8DCOnKUW4nRjHc3ZVMoZJlwceJCWhSybBrnjo6LWzvBy
+eKke4qHlh+jmSk3/Qyio6vYzvicOayDwhr0s/1X26MiYorthfiCQOg2WQ0YiRuMC
+/Ml8rukvBTRGvXikcIuBw5MFqCWsWI7ExPKaaresnHaCn37KF0A6hw==
+=4oku
+-----END PGP PUBLIC KEY BLOCK-----
+
+--------------A851F166D50529639139DD0B--
diff --git a/src/mailman_pgp/pgp/tests/test_inline.py b/src/mailman_pgp/pgp/tests/test_inline.py
index 7f82ab4..b409bee 100644
--- a/src/mailman_pgp/pgp/tests/test_inline.py
+++ b/src/mailman_pgp/pgp/tests/test_inline.py
@@ -20,7 +20,8 @@
from parameterized import parameterized
from mailman_pgp.pgp.inline import InlineWrapper
-from mailman_pgp.testing.pgp import load_key, load_message, WrapperTestCase
+from mailman_pgp.testing.pgp import (load_key, load_message, WrapperTestCase,
+ load_revoc)
class InlineWrapperTestCase(WrapperTestCase):
@@ -219,6 +220,35 @@ class TestKeys(InlineWrapperTestCase):
self.keys(message, keys)
+class TestRevocs(InlineWrapperTestCase):
+ @parameterized.expand([
+ (load_message('inline_revoc.eml'),
+ True),
+ (load_message('inline_revoc_multipart.eml'),
+ True)
+ ])
+ def test_has_revocs(self, message, has_revocs):
+ self.has_revocs(message, has_revocs)
+
+ @parameterized.expand([
+ (load_message('inline_revoc.eml'),
+ True),
+ (load_message('inline_revoc_multipart.eml'),
+ False)
+ ])
+ def test_is_revocs(self, message, is_revocs):
+ self.is_revocs(message, is_revocs)
+
+ @parameterized.expand([
+ (load_message('inline_revoc.eml'),
+ (load_revoc('rsa_1024.revoc.asc'),)),
+ (load_message('inline_revoc_multipart.eml'),
+ (load_revoc('rsa_1024.revoc.asc'),))
+ ])
+ def test_revocs(self, message, revocs):
+ self.revocs(message, revocs)
+
+
class TestCombined(InlineWrapperTestCase):
@parameterized.expand([
(load_message('clear.eml'),
diff --git a/src/mailman_pgp/pgp/tests/test_mime.py b/src/mailman_pgp/pgp/tests/test_mime.py
index 78c9e71..ec2542a 100644
--- a/src/mailman_pgp/pgp/tests/test_mime.py
+++ b/src/mailman_pgp/pgp/tests/test_mime.py
@@ -20,7 +20,8 @@
from parameterized import parameterized
from mailman_pgp.pgp.mime import MIMEWrapper
-from mailman_pgp.testing.pgp import load_key, load_message, WrapperTestCase
+from mailman_pgp.testing.pgp import (load_key, load_message, WrapperTestCase,
+ load_revoc)
class MIMEWrapperTestCase(WrapperTestCase):
@@ -179,6 +180,29 @@ class TestKeys(MIMEWrapperTestCase):
self.keys(message, keys)
+class TestRevocs(MIMEWrapperTestCase):
+ @parameterized.expand([
+ (load_message('mime_revoc.eml'),
+ True)
+ ])
+ def test_has_revocs(self, message, has_revocs):
+ self.has_revocs(message, has_revocs)
+
+ @parameterized.expand([
+ (load_message('mime_revoc.eml'),
+ False)
+ ])
+ def test_is_revocs(self, message, is_revocs):
+ self.is_revocs(message, is_revocs)
+
+ @parameterized.expand([
+ (load_message('mime_revoc.eml'),
+ (load_revoc('rsa_1024.revoc.asc'),))
+ ])
+ def test_revocs(self, message, revocs):
+ self.revocs(message, revocs)
+
+
class TestCombined(MIMEWrapperTestCase):
@parameterized.expand([
(load_message('clear.eml'),
diff --git a/src/mailman_pgp/pgp/tests/test_wrapper.py b/src/mailman_pgp/pgp/tests/test_wrapper.py
index f1f7621..465b740 100644
--- a/src/mailman_pgp/pgp/tests/test_wrapper.py
+++ b/src/mailman_pgp/pgp/tests/test_wrapper.py
@@ -22,7 +22,8 @@ from mailman_pgp.pgp.inline import InlineWrapper
from mailman_pgp.pgp.mime import MIMEWrapper
from mailman_pgp.pgp.mime_multisig import MIMEMultiSigWrapper
from mailman_pgp.pgp.wrapper import PGPWrapper
-from mailman_pgp.testing.pgp import load_key, load_message, WrapperTestCase
+from mailman_pgp.testing.pgp import (load_key, load_message, WrapperTestCase,
+ load_revoc)
class PGPWrapperTestCase(WrapperTestCase):
@@ -180,6 +181,41 @@ class TestKeys(PGPWrapperTestCase):
self.keys(message, keys)
+class TestRevocs(PGPWrapperTestCase):
+ @parameterized.expand([
+ (load_message('mime_revoc.eml'),
+ True),
+ (load_message('inline_revoc.eml'),
+ True),
+ (load_message('inline_revoc_multipart.eml'),
+ True)
+ ])
+ def test_has_revocs(self, message, has_revocs):
+ self.has_revocs(message, has_revocs)
+
+ @parameterized.expand([
+ (load_message('mime_revoc.eml'),
+ False),
+ (load_message('inline_revoc.eml'),
+ True),
+ (load_message('inline_revoc_multipart.eml'),
+ False)
+ ])
+ def test_is_revocs(self, message, is_revocs):
+ self.is_revocs(message, is_revocs)
+
+ @parameterized.expand([
+ (load_message('mime_revoc.eml'),
+ (load_revoc('rsa_1024.revoc.asc'),)),
+ (load_message('inline_revoc.eml'),
+ (load_revoc('rsa_1024.revoc.asc'),)),
+ (load_message('inline_revoc_multipart.eml'),
+ (load_revoc('rsa_1024.revoc.asc'),))
+ ])
+ def test_revocs(self, message, revocs):
+ self.revocs(message, revocs)
+
+
class TestCombined(PGPWrapperTestCase):
@parameterized.expand([
(load_message('clear.eml'),
diff --git a/src/mailman_pgp/pgp/wrapper.py b/src/mailman_pgp/pgp/wrapper.py
index 6193b57..f0519cb 100644
--- a/src/mailman_pgp/pgp/wrapper.py
+++ b/src/mailman_pgp/pgp/wrapper.py
@@ -270,7 +270,7 @@ class PGPWrapper():
Get the collection of keys in this message.
:return: A collection of keys.
- :rtype: Generator[pgpy.PGPKey]
+ :rtype: typing.Generator[pgpy.PGPKey]
"""
if self.mime.has_keys():
yield from self.mime.keys()
@@ -278,3 +278,32 @@ class PGPWrapper():
yield from self.multisig.keys()
elif self.inline.has_keys():
yield from self.inline.keys()
+
+ def has_revocs(self):
+ """
+
+ :return:
+ :rtype: bool
+ """
+ return any(wrapper.has_revocs() for wrapper in self.wrappers)
+
+ def is_revocs(self):
+ """
+
+ :return:
+ :rtype: bool
+ """
+ return any(wrapper.is_revocs() for wrapper in self.wrappers)
+
+ def revocs(self):
+ """
+
+ :return:
+ :rtype: typing.Generator[pgpy.PGPSignature]
+ """
+ if self.mime.has_revocs():
+ yield from self.mime.revocs()
+ elif self.multisig.has_revocs():
+ yield from self.multisig.revocs()
+ elif self.inline.has_revocs():
+ yield from self.inline.revocs()
diff --git a/src/mailman_pgp/testing/pgp.py b/src/mailman_pgp/testing/pgp.py
index 844b708..7f207e3 100644
--- a/src/mailman_pgp/testing/pgp.py
+++ b/src/mailman_pgp/testing/pgp.py
@@ -26,6 +26,7 @@ from pgpy import PGPKey
from pkg_resources import resource_string
from mailman_pgp.testing.layers import PGPLayer
+from mailman_pgp.utils.pgp import revoc_from_blob
def load_message(path):
@@ -41,6 +42,12 @@ def load_key(path):
return key
+def load_revoc(path):
+ return revoc_from_blob(resource_string('mailman_pgp.pgp.tests',
+ os.path.join('data', 'revocs',
+ path)))
+
+
def payload_equal(one_msg, other_msg):
one_payload = one_msg.get_payload()
other_payload = other_msg.get_payload()
@@ -139,6 +146,36 @@ class WrapperTestCase(TestCase):
fingerprints = list(map(lambda key: key.fingerprint, keys))
self.assertListEqual(loaded_fingerprints, fingerprints)
+ def attach_keys(self, message, keys):
+ wrapped = self.wrap(message)
+ for key in keys:
+ attached = wrapped.attach_key(key)
+ wrapped = self.wrap(attached)
+ loaded = list(wrapped.keys())
+
+ self.assertTrue(wrapped.has_keys())
+ loaded_fingerprints = list(map(lambda key: key.fingerprint, loaded))
+ fingerprints = list(map(lambda key: key.fingerprint, keys))
+ self.assertListEqual(loaded_fingerprints, fingerprints)
+
+ def has_revocs(self, message, has_revocs):
+ wrapped = self.wrap(message)
+ self.assertEqual(wrapped.has_revocs(), has_revocs)
+
+ def is_revocs(self, message, is_revocs):
+ wrapped = self.wrap(message)
+ self.assertEqual(wrapped.is_revocs(), is_revocs)
+
+ def revocs(self, message, revocs):
+ wrapped = self.wrap(message)
+ loaded = list(wrapped.revocs())
+ self.assertEqual(len(loaded), len(revocs))
+
+ get_issuer = lambda revoc: revoc.signer
+ loaded_issuers = list(map(get_issuer, loaded))
+ issuers = list(map(get_issuer, revocs))
+ self.assertListEqual(loaded_issuers, issuers)
+
def sign_encrypt_decrypt_verify(self, message, sign_key, encrypt_key):
wrapped = self.wrap(message)
encrypted = wrapped.sign_encrypt(sign_key, encrypt_key.pubkey)
diff --git a/src/mailman_pgp/utils/pgp.py b/src/mailman_pgp/utils/pgp.py
index 4251693..621aa02 100644
--- a/src/mailman_pgp/utils/pgp.py
+++ b/src/mailman_pgp/utils/pgp.py
@@ -16,7 +16,10 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
"""Miscellaneous PGP utilities."""
-from pgpy import PGPKey
+from pgpy import PGPKey, PGPSignature
+from pgpy.constants import SignatureType
+from pgpy.packet import Packet, Signature
+from pgpy.types import Armorable
from public import public
@@ -72,3 +75,25 @@ def key_from_file(file):
"""
key, _ = PGPKey.from_file(file)
return key
+
+
+@public
+def revoc_from_blob(blob):
+ """
+
+ :param blob:
+ :return:
+ :rtype: pgpy.PGPSignature
+ """
+ dearm = Armorable.ascii_unarmor(blob)
+ p = Packet(dearm['body'])
+
+ if not isinstance(p, Signature):
+ raise ValueError('Not a key revocation signature.')
+ if p.sigtype not in (SignatureType.KeyRevocation,
+ SignatureType.SubkeyRevocation):
+ raise ValueError('Not a key revocation.')
+
+ sig = PGPSignature()
+ sig |= p
+ return sig