diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman_pgp/config/mailman_pgp.cfg | 32 | ||||
| -rw-r--r-- | src/mailman_pgp/model/list.py | 3 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/__init__.py | 60 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/keygen.py | 28 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/tests/test_keygen.py | 38 | ||||
| -rw-r--r-- | src/mailman_pgp/rest/tests/test_lists.py | 6 | ||||
| -rw-r--r-- | src/mailman_pgp/testing/mailman_pgp.cfg | 34 |
7 files changed, 122 insertions, 79 deletions
diff --git a/src/mailman_pgp/config/mailman_pgp.cfg b/src/mailman_pgp/config/mailman_pgp.cfg index 2582929..1f0999a 100644 --- a/src/mailman_pgp/config/mailman_pgp.cfg +++ b/src/mailman_pgp/config/mailman_pgp.cfg @@ -24,7 +24,7 @@ url = sqlite:////$DATA_DIR/pgp.db [keydirs] # Key directory used to store user public keys. -user_keydir= $DATA_DIR/pgp/user_keydir/ +user_keydir = $DATA_DIR/pgp/user_keydir/ # Key directory used to store list keypairs. list_keydir = $DATA_DIR/pgp/list_keydir/ @@ -34,22 +34,26 @@ archive_keydir = $DATA_DIR/pgp/archive_keydir/ [keypairs] -# Whether to autogenerate +# Whether to autogenerate the list key on list creation. autogenerate = yes -# Length of primary list key. -key_length = 4096 +# Type of primary list key and its size. +# Format: type:size +# type is one of: +# RSA, DSA, ECDSA. +# size is the key size or curve name for ECDSA, which can be one of: +# nistp256, nistp384, nistp521, brainpoolP256r1, brainpoolP384r1, +# brainpoolP512r1, secp256k1 +primary_key = RSA:4096 -# Type of primary list key. -# One of RSA, DSA, ECDSA. -key_type = RSA - -# Length of list encryption subkey. -subkey_length = 4096 - -# Type of list encryption subkey. -# One of RSA, ECDH. -subkey_type = RSA +# Type of list encryption subkey and its size. +# Format: type:size +# type is one of: +# RSA, ECDH +# size is the key size or curve name for ECDH, which can be one of: +# nistp256, nistp384, nistp521, brainpoolP256r1, brainpoolP384r1, +# brainpoolP512r1, secp256k1 +sub_key = RSA:4096 [queues] diff --git a/src/mailman_pgp/model/list.py b/src/mailman_pgp/model/list.py index ad031c5..0163026 100644 --- a/src/mailman_pgp/model/list.py +++ b/src/mailman_pgp/model/list.py @@ -104,7 +104,8 @@ class PGPMailingList(Base): def generate_key(self, block=False): self._key = None - self._key_generator = ListKeyGenerator(config.pgp.keypair_config, + 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, diff --git a/src/mailman_pgp/pgp/__init__.py b/src/mailman_pgp/pgp/__init__.py index 31b61b3..b41c8a1 100644 --- a/src/mailman_pgp/pgp/__init__.py +++ b/src/mailman_pgp/pgp/__init__.py @@ -24,25 +24,29 @@ from os.path import join from mailman.config import config as mailman_config from mailman.utilities.string import expand from pgpy import PGPKeyring -from pgpy.constants import PubKeyAlgorithm +from pgpy.constants import PubKeyAlgorithm, EllipticCurveOID from public import public from mailman_pgp.config import config KEYDIR_CONFIG_PATHS = ['list_keydir', 'user_keydir', 'archive_keydir'] -KEYPAIR_CONFIG_VARIABLES = ['autogenerate', 'key_type', 'key_length', - 'subkey_type', 'subkey_length'] +KEYPAIR_CONFIG_VARIABLES = ['autogenerate', 'primary_key', 'sub_key'] -# The main key needs to support signing. -KEYPAIR_KEY_TYPE_VALID = ['RSA', 'DSA', 'ECDSA'] -# The subkey needs to support encryption. -KEYPAIR_SUBKEY_TYPE_VALID = ['RSA', 'ECDH'] KEYPAIR_TYPE_MAP = { 'RSA': PubKeyAlgorithm.RSAEncryptOrSign, 'DSA': PubKeyAlgorithm.DSA, 'ECDSA': PubKeyAlgorithm.ECDSA, 'ECDH': PubKeyAlgorithm.ECDH } +ECC_OID_MAP = { + 'nistp256': EllipticCurveOID.NIST_P256, + 'nistp384': EllipticCurveOID.NIST_P384, + 'nistp521': EllipticCurveOID.NIST_P521, + 'brainpoolP256r1': EllipticCurveOID.Brainpool_P256, + 'brainpoolP384r1': EllipticCurveOID.Brainpool_P384, + 'brainpoolP512r1': EllipticCurveOID.Brainpool_P512, + 'secp256k1': EllipticCurveOID.SECP256K1 +} @public @@ -56,7 +60,7 @@ class PGP: Load [keypairs] and [keydirs] config sections. Expand paths in them. """ # Get all the [keypairs] config variables. - self.keypair_config = dict( + self._keypair_config = dict( (k, config.get('keypairs', k)) for k in KEYPAIR_CONFIG_VARIABLES) @@ -66,25 +70,41 @@ class PGP: expand(config.get('keydirs', k), None, mailman_config.paths)) for k in KEYDIR_CONFIG_PATHS) + def _parse_key_directive(self, value): + key_type, key_length = value.split(':') + key_type = key_type.upper() + key_length = key_length.lower() + + if key_type not in KEYPAIR_TYPE_MAP: + raise ValueError('Invalid key type: {}.'.format(key_type)) + + out_type = KEYPAIR_TYPE_MAP[key_type] + if key_type in ('ECDSA', 'ECDH'): + if key_length not in ECC_OID_MAP: + raise ValueError('Invalid key length: {}.'.format(key_length)) + out_length = ECC_OID_MAP[key_length] + else: + out_length = int(key_length) + return (out_type, out_length) + def _validate_config(self): """ Validate [keypairs] and [keydirs] config sections. And create keydirs if necessary. """ # Validate keypair config. - key_type = self.keypair_config['key_type'].upper() - if key_type not in KEYPAIR_KEY_TYPE_VALID: - raise ValueError('Invalid key_type. {}'.format(key_type)) - self.keypair_config['key_type'] = KEYPAIR_TYPE_MAP[key_type] - self.keypair_config['key_length'] = int( - self.keypair_config['key_length']) + self.primary_key_args = self._parse_key_directive( + self._keypair_config['primary_key']) + if not self.primary_key_args[0].can_sign: + raise ValueError( + 'Invalid primary key type: {}.'.format( + self.primary_key_args[0])) - subkey_type = self.keypair_config['subkey_type'].upper() - if subkey_type not in KEYPAIR_SUBKEY_TYPE_VALID: - raise ValueError('Invalid subkey_type. {}'.format(subkey_type)) - self.keypair_config['subkey_type'] = KEYPAIR_TYPE_MAP[subkey_type] - self.keypair_config['subkey_length'] = int( - self.keypair_config['subkey_length']) + self.sub_key_args = self._parse_key_directive( + self._keypair_config['sub_key']) + if not self.sub_key_args[0].can_encrypt: + raise ValueError( + 'Invalid sub key type: {}.'.format(self.sub_key_args[0])) # Make sure the keydir paths are directories and exist. for keydir in self.keydir_config.values(): diff --git a/src/mailman_pgp/pgp/keygen.py b/src/mailman_pgp/pgp/keygen.py index b750e28..684b81a 100644 --- a/src/mailman_pgp/pgp/keygen.py +++ b/src/mailman_pgp/pgp/keygen.py @@ -29,36 +29,42 @@ from pgpy.constants import ( class ListKeyGenerator(mp.Process): """A multiprocessing list key generator.""" - def __init__(self, keypair_config, display_name, posting_address, + def __init__(self, primary_args, subkey_args, display_name, + posting_address, request_address, key_path): super().__init__( target=self.generate, - args=(keypair_config, display_name, posting_address, + args=(primary_args, subkey_args, display_name, posting_address, request_address, key_path), daemon=True) - def generate(self, keypair_config, display_name, posting_address, + def generate(self, primary_args, subkey_args, display_name, + posting_address, request_address, key_path): """ Generate the list keypair and save it. - :param keypair_config: + :param primary_args: + :param subkey_args: :param display_name: :param posting_address: :param request_address: :param key_path: """ - key = self._create(keypair_config, display_name, posting_address, + key = self._create(primary_args, subkey_args, display_name, + posting_address, request_address) with Lock(key_path + '.lock'): self._save(key, key_path) - def _create(self, config, display_name, posting_address, request_address): + def _create(self, primary_args, subkey_args, display_name, posting_address, + request_address): """ Generate the list `PGPKey` keypair, with posting and request UIDs. Use a Sign+Certify main key and Encrypt subkey. - :param config: + :param primary_args: + :param subkey_args: :param display_name: :param posting_address: :param request_address: @@ -79,9 +85,7 @@ class ListKeyGenerator(mp.Process): ) # Generate the Sign + Certify primary key. - key_type = config['key_type'] - key_length = config['key_length'] - key = PGPKey.new(key_type, key_length) + key = PGPKey.new(*primary_args) key_params = dict(usage={KeyFlags.Sign, KeyFlags.Certify}, **common_params) # Generate the posting + request uids. @@ -89,9 +93,7 @@ class ListKeyGenerator(mp.Process): request_uid = PGPUID.new(display_name, email=request_address) # Generate the Encrypt subkey. - subkey_type = config['subkey_type'] - subkey_length = config['subkey_length'] - subkey = PGPKey.new(subkey_type, subkey_length) + subkey = PGPKey.new(*subkey_args) subkey_params = dict( usage={KeyFlags.EncryptCommunications, KeyFlags.EncryptStorage}, diff --git a/src/mailman_pgp/pgp/tests/test_keygen.py b/src/mailman_pgp/pgp/tests/test_keygen.py index dab6801..bbd0c84 100644 --- a/src/mailman_pgp/pgp/tests/test_keygen.py +++ b/src/mailman_pgp/pgp/tests/test_keygen.py @@ -15,32 +15,42 @@ # You should have received a copy of the GNU General Public License along with # 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 unittest import TestCase +from parameterized import parameterized from pgpy import PGPKey -from pgpy.constants import PubKeyAlgorithm +from pgpy.constants import PubKeyAlgorithm, EllipticCurveOID from mailman_pgp.pgp.keygen import ListKeyGenerator -class TesKeygen(TestCase): +class TestKeygen(TestCase): def setUp(self): - self.keypair_config = { - 'key_type': PubKeyAlgorithm.RSAEncryptOrSign, - 'key_length': 1024, - 'subkey_type': PubKeyAlgorithm.RSAEncryptOrSign, - 'subkey_length': 1024 - } self.display_name = 'Display Name' self.posting_address = 'posting@address.com' self.request_address = 'posting-request@address.com' - def test_generate(self): + @parameterized.expand([ + # RSA + RSA + (PubKeyAlgorithm.RSAEncryptOrSign, 1024, + PubKeyAlgorithm.RSAEncryptOrSign, 1024), + # ECDSA + ECDH + (PubKeyAlgorithm.ECDSA, EllipticCurveOID.SECP256K1, + PubKeyAlgorithm.ECDH, EllipticCurveOID.SECP256K1), + # DSA + ECDH + (PubKeyAlgorithm.DSA, 1024, + PubKeyAlgorithm.ECDH, EllipticCurveOID.SECP256K1) + ]) + 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(self.keypair_config, self.display_name, + 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() @@ -50,18 +60,18 @@ class TesKeygen(TestCase): key, _ = PGPKey.from_file(key_path) self.assertEqual(key.key_algorithm, - self.keypair_config['key_type']) + primary_key_type) self.assertEqual(key.key_size, - self.keypair_config['key_length']) + primary_key_size) subs = key.subkeys self.assertEqual(len(subs), 1) keyid, sub = subs.popitem() self.assertEqual(sub.key_algorithm, - self.keypair_config['subkey_type']) + sub_key_type) self.assertEqual(sub.key_size, - self.keypair_config['subkey_length']) + sub_key_size) uids = key.userids self.assertEqual(len(uids), 2) diff --git a/src/mailman_pgp/rest/tests/test_lists.py b/src/mailman_pgp/rest/tests/test_lists.py index c5c9854..2ebac6b 100644 --- a/src/mailman_pgp/rest/tests/test_lists.py +++ b/src/mailman_pgp/rest/tests/test_lists.py @@ -21,8 +21,9 @@ from mailman.app.lifecycle import create_list from mailman.testing.helpers import call_api from pgpy import PGPKey -from mailman_pgp.database import mm_transaction +from mailman_pgp.database import mm_transaction, transaction from mailman_pgp.model.list import PGPMailingList +from mailman_pgp.pgp.tests.base import load_key from mailman_pgp.testing.layers import PGPRESTLayer @@ -63,8 +64,9 @@ class TestLists(TestCase): with mm_transaction(): mlist = create_list('another@example.com', style_name='pgp-default') + with transaction(): pgp_list = PGPMailingList.for_list(mlist) - pgp_list.generate_key(True) + pgp_list.key = load_key('ecc_p256.priv.asc') json, response = call_api( 'http://localhost:9001/3.1/plugins/pgp/lists/' diff --git a/src/mailman_pgp/testing/mailman_pgp.cfg b/src/mailman_pgp/testing/mailman_pgp.cfg index 735f1a0..871f429 100644 --- a/src/mailman_pgp/testing/mailman_pgp.cfg +++ b/src/mailman_pgp/testing/mailman_pgp.cfg @@ -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/>. -# Default PGP config +# Testing PGP config [db] # db path the PGP plugin will use to store list/user configuration (not keys!). @@ -24,7 +24,7 @@ url = sqlite:////$DATA_DIR/pgp.db [keydirs] # Key directory used to store user public keys. -user_keydir= $DATA_DIR/pgp/user_keydir/ +user_keydir = $DATA_DIR/pgp/user_keydir/ # Key directory used to store list keypairs. list_keydir = $DATA_DIR/pgp/list_keydir/ @@ -34,22 +34,26 @@ archive_keydir = $DATA_DIR/pgp/archive_keydir/ [keypairs] -# Whether to autogenerate +# Whether to autogenerate the list key on list creation. autogenerate = no -# Length of primary list key. -key_length = 1024 +# Type of primary list key and its size. +# Format: type:size +# type is one of: +# RSA, DSA, ECDSA. +# size is the key size or curve name for ECDSA, which can be one of: +# nistp256, nistp384, nistp521, brainpoolP256r1, brainpoolP384r1, +# brainpoolP512r1, secp256k1 +primary_key = ECDSA:secp256k1 -# Type of primary list key. -# One of RSA, DSA, ECDSA. -key_type = RSA - -# Length of list encryption subkey. -subkey_length = 1024 - -# Type of list encryption subkey. -# One of RSA, ECDH. -subkey_type = RSA +# Type of list encryption subkey and its size. +# Format: type:size +# type is one of: +# RSA, ECDH +# size is the key size or curve name for ECDH, which can be one of: +# nistp256, nistp384, nistp521, brainpoolP256r1, brainpoolP384r1, +# brainpoolP512r1, secp256k1 +sub_key = ECDH:secp256k1 [queues] |
