aboutsummaryrefslogtreecommitdiff
path: root/src/mailman_pgp/pgp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman_pgp/pgp')
-rw-r--r--src/mailman_pgp/pgp/base.py43
-rw-r--r--src/mailman_pgp/pgp/inline.py77
-rw-r--r--src/mailman_pgp/pgp/mime.py195
-rw-r--r--src/mailman_pgp/pgp/mime_multisig.py123
-rw-r--r--src/mailman_pgp/pgp/tests/test_wrapper.py4
-rw-r--r--src/mailman_pgp/pgp/wrapper.py78
6 files changed, 270 insertions, 250 deletions
diff --git a/src/mailman_pgp/pgp/base.py b/src/mailman_pgp/pgp/base.py
new file mode 100644
index 0000000..497e9b5
--- /dev/null
+++ b/src/mailman_pgp/pgp/base.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2017 Jan Jancar
+#
+# This file is a part of the Mailman PGP plugin.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+""""""
+from copy import deepcopy
+
+from public import public
+
+
+@public
+class BaseWrapper:
+ """"""
+
+ def __init__(self, msg, copy=False):
+ """
+ Wrap the given message.
+
+ :param msg: The message to wrap.
+ :type msg: mailman.email.message.Message
+ """
+ if copy:
+ self.msg = deepcopy(msg)
+ else:
+ self.msg = msg
+
+ def copy(self):
+ return self.__class__(self.msg, True)
+
+ def __copy__(self):
+ return self.copy()
diff --git a/src/mailman_pgp/pgp/inline.py b/src/mailman_pgp/pgp/inline.py
index fa8f878..410078c 100644
--- a/src/mailman_pgp/pgp/inline.py
+++ b/src/mailman_pgp/pgp/inline.py
@@ -24,23 +24,14 @@ from pgpy import PGPMessage
from pgpy.constants import SymmetricKeyAlgorithm
from public import public
-from mailman_pgp.utils.email import make_multipart
+from mailman_pgp.pgp.base import BaseWrapper
from mailman_pgp.utils.pgp import key_from_blob, revoc_from_blob
@public
-class InlineWrapper:
+class InlineWrapper(BaseWrapper):
"""Inline PGP wrapper."""
- def __init__(self, msg):
- """
- Wrap the given message.
-
- :param msg: The message to wrap.
- :type msg: mailman.email.message.Message
- """
- self.msg = msg
-
def get_payload(self):
for part in walk(self.msg):
if not part.is_multipart():
@@ -219,14 +210,21 @@ class InlineWrapper:
: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
+ :return:
+ :rtype: InlineWrapper
"""
- out = make_multipart(self.msg)
+ if self.msg.get_content_type() != 'multipart/mixed':
+ # wrap in multipart/mixed
+ payload = copy.deepcopy(self.msg)
+ self.msg.set_payload([])
+ self.msg.set_type('multipart/mixed')
+ self.msg['MIME-Version'] = '1.0'
+ self.msg.attach(payload)
+
for key_revocation in key_revocations:
revoc_part = MIMEText(str(key_revocation))
- out.attach(revoc_part)
- return out
+ self.msg.attach(revoc_part)
+ return self
def verify(self, key):
"""
@@ -250,11 +248,10 @@ class InlineWrapper:
:param key: The key to sign with.
:type key: pgpy.PGPKey
- :return: The signed message.
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: InlineWrapper
"""
- out = copy.deepcopy(self.msg)
- for part in walk(out):
+ for part in walk(self.msg):
if not part.is_multipart():
if self._is_signed(part):
pmsg = PGPMessage.from_blob(part.get_payload())
@@ -263,16 +260,18 @@ class InlineWrapper:
pmsg = PGPMessage.new(payload, cleartext=True)
smsg = self._sign(pmsg, key, **kwargs)
part.set_payload(str(smsg))
- return out
+ return self
def _decrypt(self, part, key):
message = PGPMessage.from_blob(part.get_payload())
- # TODO: exception safe this.
decrypted = key.decrypt(message)
if decrypted.is_signed:
part.set_payload(str(decrypted))
else:
- part.set_payload(decrypted.message)
+ dmsg = decrypted.message
+ if isinstance(dmsg, bytearray):
+ dmsg = dmsg.decode(decrypted.charset or 'utf-8')
+ part.set_payload(dmsg)
def decrypt(self, key):
"""
@@ -280,14 +279,13 @@ class InlineWrapper:
:param key: The key to decrypt with.
:type key: pgpy.PGPKey
- :return: The decrypted message.
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: InlineWrapper
"""
- out = copy.deepcopy(self.msg)
- for part in walk(out):
+ for part in walk(self.msg):
if not part.is_multipart() and self._is_encrypted(part):
self._decrypt(part, key)
- return out
+ return self
def _encrypt(self, pmsg, *keys, cipher, **kwargs):
emsg = copy.copy(pmsg)
@@ -311,19 +309,19 @@ class InlineWrapper:
:type keys: pgpy.PGPKey
:param cipher: The symmetric cipher to use.
:type cipher: SymmetricKeyAlgorithm
- :return: mailman.email.message.Message
+ :return:
+ :rtype: InlineWrapper
"""
if len(keys) == 0:
raise ValueError('At least one key necessary.')
- out = copy.deepcopy(self.msg)
- for part in walk(out):
+ for part in walk(self.msg):
if not part.is_multipart():
payload = str(part.get_payload())
pmsg = PGPMessage.new(payload)
emsg = self._encrypt(pmsg, *keys, cipher=cipher, **kwargs)
part.set_payload(str(emsg))
- return out
+ return self
def sign_encrypt(self, key, *keys, hash=None,
cipher=SymmetricKeyAlgorithm.AES256,
@@ -339,14 +337,13 @@ class InlineWrapper:
:type hash: pgpy.constants.HashAlgorithm
:param cipher:
:type cipher: pgpy.constants.SymmetricKeyAlgorithm
- :return: The signed + encrypted message.
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: InlineWrapper
"""
if len(keys) == 0:
raise ValueError('At least one key necessary.')
- out = copy.deepcopy(self.msg)
- for part in walk(out):
+ for part in walk(self.msg):
if not part.is_multipart():
if self._is_signed(part):
pmsg = PGPMessage.from_blob(part.get_payload())
@@ -356,10 +353,4 @@ class InlineWrapper:
smsg = self._sign(pmsg, key, hash=hash)
emsg = self._encrypt(smsg, *keys, cipher=cipher, **kwargs)
part.set_payload(str(emsg))
- return out
-
- def sign_then_encrypt(self, key, *keys, hash=None,
- cipher=SymmetricKeyAlgorithm.AES256,
- **kwargs):
- return self.sign_encrypt(key, *keys, hash=hash, cipher=cipher,
- **kwargs)
+ return self
diff --git a/src/mailman_pgp/pgp/mime.py b/src/mailman_pgp/pgp/mime.py
index eeeed95..9ea6384 100644
--- a/src/mailman_pgp/pgp/mime.py
+++ b/src/mailman_pgp/pgp/mime.py
@@ -23,17 +23,18 @@ from email.iterators import walk
from email.mime.application import MIMEApplication
from email.utils import collapse_rfc2231_value
-from mailman.email.message import Message, MultipartDigestMessage
+from mailman.email.message import Message
from pgpy import PGPMessage, PGPSignature
from pgpy.constants import HashAlgorithm, SymmetricKeyAlgorithm
from public import public
-from mailman_pgp.utils.email import copy_headers, make_multipart
+from mailman_pgp.pgp.base import BaseWrapper
+from mailman_pgp.utils.email import copy_headers
from mailman_pgp.utils.pgp import key_from_blob, revoc_from_blob
@public
-class MIMEWrapper:
+class MIMEWrapper(BaseWrapper):
"""PGP/MIME (RFC1847 + RFC3156) compliant wrapper."""
_signature_subtype = 'pgp-signature'
@@ -44,20 +45,14 @@ class MIMEWrapper:
_encrypted_type = 'application/' + _encryption_subtype
_keys_type = 'application/' + _keys_subtype
+ _signed_multipart = 'multipart/signed'
+ _encrypted_multipart = 'multipart/encrypted'
+
_signature_preamble = \
'This is an OpenPGP/MIME signed message (RFC 4880 and 3156)'
_encryption_preamble = \
'This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)'
- def __init__(self, msg):
- """
- Wrap the given message.
-
- :param msg: The message to wrap.
- :type msg: mailman.email.message.Message
- """
- self.msg = msg
-
def get_payload(self):
yield self.msg.as_string()
@@ -186,28 +181,40 @@ class MIMEWrapper:
continue
yield key
+ def _attach_key_part(self, obj, name, description):
+ key_part = MIMEApplication(_data=str(obj),
+ _subtype=MIMEWrapper._keys_subtype,
+ _encoder=encode_7or8bit,
+ name=name)
+ key_part.add_header('Content-Description', description)
+ key_part.add_header('Content-Disposition', 'attachment',
+ filename=name)
+ self.msg.attach(key_part)
+
def attach_keys(self, *keys):
"""
Attach a key to this message, as per RFC3156 section 7.
- :param keys: A key to attach.
+ :param keys: Keys to attach.
:type keys: pgpy.PGPKey
- :return: The message with the key attached.
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: MIMEWrapper
"""
- out = make_multipart(self.msg)
+ if len(keys) == 0:
+ return self
+
+ if self.msg.get_content_type() != 'multipart/mixed':
+ # wrap in multipart/mixed
+ payload = copy.deepcopy(self.msg)
+ self.msg.set_payload([])
+ self.msg.set_type('multipart/mixed')
+ self.msg['MIME-Version'] = '1.0'
+ self.msg.attach(payload)
+
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
+ self._attach_key_part(key, filename, 'OpenPGP key')
+ return self
def _is_revoc(self, part):
if part.get_content_type() != MIMEWrapper._keys_type:
@@ -246,22 +253,25 @@ class MIMEWrapper:
: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
+ :return:
+ :rtype: MIMEWrapper
"""
- out = make_multipart(self.msg)
+ if len(key_revocations) == 0:
+ return self
+
+ if self.msg.get_content_type() != 'multipart/mixed':
+ # wrap in multipart/mixed
+ payload = copy.deepcopy(self.msg)
+ self.msg.set_payload([])
+ self.msg.set_type('multipart/mixed')
+ self.msg['MIME-Version'] = '1.0'
+ self.msg.attach(payload)
+
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
+ self._attach_key_part(key_revocation, filename,
+ 'OpenPGP key revocation')
+ return self
def verify(self, key):
"""
@@ -298,12 +308,13 @@ class MIMEWrapper:
:param msg:
:param signature:
- :return:
"""
- micalg = self._micalg(signature.hash_algorithm)
- out = MultipartDigestMessage('signed', micalg=micalg,
- protocol=MIMEWrapper._signed_type)
- out.preamble = MIMEWrapper._signature_preamble
+ self.msg.set_payload([])
+ self.msg.attach(msg)
+ self.msg.set_type(MIMEWrapper._signed_multipart)
+ self.msg.set_param('micalg', self._micalg(signature.hash_algorithm))
+ self.msg.set_param('protocol', MIMEWrapper._signed_type)
+ self.msg.preamble = MIMEWrapper._signature_preamble
second_part = MIMEApplication(_data=str(signature),
_subtype=MIMEWrapper._signature_subtype,
@@ -313,11 +324,7 @@ class MIMEWrapper:
'OpenPGP digital signature')
second_part.add_header('Content-Disposition', 'attachment',
filename='signature.asc')
-
- out.attach(copy.deepcopy(msg))
- out.attach(second_part)
- copy_headers(msg, out)
- return out
+ self.msg.attach(second_part)
def sign(self, key, **kwargs):
"""
@@ -325,12 +332,14 @@ class MIMEWrapper:
:param key: The key to sign with.
:type key: pgpy.PGPKey
- :return: The signed message.
- :rtype: mailman.email.message.Message
+ :rtype: MIMEWrapper
"""
payload = next(iter(self.get_payload()))
signature = key.sign(payload, **kwargs)
- return self._wrap_signed(self.msg, signature)
+ original_msg = copy.deepcopy(self.msg)
+ self._wrap_signed(original_msg, signature)
+
+ return self
def decrypt(self, key):
"""
@@ -342,7 +351,6 @@ class MIMEWrapper:
:rtype: mailman.email.message.Message
"""
pmsg = next(iter(self.get_encrypted()))
- # TODO: exception safe this.
decrypted = key.decrypt(pmsg)
dmsg = decrypted.message
@@ -351,9 +359,17 @@ class MIMEWrapper:
out = message_from_string(dmsg, _class=Message)
if decrypted.is_signed:
- out = self._wrap_signed(out, decrypted.signatures.pop())
- copy_headers(self.msg, out)
- return out
+ # rewrap, self.msg should be multipart/signed,
+ # headers from out should overwrite those from self.msg
+ # self.msg payload should be [out, sig]
+ signature = next(iter(decrypted.signatures))
+ self._wrap_signed(out, signature)
+ else:
+ # self.msg payload should be out.get_payload
+ # headers from out should overwrite those from self.msg
+ self.msg.set_payload(out.get_payload())
+ copy_headers(out, self.msg, True)
+ return self
def _encrypt(self, pmsg, *keys, cipher, **kwargs):
emsg = copy.copy(pmsg)
@@ -369,16 +385,16 @@ class MIMEWrapper:
return emsg
def _wrap_encrypted(self, payload):
- out = MultipartDigestMessage('encrypted',
- protocol=MIMEWrapper._encrypted_type)
- out.preamble = MIMEWrapper._encryption_preamble
-
+ self.msg.set_payload([])
+ self.msg.set_type(MIMEWrapper._encrypted_multipart)
+ self.msg.set_param('protocol', MIMEWrapper._encrypted_type)
+ self.msg.preamble = MIMEWrapper._encryption_preamble
first_part = MIMEApplication(_data='Version: 1',
_subtype=MIMEWrapper._encryption_subtype,
_encoder=encode_7or8bit)
first_part.add_header('Content-Description',
'PGP/MIME version identification')
-
+ self.msg.attach(first_part)
second_part = MIMEApplication(_data=str(payload),
_subtype='octet-stream',
_encoder=encode_7or8bit,
@@ -387,9 +403,7 @@ class MIMEWrapper:
'OpenPGP encrypted message')
second_part.add_header('Content-Disposition', 'inline',
filename='encrypted.asc')
- out.attach(first_part)
- out.attach(second_part)
- return out
+ self.msg.attach(second_part)
def encrypt(self, *keys, cipher=SymmetricKeyAlgorithm.AES256,
**kwargs):
@@ -400,21 +414,25 @@ class MIMEWrapper:
:type keys: pgpy.PGPKey
:param cipher: The symmetric cipher to use.
:type cipher: pgpy.constants.SymmetricKeyAlgorithm
- :return: The encrypted message.
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: MIMEWrapper
"""
if len(keys) == 0:
raise ValueError('At least one key necessary.')
if self.is_signed():
+ # self.msg payload should be [ version_1, encrypted]
+ # headers should remain the same, except Content-Type
+ # signature should be combined into the PGP blob
pmsg = PGPMessage.new(next(iter(self.get_signed())))
pmsg |= next(iter(self.get_signature()))
else:
+ # self.msg payload should be [ version_1, encrypted]
+ # headers should remain the same, except Content-Type
pmsg = PGPMessage.new(next(iter(self.get_payload())))
pmsg = self._encrypt(pmsg, *keys, cipher=cipher, **kwargs)
- out = self._wrap_encrypted(pmsg)
- copy_headers(self.msg, out)
- return out
+ self._wrap_encrypted(pmsg)
+ return self
def sign_encrypt(self, key, *keys, hash=None,
cipher=SymmetricKeyAlgorithm.AES256,
@@ -432,8 +450,8 @@ class MIMEWrapper:
:type hash: pgpy.constants.HashAlgorithm
:param cipher:
:type cipher: pgpy.constants.SymmetricKeyAlgorithm
- :return: The signed + encrypted message.
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: MIMEWrapper
"""
if len(keys) == 0:
raise ValueError('At least one key necessary.')
@@ -442,36 +460,5 @@ class MIMEWrapper:
pmsg = PGPMessage.new(payload)
pmsg |= key.sign(pmsg, hash=hash)
pmsg = self._encrypt(pmsg, *keys, cipher=cipher, **kwargs)
- out = self._wrap_encrypted(pmsg)
- copy_headers(self.msg, out)
- return out
-
- def sign_then_encrypt(self, key, *keys, hash=None,
- cipher=SymmetricKeyAlgorithm.AES256,
- **kwargs):
- """
- Sign then encrypt the message.
-
- This is as per RFC 3156 section 6.1 - RFC 1847 Encapsulation.
-
- :param key: The key to sign with.
- :type key: pgpy.PGPKey
- :param keys: The key/s to encrypt with.
- :type keys: pgpy.PGPKey
- :param hash:
- :type hash: pgpy.constants.HashAlgorithm
- :param cipher:
- :type cipher: pgpy.constants.SymmetricKeyAlgorithm
- :return: The signed + encrypted message.
- :rtype: mailman.email.message.Message
- """
- if len(keys) == 0:
- raise ValueError('At least one key necessary.')
-
- out = self.sign(key, hash=hash)
- out_wrapped = MIMEWrapper(out)
- pmsg = PGPMessage.new(next(out_wrapped.get_payload()))
- pmsg = self._encrypt(pmsg, *keys, cipher=cipher, **kwargs)
- out = self._wrap_encrypted(pmsg)
- copy_headers(self.msg, out)
- return out
+ self._wrap_encrypted(pmsg)
+ return self
diff --git a/src/mailman_pgp/pgp/mime_multisig.py b/src/mailman_pgp/pgp/mime_multisig.py
index d7ad00a..1dbb73c 100644
--- a/src/mailman_pgp/pgp/mime_multisig.py
+++ b/src/mailman_pgp/pgp/mime_multisig.py
@@ -23,8 +23,8 @@ from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.utils import collapse_rfc2231_value
-from mailman.email.message import Message, MultipartDigestMessage
-from pgpy import PGPDetachedSignature, PGPSignature
+from mailman.email.message import Message
+from pgpy import PGPSignature
from mailman_pgp.pgp.mime import MIMEWrapper
from mailman_pgp.utils.email import copy_headers
@@ -74,30 +74,20 @@ class MIMEMultiSigWrapper(MIMEWrapper):
continue
yield sig
- def _wrap_signed_multiple(self, msg, payload_msg, sig_msgs, signatures,
- signature):
+ def sign(self, key, **kwargs):
"""
- As per draft-ietf-openpgp-multsig-02.
+ Sign a message with key.
- :param msg:
- :param payload_msg:
- :param sig_msgs:
- :param signatures:
- :param signature:
+ :param key: The key to sign with.
+ :type key: pgpy.PGPKey
:return:
+ :rtype: MIMEMultiSigWrapper
"""
- micalg = ', '.join(self._micalg(sig.hash_algorithm)
- for sig in signatures + signature.signatures)
- out = MultipartDigestMessage('signed', micalg=micalg,
- protocol='multipart/mixed')
- out.preamble = MIMEMultiSigWrapper._signature_preamble
- second_part = MIMEMultipart()
- for sig_msg in sig_msgs:
- second_part.attach(copy.deepcopy(sig_msg))
-
- for sig in signature.signatures:
- sig_part = MIMEApplication(_data=str(sig),
+ if self.is_signed():
+ signed = next(iter(self.get_signed()))
+ signature = key.sign(signed, **kwargs)
+ sig_part = MIMEApplication(_data=str(signature),
_subtype=MIMEWrapper._signature_subtype,
_encoder=encode_7or8bit,
name='signature.asc')
@@ -105,36 +95,34 @@ class MIMEMultiSigWrapper(MIMEWrapper):
'OpenPGP digital signature')
sig_part.add_header('Content-Disposition', 'attachment',
filename='signature.asc')
- second_part.attach(sig_part)
-
- out.attach(copy.deepcopy(payload_msg))
- out.attach(second_part)
- copy_headers(msg, out)
- return out
-
- def sign(self, key, **kwargs):
- """
- Sign a message with key.
-
- :param key: The key to sign with.
- :type key: pgpy.PGPKey
- :return: The signed message.
- :rtype: mailman.email.message.Message
- """
-
- if self.is_signed():
- payload_msg = self.msg.get_payload(0)
- sig_msgs = [part for part in self.msg.get_payload(1).get_payload()]
+ micalg = self.msg.get_param('micalg')
+ micalg += ',' + self._micalg(signature.hash_algorithm)
+ self.msg.set_param('micalg', micalg)
+ self.msg.get_payload(1).attach(sig_part)
else:
- payload_msg = self.msg
- sig_msgs = []
- # TODO: exception safe this
- signatures = [PGPSignature.from_blob(sig_msg.get_payload())
- for sig_msg in sig_msgs]
- signature = PGPDetachedSignature()
- signature |= key.sign(payload_msg.as_string(), **kwargs)
- return self._wrap_signed_multiple(self.msg, payload_msg, sig_msgs,
- signatures, signature)
+ original_msg = copy.deepcopy(self.msg)
+ to_sign = next(iter(self.get_payload()))
+ signature = key.sign(to_sign, **kwargs)
+ self.msg.set_payload([])
+ self.msg.attach(original_msg)
+ self.msg.set_type('multipart/signed')
+ self.msg['MIME-Version'] = '1.0'
+ self.msg.set_param('protocol', 'multipart/mixed')
+ self.msg.set_param('micalg',
+ self._micalg(signature.hash_algorithm))
+ sig_part = MIMEApplication(_data=str(signature),
+ _subtype=MIMEWrapper._signature_subtype,
+ _encoder=encode_7or8bit,
+ name='signature.asc')
+ sig_part.add_header('Content-Description',
+ 'OpenPGP digital signature')
+ sig_part.add_header('Content-Disposition', 'attachment',
+ filename='signature.asc')
+ second_part = MIMEMultipart()
+ second_part.attach(sig_part)
+ self.msg.attach(second_part)
+ self.msg.preamble = MIMEMultiSigWrapper._signature_preamble
+ return self
def verify(self, key):
"""
@@ -159,11 +147,10 @@ class MIMEMultiSigWrapper(MIMEWrapper):
:param key: The key to decrypt with.
:type key: pgpy.PGPKey
- :return: The decrypted message.
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: MIMEMultiSigWrapper
"""
pmsg = next(iter(self.get_encrypted()))
- # TODO: exception safe this
decrypted = key.decrypt(pmsg)
dmsg = decrypted.message
@@ -172,8 +159,30 @@ class MIMEMultiSigWrapper(MIMEWrapper):
out = message_from_string(dmsg, _class=Message)
if decrypted.is_signed:
- out = self._wrap_signed_multiple(self.msg, out, [], [],
- decrypted.detached_signature)
+ # result should be a multisig signed thing, so [out, ]
+ self.msg.set_payload([])
+ self.msg.attach(out)
+ self.msg.set_type('multipart/signed')
+ self.msg['MIME-Version'] = '1.0'
+ self.msg.set_param('protocol', 'multipart/mixed')
+ micalg = ', '.join(self._micalg(sig.hash_algorithm)
+ for sig in decrypted.signatures)
+ self.msg.set_param('micalg', micalg)
+ second_part = MIMEMultipart()
+ for signature in decrypted.signatures:
+ sig_part = MIMEApplication(_data=str(signature),
+ _subtype=MIMEWrapper._signature_subtype,
+ _encoder=encode_7or8bit,
+ name='signature.asc')
+ sig_part.add_header('Content-Description',
+ 'OpenPGP digital signature')
+ sig_part.add_header('Content-Disposition', 'attachment',
+ filename='signature.asc')
+ second_part.attach(sig_part)
+ self.msg.attach(second_part)
else:
- copy_headers(self.msg, out)
- return out
+ # result should be
+ self.msg.set_payload(out.get_payload())
+ copy_headers(out, self.msg, True)
+
+ return self
diff --git a/src/mailman_pgp/pgp/tests/test_wrapper.py b/src/mailman_pgp/pgp/tests/test_wrapper.py
index b9c157a..b49eece 100644
--- a/src/mailman_pgp/pgp/tests/test_wrapper.py
+++ b/src/mailman_pgp/pgp/tests/test_wrapper.py
@@ -242,5 +242,5 @@ class TestPGPWrapper(PGPWrapperTestCase):
def test_defaults(self):
PGPWrapper(self.msg)
for wrap_class in (InlineWrapper, MIMEWrapper, MIMEMultiSigWrapper):
- PGPWrapper(self.msg, wrap_class)
- self.assertRaises(ValueError, PGPWrapper, self.msg, 'Not a class')
+ PGPWrapper(self.msg, default=wrap_class)
+ self.assertRaises(ValueError, PGPWrapper, self.msg, default='Not a class')
diff --git a/src/mailman_pgp/pgp/wrapper.py b/src/mailman_pgp/pgp/wrapper.py
index b8ed264..f746229 100644
--- a/src/mailman_pgp/pgp/wrapper.py
+++ b/src/mailman_pgp/pgp/wrapper.py
@@ -15,11 +15,11 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
"""A combined PGP/MIME + inline PGP wrapper."""
-import copy
from pgpy.errors import PGPError
from public import public
+from mailman_pgp.pgp.base import BaseWrapper
from mailman_pgp.pgp.inline import InlineWrapper
from mailman_pgp.pgp.mime import MIMEWrapper
from mailman_pgp.pgp.mime_multisig import MIMEMultiSigWrapper
@@ -27,22 +27,25 @@ from mailman_pgp.utils.pgp import verifies
@public
-class PGPWrapper():
+class PGPWrapper(BaseWrapper):
"""A combined PGP/MIME + inline PGP wrapper."""
- def __init__(self, msg, default=MIMEWrapper):
+ def __init__(self, msg, copy=False, default=MIMEWrapper):
"""
Wrap the given message.
:param msg: The message to wrap.
:type msg: mailman.email.message.Message
- :param default:
+ :param copy: Whether to copy the message when wrapping.
+ :type copy: bool
+ :param default: The wrapper class used for active operations (sign,
+ encrypt, attach_keys, attach_revocs)
:type default: Type[MIMEWrapper|MIMEMultiSigWrapper|InlineWrapper]
"""
- self.msg = msg
- self.mime = MIMEWrapper(msg)
- self.inline = InlineWrapper(msg)
- self.multisig = MIMEMultiSigWrapper(msg)
+ super().__init__(msg, copy)
+ self.mime = MIMEWrapper(self.msg)
+ self.inline = InlineWrapper(self.msg)
+ self.multisig = MIMEMultiSigWrapper(self.msg)
self.wrappers = (self.mime, self.inline, self.multisig)
if default is MIMEWrapper:
self.default = self.mime
@@ -56,6 +59,10 @@ class PGPWrapper():
MIMEMultiSigWrapper.__name__ + ' ' +
InlineWrapper.__name__ + '.')
+ def _rewrap(self, wrapper):
+ if wrapper is not None:
+ return PGPWrapper(wrapper.msg, default=self.default.__class__)
+
def get_payload(self):
return self.default.get_payload()
@@ -111,10 +118,10 @@ class PGPWrapper():
:param key: The key to sign with.
:type key: pgpy.PGPKey
- :return: The signed message.
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: PGPWrapper
"""
- return self.default.sign(key, **kwargs)
+ return self._rewrap(self.default.sign(key, **kwargs))
def verify(self, key):
"""
@@ -172,12 +179,10 @@ class PGPWrapper():
:param keys: The key/s to encrypt with.
:type keys: pgpy.PGPKey
- :param cipher: The symmetric cipher to use.
- :type cipher: SymmetricKeyAlgorithm
- :return: The encrypted message.
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: PGPWrapper
"""
- return self.default.encrypt(*keys, **kwargs)
+ return self._rewrap(self.default.encrypt(*keys, **kwargs))
def decrypt(self, key):
"""
@@ -185,16 +190,18 @@ class PGPWrapper():
:param key: The key to decrypt with.
:type key: pgpy.PGPKey
- :return: The decrypted message.
:raises: pgpy.errors.PGPError
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: PGPWrapper
"""
+ result = None
if self.mime.is_encrypted():
- return self.mime.decrypt(key)
+ result = self.mime.decrypt(key)
elif self.multisig.is_encrypted():
- return self.multisig.decrypt(key)
+ result = self.multisig.decrypt(key)
elif self.inline.is_encrypted():
- return self.inline.decrypt(key)
+ result = self.inline.decrypt(key)
+ return self._rewrap(result)
def try_decrypt(self, key):
"""
@@ -204,12 +211,12 @@ class PGPWrapper():
:type key: pgpy.PGPKey
:return: The decrypted message, if successfully decrypted,
else original message.
- :rtype: mailman.email.message.Message
+ :rtype: PGPWrapper
"""
try:
- return self.decrypt(key)
+ return self._rewrap(self.decrypt(key))
except PGPError:
- return copy.deepcopy(self.msg)
+ return self
def sign_encrypt(self, key, *keys, **kwargs):
"""
@@ -223,27 +230,10 @@ class PGPWrapper():
:type hash: pgpy.constants.HashAlgorithm
:param cipher:
:type cipher: pgpy.constants.SymmetricKeyAlgorithm
- :return: The signed + encrypted message.
- :rtype: mailman.email.message.Message
- """
- return self.default.sign_encrypt(key, *keys, **kwargs)
-
- def sign_then_encrypt(self, key, *keys, **kwargs):
- """
- Sign then encrypt the message.
-
- :param key: The key to sign with.
- :type key: pgpy.PGPKey
- :param keys: The key/s to encrypt with.
- :type keys: pgpy.PGPKey
- :param hash:
- :type hash: pgpy.constants.HashAlgorithm
- :param cipher:
- :type cipher: pgpy.constants.SymmetricKeyAlgorithm
- :return: The signed + encrypted message.
- :rtype: mailman.email.message.Message
+ :return:
+ :rtype: PGPWrapper
"""
- return self.default.sign_then_encrypt(key, *keys, **kwargs)
+ return self._rewrap(self.default.sign_encrypt(key, *keys, **kwargs))
def is_keys(self):
"""