gpg: add 'spack gpg subcommand (#3845)
- Add a `spack gpg` subcommand in anticipation of signed binaries. - GPG keys are stored in var/spack/gpg, and the spack gpg command manages them. - Docs are included on the command.
This commit is contained in:
parent
71cc4e2ad1
commit
f38d250e50
@ -71,12 +71,14 @@ addons:
|
|||||||
- gfortran
|
- gfortran
|
||||||
- mercurial
|
- mercurial
|
||||||
- graphviz
|
- graphviz
|
||||||
|
- gnupg2
|
||||||
|
|
||||||
# Work around Travis's lack of support for Python on OSX
|
# Work around Travis's lack of support for Python on OSX
|
||||||
before_install:
|
before_install:
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
|
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew ls --versions python > /dev/null || brew install python; fi
|
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew ls --versions python > /dev/null || brew install python; fi
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew ls --versions gcc > /dev/null || brew install gcc; fi
|
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew ls --versions gcc > /dev/null || brew install gcc; fi
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew ls --versions gnupg2 > /dev/null || brew install gnupg2; fi
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then virtualenv venv; fi
|
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then virtualenv venv; fi
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then source venv/bin/activate; fi
|
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then source venv/bin/activate; fi
|
||||||
|
|
||||||
|
@ -276,6 +276,70 @@ Seeing installed packages
|
|||||||
We know that ``spack list`` shows you the names of available packages,
|
We know that ``spack list`` shows you the names of available packages,
|
||||||
but how do you figure out which are already installed?
|
but how do you figure out which are already installed?
|
||||||
|
|
||||||
|
.. _cmd-spack-gpg:
|
||||||
|
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
``spack gpg``
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Spack has support for signing and verifying packages using GPG keys. A
|
||||||
|
separate keyring is used for Spack, so any keys available in the user's home
|
||||||
|
directory are not used.
|
||||||
|
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
``spack gpg init``
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When Spack is first installed, its keyring is empty. Keys stored in
|
||||||
|
:file:`var/spack/gpg` are the default keys for a Spack installation. These
|
||||||
|
keys may be imported by running ``spack gpg init``. This will import the
|
||||||
|
default keys into the keyring as trusted keys.
|
||||||
|
|
||||||
|
-------------
|
||||||
|
Trusting keys
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Additional keys may be added to the keyring using
|
||||||
|
``spack gpg trust <keyfile>``. Once a key is trusted, packages signed by the
|
||||||
|
owner of they key may be installed.
|
||||||
|
|
||||||
|
-------------
|
||||||
|
Creating keys
|
||||||
|
-------------
|
||||||
|
|
||||||
|
You may also create your own key so that you may sign your own packages using
|
||||||
|
``spack gpg create <name> <email>``. By default, the key has no expiration,
|
||||||
|
but it may be set with the ``--expires <date>`` flag (see the ``gnupg2``
|
||||||
|
documentation for accepted date formats). It is also recommended to add a
|
||||||
|
comment as to the use of the key using the ``--comment <comment>`` flag. The
|
||||||
|
public half of the key can also be exported for sharing with others so that
|
||||||
|
they may use packages you have signed using the ``--export <keyfile>`` flag.
|
||||||
|
Secret keys may also be later exported using the
|
||||||
|
``spack gpg export <location> [<key>...]`` command.
|
||||||
|
|
||||||
|
------------
|
||||||
|
Listing keys
|
||||||
|
------------
|
||||||
|
|
||||||
|
In order to list the keys available in the keyring, the
|
||||||
|
``spack gpg list`` command will list trusted keys with the ``--trusted`` flag
|
||||||
|
and keys available for signing using ``--signing``. If you would like to
|
||||||
|
remove keys from your keyring, ``spack gpg untrust <keyid>``. Key IDs can be
|
||||||
|
email addresses, names, or (best) fingerprints.
|
||||||
|
|
||||||
|
------------------------------
|
||||||
|
Signing and Verifying Packages
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
In order to sign a package, ``spack gpg sign <file>`` should be used. By
|
||||||
|
default, the signature will be written to ``<file>.asc``, but that may be
|
||||||
|
changed by using the ``--output <file>`` flag. If there is only one signing
|
||||||
|
key available, it will be used, but if there is more than one, the key to use
|
||||||
|
must be specified using the ``--key <keyid>`` flag. The ``--clearsign`` flag
|
||||||
|
may also be used to create a signed file which contains the contents, but it
|
||||||
|
is not recommended. Signed packages may be verified by using
|
||||||
|
``spack gpg verify <file>``.
|
||||||
|
|
||||||
.. _cmd-spack-find:
|
.. _cmd-spack-find:
|
||||||
|
|
||||||
^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^
|
||||||
|
@ -14,6 +14,7 @@ before Spack is run:
|
|||||||
1. Python 2 (2.6 or 2.7) or 3 (3.3 - 3.6)
|
1. Python 2 (2.6 or 2.7) or 3 (3.3 - 3.6)
|
||||||
2. A C/C++ compiler
|
2. A C/C++ compiler
|
||||||
3. The ``git`` and ``curl`` commands.
|
3. The ``git`` and ``curl`` commands.
|
||||||
|
4. If using the ``gpg`` subcommand, ``gnupg2`` is required.
|
||||||
|
|
||||||
These requirements can be easily installed on most modern Linux systems;
|
These requirements can be easily installed on most modern Linux systems;
|
||||||
on Macintosh, XCode is required. Spack is designed to run on HPC
|
on Macintosh, XCode is required. Spack is designed to run on HPC
|
||||||
|
@ -68,6 +68,13 @@
|
|||||||
etc_path = join_path(prefix, "etc")
|
etc_path = join_path(prefix, "etc")
|
||||||
|
|
||||||
|
|
||||||
|
# GPG paths.
|
||||||
|
gpg_keys_path = join_path(var_path, "gpg")
|
||||||
|
mock_gpg_data_path = join_path(var_path, "gpg.mock", "data")
|
||||||
|
mock_gpg_keys_path = join_path(var_path, "gpg.mock", "keys")
|
||||||
|
gpg_path = join_path(opt_path, "spack", "gpg")
|
||||||
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
# Initial imports (only for use in this file -- see __all__ below.)
|
# Initial imports (only for use in this file -- see __all__ below.)
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
168
lib/spack/spack/cmd/gpg.py
Normal file
168
lib/spack/spack/cmd/gpg.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
|
||||||
|
# Produced at the Lawrence Livermore National Laboratory.
|
||||||
|
#
|
||||||
|
# This file is part of Spack.
|
||||||
|
# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
|
||||||
|
# LLNL-CODE-647188
|
||||||
|
#
|
||||||
|
# For details, see https://github.com/llnl/spack
|
||||||
|
# Please also see the LICENSE file for our notice and the LGPL.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License (as
|
||||||
|
# published by the Free Software Foundation) version 2.1, February 1999.
|
||||||
|
#
|
||||||
|
# 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 terms and
|
||||||
|
# conditions of the GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
##############################################################################
|
||||||
|
from spack.util.gpg import Gpg
|
||||||
|
import spack
|
||||||
|
import os
|
||||||
|
|
||||||
|
description = "handle GPG actions for spack"
|
||||||
|
section = "developer"
|
||||||
|
level = "long"
|
||||||
|
|
||||||
|
|
||||||
|
def setup_parser(subparser):
|
||||||
|
setup_parser.parser = subparser
|
||||||
|
subparsers = subparser.add_subparsers(help='GPG sub-commands')
|
||||||
|
|
||||||
|
verify = subparsers.add_parser('verify')
|
||||||
|
verify.add_argument('package', type=str,
|
||||||
|
help='the package to verify')
|
||||||
|
verify.add_argument('signature', type=str, nargs='?',
|
||||||
|
help='the signature file')
|
||||||
|
verify.set_defaults(func=gpg_verify)
|
||||||
|
|
||||||
|
trust = subparsers.add_parser('trust')
|
||||||
|
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')
|
||||||
|
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')
|
||||||
|
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')
|
||||||
|
sign.add_argument('package', type=str,
|
||||||
|
help='the package to sign')
|
||||||
|
sign.set_defaults(func=gpg_sign)
|
||||||
|
|
||||||
|
create = subparsers.add_parser('create')
|
||||||
|
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.set_defaults(func=gpg_create)
|
||||||
|
|
||||||
|
list = subparsers.add_parser('list')
|
||||||
|
list.add_argument('--trusted', action='store_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')
|
||||||
|
init.set_defaults(func=gpg_init)
|
||||||
|
init.set_defaults(import_dir=spack.gpg_keys_path)
|
||||||
|
|
||||||
|
export = subparsers.add_parser('export')
|
||||||
|
export.add_argument('location', type=str,
|
||||||
|
help='where to export keys')
|
||||||
|
export.add_argument('keys', nargs='*',
|
||||||
|
help='the keys to export; '
|
||||||
|
'all secret keys if unspecified')
|
||||||
|
export.set_defaults(func=gpg_export)
|
||||||
|
|
||||||
|
|
||||||
|
def gpg_create(args):
|
||||||
|
if args.export:
|
||||||
|
old_sec_keys = Gpg.signing_keys()
|
||||||
|
Gpg.create(name=args.name, email=args.email,
|
||||||
|
comment=args.comment, expires=args.expires)
|
||||||
|
if args.export:
|
||||||
|
new_sec_keys = set(Gpg.signing_keys())
|
||||||
|
new_keys = new_sec_keys.difference(old_sec_keys)
|
||||||
|
Gpg.export_keys(args.export, *new_keys)
|
||||||
|
|
||||||
|
|
||||||
|
def gpg_export(args):
|
||||||
|
keys = args.keys
|
||||||
|
if not keys:
|
||||||
|
keys = Gpg.signing_keys()
|
||||||
|
Gpg.export_keys(args.location, *keys)
|
||||||
|
|
||||||
|
|
||||||
|
def gpg_list(args):
|
||||||
|
Gpg.list(args.trusted, args.signing)
|
||||||
|
|
||||||
|
|
||||||
|
def gpg_sign(args):
|
||||||
|
key = args.key
|
||||||
|
if key is None:
|
||||||
|
keys = 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.package + '.asc'
|
||||||
|
# TODO: Support the package format Spack creates.
|
||||||
|
Gpg.sign(key, args.package, output, args.clearsign)
|
||||||
|
|
||||||
|
|
||||||
|
def gpg_trust(args):
|
||||||
|
Gpg.trust(args.keyfile)
|
||||||
|
|
||||||
|
|
||||||
|
def gpg_init(args):
|
||||||
|
for root, _, filenames in os.walk(args.import_dir):
|
||||||
|
for filename in filenames:
|
||||||
|
if not filename.endswith('.key'):
|
||||||
|
continue
|
||||||
|
Gpg.trust(os.path.join(root, filename))
|
||||||
|
|
||||||
|
|
||||||
|
def gpg_untrust(args):
|
||||||
|
Gpg.untrust(args.signing, *args.keys)
|
||||||
|
|
||||||
|
|
||||||
|
def gpg_verify(args):
|
||||||
|
# TODO: Support the package format Spack creates.
|
||||||
|
signature = args.signature
|
||||||
|
if signature is None:
|
||||||
|
signature = args.package + '.asc'
|
||||||
|
Gpg.verify(signature, args.package)
|
||||||
|
|
||||||
|
|
||||||
|
def gpg(parser, args):
|
||||||
|
if args.func:
|
||||||
|
args.func(args)
|
181
lib/spack/spack/test/cmd/gpg.py
Normal file
181
lib/spack/spack/test/cmd/gpg.py
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
|
||||||
|
# Produced at the Lawrence Livermore National Laboratory.
|
||||||
|
#
|
||||||
|
# This file is part of Spack.
|
||||||
|
# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
|
||||||
|
# LLNL-CODE-647188
|
||||||
|
#
|
||||||
|
# For details, see https://github.com/llnl/spack
|
||||||
|
# Please also see the LICENSE file for our notice and the LGPL.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License (as
|
||||||
|
# published by the Free Software Foundation) version 2.1, February 1999.
|
||||||
|
#
|
||||||
|
# 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 terms and
|
||||||
|
# conditions of the GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
##############################################################################
|
||||||
|
import argparse
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import spack
|
||||||
|
import spack.cmd.gpg as gpg
|
||||||
|
import spack.util.gpg as gpg_util
|
||||||
|
from spack.util.executable import ProcessError
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def testing_gpg_directory(tmpdir):
|
||||||
|
old_gpg_path = gpg_util.GNUPGHOME
|
||||||
|
gpg_util.GNUPGHOME = str(tmpdir.join('gpg'))
|
||||||
|
yield
|
||||||
|
gpg_util.GNUPGHOME = old_gpg_path
|
||||||
|
|
||||||
|
|
||||||
|
def has_gnupg2():
|
||||||
|
try:
|
||||||
|
gpg_util.Gpg.gpg()('--version')
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('testing_gpg_directory')
|
||||||
|
@pytest.mark.skipif(not has_gnupg2(),
|
||||||
|
reason='These tests require gnupg2')
|
||||||
|
def test_gpg(tmpdir):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
gpg.setup_parser(parser)
|
||||||
|
|
||||||
|
# Verify a file with an empty keyring.
|
||||||
|
args = parser.parse_args(['verify', os.path.join(
|
||||||
|
spack.mock_gpg_data_path, 'content.txt')])
|
||||||
|
with pytest.raises(ProcessError):
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Import the default key.
|
||||||
|
args = parser.parse_args(['init'])
|
||||||
|
args.import_dir = spack.mock_gpg_keys_path
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# List the keys.
|
||||||
|
# TODO: Test the output here.
|
||||||
|
args = parser.parse_args(['list', '--trusted'])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
args = parser.parse_args(['list', '--signing'])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Verify the file now that the key has been trusted.
|
||||||
|
args = parser.parse_args(['verify', os.path.join(
|
||||||
|
spack.mock_gpg_data_path, 'content.txt')])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Untrust the default key.
|
||||||
|
args = parser.parse_args(['untrust', 'Spack testing'])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Now that the key is untrusted, verification should fail.
|
||||||
|
args = parser.parse_args(['verify', os.path.join(
|
||||||
|
spack.mock_gpg_data_path, 'content.txt')])
|
||||||
|
with pytest.raises(ProcessError):
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Create a file to test signing.
|
||||||
|
test_path = tmpdir.join('to-sign.txt')
|
||||||
|
with open(str(test_path), 'w+') as fout:
|
||||||
|
fout.write('Test content for signing.\n')
|
||||||
|
|
||||||
|
# Signing without a private key should fail.
|
||||||
|
args = parser.parse_args(['sign', str(test_path)])
|
||||||
|
with pytest.raises(RuntimeError) as exc_info:
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
assert exc_info.value.args[0] == 'no signing keys are available'
|
||||||
|
|
||||||
|
# Create a key for use in the tests.
|
||||||
|
keypath = tmpdir.join('testing-1.key')
|
||||||
|
args = parser.parse_args(['create',
|
||||||
|
'--comment', 'Spack testing key',
|
||||||
|
'--export', str(keypath),
|
||||||
|
'Spack testing 1',
|
||||||
|
'spack@googlegroups.com'])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
keyfp = gpg_util.Gpg.signing_keys()[0]
|
||||||
|
|
||||||
|
# List the keys.
|
||||||
|
# TODO: Test the output here.
|
||||||
|
args = parser.parse_args(['list', '--trusted'])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
args = parser.parse_args(['list', '--signing'])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Signing with the default (only) key.
|
||||||
|
args = parser.parse_args(['sign', str(test_path)])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Verify the file we just verified.
|
||||||
|
args = parser.parse_args(['verify', str(test_path)])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Export the key for future use.
|
||||||
|
export_path = tmpdir.join('export.testing.key')
|
||||||
|
args = parser.parse_args(['export', str(export_path)])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Create a second key for use in the tests.
|
||||||
|
args = parser.parse_args(['create',
|
||||||
|
'--comment', 'Spack testing key',
|
||||||
|
'Spack testing 2',
|
||||||
|
'spack@googlegroups.com'])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# List the keys.
|
||||||
|
# TODO: Test the output here.
|
||||||
|
args = parser.parse_args(['list', '--trusted'])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
args = parser.parse_args(['list', '--signing'])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
test_path = tmpdir.join('to-sign-2.txt')
|
||||||
|
with open(str(test_path), 'w+') as fout:
|
||||||
|
fout.write('Test content for signing.\n')
|
||||||
|
|
||||||
|
# Signing with multiple signing keys is ambiguous.
|
||||||
|
args = parser.parse_args(['sign', str(test_path)])
|
||||||
|
with pytest.raises(RuntimeError) as exc_info:
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
assert exc_info.value.args[0] == \
|
||||||
|
'multiple signing keys are available; please choose one'
|
||||||
|
|
||||||
|
# Signing with a specified key.
|
||||||
|
args = parser.parse_args(['sign', '--key', keyfp, str(test_path)])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Untrusting signing keys needs a flag.
|
||||||
|
args = parser.parse_args(['untrust', 'Spack testing 1'])
|
||||||
|
with pytest.raises(ProcessError):
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Untrust the key we created.
|
||||||
|
args = parser.parse_args(['untrust', '--signing', keyfp])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Verification should now fail.
|
||||||
|
args = parser.parse_args(['verify', str(test_path)])
|
||||||
|
with pytest.raises(ProcessError):
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Trust the exported key.
|
||||||
|
args = parser.parse_args(['trust', str(export_path)])
|
||||||
|
gpg.gpg(parser, args)
|
||||||
|
|
||||||
|
# Verification should now succeed again.
|
||||||
|
args = parser.parse_args(['verify', str(test_path)])
|
||||||
|
gpg.gpg(parser, args)
|
120
lib/spack/spack/util/gpg.py
Normal file
120
lib/spack/spack/util/gpg.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
|
||||||
|
# Produced at the Lawrence Livermore National Laboratory.
|
||||||
|
#
|
||||||
|
# This file is part of Spack.
|
||||||
|
# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
|
||||||
|
# LLNL-CODE-647188
|
||||||
|
#
|
||||||
|
# For details, see https://github.com/llnl/spack
|
||||||
|
# Please also see the LICENSE file for our notice and the LGPL.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License (as
|
||||||
|
# published by the Free Software Foundation) version 2.1, February 1999.
|
||||||
|
#
|
||||||
|
# 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 terms and
|
||||||
|
# conditions of the GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import spack
|
||||||
|
from spack.util.executable import Executable
|
||||||
|
|
||||||
|
|
||||||
|
GNUPGHOME = spack.gpg_path
|
||||||
|
|
||||||
|
|
||||||
|
class Gpg(object):
|
||||||
|
@staticmethod
|
||||||
|
def gpg():
|
||||||
|
# TODO: Support loading up a GPG environment from a built gpg.
|
||||||
|
gpg = Executable('gpg2')
|
||||||
|
if not os.path.exists(GNUPGHOME):
|
||||||
|
os.makedirs(GNUPGHOME)
|
||||||
|
os.chmod(GNUPGHOME, 0o700)
|
||||||
|
gpg.add_default_env('GNUPGHOME', GNUPGHOME)
|
||||||
|
return gpg
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, **kwargs):
|
||||||
|
r, w = os.pipe()
|
||||||
|
r = os.fdopen(r, 'r')
|
||||||
|
w = os.fdopen(w, 'w')
|
||||||
|
w.write('''
|
||||||
|
Key-Type: rsa
|
||||||
|
Key-Length: 4096
|
||||||
|
Key-Usage: sign
|
||||||
|
Name-Real: %(name)s
|
||||||
|
Name-Email: %(email)s
|
||||||
|
Name-Comment: %(comment)s
|
||||||
|
Expire-Date: %(expires)s
|
||||||
|
%%no-protection
|
||||||
|
%%commit
|
||||||
|
''' % kwargs)
|
||||||
|
w.close()
|
||||||
|
cls.gpg()('--gen-key', '--batch', input=r)
|
||||||
|
r.close()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def signing_keys(cls):
|
||||||
|
keys = []
|
||||||
|
output = cls.gpg()('--list-secret-keys', '--with-colons',
|
||||||
|
'--fingerprint', output=str)
|
||||||
|
for line in output.split('\n'):
|
||||||
|
if line.startswith('fpr'):
|
||||||
|
keys.append(line.split(':')[9])
|
||||||
|
return keys
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def export_keys(cls, location, *keys):
|
||||||
|
cls.gpg()('--armor', '--export', '--output', location, *keys)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def trust(cls, keyfile):
|
||||||
|
cls.gpg()('--import', keyfile)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def untrust(cls, signing, *keys):
|
||||||
|
args = [
|
||||||
|
'--yes',
|
||||||
|
'--batch',
|
||||||
|
]
|
||||||
|
if signing:
|
||||||
|
signing_args = args + ['--delete-secret-keys'] + list(keys)
|
||||||
|
cls.gpg()(*signing_args)
|
||||||
|
args.append('--delete-keys')
|
||||||
|
args.extend(keys)
|
||||||
|
cls.gpg()(*args)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sign(cls, key, file, output, clearsign=False):
|
||||||
|
args = [
|
||||||
|
'--armor',
|
||||||
|
'--default-key', key,
|
||||||
|
'--output', output,
|
||||||
|
file,
|
||||||
|
]
|
||||||
|
if clearsign:
|
||||||
|
args.insert(0, '--clearsign')
|
||||||
|
else:
|
||||||
|
args.insert(0, '--detach-sign')
|
||||||
|
cls.gpg()(*args)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def verify(cls, signature, file):
|
||||||
|
cls.gpg()('--verify', signature, file)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list(cls, trusted, signing):
|
||||||
|
if trusted:
|
||||||
|
cls.gpg()('--list-public-keys')
|
||||||
|
if signing:
|
||||||
|
cls.gpg()('--list-secret-keys')
|
3
var/spack/gpg.mock/README.md
Normal file
3
var/spack/gpg.mock/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Mock GPG directory
|
||||||
|
|
||||||
|
This directory contains keys and data used in the testing Spack.
|
1
var/spack/gpg.mock/data/content.txt
Normal file
1
var/spack/gpg.mock/data/content.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
This file has a signature signed by an external key.
|
17
var/spack/gpg.mock/data/content.txt.asc
Normal file
17
var/spack/gpg.mock/data/content.txt.asc
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
Version: GnuPG v2
|
||||||
|
|
||||||
|
iQIcBAABCAAGBQJZELiKAAoJENygJBhApdriPvgP/0shBTmx4jg6QaI0zyie8a+R
|
||||||
|
+L/o9iIV4MqvBI5g+Ti+nktoCSxSOPOYFW4af740A7/43wIML9LK+gIhx/QbCrMb
|
||||||
|
bNqzyIry9/L6PK1cCuXvd10CT+MCF1P0hdaMtKihdBYB3J8f5y1i30z+a8YWsRsX
|
||||||
|
tPMVF/HunlpAkSWIpjmbJzFPT1R/UiBHl4VJ+mM3NNZYNIq8ZhKUiXwlQkZ8R8zg
|
||||||
|
M0IEFkwfFtp7JxnhG7jR0k63cNm3KSocAJpwENy46RKGsAvwvqTzRh4T2MlmQIjH
|
||||||
|
TC1MA8alJvtSdBHpkKffSU8jLewKHe1H48nc9NifMy04Ni8fSlGZe14Oe7Krqla0
|
||||||
|
qWs+XHrGCmSleyiRUQes1MKQ7NhumKEoEaU+q0/c+lUDILZp1TlfvTPg2fzng4M/
|
||||||
|
YF6+f+wqM+xY6z1/IloOMHis5oALjARSO88ldrLU4DQp/6jTKJO/+I4uWhMnPkMW
|
||||||
|
+a3GLWl1CShReHKbWZTLFtdQATZXA8M6wQ8FAsLOmRLb0AlEQ28A8fHrBCCdU2xj
|
||||||
|
tSG++U1ZUo64cMYQmIMsvIApnkTh7qCkDjaVBP1to3qc83YHncxorydz9ERpuDvP
|
||||||
|
d1IOHlJyUSM4+sLkCPvH9QyTaJn/x7D/VraznEiptGON7G6G9AgyAzIgYamm1Kwh
|
||||||
|
UDhbQDFDhLLvUSDGzO3l
|
||||||
|
=kwo9
|
||||||
|
-----END PGP SIGNATURE-----
|
30
var/spack/gpg.mock/keys/external.key
Normal file
30
var/spack/gpg.mock/keys/external.key
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Version: GnuPG v2
|
||||||
|
|
||||||
|
mQINBFkQuFIBEAC7DiUM7jQ01kaGX+4nguzVeYquBRYoEUiObl5UIVSavMn4I7Oy
|
||||||
|
aytG+qR26tUpunjEB6ftIQMJSyPKueclUJBaQ9lzQ3WpFC3ItpBNkMxHpiqPa9DX
|
||||||
|
ddMk2QtJt4TlCWJEdnhR/92mMF+vf7B5/OvFvKOi0P+AwzBHC8IKTxml/UosmeVI
|
||||||
|
Cs69FzRDXyqQxQAkATmuDmHXPaC6RkDmpVRe3ej+Kr+Xu4vcb/EBHg/vcZkFdSmi
|
||||||
|
hyOj21/8LQZzcwTg4TSgHzKqbjPtIEQM3NNksvcFYlq2X0ad4cBcxa1Hj5xV8oS/
|
||||||
|
bdYOFSdsh3QRROcEeKYVQZhvCR12qS93P4b2egbamBxCQK0Sn6QPIjlR6+Ya2/6p
|
||||||
|
/hHddF+YVA6HJ22QZjaORf9lImYfYMs1ka2GtgkczOeaFEfcJ96nIa8Qb1jcrOon
|
||||||
|
/3k/l+Ae09HRCcGB2DgKXw7S+CXKt46Oadp3bIDAyceotGnrG3cVA6A9Lwqy6U/5
|
||||||
|
ywry8ETu3wlIR3EAIwM0a/3xCPg3cC/bt9rSqsFcmXyxltGI2CBTWcTqcyjW4VAw
|
||||||
|
nVI8otBd4yNdimhpxLfx6AaMjA+D+OSltnAZUrp1fSFVhWLpTxLbcTv+HJ/g4U+x
|
||||||
|
+PAsQ79Hzmzvy/8nOvIprGzY4LCmBPbLUB47Yu761HhYQhkuJiYP1R/GzQARAQAB
|
||||||
|
tDpTcGFjayB0ZXN0aW5nIChTcGFjayB0ZXN0aW5nIGtleSkgPHNwYWNrQGdvb2ds
|
||||||
|
ZWdyb3Vwcy5jb20+iQI3BBMBCAAhBQJZELhSAhsDBQsJCAcCBhUICQoLAgQWAgMB
|
||||||
|
Ah4BAheAAAoJENygJBhApdriOnUP/iLC1ZxyBP3STSVgBBTS1L6FnRAc9ya6eXNT
|
||||||
|
EwLLoSL0I0srs0sThmhyW38ZamsXYDhggaetShxemcO0BoNAii/oNK9yQoXNF4f6
|
||||||
|
7wg2ZxCDuDjp/3VsbiI+kNlH2kj1tQ/M53ak9nYhmwLJFfKzjQBWJiyTwYZwO3MB
|
||||||
|
QvXBvLIKj6IDS20o+7jbOq8F243vo5/uNHc/6C9eC3i4jzXWVlln2+iN/e5sVt+X
|
||||||
|
ZiggLK2Goj5CZ7ZjZQvdoH4wKbSPLBg0Lh5FYSih9p0wx0UTEoi0jPqFUDw81duz
|
||||||
|
IyxjbGASSaUxoz16C2U/olPEAAXeBe4266jRQwTrn+sEIX5FD+RGoryXQ97pV5up
|
||||||
|
I9wb2anVAMHOf20iYep3vYTjnFG/81ykODm8+I4D/Jj0EEe1E2b0D+7RQ9xKNYxC
|
||||||
|
fDgY3isXBFzmS6O4h8N27P06yfzQX+zvjPrrHRB7ka2pmDT3M421p2wN0n9aCq1J
|
||||||
|
8+M5UdpF98A38oosyE53KcItoCUFLgEP3KrWPwvpDUC2sNQAOFiHeitzc+v1iwmD
|
||||||
|
RScdefCQ8qc2JJdCqMG6M0tlFy6Tw1o0eBYOhhDGa0rq/PQ4NewR2dj+yDXXBGJy
|
||||||
|
ElR0VChqniMCyd2Q4SDPnhcVrWPTYSKL1MpsL0lXED8TGOdoAHHmQNU8MWhqmdBy
|
||||||
|
zcWArNUY
|
||||||
|
=yVqw
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
5
var/spack/gpg/README.md
Normal file
5
var/spack/gpg/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# GPG Keys
|
||||||
|
|
||||||
|
This directory contains keys that should be trusted by this installation of
|
||||||
|
Spack. They are imported when running `spack gpg init`, but may also be
|
||||||
|
imported manually with `spack gpg trust path/to/key`.
|
Loading…
Reference in New Issue
Block a user