diff options
| author | J08nY | 2017-07-31 22:42:40 +0200 |
|---|---|---|
| committer | J08nY | 2017-07-31 22:42:40 +0200 |
| commit | 8c3ac4d693cfa6138a02bf2d58b98ff62fe4f234 (patch) | |
| tree | 2a606169dcd655f5dc5b69b1770fa1fd72a5298e | |
| parent | 9727be03c49d0bf6b9c2f28e125fc68147602161 (diff) | |
| parent | 39291794b39b52804b2b45d74d0d5aad0f0eefcf (diff) | |
| download | mailman-pgp-8c3ac4d693cfa6138a02bf2d58b98ff62fe4f234.tar.gz mailman-pgp-8c3ac4d693cfa6138a02bf2d58b98ff62fe4f234.tar.zst mailman-pgp-8c3ac4d693cfa6138a02bf2d58b98ff62fe4f234.zip | |
| -rw-r--r-- | src/mailman_pgp/database/__init__.py | 4 | ||||
| -rw-r--r-- | src/mailman_pgp/model/address.py | 46 | ||||
| -rw-r--r-- | src/mailman_pgp/model/db_key.py | 57 | ||||
| -rw-r--r-- | src/mailman_pgp/model/fs_key.py | 113 | ||||
| -rw-r--r-- | src/mailman_pgp/model/list.py | 74 | ||||
| -rw-r--r-- | src/mailman_pgp/model/sighash.py | 2 | ||||
| -rw-r--r-- | src/mailman_pgp/model/tests/test_db_key.py | 50 | ||||
| -rw-r--r-- | src/mailman_pgp/model/tests/test_fs_key.py | 120 | ||||
| -rw-r--r-- | src/mailman_pgp/model/tests/test_list.py | 17 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/keygen.py | 36 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/tests/test_keygen.py | 85 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/tests/test_pgp.py | 7 | ||||
| -rw-r--r-- | src/mailman_pgp/styles/base.py | 4 | ||||
| -rw-r--r-- | src/mailman_pgp/utils/file.py | 62 |
14 files changed, 553 insertions, 124 deletions
diff --git a/src/mailman_pgp/database/__init__.py b/src/mailman_pgp/database/__init__.py index 2e24cdc..11ea387 100644 --- a/src/mailman_pgp/database/__init__.py +++ b/src/mailman_pgp/database/__init__.py @@ -38,7 +38,7 @@ class Database: url = config.get('db', 'url') self._url = expand(url, None, mailman_config.paths) self.engine = create_engine(self._url) - self._scoped_session = scoped_session(sessionmaker(bind=self.engine)) + self.scoped_session = scoped_session(sessionmaker(bind=self.engine)) Base.metadata.create_all(self.engine) self.session.commit() @@ -50,7 +50,7 @@ class Database: :return: A scoped session. :rtype: scoped_session """ - return self._scoped_session() + return self.scoped_session() @public diff --git a/src/mailman_pgp/model/address.py b/src/mailman_pgp/model/address.py index 0cfefe2..9ecfa95 100644 --- a/src/mailman_pgp/model/address.py +++ b/src/mailman_pgp/model/address.py @@ -16,20 +16,20 @@ # this program. If not, see <http://www.gnu.org/licenses/>. """Model for PGP enabled addresses.""" -import os -from os.path import exists, isfile, join from mailman.database.types import SAUnicode from mailman.interfaces.usermanager import IUserManager -from pgpy import PGPKey +from public import public from sqlalchemy import Boolean, Column, Integer, String from sqlalchemy.orm import reconstructor from zope.component import getUtility from mailman_pgp.config import config from mailman_pgp.model.base import Base +from mailman_pgp.model.fs_key import FSKey +@public class PGPAddress(Base): """A PGP enabled address.""" @@ -41,15 +41,15 @@ class PGPAddress(Base): key_confirmed = Column(Boolean, default=False) def __init__(self, address): - super().__init__() + super().__init__(email=address.email) self._init() - self.email = address.email self._address = address @reconstructor def _init(self): self._address = None - self._key = None + self._key = FSKey(config.pgp.keydir_config['user_keydir'], + self.email + '.asc', True) @property def key(self): @@ -58,33 +58,22 @@ class PGPAddress(Base): :return: :rtype: pgpy.PGPKey """ - 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) - return self._key + self._key.reload() + return self._key.key @key.setter - def key(self, new_key): + def key(self, value): """ - :param new_key: - :type new_key: PGPKey + :param value: + :type value: pgpy.PGPKey """ - if self.key_fingerprint is not None: - try: - os.remove(self.key_path) - except FileNotFoundError: - pass - if new_key is None: + if value is None: self.key_fingerprint = None - self._key = None else: - self.key_fingerprint = str(new_key.fingerprint) - with open(self.key_path, 'w') as out: - out.write(str(new_key)) - self._key = new_key + self.key_fingerprint = value.fingerprint + self._key.key = value + self._key.save() @property def key_path(self): @@ -93,10 +82,7 @@ class PGPAddress(Base): :return: :rtype: str """ - if self.key_fingerprint is None: - return None - return join(config.pgp.keydir_config['user_keydir'], - self.key_fingerprint + '.asc') + return self._key.key_path @property def address(self): diff --git a/src/mailman_pgp/model/db_key.py b/src/mailman_pgp/model/db_key.py new file mode 100644 index 0000000..0b3af45 --- /dev/null +++ b/src/mailman_pgp/model/db_key.py @@ -0,0 +1,57 @@ +# 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/>. + +"""Database stored PGP key.""" +from public import public +from sqlalchemy import Column, Integer, LargeBinary +from sqlalchemy.orm import reconstructor + +from mailman_pgp.model.base import Base +from mailman_pgp.utils.pgp import key_from_blob + + +@public +class DBKey(Base): + """Database stored PGP key.""" + __tablename__ = 'keys' + + id = Column(Integer, primary_key=True) + _key_material = Column('key_material', LargeBinary) + + def __init__(self, key=None): + super().__init__() + self._init() + self.key = key + + @reconstructor + def _init(self): + self._key = None + + @property + def key(self): + if self._key is None: + self._key = key_from_blob(self._key_material) + return self._key + + @key.setter + def key(self, value): + if value is None: + self._key = None + self._key_material = bytes() + else: + self._key = value + self._key_material = bytes(value) diff --git a/src/mailman_pgp/model/fs_key.py b/src/mailman_pgp/model/fs_key.py new file mode 100644 index 0000000..118f971 --- /dev/null +++ b/src/mailman_pgp/model/fs_key.py @@ -0,0 +1,113 @@ +# 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/>. + +"""Filesystem stored PGP key.""" +from os import remove, urandom +from os.path import getmtime, getsize, join + +from public import public + +from mailman_pgp.utils.file import locked_obj +from mailman_pgp.utils.pgp import key_from_file + + +@public +class FSKey: + """Filesystem stored PGP key.""" + + def __init__(self, keydir, keyfile, load=False): + self._key = None + self._mtime = None + self.keydir = keydir + self.keyfile = keyfile + if load: + self.load() + + @property + def key(self): + """ + + :rtype: pgpy.PGPKey + """ + return self._key + + @key.setter + def key(self, value): + """ + + :param value: + :type value: pgpy.PGPKey + """ + self._key = value + + @property + def key_path(self): + return join(self.keydir, self.keyfile) + + @property + def lock_path(self): + return self.key_path + '.lock' + + def _load(self): + try: + self.key = key_from_file(self.key_path) + self._mtime = getmtime(self.key_path) + except FileNotFoundError: + self.key = None + self._mtime = None + + @locked_obj('lock_path') + def load(self): + self._load() + + @locked_obj('lock_path') + def reload(self): + if self.key is None: + self._load() + else: + mtime = getmtime(self.key_path) + if self._mtime is None or mtime > self._mtime: + self._load() + + @locked_obj('lock_path') + def save(self): + if self.key is None: + remove(self.key_path) + self._mtime = None + else: + with open(self.key_path, 'w') as key_file: + key_file.write(str(self.key)) + self._mtime = getmtime(self.key_path) + + @locked_obj('lock_path') + def delete(self): + try: + remove(self.key_path) + except FileNotFoundError: + pass + + @locked_obj('lock_path') + def shred(self): + try: + size = getsize(self.key_path) + for _ in range(10): + with open(self.key_path, 'wb') as f: + data = urandom(size) + f.write(data) + remove(self.key_path) + except FileNotFoundError: + pass diff --git a/src/mailman_pgp/model/list.py b/src/mailman_pgp/model/list.py index 8448368..946af34 100644 --- a/src/mailman_pgp/model/list.py +++ b/src/mailman_pgp/model/list.py @@ -16,15 +16,10 @@ # this program. If not, see <http://www.gnu.org/licenses/>. """Model for PGP enabled mailing lists.""" - -from os import remove -from os.path import exists, isfile, join - -from flufl.lock import Lock +from lazr.config import as_boolean from mailman.database.types import Enum, SAUnicode from mailman.interfaces.action import Action from mailman.interfaces.listmanager import (IListManager, ListDeletingEvent) -from pgpy import PGPKey from public import public from sqlalchemy import Boolean, Column, Integer from sqlalchemy.orm import reconstructor @@ -34,7 +29,7 @@ from zope.event import classhandler from mailman_pgp.config import config from mailman_pgp.database import transaction from mailman_pgp.model.base import Base -from mailman_pgp.pgp.keygen import ListKeyGenerator +from mailman_pgp.model.fs_key import FSKey @public @@ -44,7 +39,7 @@ class PGPMailingList(Base): __tablename__ = 'pgp_lists' id = Column(Integer, primary_key=True) - list_id = Column(SAUnicode, index=True) + list_id = Column(SAUnicode, index=True, unique=True) # Signature related properties unsigned_msg_action = Column(Enum(Action), default=Action.reject) @@ -61,16 +56,20 @@ class PGPMailingList(Base): encrypt_outgoing = Column(Boolean, default=True) def __init__(self, mlist): - super().__init__() + """ + + :param mlist: + :type mlist: mailman.model.mailinglist.MailingList + """ + super().__init__(list_id=mlist.list_id) self._init() - self.list_id = mlist.list_id self._mlist = mlist @reconstructor def _init(self): self._mlist = None - self._key = None - self._key_generator = None + self._key = FSKey(config.pgp.keydir_config['list_keydir'], + self.list_id + '.asc', True) @property def mlist(self): @@ -84,44 +83,34 @@ class PGPMailingList(Base): return self._mlist @property + def fs_key(self): + return self._key + + @property def key(self): """ + The private part of the list's keypair. :return: :rtype: pgpy.PGPKey """ - if self._key is None: - # Check the file - if exists(self.key_path) and isfile(self.key_path): - self._key, _ = PGPKey.from_file(self.key_path) - return self._key + self._key.reload() + return self._key.key @key.setter def key(self, value): - with Lock(self.key_path + '.lock'): - self._key = value - if value is None: - remove(self.key_path) - else: - with open(self.key_path, 'w') as key_file: - key_file.write(str(value)) + """ - def generate_key(self, block=False): - self._key = None - self._key_generator = ListKeyGenerator(config.pgp.primary_key_args, - config.pgp.sub_key_args, - self.mlist.display_name, - self.mlist.posting_address, - self.mlist.request_address, - self.key_path) - self._key_generator.start() - if block: - self._key_generator.join() - return self.key + :param value: + :type value: + """ + self._key.key = value + self._key.save() @property def pubkey(self): """ + The public part of the list's keypair. :return: :rtype: pgpy.PGPKey @@ -133,18 +122,19 @@ class PGPMailingList(Base): @property def key_path(self): """ + The path to this list's key in the `list_keydir`. - :return: + :return: List key path. :rtype: str """ - return join(config.pgp.keydir_config['list_keydir'], - self.list_id + '.asc') + return self._key.key_path @staticmethod def for_list(mlist): """ :param mlist: + :type mlist: mailman.model.mailinglist.MailingList :return: :rtype: PGPMailingList|None """ @@ -155,8 +145,12 @@ class PGPMailingList(Base): @classhandler.handler(ListDeletingEvent) def on_delete(event): + shred = as_boolean(config.get('keypairs', 'shred')) pgp_list = PGPMailingList.for_list(event.mailing_list) if pgp_list: with transaction() as session: - # TODO shred the list key + if shred: + pgp_list.fs_key.shred() + else: + pgp_list.fs_key.delete() session.delete(pgp_list) diff --git a/src/mailman_pgp/model/sighash.py b/src/mailman_pgp/model/sighash.py index 9803ffc..e8a7ca9 100644 --- a/src/mailman_pgp/model/sighash.py +++ b/src/mailman_pgp/model/sighash.py @@ -16,11 +16,13 @@ # this program. If not, see <http://www.gnu.org/licenses/>. """""" +from public import public from sqlalchemy import Column, LargeBinary, String from mailman_pgp.model.base import Base +@public class PGPSigHash(Base): """""" diff --git a/src/mailman_pgp/model/tests/test_db_key.py b/src/mailman_pgp/model/tests/test_db_key.py new file mode 100644 index 0000000..46cc487 --- /dev/null +++ b/src/mailman_pgp/model/tests/test_db_key.py @@ -0,0 +1,50 @@ +# 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 unittest import TestCase + +from mailman_pgp.config import config +from mailman_pgp.database import transaction +from mailman_pgp.model.db_key import DBKey +from mailman_pgp.testing.layers import PGPConfigLayer +from mailman_pgp.testing.pgp import load_key + + +class TestDBKey(TestCase): + layer = PGPConfigLayer + + def test_key(self): + with transaction() as t: + db_key = DBKey() + t.add(db_key) + + key_data = load_key('rsa_1024.priv.asc') + with transaction(): + db_key.key = key_data + self.assertEqual(db_key.key.fingerprint, key_data.fingerprint) + + def test_loaded(self): + key_data = load_key('rsa_1024.priv.asc') + with transaction() as t: + db_key = DBKey(key_data) + t.add(db_key) + + config.db.scoped_session.remove() + + loaded_key = DBKey.query().one() + self.assertEqual(loaded_key.key.fingerprint, key_data.fingerprint) diff --git a/src/mailman_pgp/model/tests/test_fs_key.py b/src/mailman_pgp/model/tests/test_fs_key.py new file mode 100644 index 0000000..3a4f859 --- /dev/null +++ b/src/mailman_pgp/model/tests/test_fs_key.py @@ -0,0 +1,120 @@ +# 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 os.path import exists, join +from tempfile import TemporaryDirectory +from unittest import TestCase + +from mailman_pgp.model.fs_key import FSKey +from mailman_pgp.testing.layers import PGPLayer +from mailman_pgp.testing.pgp import load_key + + +class TestFSKey(TestCase): + layer = PGPLayer + + def setUp(self): + self.tmpdir = TemporaryDirectory() + + def tearDown(self): + self.tmpdir.cleanup() + + def test_load(self): + key_name = 'something.asc' + key_path = join(self.tmpdir.name, key_name) + key_data = load_key('rsa_1024.priv.asc') + with open(key_path, 'w') as key_file: + key_file.write(str(key_data)) + + key = FSKey(self.tmpdir.name, key_name, False) + self.assertEqual(key.key_path, key_path) + + key.load() + self.assertEqual(key.key.fingerprint, key_data.fingerprint) + + def test_reload(self): + key_name = 'something.asc' + key_path = join(self.tmpdir.name, key_name) + key_data = load_key('rsa_1024.priv.asc') + new_key_data = load_key('ecc_p256.priv.asc') + with open(key_path, 'w') as key_file: + key_file.write(str(key_data)) + + key = FSKey(self.tmpdir.name, key_name, True) + + with open(key_path, 'w') as key_file: + key_file.write(str(new_key_data)) + + key.reload() + self.assertIsNotNone(key.key) + self.assertEqual(key.key.fingerprint, new_key_data.fingerprint) + + def test_reload_none(self): + key_name = 'something.asc' + key = FSKey(self.tmpdir.name, key_name, False) + key_data = load_key('rsa_1024.priv.asc') + with open(key.key_path, 'w') as key_file: + key_file.write(str(key_data)) + + self.assertIsNone(key.key) + key.reload() + self.assertIsNotNone(key.key) + self.assertEqual(key.key.fingerprint, key_data.fingerprint) + + def test_save(self): + key_name = 'something.asc' + key = FSKey(self.tmpdir.name, key_name) + key_data = load_key('rsa_1024.priv.asc') + + key.key = key_data + key.save() + self.assertTrue(exists(key.key_path)) + + def test_delete(self): + key_name = 'something.asc' + key_path = join(self.tmpdir.name, key_name) + key_data = load_key('rsa_1024.priv.asc') + with open(key_path, 'w') as key_file: + key_file.write(str(key_data)) + + key = FSKey(self.tmpdir.name, key_name, True) + + key.delete() + self.assertFalse(exists(key.key_path)) + self.assertIsNotNone(key.key) + + def test_delete_none(self): + key = FSKey(self.tmpdir.name, 'something.asc') + key.delete() + + def test_shred(self): + key_name = 'something.asc' + key_path = join(self.tmpdir.name, key_name) + key_data = load_key('rsa_1024.priv.asc') + with open(key_path, 'w') as key_file: + key_file.write(str(key_data)) + + key = FSKey(self.tmpdir.name, key_name, True) + + key.shred() + self.assertFalse(exists(key.key_path)) + self.assertIsNotNone(key.key) + + def test_shred_none(self): + key = FSKey(self.tmpdir.name, 'something.asc') + key.shred() diff --git a/src/mailman_pgp/model/tests/test_list.py b/src/mailman_pgp/model/tests/test_list.py index 58f52f2..aa07a79 100644 --- a/src/mailman_pgp/model/tests/test_list.py +++ b/src/mailman_pgp/model/tests/test_list.py @@ -16,15 +16,18 @@ # this program. If not, see <http://www.gnu.org/licenses/>. """""" +from os.path import exists from unittest import TestCase from mailman.app.lifecycle import create_list from mailman.interfaces.listmanager import IListManager from zope.component import getUtility +from mailman_pgp.config import config from mailman_pgp.database import mm_transaction from mailman_pgp.model.list import PGPMailingList from mailman_pgp.testing.layers import PGPConfigLayer +from mailman_pgp.testing.pgp import load_key class TestPGPMailingList(TestCase): @@ -34,8 +37,22 @@ class TestPGPMailingList(TestCase): with mm_transaction(): self.mlist = create_list('test@example.com', style_name='pgp-default') + pgp_list = PGPMailingList.for_list(self.mlist) + pgp_list.key = load_key('rsa_1024.priv.asc') def test_delete(self): getUtility(IListManager).delete(self.mlist) pgp_list = PGPMailingList.for_list(self.mlist) self.assertIsNone(pgp_list) + + def test_shred_key(self): + key_path = PGPMailingList.for_list(self.mlist).key_path + getUtility(IListManager).delete(self.mlist) + self.assertFalse(exists(key_path)) + + def test_delete_key(self): + self.addCleanup(config.set, 'keypairs', 'shred', 'yes') + config.set('keypairs', 'shred', 'no') + key_path = PGPMailingList.for_list(self.mlist).key_path + getUtility(IListManager).delete(self.mlist) + self.assertFalse(exists(key_path)) diff --git a/src/mailman_pgp/pgp/keygen.py b/src/mailman_pgp/pgp/keygen.py index 684b81a..57a94d7 100644 --- a/src/mailman_pgp/pgp/keygen.py +++ b/src/mailman_pgp/pgp/keygen.py @@ -25,22 +25,32 @@ from pgpy import PGPKey, PGPUID from pgpy.constants import ( CompressionAlgorithm, HashAlgorithm, KeyFlags, SymmetricKeyAlgorithm) +from mailman_pgp.config import config +from mailman_pgp.utils.pgp import key_from_file + class ListKeyGenerator(mp.Process): """A multiprocessing list key generator.""" - def __init__(self, primary_args, subkey_args, display_name, - posting_address, - request_address, key_path): + def __init__(self, pgp_list): super().__init__( - target=self.generate, - args=(primary_args, subkey_args, display_name, posting_address, - request_address, key_path), + target=self._run, + args=(config.pgp.primary_key_args, config.pgp.sub_key_args, + pgp_list.mlist.display_name, + pgp_list.mlist.posting_address, + pgp_list.mlist.request_address, + pgp_list.key_path), daemon=True) + self._pgp_list = pgp_list + + def generate(self, block=False): + self.start() + if block: + self.join() + return key_from_file(self._pgp_list.key_path) - def generate(self, primary_args, subkey_args, display_name, - posting_address, - request_address, key_path): + def _run(self, primary_args, subkey_args, display_name, posting_address, + request_address, key_path): """ Generate the list keypair and save it. @@ -51,11 +61,11 @@ class ListKeyGenerator(mp.Process): :param request_address: :param key_path: """ - key = self._create(primary_args, subkey_args, display_name, - posting_address, - request_address) + self.key = self._create(primary_args, subkey_args, display_name, + posting_address, + request_address) with Lock(key_path + '.lock'): - self._save(key, key_path) + self._save(self.key, key_path) def _create(self, primary_args, subkey_args, display_name, posting_address, request_address): diff --git a/src/mailman_pgp/pgp/tests/test_keygen.py b/src/mailman_pgp/pgp/tests/test_keygen.py index a3a5499..7de1a19 100644 --- a/src/mailman_pgp/pgp/tests/test_keygen.py +++ b/src/mailman_pgp/pgp/tests/test_keygen.py @@ -16,25 +16,29 @@ # this program. If not, see <http://www.gnu.org/licenses/>. """Test the out-of-process key generator.""" -from os.path import exists, isfile, join -from tempfile import TemporaryDirectory +from os.path import exists, isfile from unittest import TestCase +from mailman.app.lifecycle import create_list from parameterized import parameterized -from pgpy import PGPKey from pgpy.constants import EllipticCurveOID, PubKeyAlgorithm +from mailman_pgp.config import config +from mailman_pgp.database import mm_transaction +from mailman_pgp.model.list import PGPMailingList from mailman_pgp.pgp.keygen import ListKeyGenerator -from mailman_pgp.testing.layers import PGPLayer +from mailman_pgp.testing.layers import PGPConfigLayer +from mailman_pgp.utils.pgp import key_from_file class TestKeygen(TestCase): - layer = PGPLayer + layer = PGPConfigLayer def setUp(self): - self.display_name = 'Display Name' - self.posting_address = 'posting@address.com' - self.request_address = 'posting-request@address.com' + with mm_transaction(): + self.mlist = create_list('test@example.com', + style_name='pgp-default') + self.pgp_list = PGPMailingList.for_list(self.mlist) @parameterized.expand([ # RSA + RSA @@ -49,36 +53,43 @@ class TestKeygen(TestCase): ]) def test_generate(self, primary_key_type, primary_key_size, sub_key_type, sub_key_size): - with TemporaryDirectory() as temp_dir: - key_path = join(temp_dir, 'key.asc') - keygen = ListKeyGenerator((primary_key_type, primary_key_size), - (sub_key_type, sub_key_size), - self.display_name, - self.posting_address, - self.request_address, key_path) - keygen.start() - keygen.join() - self.assertTrue(exists(key_path)) - self.assertTrue(isfile(key_path)) + def reset_primary(primary_key_args): + config.pgp.primary_key_args = primary_key_args - key, _ = PGPKey.from_file(key_path) - self.assertEqual(key.key_algorithm, - primary_key_type) - self.assertEqual(key.key_size, - primary_key_size) + self.addCleanup(reset_primary, config.pgp.primary_key_args) - subs = key.subkeys - self.assertEqual(len(subs), 1) + def reset_sub(sub_key_args): + config.pgp.sub_key_args = sub_key_args - keyid, sub = subs.popitem() - self.assertEqual(sub.key_algorithm, - sub_key_type) - self.assertEqual(sub.key_size, - sub_key_size) + self.addCleanup(reset_sub, config.pgp.sub_key_args) - uids = key.userids - self.assertEqual(len(uids), 2) - for uid in uids: - self.assertEqual(uid.name, self.display_name) - self.assertIn(uid.email, - (self.posting_address, self.request_address)) + config.pgp.primary_key_args = (primary_key_type, primary_key_size) + config.pgp.sub_key_args = (sub_key_type, sub_key_size) + + key_path = self.pgp_list.key_path + keygen = ListKeyGenerator(self.pgp_list) + ret_key = keygen.generate(True) + list_key = self.pgp_list.key + self.assertTrue(exists(key_path)) + self.assertTrue(isfile(key_path)) + + key = key_from_file(key_path) + self.assertEqual(key.key_algorithm, primary_key_type) + self.assertEqual(key.key_size, primary_key_size) + self.assertEqual(ret_key.fingerprint, key.fingerprint) + self.assertEqual(list_key.fingerprint, key.fingerprint) + + subs = key.subkeys + self.assertEqual(len(subs), 1) + + keyid, sub = subs.popitem() + self.assertEqual(sub.key_algorithm, sub_key_type) + self.assertEqual(sub.key_size, sub_key_size) + + uids = key.userids + self.assertEqual(len(uids), 2) + for uid in uids: + self.assertEqual(uid.name, self.pgp_list.mlist.display_name) + self.assertIn(uid.email, + (self.pgp_list.mlist.posting_address, + self.pgp_list.mlist.request_address)) diff --git a/src/mailman_pgp/pgp/tests/test_pgp.py b/src/mailman_pgp/pgp/tests/test_pgp.py index ab2a69a..6ef531e 100644 --- a/src/mailman_pgp/pgp/tests/test_pgp.py +++ b/src/mailman_pgp/pgp/tests/test_pgp.py @@ -25,6 +25,7 @@ from mailman_pgp.config import 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.pgp.keygen import ListKeyGenerator from mailman_pgp.testing.layers import PGPConfigLayer from mailman_pgp.testing.pgp import load_key @@ -37,7 +38,7 @@ class TestPGP(TestCase): self.mlist = create_list('test@example.com', style_name='pgp-default') self.pgp_list = PGPMailingList.for_list(self.mlist) - self.list_key = self.pgp_list.generate_key(True) + self.list_key = ListKeyGenerator(self.pgp_list).generate(True) # Make Anne a member of this mailing list. self.anne = subscribe(self.mlist, 'Anne', email='anne@example.org') @@ -61,3 +62,7 @@ class TestPGP(TestCase): with keyring.key(self.anne_key.fingerprint) as key: self.assertTrue(key.is_public) self.assertEqual(key.fingerprint, self.anne_key.fingerprint) + + def test_archive_keydir(self): + keyring = config.pgp.archive_keyring + self.assertEqual(len(keyring), 0) diff --git a/src/mailman_pgp/styles/base.py b/src/mailman_pgp/styles/base.py index a7c3366..bad67ad 100644 --- a/src/mailman_pgp/styles/base.py +++ b/src/mailman_pgp/styles/base.py @@ -22,6 +22,7 @@ from public import public from mailman_pgp.config import config, mm_config from mailman_pgp.database import transaction from mailman_pgp.model.list import PGPMailingList +from mailman_pgp.pgp.keygen import ListKeyGenerator @public @@ -46,5 +47,6 @@ class PGPStyle: with transaction() as session: pgp_list = PGPMailingList(mailing_list) if generate: - pgp_list.generate_key() + keygen = ListKeyGenerator(pgp_list) + keygen.generate(True) session.add(pgp_list) diff --git a/src/mailman_pgp/utils/file.py b/src/mailman_pgp/utils/file.py new file mode 100644 index 0000000..d633c4a --- /dev/null +++ b/src/mailman_pgp/utils/file.py @@ -0,0 +1,62 @@ +# 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 functools import wraps + +from flufl.lock import Lock + + +def locked(lockfile, *lock_args, **lock_kwargs): + """ + + :param lockfile: + :param lock_args: + :param lock_kwargs: + :return: + """ + + def locked_decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + with Lock(lockfile, *lock_args, **lock_kwargs): + return func(*args, **kwargs) + + return wrapper + + return locked_decorator + + +def locked_obj(lockattr, *lock_args, **lock_kwargs): + """ + + :param lockattr: + :param lock_args: + :param lock_kwargs: + :return: + """ + + def locked_decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + lockfile = getattr(args[0], lockattr) + locked_func = locked(lockfile, *lock_args, **lock_kwargs)(func) + return locked_func(*args, **kwargs) + + return wrapper + + return locked_decorator |
