diff options
Diffstat (limited to 'src/mailman_pgp/pgp')
| -rw-r--r-- | src/mailman_pgp/pgp/base.py | 43 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/inline.py | 77 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/mime.py | 195 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/mime_multisig.py | 123 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/tests/test_wrapper.py | 4 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/wrapper.py | 78 |
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): """ |
