aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman_pgp/commands/eml_key.py2
-rw-r--r--src/mailman_pgp/commands/tests/test_key.py34
-rw-r--r--src/mailman_pgp/pgp/inline.py17
-rw-r--r--src/mailman_pgp/pgp/mime.py60
-rw-r--r--src/mailman_pgp/pgp/tests/test_inline.py11
-rw-r--r--src/mailman_pgp/pgp/tests/test_mime.py22
-rw-r--r--src/mailman_pgp/testing/pgp.py27
-rw-r--r--src/mailman_pgp/utils/email.py20
8 files changed, 139 insertions, 54 deletions
diff --git a/src/mailman_pgp/commands/eml_key.py b/src/mailman_pgp/commands/eml_key.py
index 1f39888..bea9745 100644
--- a/src/mailman_pgp/commands/eml_key.py
+++ b/src/mailman_pgp/commands/eml_key.py
@@ -270,7 +270,7 @@ def _cmd_receive(pgp_list, mlist, msg, msgdata, arguments, results):
msg['MIME-Version'] = '1.0'
msg.attach(MIMEText('Here is the public key you requested.'))
wrapped = MIMEWrapper(msg)
- msg = wrapped.attach_key(pgp_list.pubkey)
+ msg = wrapped.attach_keys(pgp_list.pubkey)
msg.send(mlist)
return ContinueProcessing.yes
diff --git a/src/mailman_pgp/commands/tests/test_key.py b/src/mailman_pgp/commands/tests/test_key.py
index cfda3e8..88b0bb4 100644
--- a/src/mailman_pgp/commands/tests/test_key.py
+++ b/src/mailman_pgp/commands/tests/test_key.py
@@ -145,7 +145,7 @@ class TestPreSubscription(unittest.TestCase):
set_message = _create_mixed('bart@example.com', 'test@example.com',
'Re: key set {}'.format(token))
wrapped_set_message = MIMEWrapper(set_message)
- set_message = wrapped_set_message.attach_key(self.bart_key.pubkey)
+ set_message = wrapped_set_message.attach_keys(self.bart_key.pubkey)
mm_config.switchboards['command'].enqueue(set_message,
listid='test.example.com')
@@ -186,7 +186,7 @@ class TestPreSubscription(unittest.TestCase):
set_message = _create_mixed('bart@example.com', 'test@example.com',
'Re: key set {}'.format(token))
wrapped_set_message = MIMEWrapper(set_message)
- set_message = wrapped_set_message.attach_key(self.bart_key.pubkey)
+ set_message = wrapped_set_message.attach_keys(self.bart_key.pubkey)
wrapped_set_message = MIMEWrapper(set_message)
set_message = wrapped_set_message.encrypt(self.pgp_list.pubkey,
self.bart_key.pubkey)
@@ -246,9 +246,9 @@ class TestPreSubscription(unittest.TestCase):
set_message = _create_mixed('bart@example.com', 'test@example.com',
'Re: key set token')
wrapped_set_message = MIMEWrapper(set_message)
- set_message = wrapped_set_message.attach_key(self.bart_key.pubkey)
+ set_message = wrapped_set_message.attach_keys(self.bart_key.pubkey)
wrapped_set_message = MIMEWrapper(set_message)
- set_message = wrapped_set_message.attach_key(self.anne_key.pubkey)
+ set_message = wrapped_set_message.attach_keys(self.anne_key.pubkey)
mm_config.switchboards['command'].enqueue(set_message,
listid='test.example.com')
@@ -263,7 +263,7 @@ class TestPreSubscription(unittest.TestCase):
set_message = _create_mixed('bart@example.com', 'test@example.com',
'Re: key set token')
wrapped_set_message = MIMEWrapper(set_message)
- set_message = wrapped_set_message.attach_key(self.bart_key)
+ set_message = wrapped_set_message.attach_keys(self.bart_key)
mm_config.switchboards['command'].enqueue(set_message,
listid='test.example.com')
@@ -293,7 +293,7 @@ class TestPreSubscription(unittest.TestCase):
def test_set_no_email(self):
message = _create_mixed('', 'test@example.com', 'key set token')
wrapped_message = MIMEWrapper(message)
- message = wrapped_message.attach_key(self.bart_key.pubkey)
+ message = wrapped_message.attach_keys(self.bart_key.pubkey)
mm_config.switchboards['command'].enqueue(message,
listid='test.example.com')
@@ -307,7 +307,7 @@ class TestPreSubscription(unittest.TestCase):
set_message = _create_mixed('bart@example.com', 'test@example.com',
'key set token')
wrapped_set_message = MIMEWrapper(set_message)
- set_message = wrapped_set_message.attach_key(self.bart_key.pubkey)
+ set_message = wrapped_set_message.attach_keys(self.bart_key.pubkey)
mm_config.switchboards['command'].enqueue(set_message,
listid='test.example.com')
@@ -326,7 +326,7 @@ class TestPreSubscription(unittest.TestCase):
set_message = _create_mixed('bart@example.com', 'test@example.com',
'key set token')
wrapped_set_message = MIMEWrapper(set_message)
- set_message = wrapped_set_message.attach_key(self.bart_key.pubkey)
+ set_message = wrapped_set_message.attach_keys(self.bart_key.pubkey)
mm_config.switchboards['command'].enqueue(set_message,
listid='test.example.com')
@@ -349,7 +349,7 @@ class TestPreSubscription(unittest.TestCase):
set_message = _create_mixed('bart@example.com', 'test@example.com',
'key set token')
wrapped_set_message = MIMEWrapper(set_message)
- set_message = wrapped_set_message.attach_key(self.bart_key.pubkey)
+ set_message = wrapped_set_message.attach_keys(self.bart_key.pubkey)
mm_config.switchboards['command'].enqueue(set_message,
listid='test.example.com')
@@ -628,7 +628,7 @@ class TestAfterSubscription(unittest.TestCase):
message = _create_mixed('bart@example.com', 'test@example.com',
'key change')
wrapped_message = MIMEWrapper(message)
- message = wrapped_message.attach_key(self.bart_new_key.pubkey)
+ message = wrapped_message.attach_keys(self.bart_new_key.pubkey)
mm_config.switchboards['command'].enqueue(message,
listid='test.example.com')
@@ -663,7 +663,7 @@ class TestAfterSubscription(unittest.TestCase):
message = _create_mixed('bart@example.com', 'test@example.com',
'key change')
wrapped_message = MIMEWrapper(message)
- message = wrapped_message.attach_key(self.bart_new_key.pubkey)
+ message = wrapped_message.attach_keys(self.bart_new_key.pubkey)
wrapped_message = MIMEWrapper(message)
message = wrapped_message.encrypt(self.pgp_list.pubkey)
@@ -700,7 +700,7 @@ class TestAfterSubscription(unittest.TestCase):
message = _create_mixed('bart@example.com', 'test@example.com',
'key change')
wrapped_message = MIMEWrapper(message)
- message = wrapped_message.attach_key(self.bart_new_key.pubkey)
+ message = wrapped_message.attach_keys(self.bart_new_key.pubkey)
mm_config.switchboards['command'].enqueue(message,
listid='test.example.com')
@@ -751,7 +751,7 @@ class TestAfterSubscription(unittest.TestCase):
def test_change_no_email(self):
message = _create_mixed('', 'test@example.com', 'key change')
wrapped_message = MIMEWrapper(message)
- message = wrapped_message.attach_key(self.bart_key.pubkey)
+ message = wrapped_message.attach_keys(self.bart_key.pubkey)
mm_config.switchboards['command'].enqueue(message,
listid='test.example.com')
@@ -765,7 +765,7 @@ class TestAfterSubscription(unittest.TestCase):
message = _create_mixed('bart@example.com', 'test@example.com',
'key change')
wrapped_message = MIMEWrapper(message)
- message = wrapped_message.attach_key(self.bart_key.pubkey)
+ message = wrapped_message.attach_keys(self.bart_key.pubkey)
mm_config.switchboards['command'].enqueue(message,
listid='test.example.com')
@@ -810,9 +810,9 @@ class TestAfterSubscription(unittest.TestCase):
'key change')
wrapped_set_message = MIMEWrapper(set_message)
- set_message = wrapped_set_message.attach_key(self.bart_key.pubkey)
+ set_message = wrapped_set_message.attach_keys(self.bart_key.pubkey)
wrapped_set_message = MIMEWrapper(set_message)
- set_message = wrapped_set_message.attach_key(self.bart_new_key.pubkey)
+ set_message = wrapped_set_message.attach_keys(self.bart_new_key.pubkey)
mm_config.switchboards['command'].enqueue(set_message,
listid='test.example.com')
@@ -835,7 +835,7 @@ class TestAfterSubscription(unittest.TestCase):
message = _create_mixed('bart@example.com', 'test@example.com',
'key change')
wrapped_message = MIMEWrapper(message)
- message = wrapped_message.attach_key(self.bart_key)
+ message = wrapped_message.attach_keys(self.bart_key)
mm_config.switchboards['command'].enqueue(message,
listid='test.example.com')
diff --git a/src/mailman_pgp/pgp/inline.py b/src/mailman_pgp/pgp/inline.py
index cf92ffc..49f0a6e 100644
--- a/src/mailman_pgp/pgp/inline.py
+++ b/src/mailman_pgp/pgp/inline.py
@@ -18,11 +18,13 @@
"""Strict inline PGP message wrapper."""
import copy
from email.iterators import walk
+from email.mime.text import MIMEText
from pgpy import PGPMessage
from pgpy.constants import SymmetricKeyAlgorithm
from public import public
+from mailman_pgp.utils.email import make_multipart
from mailman_pgp.utils.pgp import key_from_blob, revoc_from_blob
@@ -211,6 +213,21 @@ class InlineWrapper:
continue
yield revoc
+ def attach_revocs(self, *key_revocations):
+ """
+ Attach a key revocation signature to the message.
+
+ :param key_revocations: A key revocation signature to attach.
+ :type key_revocations: pgpy.PGPSignature
+ :return: The message with the signature attached.
+ :rtype: mailman.email.message.Message
+ """
+ out = make_multipart(self.msg)
+ for key_revocation in key_revocations:
+ revoc_part = MIMEText(str(key_revocation))
+ out.attach(revoc_part)
+ return out
+
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 e40f581..837e619 100644
--- a/src/mailman_pgp/pgp/mime.py
+++ b/src/mailman_pgp/pgp/mime.py
@@ -28,7 +28,7 @@ from pgpy import PGPDetachedSignature, PGPMessage
from pgpy.constants import HashAlgorithm, SymmetricKeyAlgorithm
from public import public
-from mailman_pgp.utils.email import copy_headers
+from mailman_pgp.utils.email import copy_headers, make_multipart
from mailman_pgp.utils.pgp import key_from_blob, revoc_from_blob
@@ -186,26 +186,27 @@ class MIMEWrapper:
continue
yield key
- def attach_key(self, key):
+ def attach_keys(self, *keys):
"""
Attach a key to this message, as per RFC3156 section 7.
- :param key: A key to attach.
- :type key: pgpy.PGPKey
+ :param keys: A key to attach.
+ :type keys: pgpy.PGPKey
:return: The message with the key attached.
:rtype: mailman.email.message.Message
"""
- filename = '0x' + key.fingerprint.keyid + '.asc'
- key_part = MIMEApplication(_data=str(key),
- _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)
+ out = make_multipart(self.msg)
+ for key in keys:
+ filename = '0x' + key.fingerprint.keyid + '.asc'
+ key_part = MIMEApplication(_data=str(key),
+ _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.attach(key_part)
return out
def _is_revoc(self, part):
@@ -239,26 +240,27 @@ class MIMEWrapper:
continue
yield revoc
- def attach_revoc(self, key_revocation):
+ def attach_revocs(self, *key_revocations):
"""
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
+ :param key_revocations: A key revocation signature to attach.
+ :type key_revocations: 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)
+ out = make_multipart(self.msg)
+ for key_revocation in key_revocations:
+ filename = '0x' + key_revocation.signer + '.asc'
+ revoc_part = MIMEApplication(_data=str(key_revocation),
+ _subtype=MIMEWrapper._keys_subtype,
+ _encoder=encode_7or8bit,
+ name=filename)
+ revoc_part.add_header('Content-Description',
+ 'OpenPGP key')
+ revoc_part.add_header('Content-Disposition', 'attachment',
+ filename=filename)
+ out.attach(revoc_part)
return out
def verify(self, key):
diff --git a/src/mailman_pgp/pgp/tests/test_inline.py b/src/mailman_pgp/pgp/tests/test_inline.py
index b409bee..1f1f687 100644
--- a/src/mailman_pgp/pgp/tests/test_inline.py
+++ b/src/mailman_pgp/pgp/tests/test_inline.py
@@ -248,6 +248,17 @@ class TestRevocs(InlineWrapperTestCase):
def test_revocs(self, message, revocs):
self.revocs(message, revocs)
+ @parameterized.expand([
+ (load_message('clear.eml'),
+ [load_revoc('rsa_1024.revoc.asc'),
+ load_revoc('ecc_p256.revoc.asc')]),
+ (load_message('clear_multipart.eml'),
+ [load_revoc('rsa_1024.revoc.asc'),
+ load_revoc('ecc_p256.revoc.asc')])
+ ])
+ def test_attach_revocs(self, message, revocs):
+ self.attach_revocs(message, revocs)
+
class TestCombined(InlineWrapperTestCase):
@parameterized.expand([
diff --git a/src/mailman_pgp/pgp/tests/test_mime.py b/src/mailman_pgp/pgp/tests/test_mime.py
index ec2542a..f059c8d 100644
--- a/src/mailman_pgp/pgp/tests/test_mime.py
+++ b/src/mailman_pgp/pgp/tests/test_mime.py
@@ -179,6 +179,17 @@ class TestKeys(MIMEWrapperTestCase):
def test_keys(self, message, keys):
self.keys(message, keys)
+ @parameterized.expand([
+ (load_message('clear.eml'),
+ [load_key('rsa_1024.priv.asc'),
+ load_key('ecc_p256.priv.asc')]),
+ (load_message('clear_multipart.eml'),
+ [load_key('rsa_1024.priv.asc'),
+ load_key('ecc_p256.priv.asc')])
+ ])
+ def test_attach_keys(self, message, keys):
+ self.attach_keys(message, keys)
+
class TestRevocs(MIMEWrapperTestCase):
@parameterized.expand([
@@ -202,6 +213,17 @@ class TestRevocs(MIMEWrapperTestCase):
def test_revocs(self, message, revocs):
self.revocs(message, revocs)
+ @parameterized.expand([
+ (load_message('clear.eml'),
+ [load_revoc('rsa_1024.revoc.asc'),
+ load_revoc('ecc_p256.revoc.asc')]),
+ (load_message('clear_multipart.eml'),
+ [load_revoc('rsa_1024.revoc.asc'),
+ load_revoc('ecc_p256.revoc.asc')])
+ ])
+ def test_attach_revocs(self, message, revocs):
+ self.attach_revocs(message, revocs)
+
class TestCombined(MIMEWrapperTestCase):
@parameterized.expand([
diff --git a/src/mailman_pgp/testing/pgp.py b/src/mailman_pgp/testing/pgp.py
index 7f207e3..90deb7a 100644
--- a/src/mailman_pgp/testing/pgp.py
+++ b/src/mailman_pgp/testing/pgp.py
@@ -142,20 +142,21 @@ class WrapperTestCase(TestCase):
loaded = list(wrapped.keys())
self.assertEqual(len(loaded), len(keys))
- loaded_fingerprints = list(map(lambda key: key.fingerprint, loaded))
- fingerprints = list(map(lambda key: key.fingerprint, keys))
+ get_fingerprint = lambda key: key.fingerprint
+ loaded_fingerprints = list(map(get_fingerprint, loaded))
+ fingerprints = list(map(get_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)
+ attached = wrapped.attach_keys(*keys)
+ 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))
+ get_fingerprint = lambda key: key.fingerprint
+ loaded_fingerprints = list(map(get_fingerprint, loaded))
+ fingerprints = list(map(get_fingerprint, keys))
self.assertListEqual(loaded_fingerprints, fingerprints)
def has_revocs(self, message, has_revocs):
@@ -176,6 +177,18 @@ class WrapperTestCase(TestCase):
issuers = list(map(get_issuer, revocs))
self.assertListEqual(loaded_issuers, issuers)
+ def attach_revocs(self, message, revocs):
+ wrapped = self.wrap(message)
+ attached = wrapped.attach_revocs(*revocs)
+ wrapped = self.wrap(attached)
+ loaded = list(wrapped.revocs())
+
+ self.assertTrue(wrapped.has_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/email.py b/src/mailman_pgp/utils/email.py
index a936458..9eff313 100644
--- a/src/mailman_pgp/utils/email.py
+++ b/src/mailman_pgp/utils/email.py
@@ -16,8 +16,10 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
""""""
+import copy
from email.utils import parseaddr
+from mailman.email.message import MultipartDigestMessage
from public import public
@@ -62,6 +64,24 @@ def overwrite_message(from_msg, to_msg):
@public
+def make_multipart(msg):
+ """
+
+ :param msg:
+ :type msg: email.message.Message
+ :return:
+ :rtype: email.message.MIMEMultipart|mailman.email.message.MultipartDigestMessage
+ """
+ if msg.is_multipart():
+ out = copy.deepcopy(msg)
+ else:
+ out = MultipartDigestMessage()
+ out.attach(msg)
+ copy_headers(msg, out)
+ return out
+
+
+@public
def get_email(msg):
display_name, email = parseaddr(msg['from'])
# Address could be None or the empty string.