How to verify a curl release

A while ago I wrote about how curl is verified in several ways in order to reduce the risk for unwanted or even malicious content to appear in release tarballs. Now we look deeper at exactly what needs to be done to verify a curl release – how you and everyone else can do it.

Don’t trust, verify!

This functionality is shipped starting with curl 8.8.0. Previous versions were much harder to reproduce because we did not provide the necessary information and tooling in this convenient and easily accessible way.

Prerequisites

This verification procedure verifies that whatever is in git ends up in the release tarball unaltered. Nothing more, nothing less. To make sure that what exists in git is fine, people need to review that content but that is not covered in this blog post.

(As a little side-point: we work hard to not have any binary blobs or other files in the git repository that could magically get converted into exploit code using the right key.)

Tarball releases

A release from the curl project is always a tarball. The tarball is provided using several (four over the recent years) different compression methods. Each tarball has the identical content but is compressed differently. Therefore each separate tarball is also separately signed.

The tarball is generated from git from a release tag. curl’s release tags are named like curl-3_4_5 for version 3.4.5. With underscores instead of dots for historic reasons.

The release making script is carefully written to make sure it makes the release tarballs the exact same way to make the exact same output when run multiple times.

Reproducing means making an identical copy

When someone can reproduce a curl release, it means creating a new set of tarballs that are binary identical copies of the ones from a previous curl release. That process requires a set of known tools and knowing the exact timestamp.

Tools for release

The tarball features a few files that don’t exist in git because they are generated at release time. To reproduce those files, you likely need more or less the exact version of the involved tools that the person who makes the release (me) had at the time the tarball was created.

To help with this, there is a markdown document called docs/RELEASE-TOOLS.md generated and shipped in recent tarballs. It contains a list of the exact versions of a few key tools that were used. The list might look something like this:

- autoconf: 2.71-3
- automake: 1:1.16.5-1.3
- libtool: 2.4.7-5
- make: 4.3-4.1
- perl: 5.36.0-7+deb12u1
- git: 1:2.39.2-1.1

Time of release

The release tools document also contains another key component: the exact time stamp at which the release was done – using integer second resolution. In order to generate a correct tarball clone, you need to also generate the new version using the old version’s timestamp. Because the modification date of all files in the produced tarball will be set to this timestamp.

Setting the old release’s time is done by setting the environment variable SOURCE_DATE_EPOCH to the timestamp mentioned in RELEASE-TOOLS.md before creating the tarball.

To reproduce a release tarball, you need to extract that SOURCE_DATE_EPOCH and use it when creating a clone.

With docker

We have created a Dockerfile for the specific purpose of making releases. Using it is easy. You make a curl 8.8.0 release clone like this.

$ git clone https://github.com/curl/curl
$ cd curl
$ git checkout curl-8_8_0
$ ./scripts/dmaketgz 8.8.0 $SOURCE_DATE_EPOCH

The second argument to dmaketgz is the epoch time from the release tarball. It makes the newly generated tarball use the same timestamp as the previous one.

When dmaketgz is done, you are left with four tarballs in the curl source code tree, each named like curl-8.8.0.* with four different extensions for the different compression and archiving methods. They are bit for bit identical copies of the official curl 8.8.0 release tarballs.

If you sha256sum the new files and the official release tarballs, you see that they create identical checksums. Of course!

Without docker

Making a proper clone is harder without docker. This, because we use a Debian Linux to build releases and we use a set of tools and their exact versions as shipped by Debian. You might get slightly different outputs if you do not use the exact versions this Debian version ships. Getting a single line difference by a single tool will generate a different tarball and thus fail the reproducibility test.

This is why we provide the Dockerfile as mentioned above. Without that, you probably will have a higher success rate if you try the below steps running on a Debian Linux machine.

If you have the necessary tools installed using the versions mentioned in docs/RELEASE-TOOLS.md, do this to make a release copy. Below, we pretend 1715301052 is the timestamp from the real release.

$ git clone https://github.com/curl/curl
$ cd curl
$ git checkout curl-8_8_0
$ export SOURCE_DATE_EPOCH=1715301052
$ ./maketgz 8.8.0

Signatures

Every single published release tarball is signed, and a reproduced tarball can in fact be verified using the release signature since they are binary clones.

No transparency logs

Several people have asked me for transparency logs for the signing of the tarballs, to reduce the risk even further that in case of a breach of my development environment, someone else could be able to produce a curl release and sign it.

While in general an idea I like, I have not taken any steps towards it at this point. And to do a fake curl release, you need more than just being able to generate my signature. You also need the magic to make the release appear on the curl website in the correct way, so there are in fact multiple steps with separate keys, passwords and authentication methods in place which should make it a tough challenge to any bad guy.

I am not saying it is perfect nor that there is no room for improvement: because of course this process can always get improved. I am sure we will have reasons to polish it further going forward.