Repository: spack/spack

Issue:
No way to export a gpg key created with spack gpg create
In order to use the signed buildcache I need to import the public key of the server somehow. I can't simply do `spack gpg trust file://mnt/spack-mirror` because for some unexplained in the documentation reason `spack buildcache create` does not put the public key there, even though it uses the corresponding private key to sign the packages it writes.

`spack gpg export` would work, if it exported the public key, not the private.


edit:
I have to add that `gpg` itself with its default keyring is unaware of any of the spack keys, so using for that purpose is far from obvious. `gpg --list-secret-keys` returns nothing.


   *   *   *
  
I use spack `0.13.3-935-0c0aba57` on Ubuntu 18.04.4 64 bit


Discussion:
OK. I tried to run the `spack gpg export` anyway in a hope that it would also export the public key together with the private one. 

To my surprise

```
spack gpg export adam.pub
```

wrote the **public key** and **no private key**!!! 


For the record:

```
$ $ spack gpg --help
usage: spack gpg [-h] SUBCOMMAND ...

handle GPG actions for spack

positional arguments:
  SUBCOMMAND  GPG sub-commands
    verify    verify a signed package
    trust     add a key to the keyring
    untrust   remove a key from the keyring
    sign      sign a package
    create    create a new key
    list      list keys available in the keyring
    init      add the default keys to the keyring
    export    export a secret key

optional arguments:
  -h, --help  show this help message and exit
```

and then

```
$ spack gpg export --help
usage: spack gpg export [-h] location [keys [keys ...]]

positional arguments:
  location    where to export keys
  keys        the keys to export; all secret keys if unspecified

optional arguments:
  -h, --help  show this help message and exit
```

As it can be seen you consistently claim that you do export the *private* key. WTF? Why the documentation is misleading on this? Am I missing something? 

Afaik only the public portion of the key is exported.

You would not want to export your private key.

If you want to reuse the private key you need to copy the contents of spack/opt/spack/gpg to a new spack install. You can also use GNUPGHOME=$spack/opt/spack.gpg gpg2 and the correct options to output the private key.  
@scheibelp Is anything I said incorrect?
I'm hitting the same issue when trying to read from buildcache. I can export a package successfully and sign it with ```spack gpg sign``` but when I uninstall the package and try to re-install from buildcache, it fails as:

```
gpg: Can't check signature: No public key
==> Error: Command exited with status 2:
'/usr/bin/gpg2' '--verify' '/tmp/tmp60x97gxw/cray-cnl6-mic_knl-intel-19.0.5.281-spack-dummy-package-master-yrf4hqg5u4eaviw64y3dj7zgn5b6esze.spec.yaml.asc' '/tmp/tmp60x97gxw/cray-cnl6-mic_knl-intel-19.0.5.281-spack-dummy-package-master-yrf4hqg5u4eaviw64y3dj7zgn5b6esze.spec.yaml'
```
If spack is generating a key pair, what do I need to be doing so that it can find its public key?


Resolving patch:
diff --git a/lib/spack/spack/binary_distribution.py b/lib/spack/spack/binary_distribution.py
--- a/lib/spack/spack/binary_distribution.py
+++ b/lib/spack/spack/binary_distribution.py
@@ -1540,7 +1540,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
--- 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/util/gpg.py b/lib/spack/spack/util/gpg.py
--- a/lib/spack/spack/util/gpg.py
+++ b/lib/spack/spack/util/gpg.py
@@ -336,10 +336,12 @@ def public_keys(self, *args):
                       *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)


Test patch:
diff --git a/lib/spack/spack/test/cmd/gpg.py b/lib/spack/spack/test/cmd/gpg.py
--- 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',