Page MenuHome GnuPG

gpgme_get_key fails to detect secret encryption subkey after key generation on card (until context is recreated)
Open, Needs TriagePublic

Description

Summary

When testing smartcard-based key generation with gpg --card-edit, I observed that after generating a key directly on a YubiKey (OpenPGP applet), gpgme_get_key() fails to correctly reflect the presence of the secret encryption subkey — if the same gpgme_ctx_t context is reused.

Specifically, the secret flag for the encryption subkey is false, even though the key was just generated and is present on the card.

However, if the gpgme_ctx_t context is destroyed and re-created, the encryption subkey is detected correctly as secret.

This issue is reproducible and occurred in 4 out of 5 tests.

Environment

  1. GPGME version: 1.24.2
  2. GnuPG version: 2.4.7

Reproduction Process

  1. Start smartcard editing.
  2. Use the generate command to create a key directly on the smartcard.
  3. After successful key creation, use the following C program to query the key using gpgme_get_key() without recreating the gpgme context.
  4. Observe that the encryption subkey is returned with secret == false.
  5. Now, destroy and recreate the context, query again, and the encryption key is correctly shown as secret == true.

Test Code

#include <gpgme.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

void check_error(gpgme_error_t err) {
    if (err) {
        fprintf(stderr, "GPGME Error: %s\n", gpgme_strerror(err));
        exit(1);
    }
}

void print_subkey_status(gpgme_subkey_t subkey) {
    printf("  Subkey: %s\n", subkey->keyid);
    printf("    Expiration: %s\n", subkey->expires ? ctime(&subkey->expires) : "No expiration");
    printf("    Public Key Algorithm: %d\n", subkey->pubkey_algo);
    printf("    Key Length: %u\n", subkey->length);
    printf("    Can Encrypt: %s\n", subkey->can_encrypt ? "Yes" : "No");
    printf("    Can Sign: %s\n", subkey->can_sign ? "Yes" : "No");
    printf("    Can Authenticate: %s\n", subkey->can_authenticate ? "Yes" : "No");
    printf("    Expired: %s\n", subkey->expired ? "Yes" : "No");
    printf("    Revoked: %s\n", subkey->revoked ? "Yes" : "No");
    printf("    Disabled: %s\n", subkey->disabled ? "Yes" : "No");
    printf("    Secret Key Present: %s\n", subkey->secret ? "Yes" : "No");
    printf("\n");
}

int main() {
    setlocale(LC_ALL, "");
    gpgme_check_version(NULL);

    gpgme_ctx_t ctx;
    gpgme_error_t err;

    // Initialize context once
    err = gpgme_new(&ctx);
    check_error(err);
    gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);

    char input[256];
    while (1) {
        printf("Enter Key ID or Fingerprint (or 'exit' to quit): ");
        if (!fgets(input, sizeof(input), stdin)) {
            break;
        }

        input[strcspn(input, "\n")] = 0;
        if (strcmp(input, "exit") == 0) {
            break;
        }

        gpgme_key_t key;
        err = gpgme_get_key(ctx, input, &key, 1);
        if (err) {
            fprintf(stderr, "Failed to get key: %s\n", gpgme_strerror(err));
            continue;
        }

        printf("Primary Key: %s\n", key->subkeys->keyid);
        printf("Fingerprint: %s\n", key->subkeys->fpr);
        printf("Subkey List:\n");

        gpgme_subkey_t subkey = key->subkeys;
        while (subkey) {
            print_subkey_status(subkey);
            subkey = subkey->next;
        }

        gpgme_key_release(key);
    }

    gpgme_release(ctx);
    printf("Exiting.\n");
    return 0;
}

Test Procedure

 shell

gpg/card> list

XXXXXX
XXXXXX
XXXXXX

gpg/card> generate
Sicherung des Verschlüsselungsschlüssel außerhalb der Karte erstellen? (J/n) J

gpg: Hinweis: Auf der Karte sind bereits Schlüssel gespeichert!

Vorhandene Schlüssel ersetzen? (j/N) j
Bitte wählen Sie, wie lange der Schlüssel gültig bleiben soll.
         0 = Schlüssel verfällt nie
      <n>  = Schlüssel verfällt nach n Tagen
      <n>w = Schlüssel verfällt nach n Wochen
      <n>m = Schlüssel verfällt nach n Monaten
      <n>y = Schlüssel verfällt nach n Jahren
Wie lange bleibt der Schlüssel gültig? (0) 0
Schlüssel verfällt nie
Ist dies richtig? (j/N) j

GnuPG erstellt eine User-ID, um Ihren Schlüssel identifizierbar zu machen.

Ihr Name ("Vorname Nachname"): eric
Email-Adresse: eric@bktus.com
Kommentar: 
Sie haben diese User-ID gewählt:
    "eric <eric@bktus.com>"

Ändern: (N)ame, (K)ommentar, (E)-Mail oder (F)ertig/(A)bbrechen? F
Wir müssen eine ganze Menge Zufallswerte erzeugen.  Sie können dies
unterstützen, indem Sie z.B. in einem anderen Fenster/Konsole irgendetwas
tippen, die Maus verwenden oder irgendwelche anderen Programme benutzen.
gpg: Hinweis: Sicherung des Kartenschlüssels wurde auf `/Users/erich/.gnupg/sk_46XXXXXXXXX6FC.gpg' gespeichert
gpg: Widerrufzertifikat wurde als '/Users/erich/.gnupg/openpgp-revocs.d/124527XXXXXXXXXXX571F9B8D.rev' gespeichert.
Öffentlichen und geheimen Schlüssel erzeugt und signiert.

pub   brainpoolP256r1 2025-04-21 [SC]
      12452724D065162457908D7CF17AE2C5571F9B8D
uid                      eric <eric@bktus.com>
sub   brainpoolP256r1 2025-04-21 [A]
sub   brainpoolP256r1 2025-04-21 [E]

gpg/card>

GPGME Output (reused context)

shell

Enter Key ID or Fingerprint (or 'exit' to quit): 12452724D065162457908D7CF17AE2C5571F9B8D
Primary Key: F17AE2C5571F9B8D
Fingerprint: 12452724D065162457908D7CF17AE2C5571F9B8D
Subkey List:
  Subkey: F17AE2C5571F9B8D
    Expiration: No expiration
    Public Key Algorithm: 301
    Key Length: 256
    Can Encrypt: No
    Can Sign: Yes
    Can Authenticate: No
    Expired: No
    Revoked: No
    Disabled: No
    Secret Key Present: Yes

  Subkey: 53A764C774EAE8C4
    Expiration: No expiration
    Public Key Algorithm: 301
    Key Length: 256
    Can Encrypt: No
    Can Sign: No
    Can Authenticate: Yes
    Expired: No
    Revoked: No
    Disabled: No
    Secret Key Present: Yes

  Subkey: 46643F012EAA06FC
    Expiration: No expiration
    Public Key Algorithm: 302
    Key Length: 256
    Can Encrypt: Yes
    Can Sign: No
    Can Authenticate: No
    Expired: No
    Revoked: No
    Disabled: No
    ->**Secret Key Present: No** <-

Expected Behavior

All three subkeys should report secret == true after generation, regardless of whether the context is reused.

Details

Version
1.24.2

Event Timeline

The logs of gpgme would be helpful, i.e. run your test program with GPGME_DEBUG=8:$(pwd)/gpgme-$(date +"%Y-%m-%d-%H%M%S").log to create a log file with gpgme's logs.

I doubt that this is a gpgme problem. With a gpgme log we will be able see the exact commands send to gpg and replicate this on the command line.

It might be useful to have a gpgme debug option to show just this information in the format of a command line invocation. Problem is that we pipe data to it this some args needs to be replaced or we need to have a legend with the file descriptors use.

I have now identified the exact conditions and a reproducible path for the issue I previously reported. I will also attach the relevant gpgme.log.

After generating a new keypair using a smartcard (gpg --card-edit → generate), querying the secret key immediately using the fingerprint provided by GnuPG fails — GPGME reports that the secret key is not available. However, after issuing the list command in GnuPG, and then querying the key again through GPGME, the secret key exists as expected.

This behavior appears to indicate a delay or inconsistency in secret key detection or availability between GnuPG and GPGME, possibly due to missing refresh or caching.

gpg/card> generate
Sicherung des Verschlüsselungsschlüssel außerhalb der Karte erstellen? (J/n) J

gpg: Hinweis: Auf der Karte sind bereits Schlüssel gespeichert!

Vorhandene Schlüssel ersetzen? (j/N) j
Bitte wählen Sie, wie lange der Schlüssel gültig bleiben soll.
         0 = Schlüssel verfällt nie
      <n>  = Schlüssel verfällt nach n Tagen
      <n>w = Schlüssel verfällt nach n Wochen
      <n>m = Schlüssel verfällt nach n Monaten
      <n>y = Schlüssel verfällt nach n Jahren
Wie lange bleibt der Schlüssel gültig? (0) 
Schlüssel verfällt nie
Ist dies richtig? (j/N) j

GnuPG erstellt eine User-ID, um Ihren Schlüssel identifizierbar zu machen.

Ihr Name ("Vorname Nachname"): Eric
Email-Adresse: eric@bktus.com
Kommentar: 
Sie haben diese User-ID gewählt:
    "Eric <eric@bktus.com>"

Ändern: (N)ame, (K)ommentar, (E)-Mail oder (F)ertig/(A)bbrechen? F
Wir müssen eine ganze Menge Zufallswerte erzeugen.  Sie können dies
unterstützen, indem Sie z.B. in einem anderen Fenster/Konsole irgendetwas
tippen, die Maus verwenden oder irgendwelche anderen Programme benutzen.
gpg: Hinweis: Sicherung des Kartenschlüssels wurde auf `/Users/erich/.gnupg/sk_A874804DB497B91C.gpg' gespeichert
gpg: Widerrufzertifikat wurde als '/Users/erich/.gnupg/openpgp-revocs.d/DEC0948C398A6E7B50746EC6C4A24EB0B5F2E025.rev' gespeichert.
Öffentlichen und geheimen Schlüssel erzeugt und signiert.

pub   brainpoolP256r1 2025-05-05 [SC]
      DEC0948C398A6E7B50746EC6C4A24EB0B5F2E025
uid                      Eric <eric@bktus.com>
sub   brainpoolP256r1 2025-05-05 [A]
sub   brainpoolP256r1 2025-05-05 [E]


gpg/card> list

Reader ...........: Yubico YubiKey OTP FIDO CCID
Application ID ...: D2760001240100000006180489130000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 18048913
Name of cardholder: Eric Saturn
Language prefs ...: de
Salutation .......: Hr.
URL of public key : https://bktus.com
Login data .......: eric@bktus.com
Signature PIN ....: nicht zwingend
Key attributes ...: brainpoolP256r1 brainpoolP256r1 brainpoolP256r1
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 4
KDF setting ......: off
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: DEC0 948C 398A 6E7B 5074  6EC6 C4A2 4EB0 B5F2 E025
      created ....: 2025-05-05 19:50:06
Encryption key....: 33B2 73C7 BD46 E4EB 63DD  6874 A874 804D B497 B91C
      created ....: 2025-05-05 19:50:06
Authentication key: 1AD5 96DD EC9B 8CF3 C1AC  6C41 EAFC 5EA2 9B75 8B22
      created ....: 2025-05-05 19:50:06
General key info..: 
pub  brainpoolP256r1/C4A24EB0B5F2E025 2025-05-05 Eric <eric@bktus.com>
sec>  brainpoolP256r1/C4A24EB0B5F2E025  erzeugt: 2025-05-05  verfällt: niemals   
                                        Kartennummer:0006 18048913
ssb>  brainpoolP256r1/EAFC5EA29B758B22  erzeugt: 2025-05-05  verfällt: niemals   
                                        Kartennummer:0006 18048913
ssb>  brainpoolP256r1/A874804DB497B91C  erzeugt: 2025-05-05  verfällt: niemals   
                                        Kartennummer:0006 18048913

gpg/card>
> GPGME_DEBUG=8:$(pwd)/gpgme-$(date +"%Y-%m-%d-%H%M%S").log ./a.out
Enter Key ID or Fingerprint (or 'exit' to quit): DEC0948C398A6E7B50746EC6C4A24EB0B5F2E025
Primary Key: C4A24EB0B5F2E025
Fingerprint: DEC0948C398A6E7B50746EC6C4A24EB0B5F2E025
Subkey List:
  Subkey: C4A24EB0B5F2E025
    Expiration: No expiration
    Public Key Algorithm: 301
    Key Length: 256
    Can Encrypt: No
    Can Sign: Yes
    Can Authenticate: No
    Expired: No
    Revoked: No
    Disabled: No
    Secret Key Present: Yes

  Subkey: EAFC5EA29B758B22
    Expiration: No expiration
    Public Key Algorithm: 301
    Key Length: 256
    Can Encrypt: No
    Can Sign: No
    Can Authenticate: Yes
    Expired: No
    Revoked: No
    Disabled: No
    Secret Key Present: Yes

  Subkey: A874804DB497B91C
    Expiration: No expiration
    Public Key Algorithm: 302
    Key Length: 256
    Can Encrypt: Yes
    Can Sign: No
    Can Authenticate: No
    Expired: No
    Revoked: No
    Disabled: No
    Secret Key Present: No

Enter Key ID or Fingerprint (or 'exit' to quit): DEC0948C398A6E7B50746EC6C4A24EB0B5F2E025
Primary Key: C4A24EB0B5F2E025
Fingerprint: DEC0948C398A6E7B50746EC6C4A24EB0B5F2E025
Subkey List:
  Subkey: C4A24EB0B5F2E025
    Expiration: No expiration
    Public Key Algorithm: 301
    Key Length: 256
    Can Encrypt: No
    Can Sign: Yes
    Can Authenticate: No
    Expired: No
    Revoked: No
    Disabled: No
    Secret Key Present: Yes

  Subkey: EAFC5EA29B758B22
    Expiration: No expiration
    Public Key Algorithm: 301
    Key Length: 256
    Can Encrypt: No
    Can Sign: No
    Can Authenticate: Yes
    Expired: No
    Revoked: No
    Disabled: No
    Secret Key Present: Yes

  Subkey: A874804DB497B91C
    Expiration: No expiration
    Public Key Algorithm: 302
    Key Length: 256
    Can Encrypt: Yes
    Can Sign: No
    Can Authenticate: No
    Expired: No
    Revoked: No
    Disabled: No
    Secret Key Present: Yes

Enter Key ID or Fingerprint (or 'exit' to quit): exit
Exiting.

The first call of get_key receives the following key listing from gpg:

2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: sec:-:256:19:C4A24EB0B5F2E025:1746474606:::u:::s
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: cESCA:::D2760001240100000006180489130000::brainp
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: oolP256r1:23::0:<LF>
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: fpr:::::::::DEC0948C398A6E7B50746EC6C4A24EB0B5F2
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: E025:<LF>
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: grp:::::::::06BDACFBDEDBC5783A75AE5E7251FA3369C4
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: 0FF4:<LF>
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: uid:-::::1746474606::2222D8E2F373B9BDEE0DEA2A20A
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: 9402214E9F984::Eric <eric@bktus.com>::::::::::0:
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: <LF>
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: ssb:-:256:19:EAFC5EA29B758B22:1746474606::::::a:
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: ::D2760001240100000006180489130000::brainpoolP25
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: 6r1:23:<LF>
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: fpr:::::::::1AD596DDEC9B8CF3C1AC6C41EAFC5EA29B75
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: 8B22:<LF>
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: grp:::::::::52F0797C0B0439BBD718E2534D46656A6C45
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: 6A78:<LF>
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: ssb:-:256:18:A874804DB497B91C:1746474606::::::e:
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: ::#::brainpoolP256r1:23:<LF>
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: fpr:::::::::33B273C7BD46E4EB63DD6874A874804DB497
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: B91C:<LF>
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: grp:::::::::34A1F8D9B2AA0CF07C2E042D70E10F9D4EBE
2025-05-05 21:50:23 gpgme[57059]     _gpgme_io_read: check: E734:<LF>

Note the line

ssb:-:256:18:A874804DB497B91C:1746474606::::::e:::#::brainpoolP256r1:23:<LF>

where the # marks the subkey as stub.

The second call of get_key receives the following key listing from gpg:

2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: sec:-:256:19:C4A24EB0B5F2E025:1746474606:::u:::s
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: cESCA:::D2760001240100000006180489130000::brainp
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: oolP256r1:23::0:<LF>
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: fpr:::::::::DEC0948C398A6E7B50746EC6C4A24EB0B5F2
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: E025:<LF>
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: grp:::::::::06BDACFBDEDBC5783A75AE5E7251FA3369C4
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: 0FF4:<LF>
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: uid:-::::1746474606::2222D8E2F373B9BDEE0DEA2A20A
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: 9402214E9F984::Eric <eric@bktus.com>::::::::::0:
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: <LF>
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: ssb:-:256:19:EAFC5EA29B758B22:1746474606::::::a:
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: ::D2760001240100000006180489130000::brainpoolP25
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: 6r1:23:<LF>
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: fpr:::::::::1AD596DDEC9B8CF3C1AC6C41EAFC5EA29B75
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: 8B22:<LF>
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: grp:::::::::52F0797C0B0439BBD718E2534D46656A6C45
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: 6A78:<LF>
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: ssb:-:256:18:A874804DB497B91C:1746474606::::::e:
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: ::D2760001240100000006180489130000::brainpoolP25
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: 6r1:23:<LF>
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: fpr:::::::::33B273C7BD46E4EB63DD6874A874804DB497
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: B91C:<LF>
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: grp:::::::::34A1F8D9B2AA0CF07C2E042D70E10F9D4EBE
2025-05-05 21:50:28 gpgme[57059]     _gpgme_io_read: check: E734:<LF>

Here we see the line

ssb:-:256:18:A874804DB497B91C:1746474606::::::e:::D2760001240100000006180489130000::brainpoolP256r1:23:<LF>

i.e. now the serial number of the smart card is shown instead of the stub marker #.

I see that you generated the secret encryption subkey with backup. This means that the secret subkey is generated on your computer, then copied to the card, and then deleted from your computer. The deletion is the reason why the subkey is marked as stub. Only after listing the keys on the card gpg notices that the secret key is actually on the card.

I see that you generated the secret encryption subkey with backup. This means that the secret subkey is generated on your computer, then copied to the card, and then deleted from your computer. The deletion is the reason why the subkey is marked as stub. Only after listing the keys on the card gpg notices that the secret key is actually on the card.

I think it would be much better if GnuPG automatically performed a key listing immediately after key generation when a smartcard is involved. This would allow GnuPG to detect the presence of the subkey on the card right away, rather than leaving it marked as a stub until the user manually lists keys.

The current behavior could be confusing for users—especially for applications using GPGME, where the internal key stub state may cause incorrect assumptions about key availability in GUI interfaces.

I think it would be much better if GnuPG automatically performed a key listing immediately after key generation when a smartcard is involved. This would allow GnuPG to detect the presence of the subkey on the card right away, rather than leaving it marked as a stub until the user manually lists keys.

I fully agree. I have just analyzed the problem so that somebody else can make the necessary changes.