diff options
| author | J08nY | 2017-08-01 22:53:15 +0200 |
|---|---|---|
| committer | J08nY | 2017-08-02 01:35:10 +0200 |
| commit | dff7befbc5860f2f78f63ab694ef88d21a53771f (patch) | |
| tree | 458e8004705eab2ca024da0cd21057cc2f5f56f3 | |
| parent | 7c0aaf28767e494323557b6cb8fb6500df5822e5 (diff) | |
| download | mailman-pgp-dff7befbc5860f2f78f63ab694ef88d21a53771f.tar.gz mailman-pgp-dff7befbc5860f2f78f63ab694ef88d21a53771f.tar.zst mailman-pgp-dff7befbc5860f2f78f63ab694ef88d21a53771f.zip | |
| -rw-r--r-- | src/mailman_pgp/pgp/inline.py | 36 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/mime.py | 55 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/tests/data/messages/inline_revoc.eml | 19 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/tests/data/messages/inline_revoc_multipart.eml | 30 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/tests/data/messages/mime_revoc.eml | 35 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/tests/test_inline.py | 32 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/tests/test_mime.py | 26 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/tests/test_wrapper.py | 38 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/wrapper.py | 31 | ||||
| -rw-r--r-- | src/mailman_pgp/testing/pgp.py | 37 | ||||
| -rw-r--r-- | src/mailman_pgp/utils/pgp.py | 27 |
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 |
