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
- GPGME version: 1.24.2
- GnuPG version: 2.4.7
Reproduction Process
- Start smartcard editing.
- Use the generate command to create a key directly on the smartcard.
- After successful key creation, use the following C program to query the key using gpgme_get_key() without recreating the gpgme context.
- Observe that the encryption subkey is returned with secret == false.
- 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.