Ignore revoked UIDs when listing the identities of a GPG key
What does this MR do and why?
Revoking a UID adds a revocation signature to the key rather than removing the UID, so Gitlab::Gpg.user_infos_from_key kept listing every revoked identity. GitLab offered those addresses for verification in User Settings > GPG keys and accepted them in GpgKey#verified_and_belongs_to_email?, which decides the Verified badge on a signed commit. Deleting and re-adding the key did not help, because the UID is still inside the key. This is #24572.
The fix skips the UIDs GPGME reports as revoked (GPGME::UserID#revoked?, the r validity letter of the uid record). A revoked identity is then neither shown in the settings page nor accepted as a verified email: a commit signed under it lands on same_user_different_email when the user has confirmed that address and on other_user otherwise, and a key whose every UID is revoked verifies nothing, so its commits land on unverified_key.
Excluding the UID rather than returning it with a marker follows what revocation already means in GitLab: the Revoke button warns that all commits signed with the key will be unverified and GpgKey#revoke rewrites them, while Remove is the twin that leaves history alone; !231021 (merged) already re-derives a signature's status from the key's current state. A marker would have to be understood by every consumer of user_infos (emails_with_verified_status, verified_user_infos, the settings partial and Gitlab::Gpg::Signature#user_infos), while the exclusion is a single point.
Which already verified signatures change is bounded by InvalidGpgSignatureUpdater, which only touches rows without a gpg_key_id or not yet verified. A verified row whose key stays in place is never re-derived. Two kinds of rows are, and the specs pin both: subkey-signed rows, which never carry a gpg_key_id, and every row of a key that was removed and added again, since removing a key nulls gpg_key_id. Those signed under the revoked identity then move to same_user_different_email or other_user, which is the outcome the revocation asks for.
GPGME::UserID#invalid? is deliberately not checked, unlike in the earlier attempt !36315 (closed) by @T4cC0re, which this MR builds on. GnuPG drops a UID without a valid self-signature at import, which is the path GPGME::Key.import takes into the temporary keychain, so such a UID never reaches the listing; even under --allow-non-selfsigned-uid GnuPG 2.2 lists it with validity - and GPGME reports it as not invalid. No genuine fixture can be built for it, and the comment in lib/gitlab/gpg.rb says so.
References
- Closes #24572
- Earlier attempt: !36315 (closed) (@T4cC0re)
- Precedent for deriving the status from the key's current state: !231021 (merged)
How to set up and validate locally
- Create a key with two UIDs and revoke one:
gpg --quick-gen-key "A <a@example.com>" rsa2048 sign never, thengpg --quick-add-uid <fpr> "A <old@example.com>"andgpg --quick-revoke-uid <fpr> "A <old@example.com>". - Add
gpg --armor --export <fpr>in User Settings > GPG keys. Before this change both addresses are listed andold@example.comcan show as Verified; after it onlya@example.comis listed. - Sign a commit with
old@example.comas committer email and push it: the badge is no longer Verified.
The specs use two fixtures generated with GnuPG 2.2.40 (GpgHelpers::UserWithRevokedUid, with a live UID, a revoked UID and a signing subkey, signed with both; GpgHelpers::UserWithOnlyRevokedUid, whose only remaining UID is revoked). Both keys carry their secret key beside the public one, like the existing fixtures, so the signatures can be regenerated.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.