Page MenuHome GnuPG

gpg --quick-gen-key userid card fails on first run resp. for unknown key
Closed, ResolvedPublic

Description

How to reproduce:

  • Create an empty GNUPGHOME, e.g. /tmp/genkeyfromcard, and export it as environment variable
  • Insert a card with OpenPGP keys (e.g. a Gnuk)
  • Try to generate keys from the card
$ GNUPGHOME=/tmp/genkeyfromcard gpg --quick-gen-key --yes gnuk@example.net card
gpg: NOTE: THIS IS A DEVELOPMENT VERSION!
gpg: It is only intended for test purposes and should NOT be
gpg: used in a production environment or with production keys!
gpg: keybox '/tmp/genkeyfromcard/pubring.kbx' created
gpg: Serial number of the card: D276000124010200FFFE4231126D0000
gpg: Serial number of the card: D276000124010200FFFE4231126D0000
gpg: signing failed: Not implemented
gpg: make_keysig_packet failed: Not implemented
Key generation failed: Not implemented

Result: In $GNUPGHOME/private-keys-v1.d/ a file has been created, but that's all.

  • Try again
$ GNUPGHOME=/tmp/genkeyfromcard gpg --quick-gen-key --yes gnuk@example.net card
gpg: NOTE: THIS IS A DEVELOPMENT VERSION!
gpg: It is only intended for test purposes and should NOT be
gpg: used in a production environment or with production keys!
gpg: keybox '/tmp/genkeyfromcard/pubring.kbx' created
gpg: Serial number of the card: D276000124010200FFFE4231126D0000
gpg: Serial number of the card: D276000124010200FFFE4231126D0000
gpg: /tmp/genkeyfromcard/trustdb.gpg: trustdb created
gpg: key B16F599516474ABA marked as ultimately trusted
gpg: directory '/tmp/genkeyfromcard/openpgp-revocs.d' created
gpg: revocation certificate stored as '/tmp/genkeyfromcard/openpgp-revocs.d/DB8E020E328C30942060BF21B16F599516474ABA.rev'
public and secret key created and signed.

pub   ed25519 2020-08-03 [SC] [expires: 2022-08-03]
      DB8E020E328C30942060BF21B16F599516474ABA
uid                      gnuk@example.net
sub   cv25519 2020-08-03 [E]

Result: On the 2nd try it worked.

$ GNUPGHOME=/tmp/genkeyfromcard gpg -K
gpg: NOTE: THIS IS A DEVELOPMENT VERSION!
gpg: It is only intended for test purposes and should NOT be
gpg: used in a production environment or with production keys!
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: next trustdb check due at 2022-08-03
/tmp/genkeyfromcard/pubring.kbx
--------------------------------------------------------------
sec>  ed25519 2020-08-03 [SC] [expires: 2022-08-03]
      DB8E020E328C30942060BF21B16F599516474ABA
      Card serial no. = FFFE 4231126D
uid           [ultimate] gnuk@example.net
ssb#  cv25519 2020-08-03 [E]

Further observations:

  • It does not make a difference if gpg-agent and scdaemon are already running.
  • It seems to make a difference whether the card keys are known locally.
$ GNUPGHOME=/tmp/genkeyfromcard gpg--delete-secret-keys DB8E020E328C30942060BF21B16F599516474ABA
gpg (GnuPG) 2.3.0-beta1490; Copyright (C) 2020 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.

gpg: NOTE: THIS IS A DEVELOPMENT VERSION!
gpg: It is only intended for test purposes and should NOT be
gpg: used in a production environment or with production keys!

sec  ed25519/B16F599516474ABA 2020-08-03 gnuk@example.net

Delete this key from the keyring? (y/N) y
This is a secret key! - really delete? (y/N) y

$ GNUPGHOME=/tmp/genkeyfromcard gpg --delete-keys DB8E020E328C30942060BF21B16F599516474ABA
gpg (GnuPG) 2.3.0-beta1490; Copyright (C) 2020 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.

gpg: NOTE: THIS IS A DEVELOPMENT VERSION!
gpg: It is only intended for test purposes and should NOT be
gpg: used in a production environment or with production keys!

pub  ed25519/B16F599516474ABA 2020-08-03 gnuk@example.net

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

$ GNUPGHOME=/tmp/genkeyfromcard gpg -K 
gpg: NOTE: THIS IS A DEVELOPMENT VERSION!
gpg: It is only intended for test purposes and should NOT be
gpg: used in a production environment or with production keys!
gpg: checking the trustdb
gpg: no ultimately trusted keys found

$ GNUPGHOME=/tmp/genkeyfromcard gpg -k
gpg: NOTE: THIS IS A DEVELOPMENT VERSION!
gpg: It is only intended for test purposes and should NOT be
gpg: used in a production environment or with production keys!

$ ls /tmp/genkeyfromcard/private-keys-v1.d

$ GNUPGHOME=/tmp/genkeyfromcard gpg --quick-gen-key --yes gnuk@example.net card
gpg: NOTE: THIS IS A DEVELOPMENT VERSION!
gpg: It is only intended for test purposes and should NOT be
gpg: used in a production environment or with production keys!
gpg: Serial number of the card: D276000124010200FFFE4231126D0000
gpg: Serial number of the card: D276000124010200FFFE4231126D0000
gpg: signing failed: Not implemented
gpg: make_keysig_packet failed: Not implemented
Key generation failed: Not implemented

$ ls /tmp/genkeyfromcard/private-keys-v1.d
C996D8B6E926F14807FD6A748D3398CD11D143C3.key

$ GNUPGHOME=/tmp/genkeyfromcard gpg --quick-gen-key --yes gnuk@example.net card
gpg: NOTE: THIS IS A DEVELOPMENT VERSION!
gpg: It is only intended for test purposes and should NOT be
gpg: used in a production environment or with production keys!
gpg: Serial number of the card: D276000124010200FFFE4231126D0000
gpg: Serial number of the card: D276000124010200FFFE4231126D0000
gpg: key B16F599516474ABA marked as ultimately trusted
gpg: revocation certificate stored as '/tmp/genkeyfromcard/openpgp-revocs.d/DB8E020E328C30942060BF21B16F599516474ABA.rev'
public and secret key created and signed.

pub   ed25519 2020-08-03 [SC] [expires: 2022-08-03]
      DB8E020E328C30942060BF21B16F599516474ABA
uid                      gnuk@example.net
sub   cv25519 2020-08-03 [E]

Last entries in gpg-agent log after failure (debug=ipc):

2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 <- RESET
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 -> OK
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 <- SIGKEY C996D8B6E926F14807FD6A748D3398CD11D143C3
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 -> OK
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 <- SETKEYDESC Please+enter+the+passphrase+to+unlock+the+OpenPGP+secret+key:%0A%22[User+ID+not+found]%22%0A255-bit+EDDSA+key,+ID+B16F599516474ABA,%0Acreated+2020-08-03.%0A
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 -> OK
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 <- SETHASH 10 EC265D094961D37C95CA791ECF27CB54EF9161ADFEBDD766CB3C926266C9A0BA5E5B57D604F57F4CE727D9D477C50C9B18AB99C49C7072118961FE54E225CA85
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 -> OK
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 <- PKSIGN
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 -> SERIALNO --all
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- S SERIALNO D276000124010200FFFE4231126D0000
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- OK
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 -> READKEY --info -- C996D8B6E926F14807FD6A748D3398CD11D143C3
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- S KEYPAIRINFO C996D8B6E926F14807FD6A748D3398CD11D143C3 OPENPGP.1 sc 1596458579 ed25519
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- [ 44 20 28 31 30 3a 70 75 62 6c 69 63 2d 6b 65 79 ...(83 byte(s) skipped) ]
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- OK
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 -> SERIALNO --all
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- S SERIALNO D276000124010200FFFE4231126D0000
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- OK
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 -> KEYINFO C996D8B6E926F14807FD6A748D3398CD11D143C3
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- S KEYINFO C996D8B6E926F14807FD6A748D3398CD11D143C3 T D276000124010200FFFE4231126D0000 OPENPGP.1
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- OK
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 -> SETDATA 3051300D060960864801650304020305000440EC265D094961D37C95CA791ECF27CB54EF9161ADFEBDD766CB3C926266C9A0BA5E5B57D604F57F4CE727D9D477C50C9B18AB99C49C7072118961FE54E225CA85
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- OK
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 -> PKSIGN --hash=sha512 C996D8B6E926F14807FD6A748D3398CD11D143C3
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_11 <- [ 49 4e 51 55 49 52 45 20 4e 45 45 44 50 49 4e 20 ...(93 byte(s) skipped) ]
2020-12-04 12:26:10 gpg-agent[11037] starting a new PIN Entry
2020-12-04 12:26:10 gpg-agent[11037] DBG: connection to PIN entry established
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 -> INQUIRE PINENTRY_LAUNCHED 11947 qt 1.1.1-beta42 /dev/pts/7 xterm-256color :0 20600/1000/5 1000/100 0
2020-12-04 12:26:10 gpg-agent[11037] DBG: chan_10 <- END
2020-12-04 12:26:14 gpg-agent[11037] DBG: chan_11 -> [ 44 20 31 32 33 34 35 36 00 00 00 00 00 00 00 00 ...(76 byte(s) skipped) ]
2020-12-04 12:26:14 gpg-agent[11037] DBG: chan_11 -> END
2020-12-04 12:26:14 gpg-agent[11037] DBG: chan_11 <- [ 44 20 49 c2 32 2c e3 b9 58 11 a1 b6 7d 3b d8 39 ...(54 byte(s) skipped) ]
2020-12-04 12:26:14 gpg-agent[11037] DBG: chan_11 <- OK
2020-12-04 12:26:14 gpg-agent[11037] failed to convert sigbuf returned by divert_pksign into S-Exp: Not implemented
2020-12-04 12:26:14 gpg-agent[11037] command 'PKSIGN' failed: Not implemented
2020-12-04 12:26:14 gpg-agent[11037] DBG: chan_10 -> ERR 67108933 Not implemented <GPG Agent>
2020-12-04 12:26:14 gpg-agent[11037] DBG: chan_10 <- [eof]
2020-12-04 12:26:14 gpg-agent[11037] DBG: chan_11 -> RESTART
2020-12-04 12:26:14 gpg-agent[11037] DBG: chan_11 <- OK

Event Timeline

I think that the semantics of gpg --quick-gen-key <KEY> card (currently) assumes keys are available on card.
IIUC, it is for some specific (very special) use case to specify same key creation time to the key on card.
I don't know well about this use case.

Anyway, because of this, (currently) the first run results undefined behavior.

It would be good if it just means "creating key(s) on card".

I think that the semantics of gpg --quick-gen-key <KEY> card (currently) assumes keys are available on card.
IIUC, it is for some specific (very special) use case to specify same key creation time to the key on card.
I don't know well about this use case.

Anyway, because of this, (currently) the first run results undefined behavior.

It would be good if it just means "creating key(s) on card".

I think there is a misunderstanding. gpg --quick-gen-key <userid> card does "create an OpenPGP key from the keys available on the currently inserted smartcard" (see man gpg), i.e. not "on card", but off-card based on the keys on the existing key(s) on the card.

The problem is that in agent_pksign_do() the algo is read from s_skey (pksign.c:328), but s_skey is NULL because agent_key_from_file() fails to find a local KEYGRIP.key file in private-keys-v1.d. The code then reads the public key from the card (or a stub file), but it fails to set algo from s_pkey. The following patch fixes this:

diff --git a/agent/pksign.c b/agent/pksign.c
index c94c1a197..1a3ba8c48 100644
--- a/agent/pksign.c
+++ b/agent/pksign.c
@@ -388,6 +388,7 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
               goto leave;
             }
         }
+      algo = get_pk_algo_from_key (s_pkey);
 
       {
         char *desc2 = NULL;

Maybe the line (pksign.c:328)

algo = get_pk_algo_from_key (s_skey);

should be moved to the start of the else-branch (pksign.c:484):

else
  {
    /* No smartcard, but a private key (in S_SKEY). */

    /* Put the hash into a sexp */
    if (algo == GCRY_PK_EDDSA)

because it just causes confusion that it's set (or not set if s_skey is NULL) in line 328.

Maybe the line (pksign.c:328)

algo = get_pk_algo_from_key (s_skey);

should be moved to the start of the else-branch (pksign.c:484):

Or maybe not because algo is set from s_skey also if shadow_info is true.

What about the following patch:

diff --git a/agent/pksign.c b/agent/pksign.c
index c94c1a197..d9ffe6019 100644
--- a/agent/pksign.c
+++ b/agent/pksign.c
@@ -324,8 +324,8 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
       log_error ("failed to read the secret key\n");
       goto leave;
     }
-
-  algo = get_pk_algo_from_key (s_skey);
+  else
+    algo = get_pk_algo_from_key (s_skey);
 
   if (shadow_info || no_shadow_info)
     {
@@ -374,6 +374,8 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
           if (keyref)
             agent_write_shadow_key (ctrl->keygrip, serialno, keyref, pkbuf, 0);
 
+          algo = get_pk_algo_from_key (s_pkey);
+
           xfree (serialno);
           xfree (pkbuf);
           xfree (keyref);
gniibe changed the task status from Open to Testing.Dec 8 2020, 7:14 AM
gniibe triaged this task as Normal priority.

Pushed the change by Ingo.