aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman_pgp/commands/eml_key.py14
-rw-r--r--src/mailman_pgp/config/mailman.cfg13
-rw-r--r--src/mailman_pgp/config/mailman_pgp.cfg7
-rw-r--r--src/mailman_pgp/pgp/inline.py10
-rw-r--r--src/mailman_pgp/pgp/mime.py14
-rw-r--r--src/mailman_pgp/pgp/mime_multisig.py6
-rw-r--r--src/mailman_pgp/pgp/wrapper.py2
-rw-r--r--src/mailman_pgp/rules/signature.py11
-rw-r--r--src/mailman_pgp/rules/tests/test_signature.py21
-rw-r--r--src/mailman_pgp/utils/pgp.py16
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