Page MenuHome GnuPG

GPG does not import secret subkeys from --export-secret-subkeys output if subkey stubs existed before
Closed, ResolvedPublic

Description

This is similar to T1543, but there the commenter writes:

if the secret key is deleted from the keyring, then afterwards --import properly imports all the subkeys from the file.

But this is not the case for me. Even when I delete the secret keys and do the import after that, the keys always come back as ssb> instead of ssb.

Example:

I start with

➤ gpg2 --list-secret-keys
/home/niklas/.gnupg/pubring.gpg
-------------------------------

gpg: error computing keygrip
sec#  rsa4096/2DFBFA03 2015-11-21 [SC] [expires: 2021-11-19]
      Key fingerprint = C339 0A90 6C9D 275E 4116  EE00 AC34 326A 2DFB FA03
uid         [ unknown] Niklas Hambüchen <niklas@nh2.me>
uid         [ unknown] Niklas Hambuechen <mail@nh2.me>
uid         [ unknown] Niklas Hambuechen <niklas@nh2.me>
uid         [ unknown] Niklas Hambuechen <nh2@deditus.de>
uid         [ unknown] Niklas Hambüchen <mail@nh2.me>
uid         [ unknown] Niklas Hambüchen <nh2@deditus.de>
ssb>  rsa2048/5E1316BA 2015-11-21 [E] [expires: 2021-11-19]
ssb>  rsa2048/A2A0E898 2015-11-21 [S] [expires: 2021-11-19]
ssb>  rsa2048/41F742E3 2015-11-21 [A] [expires: 2021-11-19]

Then I delete it:

➤ gpg2 --delete-secret-and-public-key 2DFBFA03
gpg (GnuPG) 2.1.11; Copyright (C) 2016 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pub  rsa4096/2DFBFA03 2015-11-21 Niklas Hambüchen <niklas@nh2.me>

Delete this key from the keyring? (y/N) y

and it's gone from the listing of --list-secret-keys.

Then I import it from another machine that has the non-stub secret subkeys keys (shown as ssb on that machine) like this:

➤ ssh othermachine 'gpg2 --armor --export-secret-subkeys 2DFBFA03' | gpg2 --import
gpg: key 2DFBFA03: public key "Niklas Hambüchen <niklas@nh2.me>" imported
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key 2DFBFA03: secret key imported
gpg: Total number processed: 5
gpg:               imported: 1
gpg:       secret keys read: 5
gpg:  secret keys unchanged: 3

And that leaves me with:

➤ gpg2 --list-secret-keys
/home/niklas/.gnupg/pubring.gpg
-------------------------------

gpg: error computing keygrip
sec#  rsa4096/2DFBFA03 2015-11-21 [SC] [expires: 2021-11-19]
      Key fingerprint = C339 0A90 6C9D 275E 4116  EE00 AC34 326A 2DFB FA03
uid         [ unknown] Niklas Hambüchen <niklas@nh2.me>
uid         [ unknown] Niklas Hambuechen <mail@nh2.me>
uid         [ unknown] Niklas Hambuechen <niklas@nh2.me>
uid         [ unknown] Niklas Hambuechen <nh2@deditus.de>
uid         [ unknown] Niklas Hambüchen <mail@nh2.me>
uid         [ unknown] Niklas Hambüchen <nh2@deditus.de>
ssb>  rsa2048/5E1316BA 2015-11-21 [E] [expires: 2021-11-19]
ssb>  rsa2048/A2A0E898 2015-11-21 [S] [expires: 2021-11-19]
ssb>  rsa2048/41F742E3 2015-11-21 [A] [expires: 2021-11-19]

Note how the keys are ssb> stubs again!

Also, why did the import say secret keys unchanged: 3? Didn't I delete them?

I suspect that GPG stores some information somewhere that makes it remember something that lives on past deletion. And my usual workaround for this issue is to fully remove the ~/.gnupg directory; then importing works fine.

I'm using gpg (GnuPG) 2.1.11, and gpg (GnuPG) 2.0.22 on the othermachine where I export them from.

Event Timeline

nh2 created this object in space S1 Public.

Potentially useful to know: This is how the import looks like into an empty ~/.gnupg directory:

➤ mv ~/.gnupg/ ~/.gnupg.orig
                                                                                                                                                                         
➤ ssh k4 cat ~/.gnupg/gpg-niklas-hambuechen-0x2DFBFA03-secret-subkeys.asc | gpg2 --import
gpg: directory '/home/niklas/.gnupg' created
gpg: new configuration file '/home/niklas/.gnupg/dirmngr.conf' created
gpg: new configuration file '/home/niklas/.gnupg/gpg.conf' created
gpg: keybox '/home/niklas/.gnupg/pubring.kbx' created
Enter passphrase for key '/home/niklas/.ssh/id_rsa': 
gpg: /home/niklas/.gnupg/trustdb.gpg: trustdb created
gpg: key 2DFBFA03: public key "Niklas Hambüchen <niklas@nh2.me>" imported
gpg: To migrate 'secring.gpg', with each smartcard, run: gpg --card-status
gpg: key 2DFBFA03: secret key imported
gpg: Total number processed: 5
gpg:               imported: 1
gpg:       secret keys read: 5
gpg:   secret keys imported: 3

Notice this time it says secret keys imported: 3 instead of secret keys unchanged: 3.

It also popped up a window asking for my passphrase, which it didn't do before.

gnupg 2.1.11 is pretty old and has quite some bugs. Please try at least the Debian version which is 2.1.18 plus a couple of backported fixes. Or yet better, the current stable 2.2.x

werner triaged this task as Normal priority.Oct 24 2017, 3:09 PM
werner added a project: Info Needed.

In agent_write_private_key of agent/findkey.c, when file is available, it returns GPG_ERR_EEXIST error. Thus, private (stub) key will be kept.

I'm going to support this use case by the following approach, in 2.3.

(1) When importing private key, the front-end side (gpg) will check if key is already there, by agent's KEYINFO command.
(2) If not available, it's same as current behavior.
(3) If stub key is available, do force import.
(4) Otherwise, raise GPG_ERR_EEXIST error.

Changes may be something like:

diff --git a/g10/import.c b/g10/import.c
index 1ed40a63c..91ff0c8ec 100644
--- a/g10/import.c
+++ b/g10/import.c
@@ -2706,6 +2706,20 @@ transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats,
           goto leave;
         }
 
+      if (!force)
+        {
+          int already_exist = agent_probe_secret_key (ctrl, pk);
+
+          if (already_exist == 1)
+            {
+              err = gpg_error (GPG_ERR_EEXIST);
+              goto exist;
+            }
+          else if (already_exist == 2)
+            /* It's a card stub.  */
+            force = 1;
+        }
+
       if (ski->is_protected)
         {
           char countbuf[35];
@@ -2790,6 +2804,7 @@ transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats,
         }
       else if ( gpg_err_code (err) == GPG_ERR_EEXIST )
         {
+        exist:
           if (opt.verbose)
             log_info (_("key %s: secret key already exists\n"),
                       keystr_from_pk_with_sub (main_pk, pk));

I'm going to test this. When no problems, this will be applied.

To minimize the impact of the change, I updated:

diff --git a/g10/import.c b/g10/import.c
index 1ed40a63c..345e8cc75 100644
--- a/g10/import.c
+++ b/g10/import.c
@@ -2955,9 +2955,23 @@ do_transfer (ctrl_t ctrl, kbnode_t keyblock, PKT_public_key *pk,
 {
   gpg_error_t err;
   struct import_stats_s subkey_stats = {0};
+  int force = 0;
+  int already_exist = agent_probe_secret_key (ctrl, pk);
+
+#ifndef OK_TO_CHANGE_ERROR_BEHAVIOR
+  if (already_exist == 1)
+    return gpg_error (GPG_ERR_EEXIST);
+#endif
+  if (already_exist == 2)
+    {
+      if (!opt.quiet)
+        log_info (_("key %s: card reference is overridden by key material\n"),
+                  keystr_from_pk (pk));
+      force = 1;
+    }
 
   err = transfer_secret_keys (ctrl, &subkey_stats, keyblock,
-                              batch, 0, only_marked);
+                              batch, force, only_marked);
   if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED)
     {
       /* TRANSLATORS: For a smartcard, each private key on host has a

The conditional part by OK_TO_CHANGE_ERROR_BEHAVIOR is better not to be included (minimum impact). I put it to show implementing for (4) in my comment.

gniibe changed the task status from Open to Testing.Apr 18 2023, 2:59 AM

Pushed the change not including OK_TO_CHANGE_ERROR_BEHAVIOR part.
Note that the modification affects main key case, too.

This works if the smartcard with the same private key is not connected, which it usually shouldn't be (outside of testing situations) so that's ok for me.
But I think we should inform the user what is done or isn't.
Currently we get in both cases:

gpg: Anzahl insgesamt bearbeiteter Schlüssel: 1
gpg:                             unverändert: 1
gpg:              gelesene geheime Schlüssel: 1
gpg:            geheime Schlüssel importiert: 1

Which is not very clear in any case but in case the smart card is connected it is in fact wrong, there is no secret key imported, the keystub remains.

For importing key/subkey case, it doesn't matter if the smartcard is connected or not. The data in the file will be overwritten by import.

Isn't it another access to smartcard which again changes the file content to stub?
Note that gpg --card-status or gpg --change-pin change the file content to stub.

Sorry, the comment above is my misunderstanding.

I'm going to update the change for the case of smartcard available.

ebo moved this task from Restricted Project Column to Restricted Project Column on the Restricted Project board.

works, Gpg4win-4.1.1-beta295

werner edited projects, added gnupg22; removed gnupg.
werner moved this task from Backlog to WiP on the gnupg22 board.

Backported to 2.2 branch.

werner changed the task status from Open to Testing.Sep 6 2023, 8:25 AM
werner moved this task from WiP to QA on the gnupg22 board.

Thank you.

For VS-Desktop-3.2.0.0-beta214 this does not work yet. If a keystub exists, it is not overwritten.

I get the following trying to import via gpg --import:

[...]
gpg: sec  brainpoolP384r1/458612006D8E6F0D 2023-03-08  Berta Boss <Berta.Boss@demo.gnupg.com>
gpg: Schlüssel 458612006D8E6F0D: "Berta Boss <Berta.Boss@demo.gnupg.com>" nicht geändert
gpg: Schlüssel 458612006D8E6F0D/458612006D8E6F0D: geheimer Schlüssel bereits vorhanden
gpg: Schlüssel 458612006D8E6F0D/FFA2FCCB2EC589F8: geheimer Schlüssel bereits vorhanden
gpg: Schlüssel 458612006D8E6F0D: geheimer Schlüssel importiert
gpg: Anzahl insgesamt bearbeiteter Schlüssel: 1
gpg:                             unverändert: 1
gpg:              gelesene geheime Schlüssel: 1
gpg:                 unveränderte geh. Schl.: 1
gpg: keydb: handles=3 locks=1 parse=3 get=3
gpg:        build=0 update=0 insert=0 delete=0
gpg:        reset=0 found=3 not=0 cache=0 not=0
gpg: kid_not_found_cache: count=0 peak=0 flushes=0
gpg: sig_cache: total=10 cached=8 good=8 bad=0
gpg: random usage: poolsize=600 mixed=0 polls=0/0 added=0/0
              outmix=0 getlvl1=0/0 getlvl2=0/0
gpg: rndjent stat: collector=0x00000000 calls=0 bytes=0
gpg: secmem usage: 0/32768 bytes in 0 blocks

In 2.2, KEYINFO output doesn't support A-flag for the information if card is online or not.
We need to clean up this discrepancy.

works now with VS-Desktop-3.1.90.246-Beta

ebo edited projects, added gnupg22 (gnupg-2.2.42); removed gnupg22.