aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJ08nY2017-07-17 21:21:38 +0200
committerJ08nY2017-07-17 21:21:38 +0200
commit2703337c99eb62cb1ae8b516789f41732a4171d7 (patch)
treeeb42c6ab55466352db20f35e7e44a73510147d21 /src
parent20317c3444e0b11d8d4676dec23c34222d4d7340 (diff)
downloadmailman-pgp-2703337c99eb62cb1ae8b516789f41732a4171d7.tar.gz
mailman-pgp-2703337c99eb62cb1ae8b516789f41732a4171d7.tar.zst
mailman-pgp-2703337c99eb62cb1ae8b516789f41732a4171d7.zip
Diffstat (limited to 'src')
-rw-r--r--src/mailman_pgp/model/list.py30
-rw-r--r--src/mailman_pgp/model/sighash.py16
-rw-r--r--src/mailman_pgp/pgp/wrapper.py6
-rw-r--r--src/mailman_pgp/rules/signature.py25
-rw-r--r--src/mailman_pgp/rules/tests/test_signature.py42
-rw-r--r--src/mailman_pgp/utils/email.py5
-rw-r--r--src/mailman_pgp/utils/moderation.py3
-rw-r--r--src/mailman_pgp/utils/pgp.py49
-rw-r--r--src/mailman_pgp/workflows/key_change.py2
-rw-r--r--src/mailman_pgp/workflows/pubkey.py2
10 files changed, 137 insertions, 43 deletions
diff --git a/src/mailman_pgp/model/list.py b/src/mailman_pgp/model/list.py
index 8b2b5bc..9bee16d 100644
--- a/src/mailman_pgp/model/list.py
+++ b/src/mailman_pgp/model/list.py
@@ -44,36 +44,24 @@ class PGPMailingList(Base):
list_id = Column(SAUnicode, index=True)
# Signature related properties
- unsigned_msg_action = Column(Enum(Action))
- inline_pgp_action = Column(Enum(Action))
- expired_sig_action = Column(Enum(Action))
- revoked_sig_action = Column(Enum(Action))
- # duplicate_sig_action = Column(Enum(Action))
- invalid_sig_action = Column(Enum(Action))
- strip_original_sig = Column(Boolean)
- sign_outgoing = Column(Boolean)
+ unsigned_msg_action = Column(Enum(Action), default=Action.reject)
+ inline_pgp_action = Column(Enum(Action), default=Action.defer)
+ expired_sig_action = Column(Enum(Action), default=Action.reject)
+ revoked_sig_action = Column(Enum(Action), default=Action.reject)
+ invalid_sig_action = Column(Enum(Action), default=Action.reject)
+ duplicate_sig_action = Column(Enum(Action), default=Action.reject)
+ strip_original_sig = Column(Boolean, default=False)
+ sign_outgoing = Column(Boolean, default=False)
# Encryption related properties
- nonencrypted_msg_action = Column(Enum(Action))
+ nonencrypted_msg_action = Column(Enum(Action), default=Action.reject)
def __init__(self, mlist):
super().__init__()
self._init()
- self._defaults()
self.list_id = mlist.list_id
self._mlist = mlist
- def _defaults(self):
- self.unsigned_msg_action = Action.reject
- self.inline_pgp_action = Action.defer
- self.expired_sig_action = Action.reject
- self.revoked_sig_action = Action.reject
- self.invalid_sig_action = Action.reject
- self.strip_original_sig = False
- self.sign_outgoing = False
-
- self.nonencrypted_msg_action = Action.reject
-
@reconstructor
def _init(self):
self._mlist = None
diff --git a/src/mailman_pgp/model/sighash.py b/src/mailman_pgp/model/sighash.py
index 2dddc02..6511484 100644
--- a/src/mailman_pgp/model/sighash.py
+++ b/src/mailman_pgp/model/sighash.py
@@ -16,7 +16,7 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
""""""
-from sqlalchemy import Column, DateTime, LargeBinary, String
+from sqlalchemy import Column, LargeBinary, String
from mailman_pgp.model.base import Base
@@ -24,19 +24,13 @@ from mailman_pgp.model.base import Base
class PGPSigHash(Base):
""""""
- __tablename__ = "sighash"
+ __tablename__ = 'sighash'
hash = Column(LargeBinary, primary_key=True)
fingerprint = Column(String(50), index=True)
- time = Column(DateTime)
@staticmethod
- def find(hash=None, fingerprint=None):
- kws = {}
- if hash is not None:
- kws['hash'] = hash
- if fingerprint is not None:
- kws['fingerprint'] = fingerprint
- if len(kws) == 0:
+ def hashes(hashes):
+ if hashes is None or len(hashes) == 0:
return None
- return PGPSigHash.query().filter_by(**kws).all()
+ return PGPSigHash.query().filter(PGPSigHash.hash.in_(hashes)).all()
diff --git a/src/mailman_pgp/pgp/wrapper.py b/src/mailman_pgp/pgp/wrapper.py
index f5cc8e1..cbfa7f2 100644
--- a/src/mailman_pgp/pgp/wrapper.py
+++ b/src/mailman_pgp/pgp/wrapper.py
@@ -20,6 +20,7 @@ from public import public
from mailman_pgp.pgp.inline import InlineWrapper
from mailman_pgp.pgp.mime import MIMEWrapper
+from mailman_pgp.utils.pgp import verifies
@public
@@ -108,10 +109,7 @@ class PGPWrapper():
yield from self.inline.verify(key)
def verifies(self, key):
- return all(bool(verification) and
- all(not sigsubj.signature.is_expired
- for sigsubj in verification.good_signatures) for
- verification in self.verify(key))
+ return verifies(self.verify(key))
def is_encrypted(self):
"""
diff --git a/src/mailman_pgp/rules/signature.py b/src/mailman_pgp/rules/signature.py
index 55b9b87..395dd7d 100644
--- a/src/mailman_pgp/rules/signature.py
+++ b/src/mailman_pgp/rules/signature.py
@@ -17,6 +17,7 @@
"""Signature checking rule for the pgp-posting-chain."""
from email.utils import parseaddr
+from operator import attrgetter
from mailman.core.i18n import _
from mailman.interfaces.action import Action
@@ -28,8 +29,10 @@ from zope.interface import implementer
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.wrapper import PGPWrapper
from mailman_pgp.utils.moderation import record_action
+from mailman_pgp.utils.pgp import hashes, verifies
@public
@@ -89,14 +92,34 @@ class Signature:
'No key set for address {}.'.format(email))
return True
+ if not pgp_address.key_confirmed:
+ record_action(msg, msgdata, Action.reject, email,
+ 'Key not confirmed.')
+ return True
+
+ verifications = list(wrapped.verify(key))
+
# Take the `invalid_sig_action` if the verification failed.
- if not wrapped.verifies(key):
+ if not verifies(verifications):
action = pgp_list.invalid_sig_action
if action != Action.defer:
record_action(msg, msgdata, action, email,
'Signature did not verify.')
return True
+ sig_hashes = set(hashes(verifications))
+ duplicates = set(PGPSigHash.hashes(sig_hashes))
+ if duplicates:
+ fingerprints = map(attrgetter('fingerprint'), duplicates)
+ if key.fingerprint in fingerprints:
+ action = pgp_list.duplicate_sig_action
+ if action != Action.defer:
+ record_action(msg, msgdata, action, email,
+ 'Signature duplicate.')
+ return True
+
+ # TODO: add the sig hashes to the db.
+
# XXX: we need to track key revocation separately to use it here
# TODO: check key revocation here
diff --git a/src/mailman_pgp/rules/tests/test_signature.py b/src/mailman_pgp/rules/tests/test_signature.py
index f5c5dc3..d8d0537 100644
--- a/src/mailman_pgp/rules/tests/test_signature.py
+++ b/src/mailman_pgp/rules/tests/test_signature.py
@@ -28,9 +28,12 @@ from mailman_pgp.config import mm_config
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.tests.base import load_key, load_message
+from mailman_pgp.pgp.wrapper import PGPWrapper
from mailman_pgp.rules.signature import Signature
from mailman_pgp.testing.layers import PGPConfigLayer
+from mailman_pgp.utils.pgp import hashes
class TestSignatureRule(TestCase):
@@ -49,10 +52,11 @@ class TestSignatureRule(TestCase):
self.pgp_list = PGPMailingList.for_list(self.mlist)
- sender_key = load_key('rsa_1024.pub.asc')
+ self.sender_key = load_key('rsa_1024.priv.asc')
with transaction() as t:
self.pgp_sender = PGPAddress(self.sender.preferred_address)
- self.pgp_sender.key = sender_key
+ self.pgp_sender.key = self.sender_key.pubkey
+ self.pgp_sender.key_confirmed = True
t.add(self.pgp_sender)
self.msg_clear = load_message('clear.eml')
@@ -98,7 +102,7 @@ To: test@example.com
self.assertTrue(matches)
self.assertAction(msgdata, Action.reject, [
'No key set for address {}.'.format(
- self.pgp_sender.address.original_email)])
+ self.pgp_sender.address.original_email)])
def assertAction(self, msgdata, action, reasons):
self.assertEqual(msgdata['moderation_action'], action.name)
@@ -111,6 +115,7 @@ To: test@example.com
self.pgp_list.expired_sig_action = Action.defer
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 = {}
matches = self.rule.check(self.mlist, self.msg_clear, msgdata)
@@ -130,6 +135,7 @@ To: test@example.com
self.pgp_list.expired_sig_action = Action.defer
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 = {}
matches = self.rule.check(self.mlist, self.msg_inline_signed, msgdata)
@@ -146,6 +152,7 @@ To: test@example.com
self.pgp_list.expired_sig_action = Action.defer
self.pgp_list.invalid_sig_action = Action.hold
self.pgp_list.revoked_sig_action = Action.defer
+ self.pgp_list.duplicate_sig_action = Action.defer
msgdata = {}
matches = self.rule.check(self.mlist, self.msg_inline_signed_invalid,
@@ -158,3 +165,32 @@ To: test@example.com
msgdata)
self.assertTrue(matches)
self.assertAction(msgdata, Action.hold, ['Signature did not verify.'])
+
+ def test_duplicate_sig_action(self):
+ with transaction() as t:
+ self.pgp_list.unsigned_msg_action = Action.defer
+ self.pgp_list.inline_pgp_action = Action.defer
+ self.pgp_list.expired_sig_action = Action.defer
+ self.pgp_list.invalid_sig_action = Action.defer
+ self.pgp_list.revoked_sig_action = Action.defer
+ self.pgp_list.duplicate_sig_action = Action.hold
+
+ wrapped = PGPWrapper(self.msg_mime_signed)
+ sig_hashes = set(hashes(wrapped.verify(self.sender_key.pubkey)))
+ wrapped = PGPWrapper(self.msg_inline_signed)
+ sig_hashes |= set(hashes(wrapped.verify(self.sender_key.pubkey)))
+ for hash in sig_hashes:
+ sig_hash = PGPSigHash()
+ sig_hash.hash = hash
+ sig_hash.fingerprint = self.sender_key.pubkey.fingerprint
+ t.add(sig_hash)
+
+ msgdata = {}
+ matches = self.rule.check(self.mlist, self.msg_mime_signed, msgdata)
+ self.assertTrue(matches)
+ self.assertAction(msgdata, Action.hold, ['Signature duplicate.'])
+
+ msgdata = {}
+ matches = self.rule.check(self.mlist, self.msg_inline_signed, msgdata)
+ self.assertTrue(matches)
+ self.assertAction(msgdata, Action.hold, ['Signature duplicate.'])
diff --git a/src/mailman_pgp/utils/email.py b/src/mailman_pgp/utils/email.py
index 4d08d8a..4541313 100644
--- a/src/mailman_pgp/utils/email.py
+++ b/src/mailman_pgp/utils/email.py
@@ -16,7 +16,10 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
""""""
+from public import public
+
+@public
def copy_headers(from_msg, to_msg, overwrite=False):
"""
Copy the headers and unixfrom from a message to another one.
@@ -32,4 +35,4 @@ def copy_headers(from_msg, to_msg, overwrite=False):
if key not in to_msg:
to_msg[key] = value
if to_msg.get_unixfrom() is None:
- to_msg.set_unixfrom(from_msg.get_unixfrom()) \ No newline at end of file
+ to_msg.set_unixfrom(from_msg.get_unixfrom())
diff --git a/src/mailman_pgp/utils/moderation.py b/src/mailman_pgp/utils/moderation.py
index a27ec48..850f885 100644
--- a/src/mailman_pgp/utils/moderation.py
+++ b/src/mailman_pgp/utils/moderation.py
@@ -16,7 +16,10 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
""""""
+from public import public
+
+@public
def record_action(msg, msgdata, action, sender, reason):
"""
diff --git a/src/mailman_pgp/utils/pgp.py b/src/mailman_pgp/utils/pgp.py
new file mode 100644
index 0000000..7b2f958
--- /dev/null
+++ b/src/mailman_pgp/utils/pgp.py
@@ -0,0 +1,49 @@
+# 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/>.
+
+"""Miscellaneous PGP utilities."""
+from public import public
+
+
+@public
+def verifies(verifications):
+ """
+
+ :param verifications:
+ :type verifications: Sequence[pgpy.types.SignatureVerification]
+ :return: bool
+ """
+ return all(bool(verification) and
+ all(not sigsubj.signature.is_expired
+ for sigsubj in verification.good_signatures) for
+ verification in verifications)
+
+
+@public
+def hashes(verifications):
+ """
+
+ :param verifications:
+ :return:
+ :rtype: Generator[str]
+ """
+ for verification in verifications:
+ for sigsubj in verification.good_signatures:
+ data = sigsubj.signature.hashdata(sigsubj.subject)
+ hasher = sigsubj.signature.hash_algorithm.hasher
+ hasher.update(data)
+ yield hasher.digest()
diff --git a/src/mailman_pgp/workflows/key_change.py b/src/mailman_pgp/workflows/key_change.py
index 50a6c5b..01693f5 100644
--- a/src/mailman_pgp/workflows/key_change.py
+++ b/src/mailman_pgp/workflows/key_change.py
@@ -29,8 +29,8 @@ from zope.interface import implementer
from mailman_pgp.database import transaction
from mailman_pgp.model.address import PGPAddress
from mailman_pgp.model.list import PGPMailingList
-from mailman_pgp.utils.email import copy_headers
from mailman_pgp.pgp.wrapper import PGPWrapper
+from mailman_pgp.utils.email import copy_headers
CHANGE_CONFIRM_REQUEST = """\
----------
diff --git a/src/mailman_pgp/workflows/pubkey.py b/src/mailman_pgp/workflows/pubkey.py
index 8196eef..a13d491 100644
--- a/src/mailman_pgp/workflows/pubkey.py
+++ b/src/mailman_pgp/workflows/pubkey.py
@@ -5,8 +5,8 @@ from pgpy import PGPKey
from mailman_pgp.database import transaction
from mailman_pgp.model.address import PGPAddress
from mailman_pgp.model.list import PGPMailingList
-from mailman_pgp.utils.email import copy_headers
from mailman_pgp.pgp.wrapper import PGPWrapper
+from mailman_pgp.utils.email import copy_headers
KEY_REQUEST = """\
----------