GPG/PGP subkey rotation/revocation and verified commits

We’re considering commit signing to make it easier to audit some of our code. The idea is to have safe (offline) GPG master keys for every developer, and to create subkeys stored on Yubikeys for the actual commit signing. GitHub - drduh/YubiKey-Guide: Guide to using YubiKey for GPG and SSH is a great documentation for this.

I did some experiments with this and it turns out there is one area where results surprised me, namely subkey revocation or rotation.

Let’s assume we create signing subkeys with a limited validity of, say, 1 year to force ourselves to do regular key rotation. Or, maybe a Yubikey with a signing key is lost and we revoke that subkey in the corresponding master key. Either way, a new subkey is added to the master key and used from then on.

Now it seems commits with this new subkey are shown as “unverified” in GitHub until I create a new export of the (public) master and subkeys and register it in my account settings. At the same time, at least for a revoked subkey, previously “verified” commits will become “unverified”.

This is what it looks like in the UI after I added a new subkey, revoked the key used in the previous commit and updated the key in my account settings:

Here is the same repo, with output from git --log --signature:

What I would like to avoid is that previously signed commits suddenly appear as unverified, since that would surely raise questions (especially if everyone gets used to commits always showing green “verified” in the normal case).

So what is the recommended approach here?

Avoid planned subkey rotation or key expiration as much as possible? Don’t revoke subkeys when the Yubikey device is lost?

Is it a conceptual problem that once a subkey becomes invalid, you cannot (with certainity) tell whether a commit signed with it was made before or after the revocation/expiration?

Would it make sense for the UI to have more detailed statuses? Not only “verified” or “unverified”, but also something like “verfied, key superseded”? There are a few reasonse that can be given when the revkey commad is used in gpg to revoke a subkey.

I have read Updating an expired GPG key - GitHub Docs over and over again, but I cannot fully make sense of it.

When verifying a signature, GitHub checks that the key is not revoked or expired. If your signing key is revoked or expired, GitHub cannot verify your signatures.

When “key” also applies to subkeys, that makes sense. It is consistent with the observation that a signature made with a revoked key is shown as “unverified”.

If your key is revoked, use the primary key or another key that is not revoked to sign your commits.

Hm, this just says “when your key is invalid, use another valid key instead”?

If your key is expired, you must update the expiration, export the new key, delete the expired key in your GitHub account, and upload the new key to GitHub. Your previous commits and tags will show as verified, as long as the key meets all other verification requirements.

This suggests to update expiration times over and over again instead of recreating keys when they expire.

If your key is invalid and you don’t use another valid key in your key set, but instead generate a new GPG key with a new set of credentials, then your commits made with the revoked or expired key will continue to show as unverified.

This is confusing. In my tests described above, I did not create “a new GPG key with a new set of credentials” (that would be a new master key + identity?), but I added another (new) valid subkey and used that. This section suggests that the commits made with the old key would become “verified” again…?

This should probably work. It explains how to rotate the key and cross sign both keys to make a hard link between old and new key.