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