aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman_pgp/chains/default.py3
-rw-r--r--src/mailman_pgp/commands/eml_key.py2
-rw-r--r--src/mailman_pgp/config/__init__.py11
-rw-r--r--src/mailman_pgp/database/__init__.py23
-rw-r--r--src/mailman_pgp/model/address.py8
-rw-r--r--src/mailman_pgp/model/base.py9
-rw-r--r--src/mailman_pgp/model/list.py15
-rw-r--r--src/mailman_pgp/pgp/inline.py47
-rw-r--r--src/mailman_pgp/pgp/keygen.py18
-rw-r--r--src/mailman_pgp/pgp/mime.py40
-rw-r--r--src/mailman_pgp/pgp/wrapper.py34
-rw-r--r--src/mailman_pgp/rules/signature.py2
12 files changed, 160 insertions, 52 deletions
diff --git a/src/mailman_pgp/chains/default.py b/src/mailman_pgp/chains/default.py
index e55e0d3..8fc22bf 100644
--- a/src/mailman_pgp/chains/default.py
+++ b/src/mailman_pgp/chains/default.py
@@ -15,7 +15,8 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
-""""""
+"""A PGP enabled posting chain."""
+
from mailman.chains.base import Link
from mailman.core.i18n import _
from mailman.interfaces.chain import IChain, LinkAction
diff --git a/src/mailman_pgp/commands/eml_key.py b/src/mailman_pgp/commands/eml_key.py
index a2194d0..9514f48 100644
--- a/src/mailman_pgp/commands/eml_key.py
+++ b/src/mailman_pgp/commands/eml_key.py
@@ -25,6 +25,8 @@ from zope.interface import implementer
@public
@implementer(IEmailCommand)
class KeyCommand:
+ """The `key` command."""
+
name = 'key'
argument_description = '<change|revoke|sign>'
short_description = ''
diff --git a/src/mailman_pgp/config/__init__.py b/src/mailman_pgp/config/__init__.py
index c3d5719..803b0f9 100644
--- a/src/mailman_pgp/config/__init__.py
+++ b/src/mailman_pgp/config/__init__.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
-""""""
+"""Mailman PGP configuration module."""
from configparser import ConfigParser
@@ -26,10 +26,15 @@ from public.public import public
@public
class Config(ConfigParser):
- def __init__(self):
- super().__init__()
+ """A ConfigParser with a name."""
def load(self, name):
+ """
+ Load the plugin configuration, and set our name.
+
+ :param name: The name to set/load configuration for.
+ :type name: str
+ """
self.name = name
self.read(expand_path(
dict(mailman_config.plugin_configs)[self.name].configuration))
diff --git a/src/mailman_pgp/database/__init__.py b/src/mailman_pgp/database/__init__.py
index 950c6b1..bbb7840 100644
--- a/src/mailman_pgp/database/__init__.py
+++ b/src/mailman_pgp/database/__init__.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
-""""""
+"""Common database functions and class."""
from contextlib import contextmanager
@@ -31,6 +31,8 @@ from mailman_pgp.model.base import Base
@public
class Database:
+ """A SQLAlchemy database."""
+
def __init__(self):
url = config.get('db', 'url')
self._url = expand(url, None, mailman_config.paths)
@@ -41,12 +43,24 @@ class Database:
@property
def session(self):
+ """
+ Get a scoped_session.
+
+ :return: A scoped session.
+ :rtype: scoped_session
+ """
return self._scoped_session()
@public
@contextmanager
def transaction():
+ """
+ A transaction context manager.
+
+ :return: A session for convenience.
+ :rtype: scoped_session
+ """
try:
yield config.db.session
except:
@@ -58,4 +72,11 @@ def transaction():
@public
def query(cls):
+ """
+ A query helper.
+
+ :param cls: Class to query.
+ :return: A query on the class.
+ :rtype: sqlalchemy.orm.query.Query
+ """
return config.db.session.query(cls)
diff --git a/src/mailman_pgp/model/address.py b/src/mailman_pgp/model/address.py
index 60c6e2f..1dc3840 100644
--- a/src/mailman_pgp/model/address.py
+++ b/src/mailman_pgp/model/address.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
-""""""
+"""Model for PGP enabled addresses."""
from os.path import exists, isfile, join
@@ -29,6 +29,8 @@ from mailman_pgp.model.base import Base
class PGPAddress(Base):
+ """A PGP enabled address."""
+
__tablename__ = 'pgp_addresses'
id = Column(Integer, primary_key=True)
@@ -46,6 +48,8 @@ class PGPAddress(Base):
@property
def key(self):
+ if self.key_fingerprint is None:
+ return None
if self._key is None:
if exists(self.key_path) and isfile(self.key_path):
self._key, _ = PGPKey.from_file(self.key_path)
@@ -53,5 +57,7 @@ class PGPAddress(Base):
@property
def key_path(self):
+ if self.key_fingerprint is None:
+ return None
return join(config.pgp.keydir_config['user_keydir'],
self.key_fingerprint + '.asc')
diff --git a/src/mailman_pgp/model/base.py b/src/mailman_pgp/model/base.py
index 646bb7d..f5d8e77 100644
--- a/src/mailman_pgp/model/base.py
+++ b/src/mailman_pgp/model/base.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
-""""""
+"""Base class for all models."""
from public import public
from sqlalchemy.ext.declarative import as_declarative
@@ -26,9 +26,16 @@ from mailman_pgp.database import query
@public
@as_declarative()
class Base:
+ """Custom declarative base."""
@classmethod
def query(cls):
+ """
+ A query helper.
+
+ :return: A query on class.
+ :rtype: sqlalchemy.orm.query.Query
+ """
return query(cls)
diff --git a/src/mailman_pgp/model/list.py b/src/mailman_pgp/model/list.py
index 84dd1a8..e1de7cc 100644
--- a/src/mailman_pgp/model/list.py
+++ b/src/mailman_pgp/model/list.py
@@ -15,18 +15,18 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
-""""""
+"""Model for PGP enabled mailing lists."""
from os.path import exists, isfile, join
-from mailman.config import config as mailman_config
from mailman.database.types import Enum, SAUnicode
from mailman.interfaces.action import Action
-from mailman.model.mailinglist import MailingList
+from mailman.interfaces.listmanager import IListManager
from pgpy import PGPKey
from public import public
from sqlalchemy import Boolean, Column, Integer
from sqlalchemy.orm import reconstructor
+from zope.component import getUtility
from mailman_pgp.config import config
from mailman_pgp.model.base import Base
@@ -35,6 +35,8 @@ from mailman_pgp.pgp.keygen import ListKeyGenerator
@public
class PGPMailingList(Base):
+ """A PGP enabled mailing list."""
+
__tablename__ = 'pgp_lists'
id = Column(Integer, primary_key=True)
@@ -76,10 +78,9 @@ class PGPMailingList(Base):
@property
def mlist(self):
- if self._mlist is not None:
- return self._mlist
- return mailman_config.db.store.query(MailingList).filter_by(
- _list_id=self.list_id).first()
+ if self._mlist is None:
+ self._mlist = getUtility(IListManager).get_by_list_id(self.list_id)
+ return self._mlist
@property
def key(self):
diff --git a/src/mailman_pgp/pgp/inline.py b/src/mailman_pgp/pgp/inline.py
index 646d13a..d87b3da 100644
--- a/src/mailman_pgp/pgp/inline.py
+++ b/src/mailman_pgp/pgp/inline.py
@@ -16,16 +16,23 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
"""Strict inline PGP message wrapper."""
-from email.message import Message
-from pgpy import PGPKey, PGPMessage
+from pgpy import PGPMessage
from pgpy.types import Armorable
from public import public
@public
class InlineWrapper:
- def __init__(self, msg: Message):
+ """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 _is_inline(self):
@@ -45,32 +52,50 @@ class InlineWrapper:
def is_signed(self):
"""
+ Whether the message is inline signed (cleartext).
- :return:
+ :return: If the message is inline signed.
+ :rtype: bool
"""
+ # XXX: This doesnt handle non-cleartext signatures (gpg -s -a)
return self._is_inline() and self._has_armor('SIGNATURE')
def is_encrypted(self):
"""
+ Whether the message is inline encrypted.
- :return:
+ :return: If the message is inline encrypted.
+ :rtype: bool
"""
+ # XXX: This mistakes non-cleartext signature as encrypted.
return self._is_inline() and self._has_armor('MESSAGE')
- def verify(self, key: PGPKey):
+ def verify(self, key):
"""
+ Verify the signature of this message with key.
- :param key:
- :return:
+ :param key: The key to verify with.
+ :type key: pgpy.PGPKey
+ :return: The verified signature.
+ :rtype: pgpy.types.SignatureVerification
"""
message = PGPMessage.from_blob(self._as_string())
return key.verify(message)
- def decrypt(self, key: PGPKey):
+ def sign(self):
+ pass
+
+ def decrypt(self, key):
"""
+ Decrypt this message with key.
- :param key:
- :return:
+ :param key: The key to decrypt with.
+ :type key: pgpy.PGPKey
+ :return: The decrypted message.
+ :rtype: PGPMessage
"""
message = PGPMessage.from_blob(self._as_string())
return key.decrypt(message)
+
+ def encrypt(self):
+ pass
diff --git a/src/mailman_pgp/pgp/keygen.py b/src/mailman_pgp/pgp/keygen.py
index bff5450..06ca22b 100644
--- a/src/mailman_pgp/pgp/keygen.py
+++ b/src/mailman_pgp/pgp/keygen.py
@@ -28,22 +28,20 @@ from pgpy.constants import (
class ListKeyGenerator(mp.Process):
- """"""
+ """A multiprocessing list key generator."""
def __init__(self, keypair_config, display_name, posting_address,
request_address, key_path):
super().__init__(
target=self.generate,
- args=(
- keypair_config, display_name, posting_address, request_address,
- key_path),
- daemon=True)
+ args=(keypair_config, display_name, posting_address,
+ request_address, key_path))
def generate(self, keypair_config, display_name, posting_address,
request_address, key_path):
"""
- Generates the list keypair and saves it to key_path, if it does not
- exist.
+ Generate the list keypair and save it, if it does not exist.
+
:param keypair_config:
:param display_name:
:param posting_address:
@@ -59,8 +57,9 @@ class ListKeyGenerator(mp.Process):
def _create(self, config, display_name, posting_address, request_address):
"""
- Generates the list `PGPKey` keypair, with posting and request UIDs.
- Uses a Sign+Certify main key and Encrypt subkey.
+ Generate the list `PGPKey` keypair, with posting and request UIDs.
+
+ Use a Sign+Certify main key and Encrypt subkey.
:param config:
:param display_name:
:param posting_address:
@@ -105,6 +104,7 @@ class ListKeyGenerator(mp.Process):
def _save(self, key, key_path):
"""
Save the generated key.
+
:param key:
:param key_path:
"""
diff --git a/src/mailman_pgp/pgp/mime.py b/src/mailman_pgp/pgp/mime.py
index b8ab882..fde9d49 100644
--- a/src/mailman_pgp/pgp/mime.py
+++ b/src/mailman_pgp/pgp/mime.py
@@ -16,19 +16,27 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
"""RFC1847 and RFC3156 compliant message wrapped."""
-from email.message import Message
+
from email.utils import collapse_rfc2231_value
-from pgpy import PGPKey, PGPMessage, PGPSignature
+from pgpy import PGPMessage, PGPSignature
from public import public
@public
class MIMEWrapper:
+ """PGP/MIME (RFC1847 + RFC3156) compliant wrapper."""
+
_signed_subtype = 'application/pgp-signature'
_encrypted_subtype = 'application/pgp-encrypted'
- def __init__(self, msg: Message):
+ def __init__(self, msg):
+ """
+ Wrap the given message.
+
+ :param msg: The message to wrap.
+ :type msg: mailman.email.message.Message
+ """
self.msg = msg
def _is_mime(self):
@@ -40,7 +48,9 @@ class MIMEWrapper:
def is_signed(self):
"""
Whether the whole message is MIME signed as per RFC3156 section 5.
- :return:
+
+ :return: If the message is MIME signed.
+ :rtype: bool
"""
if not self._is_mime():
return False
@@ -56,7 +66,9 @@ class MIMEWrapper:
def is_encrypted(self):
"""
Whether the whole message is MIME encrypted as per RFC3156 section 4.
- :return:
+
+ :return: If the message is MIME encrypted.
+ :rtype: bool
"""
if not self._is_mime():
return False
@@ -73,11 +85,14 @@ class MIMEWrapper:
content_subtype == 'encrypted' and \
protocol_param == MIMEWrapper._encrypted_subtype
- def verify(self, key: PGPKey):
+ def verify(self, key):
"""
+ Verify the signature of this message with key.
- :param key:
- :return:
+ :param key: The key to verify with.
+ :type key: pgpy.PGPKey
+ :return: The verified signature.
+ :rtype: pgpy.types.SignatureVerification
"""
clear_text = self.msg.get_payload(0).as_string()
sig_text = self.msg.get_payload(1).get_payload()
@@ -87,11 +102,14 @@ class MIMEWrapper:
def sign(self):
pass
- def decrypt(self, key: PGPKey):
+ def decrypt(self, key):
"""
+ Decrypt this message with key.
- :param key:
- :return:
+ :param key: The key to decrypt with.
+ :type key: pgpy.PGPKey
+ :return: The decrypted message.
+ :rtype: PGPMessage
"""
msg_text = self.msg.get_payload(1).get_payload()
msg = PGPMessage.from_blob(msg_text)
diff --git a/src/mailman_pgp/pgp/wrapper.py b/src/mailman_pgp/pgp/wrapper.py
index 5d8bf41..9a004c4 100644
--- a/src/mailman_pgp/pgp/wrapper.py
+++ b/src/mailman_pgp/pgp/wrapper.py
@@ -14,10 +14,8 @@
# 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 email.message import Message
+"""A combined PGP/MIME + inline PGP wrapper."""
-from pgpy import PGPKey
from public import public
from mailman_pgp.pgp.inline import InlineWrapper
@@ -26,7 +24,15 @@ from mailman_pgp.pgp.mime import MIMEWrapper
@public
class PGPWrapper():
- def __init__(self, msg: Message):
+ """A combined PGP/MIME + 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
self.mime = MIMEWrapper(msg)
self.inline = InlineWrapper(msg)
@@ -40,7 +46,15 @@ class PGPWrapper():
def is_signed(self):
return self.is_mime_signed() or self.is_inline_signed()
- def verify(self, key: PGPKey):
+ def verify(self, key):
+ """
+ Verify the signature of this message with key.
+
+ :param key: The key to verify with.
+ :type key: pgpy.PGPKey
+ :return: The verified signature.
+ :rtype: pgpy.types.SignatureVerification
+ """
if self.is_mime_signed():
return self.mime.verify(key)
else:
@@ -55,7 +69,15 @@ class PGPWrapper():
def is_encrypted(self):
return self.is_mime_encrypted() or self.is_inline_encrypted()
- def decrypt(self, key: PGPKey):
+ def decrypt(self, key):
+ """
+ Decrypt this message with key.
+
+ :param key: The key to decrypt with.
+ :type key: pgpy.PGPKey
+ :return: The decrypted message.
+ :rtype: PGPMessage
+ """
if self.is_mime_encrypted():
return self.mime.decrypt(key)
else:
diff --git a/src/mailman_pgp/rules/signature.py b/src/mailman_pgp/rules/signature.py
index 0ffd76b..5f5b802 100644
--- a/src/mailman_pgp/rules/signature.py
+++ b/src/mailman_pgp/rules/signature.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
-""""""
+"""Signature checking rule for the pgp-posting-chain."""
from mailman.core.i18n import _
from mailman.interfaces.rules import IRule