diff --git a/scriptworker/ed25519.py b/scriptworker/ed25519.py index f8ab2c13..e7c85028 100644 --- a/scriptworker/ed25519.py +++ b/scriptworker/ed25519.py @@ -5,6 +5,7 @@ log (logging.Logger): the log object for the module """ +import argparse import base64 from binascii import Error as Base64Error from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm @@ -12,6 +13,8 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey import functools import logging +import sys +from scriptworker.constants import DEFAULT_CONFIG from scriptworker.exceptions import ScriptWorkerEd25519Error, ScriptWorkerException from scriptworker.utils import read_from_file @@ -130,3 +133,51 @@ def verify_ed25519_signature(public_key, contents, signature, message): public_key.verify(signature, contents) except (UnsupportedAlgorithm, InvalidSignature) as exc: raise ScriptWorkerEd25519Error(message % {'exc': str(exc)}) + + +# verify_ed25519_signature_cmdln {{{1 +def verify_ed25519_signature_cmdln(args=None, exception=SystemExit): + """Verify an ed25519 signature from the command line. + + Args: + args (list, optional): the commandline args to parse. If ``None``, use + ``sys.argv[1:]``. Defaults to ``None``. + exception (Exception, optional): the exception to raise on failure. + Defaults to ``SystemExit``. + + """ + args = args or sys.argv[1:] + parser = argparse.ArgumentParser( + description="""Verify an ed25519 signature from the command line. + +Given a file and its detached signature, verify that it has been signed with +a valid key. This key can be specified on the command line; otherwise we'll +default to ``config['ed25519_public_keys']``.""") + parser.add_argument('--pubkey', help='path to a base64-encoded ed25519 pubkey, optional') + parser.add_argument('file_path') + parser.add_argument('sig_path') + opts = parser.parse_args(args) + log = logging.getLogger('scriptworker') + log.setLevel(logging.DEBUG) + logging.basicConfig() + pubkeys = {} + if opts.pubkey: + pubkeys['cmdln'] = [read_from_file(opts.pubkey)] + pubkeys.update(dict(DEFAULT_CONFIG['ed25519_public_keys'])) + contents = read_from_file(opts.file_path, file_type='binary') + signature = read_from_file(opts.sig_path, file_type='binary') + for key_type, seeds in pubkeys.items(): + for seed in seeds: + try: + verify_ed25519_signature( + ed25519_public_key_from_string(seed), contents, signature, + "didn't work with {}".format(seed) + ) + log.info("Verified good with {} seed {} !".format( + key_type, seed + )) + sys.exit(0) + except ScriptWorkerEd25519Error: + pass + else: + raise exception("This is not a valid signature!") diff --git a/scriptworker/test/test_ed25519.py b/scriptworker/test/test_ed25519.py index 4d95be60..120aa72b 100644 --- a/scriptworker/test/test_ed25519.py +++ b/scriptworker/test/test_ed25519.py @@ -68,3 +68,23 @@ def test_verify_ed25519_signature(unsigned_path, signature_path, public_key_path def test_bad_ed25519_public_key_from_string(): with pytest.raises(ScriptWorkerEd25519Error): swed25519.ed25519_public_key_from_string('bad_base64_string') + + +@pytest.mark.parametrize('pubkey_path, file_path, sig_path, exception', (( + os.path.join(ED25519_DIR, 'scriptworker_public_key'), + os.path.join(ED25519_DIR, 'foo.json'), + os.path.join(ED25519_DIR, 'foo.json.scriptworker.sig'), + SystemExit +), ( + None, + os.path.join(ED25519_DIR, 'foo.json'), + os.path.join(ED25519_DIR, 'foo.json.scriptworker.sig'), + ScriptWorkerEd25519Error +))) +def test_ed25519_cmdln(pubkey_path, file_path, sig_path, exception): + args = [] + if pubkey_path is not None: + args.extend(['--pubkey', pubkey_path]) + args.extend([file_path, sig_path]) + with pytest.raises(exception): + swed25519.verify_ed25519_signature_cmdln(args=args, exception=ScriptWorkerEd25519Error) diff --git a/setup.py b/setup.py index d1e7cce6..3a44c8c4 100644 --- a/setup.py +++ b/setup.py @@ -82,6 +82,7 @@ def run_tests(self): "scriptworker = scriptworker.worker:main", "rebuild_gpg_homedirs = scriptworker.gpg:rebuild_gpg_homedirs", "verify_cot = scriptworker.cot.verify:verify_cot_cmdln", + "verify_ed25519_signature = scriptworker.ed25519:verify_ed25519_signature_cmdln", ], }, zip_safe=False,