Page MenuHome GnuPG

Primary Key Binding Signature not updated when updating Subkey Binding Signature
Open, NormalPublic

Description

While examining the Debian Keyring, I discovered many uses of SHA1. One use stuck out, however: there are several signing-capable subkeys that use SHA256 or SHA512 for the subkey binding signature, but SHA1 for the primary key binding signature (the backsig). This appears to be because gpg reuses any existing backsig when updating the subkey binding signature (e.g., when extending the subkey's expiration time). As far as I know there is no way to force gpg to update the backsig (please correct me if I am wrong). To make it less painful for users when SHA1 is bad listed, it would be helpful if the primary key binding signature were also updated when the subkey binding signature is updated (if the signing key's secret key material is available as is usually the case).

I created the following test data (attached):

$ export GNUPGHOME=$(mktemp -d)
$ gpg1 --gen-key
...
pub   1024R/0F66162A 2020-10-23
      Key fingerprint = 29B2 8740 3105 1C66 718E  C51E A23C 9525 0F66 162A
uid                  SHA1 Test <sha1@example.org>
sub   1024R/3AB6951D 2020-10-23
$ gpg1 --edit-key sha1@example.org
...
gpg> addkey
This key is not protected.
Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
Your selection? 4
...
pub  1024R/0F66162A  created: 2020-10-23  expires: never       usage: SC  
                     trust: ultimate      validity: ultimate
sub  1024R/3AB6951D  created: 2020-10-23  expires: never       usage: E   
sub  1024R/F07D9EC7  created: 2020-10-23  expires: never       usage: S   
[ultimate] (1). SHA1 Test <sha1@example.org>
...
$ gpg --export-secret-key sha1@example.org >/tmp/sha1-key.pgp
...
$ gpg --edit-key sha1@example.org
...
gpg> key 2

sec  rsa1024/A23C95250F66162A
     created: 2020-10-23  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa1024/5F7E2C5F3AB6951D
     created: 2020-10-23  expires: never       usage: E   
ssb* rsa1024/88DEBBA1F07D9EC7
     created: 2020-10-23  expires: never       usage: S   
[ultimate] (1). SHA1 Test <sha1@example.org>
...
gpg> expire
Changing expiration time for a subkey.
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 3y
Key expires at Mon 23 Oct 2023 09:36:15 AM CEST
Is this correct? (y/N) y

sec  rsa1024/A23C95250F66162A
     created: 2020-10-23  expires: never       usage: SC  
     trust: ultimate      validity: ultimate
ssb  rsa1024/5F7E2C5F3AB6951D
     created: 2020-10-23  expires: never       usage: E   
ssb* rsa1024/88DEBBA1F07D9EC7
     created: 2020-10-23  expires: 2023-10-23  usage: S   
[ultimate] (1). SHA1 Test <sha1@example.org>

gpg> save
$ gpg --export-secret-key sha1@example.org >/tmp/sha1-key2.pgp
$ gpg --version
gpg: WARNING: unsafe permissions on homedir '/tmp/tmp.J0eBv81zH9'
gpg (GnuPG) 2.2.20
libgcrypt 1.8.4
...

Examing the first variant of the key, we see that the signing subkey's binding signature uses SHA1 as does its primary key binding signature:

$ pgpdump /tmp/sha1-key.pgp
Old: Secret Subkey Packet(tag 7)(472 bytes)
	Ver 4 - new
	Public key creation time - Fri Oct 23 09:31:05 CEST 2020
	Pub alg - RSA Encrypt or Sign(pub 1)
	RSA n(1024 bits) - ...
	RSA e(17 bits) - ...
	RSA d(1021 bits) - ...
	RSA p(512 bits) - ...
	RSA q(512 bits) - ...
	RSA u(512 bits) - ...
	Checksum - a2 60 
Old: Signature Packet(tag 2)(317 bytes)
	Ver 4 - new
	Sig type - Subkey Binding Signature(0x18).
	Pub alg - RSA Encrypt or Sign(pub 1)
-->	Hash alg - SHA1(hash 2)
	Hashed Sub: signature creation time(sub 2)(4 bytes)
		Time - Fri Oct 23 09:31:05 CEST 2020
	Hashed Sub: key flags(sub 27)(1 bytes)
		Flag - This key may be used to sign data
	Sub: issuer key ID(sub 16)(8 bytes)
		Key ID - 0xA23C95250F66162A
	Sub: embedded signature(sub 32)(156 bytes)
	Ver 4 - new
	Sig type - Primary Key Binding Signature(0x19).
	Pub alg - RSA Encrypt or Sign(pub 1)
-->	Hash alg - SHA1(hash 2)
	Hashed Sub: signature creation time(sub 2)(4 bytes)
		Time - Fri Oct 23 09:31:05 CEST 2020
	Sub: issuer key ID(sub 16)(8 bytes)
		Key ID - 0x88DEBBA1F07D9EC7
-->	Hash left 2 bytes - 18 9d 
	RSA m^d mod n(1022 bits) - ...
		-> PKCS-1
	Hash left 2 bytes - 15 54 
	RSA m^d mod n(1024 bits) - ...
		-> PKCS-1
$ pgpdump /tmp/sha1-key2.pgp
...
Old: Secret Subkey Packet(tag 7)(472 bytes)
	Ver 4 - new
	Public key creation time - Fri Oct 23 09:31:05 CEST 2020
	Pub alg - RSA Encrypt or Sign(pub 1)
	RSA n(1024 bits) - ...
	RSA e(17 bits) - ...
	RSA d(1021 bits) - ...
	RSA p(512 bits) - ...
	RSA q(512 bits) - ...
	RSA u(512 bits) - ...
	Checksum - a2 60 
Old: Signature Packet(tag 2)(346 bytes)
	Ver 4 - new
	Sig type - Subkey Binding Signature(0x18).
	Pub alg - RSA Encrypt or Sign(pub 1)
-->	Hash alg - SHA512(hash 10)
	Hashed Sub: key flags(sub 27)(1 bytes)
		Flag - This key may be used to sign data
	Hashed Sub: issuer fingerprint(sub 33)(21 bytes)
	 v4 -	Fingerprint - 29 b2 87 40 31 05 1c 66 71 8e c5 1e a2 3c 95 25 0f 66 16 2a 
	Hashed Sub: signature creation time(sub 2)(4 bytes)
		Time - Fri Oct 23 09:36:17 CEST 2020
	Hashed Sub: key expiration time(sub 9)(4 bytes)
		Time - Mon Oct 23 09:36:17 CEST 2023
	Sub: embedded signature(sub 32)(156 bytes)
	Ver 4 - new
	Sig type - Primary Key Binding Signature(0x19).
	Pub alg - RSA Encrypt or Sign(pub 1)
-->	Hash alg - SHA1(hash 2)
	Hashed Sub: signature creation time(sub 2)(4 bytes)
		Time - Fri Oct 23 09:31:05 CEST 2020
	Sub: issuer key ID(sub 16)(8 bytes)
		Key ID - 0x88DEBBA1F07D9EC7
-->	Hash left 2 bytes - 18 9d 
	RSA m^d mod n(1022 bits) - ...
		-> PKCS-1
	Sub: issuer key ID(sub 16)(8 bytes)
		Key ID - 0xA23C95250F66162A
	Hash left 2 bytes - e9 34 
	RSA m^d mod n(1024 bits) - ...
		-> PKCS-1

In short, I created a key using gpg1 and added a signing subkey to it that uses SHA1. Then, I set the signing subkey to expire using GnuPG 2.2.20 from Debian Testing. Using pgpdump, we see that the new subkey binding signature uses SHA512, but the primary key binding signature uses SHA1. Looking at the primary key binding signature's creation time, and the hash prefix ("hash left 2 bytes"), it appears that the embedded signature is just a copy of the original primary key binding signature.

Event Timeline

werner triaged this task as Normal priority.Oct 23 2020, 6:45 PM
werner added a project: gnupg.

Note: menu_backsign can be enhanced to detect such a case in the same way it detects missing backsigs.

In order to remove the SHA-1 algorithm in Arch Linux package keyring, I need to resign one of my sub keys but the backsig (0x19) remain in SHA-1 as reported here.
I didn't find any solution with gnupg to update it since this bug report was opened in 2020. Do you plan to address this in a near future?

I have also not found a straightforward way to correct a cross-signature that was made with a weak digest algorithm using GnuPG.

On the one hand, if you only care about newer clients then you can just make a new signing-capable subkey and discard the old one. You'll have to distribute an updated certificate anyway to get the verifier to accept those signatures anyway. This is comparatively straightforward to do: addkey in the --edit-key menu.

On the other hand, if you want signatures to continue to validate for weaker clients that are willing to accept a cross-signature with a weak digest, and might not receive an updated certificate, you probably want to reuse the same signing-capable subkey. This is the approach that does not appear to be straightforward.

The only way i've figured out how to do the latter approach with gpg is something horrible like te following (instructions for gpg 2.2.40, i don't know what other versions this will work for and i am uninterested in maintaining it):

  • export the secret key into an ephemeral GNUPGHOME directory $EPHEMERALHOMEDIR, so that you can experiment without breaking it
  • using the ephemeral homedir, use gpg --homedir "$EPHEMERALHOMEDIR" --with-colons --with-keygrip --list-keys to get the creation time and keygrip of the subkey in question. Set these to $CREATIONTIME (in seconds since the epoch) and $KEYGRIP
  • choose the updated expiration time you want to use and write it in ISO-8601-compliant format in UTC (e.g. 20241231T235959Z for the last second in 2024, UTC) as $EXPIRATIONTIME
  • use gpg --homedir "$EPHEMERALHOMEDIR" --edit-key "$PGPID" followed by key X to select the specific subkey, and then delkey to remove it entirely. Don't forget to type save to exit the prompt and keep the changes!
  • write a brief script to a text file like so: printf 'addkey\n13\n%s\ne\nq\n%s\nsave\n' "$KEYGRIP" "$EXPIRATIONTIME" > scriptfile
  • run gpg --homedir "$EPHEMERALHOMEDIR" --cert-digest-algo SHA512 --command-fd 3 --faked-system-time "$CREATIONTIME" --expert --batch --edit-key "$PGPID" 3<scriptfile
  • Now you should have an updated subkey with a novel cross-sig using SHA512, but it still won't be imported by most instances of GnuPG because both the novel cross-sig and the novel subkey binding signature have timestamps just as old (or older!) the original subkey binding signature. So you still need to...
  • run gpg --homedir "$EPHEMERALHOMEDIR" --cert-digest-algo SHA512 --edit-key "$PGPID" and then do key X to select the specific subkey, and then setexpire to tweak the expiration date a little bit. Don't forget to type save afterward!
  • now you can export this updated cert with gpg --homedir "$EPHEMERALHOMEDIR" --armor --export "$PGPID"

This is really not a sensible interface for anyone to use. It is a good reminder that we need a standard sop update-key subcommand, which should handle this sort of thing automatically with reasonable defaults.

-