Skip to content
Snippets Groups Projects
Commit f38d250e authored by Ben Boeckel's avatar Ben Boeckel Committed by Todd Gamblin
Browse files

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.
parent 71cc4e2a
Branches
Tags
No related merge requests found
...@@ -71,12 +71,14 @@ addons: ...@@ -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 ...@@ -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: ...@@ -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 @@ ...@@ -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.)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
......
##############################################################################
# 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)
##############################################################################
# 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)
##############################################################################
# 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')
# Mock GPG directory
This directory contains keys and data used in the testing Spack.
This file has a signature signed by an external key.
-----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-----
-----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-----
# 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`.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment