Git Verification with Guix

GNU Guix provides a utility to verify the commit history of a repository. The motivation and high-level overview can be found in their blog post (archive) and usage information can be found in the manual.

Usability & Security Implications

In order to make the process as convenient as possible, most information is stored inside the repository being verified. The .guix-authorizations file contains the list of trusted fingerprints, and the keyring branch contains the public PGP keys.

This does not cause the problem of trusting the message to authenticate itself, because the first authenticated commit and expected PGP key are supplied by the user.

Additionally, it would not be possible to smuggle a malicious commit into the history before the first authenticated commit. Rebasing onto a new commit would change each rebased commit, so the attacker would need to re-sign all of the previously verified commits with the secret key. Additionally, the first commit provided by the user would not exist in this rewritten history. The history prior to the first commit is still a source of concern, but it is only exploitable before the first authenticated commit is chosen.

It is debatable whether or not it is useful to authenticate every commit since a certain point, rather than verifying only the specific commit in use, due to the above implications of history rewriting. Checking every commit has the advantage of reducing the possibility of human error. Somebody could insert an unsigned commit during a busy period which goes unnoticed, causing future signed commits to implicitly authenticate the malicious commit. With this tool, the signed commits would be rejected because they are based on an unsigned commit. This increases the maintenance cost because an accidentally unsigned commit causes technical problems where it would not have before. However, my position is that it is good for this social problem (an untrustworthy message) to cause a technical error in order to make sure that the problem is obvious and given due attention.

Verifying an Arbitrary Repository

The tool is highly usable for repositories which contain the expected information, but it still works for a repository which contains none of the information, provided that the user is willing to supply it. This section contains a walkthrough of verifying a local checkout of the source code to this website.

The following things are needed:

  1. A checkout of the source (use commit 5f2188a778cb57bcc5b25114b8702adf9814cf63)
  2. The earliest commit to authenticate
  3. A keyring branch
  4. An authorizations file

The process of building trust in the inputs (trusted keys, earliest commit) is out of scope for this document.

Check out the source and create a branch on the target commit (for reproducibility in the future, after key rotations have happened):

$ git clone https://git.sr.ht/~skyvine/website
$ git checkout 5f2188a778cb57bcc5b25114b8702adf9814cf63
$ git branch commits-to-verify

Every commit in the history should be signed, so the earliest commit to authenticate is the first commit, ee3bb367c64235bf5bb256e397dfde67e4b32579.

The keyring branch needs to be set up locally:

$ git branch keyring
$ git checkout keyring
# The next command will start an interactive rebase with all commits listed in the file.
# Change the first line to "edit" and delete all other lines in order to create a branch
# that is completely unrelated to the source.
$ git rebase -i --root
$ git rm -r *
$ KEYS="FC06E8609177F69AD8F8AF01E47668FD94A6AEA1 2E09E60A01EAC2DBA6707A6441FA4A1349F791F5"
$ gpg --keyserver keyserver.ubuntu.com --recv-keys $KEYS
$ for key in $KEYS; do
$   gpg --export --armor $key > $key.key
$ done
$ git add *.key
$ git commit --amend

Create ../historical-authorizations.scm with the following contents:

(authorizations
  (version 0)
  (("FC06 E860 9177 F69A D8F8 AF01 E476 68FD 94A6 AEA1" (name "skyler"))
   ("2E09 E60A 01EA C2DB A670 7A64 41FA 4A13 49F7 91F5" (name "skyler"))))

Now guix can verify the commits. Unfortunately, there is one commit that slipped through without a signature. This should be reported:

$ git checkout commits-to-verify
$ guix git authenticate --historical-authorizations=../historical-authorizations.scm \
                        ee3bb367c64235bf5bb256e397dfde67e4b32579 \
                        FC06E8609177F69AD8F8AF01E47668FD94A6AEA1
Authenticating commits ee3bb36 to 5f2188a (42 new commits)...
▕█▊          ▏guix git: error: commit 961b9fa8594b899cd8df47c460972267cbe77344 lacks a signature

Review the commit to ensure that it is not malicious, then retry against the following commit. This time, there should not be any error.

$ git show --ignore-all-space 961b9fa8594b899cd8df47c460972267cbe77344
$ guix git authenticate --historical-authorizations=../historical-authorizations.scm \
                        0dd566084a54a7a140c9e93792405ea87afeffbe \
                        FC06E8609177F69AD8F8AF01E47668FD94A6AEA1

TODO: If there are multiple unsigned commits, will guix report all of them, the earliest one, the latest one, or do something else?

Download the markdown source and signature.