Page MenuHome GnuPG

signatures from revoked or expired keys show up as missing keys
Open, HighPublic

Description

On the stable branch for 2.4, the fixes for T7527 appear to have a surprising consequence. A signature from a revoked key now shows up as a missing key.

In particular, in 2.4.7, the status output for evaluating a signature from a revoked key looks like this:

[GNUPG:] NEWSIG
[GNUPG:] KEY_CONSIDERED 5E13F2DF8D46E26AAC0B055CD808B9D3D20FBCCE 0
[GNUPG:] SIG_ID NFOUKuyqo/qGbuxXzbj0WZrlL/8 2025-02-27 1740676664
[GNUPG:] KEY_CONSIDERED 5E13F2DF8D46E26AAC0B055CD808B9D3D20FBCCE 0
[GNUPG:] REVKEYSIG AC54359E527359FA test key
[GNUPG:] VALIDSIG C8C34B4E807DF6907B618FB3AC54359E527359FA 2025-02-27 1740676664 0 4 0 22 10 01 5E13F2DF8D46E26AAC0B055CD808B9D3D20FBCCE
[GNUPG:] KEY_CONSIDERED 5E13F2DF8D46E26AAC0B055CD808B9D3D20FBCCE 0
[GNUPG:] KEYREVOKED
[GNUPG:] KEY_CONSIDERED 5E13F2DF8D46E26AAC0B055CD808B9D3D20FBCCE 0
[GNUPG:] TRUST_UNDEFINED 0 pgp

But built from STABLE-BRANCH-2-4 (in particular, with rG25d748c3dfc0102f9e54afea59ff26b3969bd8c1 , rG9cd371b12d80cfc5bc85cb6e5f5eebb4decbe94f and rGda0164efc7f32013bc24d97b9afa9f8d67c318bb applied), the status output looks like:

[GNUPG:] NEWSIG
[GNUPG:] KEY_CONSIDERED 5E13F2DF8D46E26AAC0B055CD808B9D3D20FBCCE 3
[GNUPG:] KEY_CONSIDERED 5E13F2DF8D46E26AAC0B055CD808B9D3D20FBCCE 3
[GNUPG:] ERRSIG AC54359E527359FA 22 10 01 1740676664 9 C8C34B4E807DF6907B618FB3AC54359E527359FA
[GNUPG:] NO_PUBKEY AC54359E527359FA
[GNUPG:] FAILURE gpg-exit 33554433

I don't know enough about the expectations of the broader ecosystem here, but this is at least tickling some brittle test suites that include the use of GnuPG, such as https://bugs.debian.org/1098995

Details

Version
STABLE-BRANCH-2-4

Event Timeline

dkg renamed this task from signatures from revoked keys show up as missing keys to signatures from revoked or expired keys show up as missing keys.Feb 27 2025, 10:36 PM

The same effect seems to be happening on signatures made from expired keys.

From 2.4.7, before those changes, we see the following status:

[GNUPG:] NEWSIG
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] KEY_CONSIDERED DCE608D4BC44587AE23652E641904183CD74E90B 0
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] SIG_ID 1pD6BtAaWlwZ52hYZh3ML3cOaa8 2023-01-01 1672549200
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] KEY_CONSIDERED DCE608D4BC44587AE23652E641904183CD74E90B 0
[GNUPG:] EXPKEYSIG 95904693B6DB5286 very old3
[GNUPG:] VALIDSIG 7CBC9E67C1134FCE0810F42495904693B6DB5286 2023-01-01 1672549200 0 4 0 22 10 01 DCE608D4BC44587AE23652E641904183CD74E90B
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] KEY_CONSIDERED DCE608D4BC44587AE23652E641904183CD74E90B 0

But with those changes applied, we see the following status:

[GNUPG:] NEWSIG
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] KEY_CONSIDERED DCE608D4BC44587AE23652E641904183CD74E90B 3
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] KEYEXPIRED 1735534800
[GNUPG:] KEY_CONSIDERED DCE608D4BC44587AE23652E641904183CD74E90B 3
[GNUPG:] ERRSIG 95904693B6DB5286 22 10 01 1672549200 9 7CBC9E67C1134FCE0810F42495904693B6DB5286
[GNUPG:] NO_PUBKEY 95904693B6DB5286
[GNUPG:] FAILURE gpg-exit 33554433

Aside from the status file, the normal output and the return code of the program changes. Without the changes, 2.4.7 returns 0:

0 $  gpg --status-file gpg.status.expired.old --verify < test.msg 
gpg: Signature made Sun 01 Jan 2023 12:00:00 AM EST
gpg:                using EDDSA key 7CBC9E67C1134FCE0810F42495904693B6DB5286
gpg: Good signature from "very old3" [expired]
gpg: Note: This key has expired!
Primary key fingerprint: DCE6 08D4 BC44 587A E236  52E6 4190 4183 CD74 E90B
       Subkey fingerprint: 7CBC 9E67 C113 4FCE 0810  F424 9590 4693 B6DB 5286
0 $

But with the changes, we get a return code of 2:

0 $ gpg --status-file gpg.status.expired.new --verify < test.msg 
gpg: Signature made Sun 01 Jan 2023 12:00:00 AM EST
gpg:                using EDDSA key 7CBC9E67C1134FCE0810F42495904693B6DB5286
gpg: Can't check signature: No public key
2 $

You should be able to replicate this with the old3.cert OpenPGP certificate, which is already expired:

Over this inline-signed message, which was made when the certificate was *not* expired:

ikloecker triaged this task as Unbreak Now! priority.
ikloecker added a subscriber: ikloecker.

Thanks for the report! That's indeed a regression introduced by the changes for T7527: Keyring/keybox denial of service. Commenting/Removing line https://dev.gnupg.org/source/gnupg/browse/master/g10/getkey.c$343 seems to fix the regression, but (very likely) this would reintroduce the issues reported in T7527: Keyring/keybox denial of service.

ikloecker moved this task from Backlog to WIP on the gnupg26 board.
werner lowered the priority of this task from Unbreak Now! to High.Thu, Mar 6, 11:23 AM
werner added a subscriber: werner.

Please use "unbreak now" only for *released* software with a criticial bug.

werner changed the task status from Open to Testing.Thu, Mar 6, 5:58 PM
werner moved this task from WIP to QA on the gnupg26 board.
werner moved this task from Backlog to QA on the gnupg24 board.

it would be great to include a test in the test suite that ensures that the --status output behaves as expected in the face of expired or revoked keys.

With the patch "gpg: Fix regression for the recent malicious subkey DoS fix", there is a change in how gpg --check-sigs reports certifications from expired keys.

I discovered this when running the test suite from perl's GnuPG::Interface (https://metacpan.org/release/GnuPG-Interface), and the sample certificates are extracted from there.

To run the test, i did the following with a version of 2.4 without the patch:

mkdir -m 0700 test.unpatched
gpg --homedir test.unpatched  --batch --import keys.pgp
gpg --homedir test.unpatched --with-colons --fixed-list-mode --trust-model=always --check-sigs  0x93AFC4B1B0288A104996B44253AE596EF950DA9C > output.unpatched

And then again with the patch:

mkdir -m 0700 test.patched
gpg --homedir test.patched  --batch --import keys.pgp
gpg --homedir test.patched --with-colons --fixed-list-mode --trust-model=always --check-sigs  0x93AFC4B1B0288A104996B44253AE596EF950DA9C > output.patched

The change looks like this:

$ diff -u output.unpatched output.patched  
--- output.unpatched	2025-03-10 19:40:09.471383675 -0400
+++ output.patched	2025-03-10 19:39:50.557202223 -0400
@@ -5,12 +5,12 @@
 sig:!::17:53AE596EF950DA9C:978325209::::GnuPG test key (for testing purposes only):1fx:::::2:
 uid:-::::1177086597::17B89648BB5C41DAFB56555657DFDD970585CB27::GnuPG test key (for testing purposes only)::::::::::0:
 sig:!::17:53AE596EF950DA9C:1177086597::::GnuPG test key (for testing purposes only):13x:::::2:
-sig:?::17:56FFD10A260C4FA3:953180097:::::10x:::::2:
+sig:!::17:56FFD10A260C4FA3:953180097::::Frank J. Tobin <ftobin@neverending.org>:10x:::::2:
 sig:!::17:53AE596EF950DA9C:949813093::::GnuPG test key (for testing purposes only):13x:::::2:
 sig:!::17:53AE596EF950DA9C:1177086329::::GnuPG test key (for testing purposes only):13x:::::2:
 uid:-::::1177086330::0FCE6B9BEC50CCC8B84E5B5A1FF30623ACE73DBF::Foo Bar (1)::::::::::0:
 sig:!::17:53AE596EF950DA9C:1177086330::::GnuPG test key (for testing purposes only):13x:::::2:
-sig:?::17:56FFD10A260C4FA3:953180103:::::10x:::::2:
+sig:!::17:56FFD10A260C4FA3:953180103::::Frank J. Tobin <ftobin@neverending.org>:10x:::::2:
 sig:!::17:53AE596EF950DA9C:953179891::::GnuPG test key (for testing purposes only):13x:::::2:
 sub:-:768:16:ADB99D9C2E854A6B:949813119::::::e:::::::
 fpr:::::::::7466B7E98C4CCB64C2CE738BADB99D9C2E854A6B:

In particular, note that the certification from the expired certificate now appears with "trust" of ! instead of ?, and it shows the user ID.

This is a change from 2.2, but i think it might be a reversion to the behavior from before 2.2, based on comments in the GnuPG::Interface test suite (https://sources.debian.org/src/libgnupg-interface-perl/1.04-4/t/MyTestSpecific.pm/#L147), which say:

# blank user_id_string and different validity for expired sig in GPG 2.2.x vs 1.x, 2.1

This workaround was added to`GnuPG::Interface` in https://github.com/bestpractical/gnupg-interface/commit/2a573192319e4acc9364d1ad89e2fcb97203a0df by Aaron Trevina.

Should users expect the old 1.x/2.1 behavior or the behavior introduced in 2.2? if you intend to stick with the behavior introduced in 2.2, then more patches are necessary. If you intend to revert to the 1.x or 2.1 behavior, then it'd be great to see some kind of documentation update that explains what the consumer should expect.

werner changed the task status from Testing to Open.Tue, Mar 11, 11:00 AM

hm, digging a bit further, i think the above changes have to do with third-party signatures using SHA1, *not* with expired certifiers. in 2.4.7, i see a change from % to ! for these certifications. (2.2.x, which i know is EOL) shows the difference between ? and !. I'm trying to make a simpler replicator now.

OK, now i really don't know what the issue is on the 2.4 branch. trying to replicate it with and without this patch, the --with-colons output of --check-sigs appears to depend on the order in which the certificates were ingested.

Compare these two pruned certificate bundles, each of which contains exactly the same two minimized certificates, but in different order:

With the patch applied, i see this difference in filtered.pgp:

diff
--- filtered-out.2.4.7-7.txt	2025-03-11 17:50:47.181367755 -0400
+++ filtered-out.2.4.7-8.txt	2025-03-11 17:52:15.341126404 -0400
@@ -5,7 +5,7 @@
 sig:!::17:53AE596EF950DA9C:978325209::::GnuPG test key (for testing purposes only):1fx:::::2:
 uid:-::::1177086597::17B89648BB5C41DAFB56555657DFDD970585CB27::GnuPG test key (for testing purposes only)::::::::::0:
 sig:!::17:53AE596EF950DA9C:1177086597::::GnuPG test key (for testing purposes only):13x:::::2:
-sig:?::17:56FFD10A260C4FA3:953180097:::::10x:::::2:
+sig:%::17:56FFD10A260C4FA3:953180097::::[Invalid digest algorithm] :10x:::::2:
 pub:e:1024:17:56FFD10A260C4FA3:924764593:987836593::-:::sca::::::::0:
 fpr:::::::::4F863BBBA8166F0A340F600356FFD10A260C4FA3:
 uid:e::::924764594::0A0F0381088F2073B644874E600451FA8682066E::Frank J. Tobin <ftobin@bigfoot.com>::::::::::0:

But there is no difference in the output of filtered2.pgp based on these two things. So i'm very confused about what is happening here, and what is supposed to be happening here.

If there are other tests that you'd like me to run, please let me know what they are.

Did you also tried with --no-sig-cache ? That could help to get a better insight into the reason for that difference.

with --no-sig-cache --check-sigs i get the following error with the patch applied:

free(): double free detected in tcache 2

I can't replicate your findings here . In a test directory w/o a gpg.conf:

rm pubring.kbx trustdb.gpg
gpg --import keys.pgp
gpg --import filtered.pgp
gpg --check-sigs --with-colons >filtered.out

And repeated that with s/filtered/filtered2/ git diff shows:

-tru::1:1741791487:0:3:1:5
+tru::1:1741791513:0:3:1:5
 pub:-:1024:17:53AE596EF950DA9C:949813093:::-:::scaESCA::::::::0:
 rvk:::17::::::4F863BBBA8166F0A340F600356FFD10A260C4FA3:80:
 fpr:::::::::93AFC4B1B0288A104996B44253AE596EF950DA9C:

Looks okay to me.

Doing the first again with

gpg --check-sigs --with-colons --no-sig-cache >filtered-no-sig-cache.out

throws the malloc bug, though.

Please test without the --import keys.pgp -- just import filtered.pgp or filtered2.pgp.

I have the patched and unpatched versions of gpg stored as patched/gpg and unpatched/gpg. I'm using the following test:

for keys in filtered filtered2; do
    for bin in patched unpatched; do
        printf "=== %s: %s ===\n" "$bin" "$keys"
        rm -rf g
        mkdir -m 0700 g
        "$bin/gpg" --batch --homedir g --import < "$keys.pgp" &&
            "$bin/gpg" --batch --homedir g --trust-model always --with-colons --check-sigs > "$keys.$bin"
    done
done

Interestingly, from this i'm learning that the patch actually *normalizes* the output so that we see the same thing regardless of ordering. the different output based on certificate order happens only in the unpatched version.

Here's my new testing script, which makes a sorted copy of the output with the suffix .sort :

for keys in filtered filtered2; do
    for bin in patched unpatched; do
        printf "=== %s: %s ===\n" "$bin" "$keys"
        rm -rf g
        mkdir -m 0700 g
        "$bin/gpg" --quiet --batch --homedir g --import < "$keys.pgp" &&
            "$bin/gpg" --quiet --batch --homedir g --trust-model always --with-colons --check-sigs > "$keys.$bin"
        sort < "$keys.$bin" > "$keys.$bin.sorted"
    done
done

And here's what i'm seeing as a result:

$ sha1sum *.sorted | sort
029f8481015fe0203786901fa603fd7375d8e58d  filtered.unpatched.sorted
4781b9199ad676fb520a92746953e2570318f195  filtered2.patched.sorted
4781b9199ad676fb520a92746953e2570318f195  filtered2.unpatched.sorted
4781b9199ad676fb520a92746953e2570318f195  filtered.patched.sorted
$ 

Thanks for the fix for the double-free on --no-sig-cache, that appears to be an issue on all released gpg versions, as i can crash them directly when i --no-sig-cache.

I've fine-tuned my reproducer, so it uses two minified certificates, as separate files:


#!/bin/bash

for order in "ftobin testkey" "testkey ftobin"; do
    for bin in patched unpatched; do
        printf "=== %s: %s ===\n" "$bin" "$order"
        rm -rf g
        mkdir -m 0700 g
        for cert in $order; do
            "$bin/gpg" --quiet --batch --homedir g --import < "$cert.pgp" || exit 1
        done
        "$bin/gpg" --quiet --batch --homedir g --trust-model always --with-colons --check-sigs > "${order/ /_}.$bin"
        sort < "${order/ /_}.$bin" > "${order/ /_}.$bin.sorted"
    done
done
sha1sum *.sorted

Running it, i see this:

$ ./check-sigs
=== patched: ftobin testkey ===
=== unpatched: ftobin testkey ===
=== patched: testkey ftobin ===
=== unpatched: testkey ftobin ===
16ce36afc9afda7755ac1813335ec6455ea0cf60  ftobin_testkey.patched.sorted
16ce36afc9afda7755ac1813335ec6455ea0cf60  ftobin_testkey.unpatched.sorted
16ce36afc9afda7755ac1813335ec6455ea0cf60  testkey_ftobin.patched.sorted
b1d0aa593520b8ab9c10bbf258ea8316b669bf50  testkey_ftobin.unpatched.sorted
$ 

This suggests to me that these fixes are actually *normalizing* the output so that it is independent of the order in which the certificates are encountered. So, I think that's a net win -- i've always seen variation in output based on the order that certificates happen to be encountered as a flaw, so i wouldn't argue that it's a regression.

Perhaps the fix needs to be made in the GnuPG::Interface test suite at this point.

We have two options when offering a fix for the test suite:

  • test suite passes only with a version of a gpg with these patches applied, or
  • test suite is flexible enough to pass with either unpatched or patches-applied gpg

Do you have any preference for how GnuPG::Interface's test suite should behave?

Well, we also have the gpgme test suite which tests a couple of other things and for obvious reasons we need to keep this stable. Granted, sometimes we had to change the gpgme test suite as well. My personal preference would be your second choice.

Alternately, i suppose we could ask GnuPG::Interface to drop the variant parts of that test entirely. @werner, If you have a preference for what they test, it would be good to know. I suspect your opinion would carry weight with the maintainer there.

I'll work on making a patch to offer a flexible test suite.

I've offered https://github.com/bestpractical/gnupg-interface/pull/16 to GnuPG::Interface, and am testing it out in debian unstable.