diff options
| -rw-r--r-- | src/mailman_pgp/commands/eml_key.py | 14 | ||||
| -rw-r--r-- | src/mailman_pgp/config/mailman.cfg | 13 | ||||
| -rw-r--r-- | src/mailman_pgp/config/mailman_pgp.cfg | 7 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/inline.py | 10 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/mime.py | 14 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/mime_multisig.py | 6 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/wrapper.py | 2 | ||||
| -rw-r--r-- | src/mailman_pgp/rules/signature.py | 11 | ||||
| -rw-r--r-- | src/mailman_pgp/rules/tests/test_signature.py | 21 | ||||
| -rw-r--r-- | src/mailman_pgp/utils/pgp.py | 16 |
10 files changed, 86 insertions, 28 deletions
diff --git a/src/mailman_pgp/commands/eml_key.py b/src/mailman_pgp/commands/eml_key.py index 97a1a6f..8d140f4 100644 --- a/src/mailman_pgp/commands/eml_key.py +++ b/src/mailman_pgp/commands/eml_key.py @@ -489,12 +489,24 @@ class KeyCommand: A command used to change the address public key. `key revoke` + A command used to revoke a part of/the whole address public key, when the + user has access to a revocation certificate (but not the signing + capability of the key to use `key change`). A revocation certificate + must be attached to the message like a PGP key would. The revocation + signature is verified, and the proper revocation is performed, if the + key is left unusable after that, you will be prompted to set and confirm + a new one. `key sign` + A command used to add your signature to the list key, if you trust it. It + requires exactly one PGP public key attached, which should be the list + public key, signed with your address key. It is scanned for all new + certifications by your address key and the valid ones are imported. A + list might be configured to allow even a non-subscriber to sign its key. `key receive` A command used to request the list public key. The list public key will - be send in a response. + be sent in a response. """ def process(self, mlist, msg, msgdata, arguments, results): diff --git a/src/mailman_pgp/config/mailman.cfg b/src/mailman_pgp/config/mailman.cfg index 24dc3bc..b2adfe8 100644 --- a/src/mailman_pgp/config/mailman.cfg +++ b/src/mailman_pgp/config/mailman.cfg @@ -15,19 +15,30 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see <http://www.gnu.org/licenses/>. -# Example additions to mailman.cfg to enable PGP +# Example additions to mailman.cfg to enable mailman-pgp. +# Setup the mailman-pgp plugin under the `pgp` name. To use the django-pgpmailman +# web UI. The `MAILMAN_PGP_PLUGIN_NAME` in its project settings.py must be set +# to the name of the plugin, as thats where Mailman roots the plugins REST api +# endpoint. [plugin.pgp] class: mailman_pgp.plugin.PGPMailman path: mailman_pgp enable: yes configuration: python:mailman_pgp.config.mailman_pgp +# Use the custom PGP enabled deliver callable, performs the signing and encryption +# on PGP enabled lists which are configured to do so. [mta] outgoing: mailman_pgp.mta.deliver.deliver +# Use the custom PGP enabled runner on the default `in` queue. [runner.in] class: mailman_pgp.runners.incoming.PGPIncomingRunner +# This runners name needs to be the same as the `[queues].in` config option in +# the mailman-pgp config file. It runs the default IncomingRunner on a queue +# of a different name, so that messages come into the mailman-pgp incoming runner +# and can be then passed to the default incoming runner, defined here. [runner.in_default] class: mailman.runners.incoming.IncomingRunner diff --git a/src/mailman_pgp/config/mailman_pgp.cfg b/src/mailman_pgp/config/mailman_pgp.cfg index 4c52e10..5db31dd 100644 --- a/src/mailman_pgp/config/mailman_pgp.cfg +++ b/src/mailman_pgp/config/mailman_pgp.cfg @@ -75,11 +75,12 @@ shred: yes # similar. shred_command: -# Delete list keypair on list deletion? +# Delete list keypair on list deletion. delete: yes [queues] -# The queue to which processed incoming messages are passed. +# The queue to which processed incoming messages are passed. Must be a name of +# a queue which is managed by the Mailman IncomingRunner. in: in_default @@ -94,7 +95,7 @@ change_request_lifetime: 1d # to export the list private key. allow_read_private_key: yes -# Allow the modification of a list private key through the REST API? +# Allow the modification of a list private key through the REST API. # This is necessary for the django-pgpmailman web ui to allow a list owner # to change the list private key. allow_write_private_key: yes diff --git a/src/mailman_pgp/pgp/inline.py b/src/mailman_pgp/pgp/inline.py index 49f0a6e..fa8f878 100644 --- a/src/mailman_pgp/pgp/inline.py +++ b/src/mailman_pgp/pgp/inline.py @@ -239,19 +239,17 @@ class InlineWrapper: """ yield from map(key.verify, self.get_signature()) - def _sign(self, pmsg, key, hash): + def _sign(self, pmsg, key, **kwargs): smsg = copy.copy(pmsg) - smsg |= key.sign(smsg, hash=hash) + smsg |= key.sign(smsg, **kwargs) return smsg - def sign(self, key, hash=None): + def sign(self, key, **kwargs): """ Sign a message with key. :param key: The key to sign with. :type key: pgpy.PGPKey - :param hash: The hash algorithm to use. - :type hash: pgpy.constants.HashAlgorithm :return: The signed message. :rtype: mailman.email.message.Message """ @@ -263,7 +261,7 @@ class InlineWrapper: else: payload = str(part.get_payload()) pmsg = PGPMessage.new(payload, cleartext=True) - smsg = self._sign(pmsg, key, hash) + smsg = self._sign(pmsg, key, **kwargs) part.set_payload(str(smsg)) return out diff --git a/src/mailman_pgp/pgp/mime.py b/src/mailman_pgp/pgp/mime.py index 878fe59..eeeed95 100644 --- a/src/mailman_pgp/pgp/mime.py +++ b/src/mailman_pgp/pgp/mime.py @@ -24,7 +24,7 @@ from email.mime.application import MIMEApplication from email.utils import collapse_rfc2231_value from mailman.email.message import Message, MultipartDigestMessage -from pgpy import PGPDetachedSignature, PGPMessage +from pgpy import PGPMessage, PGPSignature from pgpy.constants import HashAlgorithm, SymmetricKeyAlgorithm from public import public @@ -99,10 +99,10 @@ class MIMEWrapper: """ :return: - :rtype: typing.Generator[pgpy.PGPDetachedSignature] + :rtype: typing.Generator[pgpy.PGPSignature] """ try: - sig = PGPDetachedSignature.from_blob( + sig = PGPSignature.from_blob( self.msg.get_payload(1).get_payload()) except: return @@ -319,19 +319,17 @@ class MIMEWrapper: copy_headers(msg, out) return out - def sign(self, key, hash=None): + def sign(self, key, **kwargs): """ Sign a message with key. :param key: The key to sign with. :type key: pgpy.PGPKey - :param hash: - :type hash: pgpy.constants.HashAlgorithm :return: The signed message. :rtype: mailman.email.message.Message """ payload = next(iter(self.get_payload())) - signature = key.sign(payload, hash=hash) + signature = key.sign(payload, **kwargs) return self._wrap_signed(self.msg, signature) def decrypt(self, key): @@ -470,7 +468,7 @@ class MIMEWrapper: if len(keys) == 0: raise ValueError('At least one key necessary.') - out = self.sign(key, hash) + 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) diff --git a/src/mailman_pgp/pgp/mime_multisig.py b/src/mailman_pgp/pgp/mime_multisig.py index 1061b64..d7ad00a 100644 --- a/src/mailman_pgp/pgp/mime_multisig.py +++ b/src/mailman_pgp/pgp/mime_multisig.py @@ -112,14 +112,12 @@ class MIMEMultiSigWrapper(MIMEWrapper): copy_headers(msg, out) return out - def sign(self, key, hash=None): + def sign(self, key, **kwargs): """ Sign a message with key. :param key: The key to sign with. :type key: pgpy.PGPKey - :param hash: - :type hash: pgpy.constants.HashAlgorithm :return: The signed message. :rtype: mailman.email.message.Message """ @@ -134,7 +132,7 @@ class MIMEMultiSigWrapper(MIMEWrapper): signatures = [PGPSignature.from_blob(sig_msg.get_payload()) for sig_msg in sig_msgs] signature = PGPDetachedSignature() - signature |= key.sign(payload_msg.as_string(), hash=hash) + signature |= key.sign(payload_msg.as_string(), **kwargs) return self._wrap_signed_multiple(self.msg, payload_msg, sig_msgs, signatures, signature) diff --git a/src/mailman_pgp/pgp/wrapper.py b/src/mailman_pgp/pgp/wrapper.py index f0519cb..b8ed264 100644 --- a/src/mailman_pgp/pgp/wrapper.py +++ b/src/mailman_pgp/pgp/wrapper.py @@ -111,8 +111,6 @@ class PGPWrapper(): :param key: The key to sign with. :type key: pgpy.PGPKey - :param hash: - :type hash: HashAlgorithm :return: The signed message. :rtype: mailman.email.message.Message """ diff --git a/src/mailman_pgp/rules/signature.py b/src/mailman_pgp/rules/signature.py index 998e9c3..b7d4b5c 100644 --- a/src/mailman_pgp/rules/signature.py +++ b/src/mailman_pgp/rules/signature.py @@ -35,7 +35,7 @@ from mailman_pgp.model.sighash import PGPSigHash from mailman_pgp.pgp.wrapper import PGPWrapper from mailman_pgp.utils.email import get_email from mailman_pgp.utils.moderation import record_action -from mailman_pgp.utils.pgp import hashes, verifies +from mailman_pgp.utils.pgp import hashes, verifies, expired @public @@ -96,6 +96,15 @@ class Signature: return True verifications = list(wrapped.verify(key)) + # verifications is a list of SignatureVerification, only contains + # sigs that appear to be by the pgp_address.key + + if expired(verifications): + action = pgp_list.expired_sig_action + if action != Action.defer: + record_action(msg, msgdata, action, email, + 'Signature is expired.') + return True # Take the `invalid_sig_action` if the verification failed. if not verifies(verifications): diff --git a/src/mailman_pgp/rules/tests/test_signature.py b/src/mailman_pgp/rules/tests/test_signature.py index ff9b9c4..72f2c70 100644 --- a/src/mailman_pgp/rules/tests/test_signature.py +++ b/src/mailman_pgp/rules/tests/test_signature.py @@ -16,6 +16,8 @@ # this program. If not, see <http://www.gnu.org/licenses/>. """""" +import time +from datetime import timedelta from unittest import TestCase from mailman.app.lifecycle import create_list @@ -35,6 +37,7 @@ from mailman_pgp.database import mm_transaction, transaction from mailman_pgp.model.address import PGPAddress from mailman_pgp.model.list import PGPMailingList from mailman_pgp.model.sighash import PGPSigHash +from mailman_pgp.pgp.mime import MIMEWrapper from mailman_pgp.pgp.wrapper import PGPWrapper from mailman_pgp.rules.signature import Signature from mailman_pgp.testing.layers import PGPConfigLayer @@ -160,6 +163,24 @@ To: test@example.com matches = self.rule.check(self.mlist, self.msg_mime_signed, msgdata) self.assertFalse(matches) + def test_expired_sig_action(self): + with transaction(): + self.pgp_list.unsigned_msg_action = Action.defer + self.pgp_list.inline_pgp_action = Action.defer + self.pgp_list.expired_sig_action = Action.hold + self.pgp_list.invalid_sig_action = Action.defer + self.pgp_list.revoked_sig_action = Action.defer + self.pgp_list.duplicate_sig_action = Action.defer + + msgdata = {} + wrapped = MIMEWrapper(self.msg_clear) + msg = wrapped.sign(self.sender_key, expires=timedelta(seconds=1)) + time.sleep(2) + matches = self.rule.check(self.mlist, msg, msgdata) + + self.assertTrue(matches) + self.assertAction(msgdata, Action.hold, ['Signature is expired.']) + def test_invalid_sig_action(self): with transaction(): self.pgp_list.unsigned_msg_action = Action.defer diff --git a/src/mailman_pgp/utils/pgp.py b/src/mailman_pgp/utils/pgp.py index eec7b67..c44a27e 100644 --- a/src/mailman_pgp/utils/pgp.py +++ b/src/mailman_pgp/utils/pgp.py @@ -25,6 +25,18 @@ from public import public @public +def expired(verifications): + """ + + :param verifications: + :return: + """ + return any(any(sigsubj.signature.is_expired + for sigsubj in verification.good_signatures) + for verification in verifications) + + +@public def verifies(verifications): """ @@ -34,8 +46,8 @@ def verifies(verifications): """ return all(bool(verification) and all(not sigsubj.signature.is_expired - for sigsubj in verification.good_signatures) for - verification in verifications) + for sigsubj in verification.good_signatures) + for verification in verifications) @public |
