Page MenuHome GnuPG

Key expiration time sometimes improperly interpreted as a signed 32-bit value
Closed, ResolvedPublic

Description

A version 4 key packet indicates its expiration date in its self-signature packet via a Key Expiration Time subpacket (type=9), whose data is a 4-octet field: "This is the number of seconds after the key creation time that the key expires." (RFC 4880 5.2.3.6). Further, in 3.1, it says about scalar values: "Scalar numbers are unsigned and are always stored in big-endian format." So in two ways this 4-octet field should be interpreted as an unsigned 32-bit value. However, some parts of GnuPG currently parse it as a signed 32-bit value and will, in certain cases, use the wrong expiration date, far in the past instead of far in the future.

Here's an example using GnuPG 2.2.17. This is an OpenPGP key with an expiration date set 100 years in the future (e.g. year 2119):

-----BEGIN PGP PUBLIC KEY BLOCK-----

xjMEXUdXNBYJKwYBBAHaRw8BAQdAKTUvEQJiYcC9ODhvNWkMfdCLPm8IjFgq9V08
6sPqFQXNB0V4YW1wbGXCZwQTFggAGQUCXUdXNAkQW6mrUJjcbXkCGwMFCbv4HgAA
AHGkAQDr/xqQJb0M9hX6AHdH+AVjhTbH6i8a1SP273NRc0LNvQD/ShL8cnRhLqCL
zujHvGj7j4qxIV8V/zPqnBAD4zmTVgY=
=l7Zh
-----END PGP PUBLIC KEY BLOCK-----

The --list-packets command indicates such:

$ gpg --list-packets 100y.asc 
...
        hashed subpkt 9 len 4 (key expires after 100y0d0h0m)
...

This is the correct interpretation according to the RFC. However, --show-key and importing the key both use the signed interpretation, setting the expiration far in the past:

$ gpg --show-key 100y.asc 
pub   ed25519 2019-08-04 [SC] [expired: 1983-06-04]
      AEEC92792DEAD8BE023A8D4C5BA9AB5098DC6D79
uid                      Example

The same issue is present in the key generation interface (run just now in 2019):

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) 100y
Key expires at Sat Jun  4 16:01:21 1983 UTC
invalid value

For any user unfamiliar with the intricate details of the OpenPGP format, getting the year 1983 from "100y" is certainly surprising. Overflow is generally undetected in this interface:

Key is valid for? (0) 137y
Key expires at Mon May 25 16:04:46 2020 UTC

137 years becomes ~1 year. If it weren't for the prompt, the user would not even know GnuPG had made a mistake computing the expiration date.

As far as I can tell, GnuPG always gets it wrong by too early, not too late, so this probably isn't a security issue.

Details

Version
2.2.17

Event Timeline

I'm using Debian 10 "Buster" on x86-64, but for this ticket I used my own build of GnuPG so that I could demonstrate with the latest version. The system's GnuPG 2.2.12 has the same behaviors I showed here.

Re-examining this now, I'm noticing the problem is not at all that it's being treated as signed, but that GnuPG is internally using a 32-bit unsigned integer for the time even though the key expiration scheme allows for expiration dates beyond 2106. Seeing dates in the past threw me off, and when I had originally tried using a zero creation time to test a broader range I ran into T4670.

werner triaged this task as Normal priority.Aug 5 2019, 7:50 PM
werner added projects: gnupg, OpenPGP.

I can't find any places where it is interpreted as signed integer.

What you claim is an overflow of unsigned 32-bit integer.

In OpenPGP, it uses Unix time for timestamp, which range is < 2106.
https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs#Year_2106

It is true that the key expiration scheme itself doesn't limit < 2106, but it requires implementation to have representation of > 32-bit integer internally.

werner claimed this task.
werner edited projects, added Not A Bug; removed Bug Report.