Source code for spack.cmd.gpg

# Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

import argparse
import os

import spack.binary_distribution
import spack.cmd.common.arguments as arguments
import spack.paths
import spack.util.gpg

description = "handle GPG actions for spack"
section = "packaging"
level = "long"


[docs]def setup_parser(subparser): setup_parser.parser = subparser subparsers = subparser.add_subparsers(help='GPG sub-commands') verify = subparsers.add_parser('verify', help=gpg_verify.__doc__) arguments.add_common_arguments(verify, ['installed_spec']) verify.add_argument('signature', type=str, nargs='?', help='the signature file') verify.set_defaults(func=gpg_verify) trust = subparsers.add_parser('trust', help=gpg_trust.__doc__) trust.add_argument('keyfile', type=str, help='add a key to the trust store') trust.set_defaults(func=gpg_trust) untrust = subparsers.add_parser('untrust', help=gpg_untrust.__doc__) untrust.add_argument('--signing', action='store_true', help='allow untrusting signing keys') untrust.add_argument('keys', nargs='+', type=str, help='remove keys from the trust store') untrust.set_defaults(func=gpg_untrust) sign = subparsers.add_parser('sign', help=gpg_sign.__doc__) sign.add_argument('--output', metavar='DEST', type=str, help='the directory to place signatures') sign.add_argument('--key', metavar='KEY', type=str, help='the key to use for signing') sign.add_argument('--clearsign', action='store_true', help='if specified, create a clearsign signature') arguments.add_common_arguments(sign, ['installed_spec']) sign.set_defaults(func=gpg_sign) create = subparsers.add_parser('create', help=gpg_create.__doc__) create.add_argument('name', type=str, help='the name to use for the new key') create.add_argument('email', type=str, help='the email address to use for the new key') create.add_argument('--comment', metavar='COMMENT', type=str, default='GPG created for Spack', help='a description for the intended use of the key') create.add_argument('--expires', metavar='EXPIRATION', type=str, default='0', help='when the key should expire') create.add_argument('--export', metavar='DEST', type=str, help='export the public key to a file') create.add_argument('--export-secret', metavar="DEST", type=str, dest="secret", help='export the private key to a file.') create.set_defaults(func=gpg_create) list = subparsers.add_parser('list', help=gpg_list.__doc__) list.add_argument('--trusted', action='store_true', default=True, help='list trusted keys') list.add_argument('--signing', action='store_true', help='list keys which may be used for signing') list.set_defaults(func=gpg_list) init = subparsers.add_parser('init', help=gpg_init.__doc__) init.add_argument('--from', metavar='DIR', type=str, dest='import_dir', help=argparse.SUPPRESS) init.set_defaults(func=gpg_init) export = subparsers.add_parser('export', help=gpg_export.__doc__) export.add_argument('location', type=str, help='where to export keys') export.add_argument('keys', nargs='*', help='the keys to export; ' 'all public keys if unspecified') export.add_argument('--secret', action='store_true', help='export secret keys') export.set_defaults(func=gpg_export) publish = subparsers.add_parser('publish', help=gpg_publish.__doc__) output = publish.add_mutually_exclusive_group(required=True) output.add_argument('-d', '--directory', metavar='directory', type=str, help="local directory where " + "keys will be published.") output.add_argument('-m', '--mirror-name', metavar='mirror-name', type=str, help="name of the mirror where " + "keys will be published.") output.add_argument('--mirror-url', metavar='mirror-url', type=str, help="URL of the mirror where " + "keys will be published.") publish.add_argument('--rebuild-index', action='store_true', default=False, help=( "Regenerate buildcache key index " "after publishing key(s)")) publish.add_argument('keys', nargs='*', help='the keys to publish; ' 'all public keys if unspecified') publish.set_defaults(func=gpg_publish)
[docs]def gpg_create(args): """create a new key""" if args.export or args.secret: old_sec_keys = spack.util.gpg.signing_keys() # Create the new key spack.util.gpg.create(name=args.name, email=args.email, comment=args.comment, expires=args.expires) if args.export or args.secret: new_sec_keys = set(spack.util.gpg.signing_keys()) new_keys = new_sec_keys.difference(old_sec_keys) if args.export: spack.util.gpg.export_keys(args.export, new_keys) if args.secret: spack.util.gpg.export_keys(args.secret, new_keys, secret=True)
[docs]def gpg_export(args): """export a gpg key, optionally including secret key.""" keys = args.keys if not keys: keys = spack.util.gpg.signing_keys() spack.util.gpg.export_keys(args.location, keys, args.secret)
[docs]def gpg_list(args): """list keys available in the keyring""" spack.util.gpg.list(args.trusted, args.signing)
[docs]def gpg_sign(args): """sign a package""" key = args.key if key is None: keys = spack.util.gpg.signing_keys() if len(keys) == 1: key = keys[0] elif not keys: raise RuntimeError('no signing keys are available') else: raise RuntimeError('multiple signing keys are available; ' 'please choose one') output = args.output if not output: output = args.spec[0] + '.asc' # TODO: Support the package format Spack creates. spack.util.gpg.sign(key, ' '.join(args.spec), output, args.clearsign)
[docs]def gpg_trust(args): """add a key to the keyring""" spack.util.gpg.trust(args.keyfile)
[docs]def gpg_init(args): """add the default keys to the keyring""" import_dir = args.import_dir if import_dir is None: import_dir = spack.paths.gpg_keys_path for root, _, filenames in os.walk(import_dir): for filename in filenames: if not filename.endswith('.key'): continue spack.util.gpg.trust(os.path.join(root, filename))
[docs]def gpg_untrust(args): """remove a key from the keyring""" spack.util.gpg.untrust(args.signing, *args.keys)
[docs]def gpg_verify(args): """verify a signed package""" # TODO: Support the package format Spack creates. signature = args.signature if signature is None: signature = args.spec[0] + '.asc' spack.util.gpg.verify(signature, ' '.join(args.spec))
[docs]def gpg_publish(args): """publish public keys to a build cache""" # TODO(opadron): switch to using the mirror args once #17547 is merged mirror = args.directory spack.binary_distribution.push_keys( mirror, keys=args.keys, regenerate_index=args.rebuild_index)
[docs]def gpg(parser, args): if args.func: args.func(args)