From 6f534acbefa1ff1d0b389f72221a8dbdd87609b0 Mon Sep 17 00:00:00 2001 From: Vanessasaurus <814322+vsoch@users.noreply.github.com> Date: Sat, 29 May 2021 00:32:57 -0600 Subject: adding support for export of private gpg key (#22557) This PR allows users to `--export`, `--export-secret`, or both to export GPG keys from Spack. The docs are updated that include a warning that this usually does not need to be done. This addresses an issue brought up in slack, and also represented in #14721. Signed-off-by: vsoch Co-authored-by: vsoch --- lib/spack/docs/getting_started.rst | 76 +++++++++++++++++++++++++++++++++- lib/spack/spack/binary_distribution.py | 4 +- lib/spack/spack/cmd/gpg.py | 23 +++++++--- lib/spack/spack/test/cmd/gpg.py | 14 +++++++ lib/spack/spack/util/gpg.py | 10 +++-- 5 files changed, 115 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/spack/docs/getting_started.rst b/lib/spack/docs/getting_started.rst index 805149af90..fa77d74cd6 100644 --- a/lib/spack/docs/getting_started.rst +++ b/lib/spack/docs/getting_started.rst @@ -1119,6 +1119,33 @@ Secret keys may also be later exported using the `_ provides a good overview of sources of randomness. +Here is an example of creating a key. Note that we provide a name for the key first +(which we can use to reference the key later) and an email address: + +.. code-block:: console + + $ spack gpg create dinosaur dinosaur@thedinosaurthings.com + + +If you want to export the key as you create it: + + +.. code-block:: console + + $ spack gpg create --export key.pub dinosaur dinosaur@thedinosaurthings.com + +Or the private key: + + +.. code-block:: console + + $ spack gpg create --export-secret key.priv dinosaur dinosaur@thedinosaurthings.com + + +You can include both ``--export`` and ``--export-secret``, each with +an output file of choice, to export both. + + ^^^^^^^^^^^^ Listing keys ^^^^^^^^^^^^ @@ -1127,7 +1154,22 @@ 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 ``. Key IDs can be -email addresses, names, or (best) fingerprints. +email addresses, names, or (best) fingerprints. Here is an example of listing +the key that we just created: + +.. code-block:: console + + gpgconf: socketdir is '/run/user/1000/gnupg' + /home/spackuser/spack/opt/spack/gpg/pubring.kbx + ---------------------------------------------------------- + pub rsa4096 2021-03-25 [SC] + 60D2685DAB647AD4DB54125961E09BB6F2A0ADCB + uid [ultimate] dinosaur (GPG created for Spack) + + +Note that the name "dinosaur" can be seen under the uid, which is the unique +id. We might need this reference if we want to export or otherwise reference the key. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Signing and Verifying Packages @@ -1142,6 +1184,38 @@ 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 ``. + +^^^^^^^^^^^^^^ +Exporting Keys +^^^^^^^^^^^^^^ + +You likely might want to export a public key, and that looks like this. Let's +use the previous example and ask spack to export the key with uid "dinosaur." +We will provide an output location (typically a `*.pub` file) and the name of +the key. + +.. code-block:: console + + $ spack gpg export dinosaur.pub dinosaur + +You can then look at the created file, `dinosaur.pub`, to see the exported key. +If you want to include the private key, then just add `--secret`: + +.. code-block:: console + + $ spack gpg export --secret dinosaur.priv dinosaur + +This will write the private key to the file `dinosaur.priv`. + +.. warning:: + + You should be very careful about exporting private keys. You likely would + only want to do this in the context of moving your spack installation to + a different server, and wanting to preserve keys for a buildcache. If you + are unsure about exporting, you can ask your local system administrator + or for help on an issue or the Spack slack. + + .. _cray-support: ------------- diff --git a/lib/spack/spack/binary_distribution.py b/lib/spack/spack/binary_distribution.py index ba4921b677..46ca4fe88a 100644 --- a/lib/spack/spack/binary_distribution.py +++ b/lib/spack/spack/binary_distribution.py @@ -1558,7 +1558,9 @@ def push_keys(*mirrors, **kwargs): filename = fingerprint + '.pub' export_target = os.path.join(prefix, filename) - spack.util.gpg.export_keys(export_target, fingerprint) + + # Export public keys (private is set to False) + spack.util.gpg.export_keys(export_target, [fingerprint]) # If mirror is local, the above export writes directly to the # mirror (export_target points directly to the mirror). diff --git a/lib/spack/spack/cmd/gpg.py b/lib/spack/spack/cmd/gpg.py index e34c2c4c84..3a65f1abc3 100644 --- a/lib/spack/spack/cmd/gpg.py +++ b/lib/spack/spack/cmd/gpg.py @@ -60,6 +60,9 @@ def setup_parser(subparser): 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__) @@ -79,7 +82,9 @@ def setup_parser(subparser): help='where to export keys') export.add_argument('keys', nargs='*', help='the keys to export; ' - 'all secret keys if unspecified') + '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__) @@ -112,22 +117,28 @@ def setup_parser(subparser): def gpg_create(args): """create a new key""" - if args.export: + 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: + if args.export or args.secret: new_sec_keys = set(spack.util.gpg.signing_keys()) new_keys = new_sec_keys.difference(old_sec_keys) - spack.util.gpg.export_keys(args.export, *new_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) def gpg_export(args): - """export a secret key""" + """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) + spack.util.gpg.export_keys(args.location, keys, args.secret) def gpg_list(args): diff --git a/lib/spack/spack/test/cmd/gpg.py b/lib/spack/spack/test/cmd/gpg.py index 099d53e039..eb7eab2734 100644 --- a/lib/spack/spack/test/cmd/gpg.py +++ b/lib/spack/spack/test/cmd/gpg.py @@ -117,6 +117,20 @@ def test_gpg(tmpdir, mock_gnupghome): export_path = tmpdir.join('export.testing.key') gpg('export', str(export_path)) + # Test exporting the private key + private_export_path = tmpdir.join('export-secret.testing.key') + gpg('export', '--secret', str(private_export_path)) + + # Ensure we exported the right content! + with open(str(private_export_path), 'r') as fd: + content = fd.read() + assert "BEGIN PGP PRIVATE KEY BLOCK" in content + + # and for the public key + with open(str(export_path), 'r') as fd: + content = fd.read() + assert "BEGIN PGP PUBLIC KEY BLOCK" in content + # Create a second key for use in the tests. gpg('create', '--comment', 'Spack testing key', diff --git a/lib/spack/spack/util/gpg.py b/lib/spack/spack/util/gpg.py index aa376bc041..03b33af234 100644 --- a/lib/spack/spack/util/gpg.py +++ b/lib/spack/spack/util/gpg.py @@ -336,10 +336,12 @@ class Gpg(object): *args, output=str) return parse_public_keys_output(output) - def export_keys(self, location, *keys): - self('--batch', '--yes', - '--armor', '--export', - '--output', location, *keys) + def export_keys(self, location, keys, secret=False): + if secret: + self("--export-secret-keys", "--armor", "--output", location, *keys) + else: + self('--batch', '--yes', '--armor', '--export', '--output', + location, *keys) def trust(self, keyfile): self('--import', keyfile) -- cgit v1.2.3-70-g09d2