# Copyright 2013-2022 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.mirror
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"""
mirror = None
if args.directory:
mirror = spack.mirror.Mirror(args.directory, args.directory)
elif args.mirror_name:
mirror = spack.mirror.MirrorCollection().lookup(args.mirror_name)
elif args.mirror_url:
mirror = spack.mirror.Mirror(args.mirror_url, args.mirror_url)
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)