Releasing a timelocked responsible disclosure

We have previously announced a timelocked responsible disclosure and it is accessible since February 23, 2023 at 00:00 (CEST). The previously encrypted report can now be decrypted at timevault.drand.love.

Let’s explain the details of the finding. The problem was found during our audit of Protocol Labs timelock encryption. tlock is the command tool providing time based encryption. It is a Go program implementing the tle command line tool providing similar features as the website timevault.drand.love. For example to encrypt a file 7 years 11 months and 1 day in the future the following command can be used:

$ ./tle -D 7y11m1d -o encrypted.dat msg.txt
$ cat encrypted.dat
age-encryption.org/v1
-> tlock 81988175 7672797f548f3f4748ac4bf3352fc6c6b6468c9ad40ad456a397545c6e2df5bf
h9Dyx8j8c7kUN7aElXFtNgt8S5ZRHFD9NxzPSceVwuyGrRNGXMEx3sFcHQcFdP5T
uSOdCywzK6HidhKh3Z/IMnZsHLPcM4i+I0gpW58/G4Q
--- yG9ANJMk1CblUDXWX4wknXtE5FcbfI+ENgDtS/nyL3c

We noticed that in the encrypted file the value after the string “tlock” is the Drand round number at which the file will be available. The current round number can be found here. Thus, the program converts the duration 7y11m1d to the round number 81988175. We found that giving a year too far in the future as an argument of tlock leads to encryption for round 1 of Drand and thus, the cleartext is immediately accessible. Here is an example of the problem:

$ ./tle -D 292277024627y -o encrypted_file data_to_encrypt
$ cat encrypted_file
age-encryption.org/v1
-> tlock 1
7672797f548f3f4748ac4bf3352fc6c6b6468c9ad40ad456a397545c6e2df5bf

hH/Rge2Um1qQVldiRByfg8MftReTkvr36gOlYDNj4jqdMJu3xuUdPsJ+ZDEnFRC8
+814SBSK+1frE6eoPzoATpClIy1jRwlsdStgFW7yHYU
--- ML9Z9pxb8gGuc3Cu8ng3wyZtFENsWA41TrfQhEY3vK0

For this example the round number has been set to 1. The problem is located in the conversion of the duration into a round number. The function parseDuration extracts the year number and subtracts it to the current date:

		years, err := strconv.Atoi(pieces[0])
		if err != nil {
			return time.Second, fmt.Errorf("parse year duration: %w", err)
		}
		diff := now.AddDate(years, 0, 0).Sub(now)
		return diff, nil

AddDate function is in the time package which is part of Go’s standard library providing date related functions. There is an integer overflow in the Date function called by AddDate, the year is converted to the number of days and then multiplied to the number of second in a day without any check. It leads to an erroneous negative result if the year number is too large. Here is an example of the problem: https://go.dev/play/p/Nz3aFaoA2iF. Then in tlock, the function RoundNumber, which computes the round number associated to a date, returns 1 for such negative results.

This may be a problem if the attacker can control the input date and tricks the server into encrypting something in the future whereas but the result will be accessible immediately.

The problem was reported and later corrected by Protocol Labs in the code of tlock with commit 96b5251ca25e105d241e46bcca30837fc4dcf150. An issue has been opened in Go language and a patch has been proposed but it is still present in the current version of Go (1.20) language so be careful if your program rely on the Date function for sensitive operations.

We were happy to have the opportunity to timelock encrypt and disclose our finding of a bug affecting timelock encryption itself!

Leave a Reply