1 unstable release

0.1.0 Sep 19, 2023

#1916 in Cryptography

LGPL-2.0-or-later

170KB
3K SLoC

Sequoia git

sequoia-git is tool that can be used to improve a project's supply chain security.

Introduction

A version control system like git doesn't just track changes, it also provides a record of who made those changes. This information can be used to check that commits are authorized, which can improve software supply chain security. In particular, checking a change's provenance can be used to remove intermediaries like forges, and package registries from a user's trusted computing base. But, authorship information can easily be forged.

An obvious solution to prevent forgeries would be to require that commits are digitally signed. But by itself a valid digital signature doesn't prevent forgeries. The certificate that was used to make the signature could claim to be one of the project's maintainers. What is needed is not only a list of entities who are allowed to modify the repository, but also the keys they use to sign the commits. In other words, to authenticate a commit we need a signing policy, which says what keys are authorized to make changes.

Creating a policy isn't complicated. A project's maintainers could curate a list of entities who are allowed to add commits, and enumerate the certificates they use to sign them. The tricky part is applying the policy. There are a number of edge cases that need to be handled like how merge changes from external contributions, who is allowed to change the policy, and how to deal with compromised keys.

Sequoia git is a project that specifies a set of semantics, defines a policy language, and provides a set of tools to manage a policy file, and authenticate commits.

Using Sequoia git is relatively straightforward. You start by adding a policy file, openpgp-policy.toml, to your project's repository. The policy is maintained in band to allow it to evolve, just like the rest of the project. The openpgp-policy.toml file is basically a list of OpenPGP certificates and the type of changes they are authorized to make. sq-git can help you create it.

Then, before you merge a pull request, you check that commits are authorized by the policy. Locally, this is done by running sq-git log on the range of commits that you want to push. A commit is considered authorized if the commit has a valid signature, and at least one immediate parent's policy allows the signer to make that type of change. Projects hosted on GitHub can use this action to automatically check that a pull request is authorized when it is opened, or updated.

Downstream users can use Sequoia git to check that there is a chain of trust from an older, known-good version of the software to a new version. This helps prevent the use of versions that include modifications that weren't authorized by the project's maintainers.

See the specification for an in-depth discussion of semantics and implementation.

Deploying sq-git

To start using Sequoia git in a git repository, you first add one or more certificates to the project's policy, and grant them some rights.

The policy is called openpgp-policy.toml, and is stored in the root of the repository. It is a toml file, which means it can be edited by hand, but sq-git provides tools that make it easier to examine and modify it.

There are six different rights: add-user, retire-user, audit, sign-tag, sign-archive, and sign-commit. Only users who have the add-user right can add new users to the policy. Similarly, the retire-user right is needed to remove users from the policy. The audit right is needed to good list (using sq-git policy goodlist) a commit that was signed by a certificate that was subsequently hard revoked. The sign-tag, sign-archive, and sign-commit rights are needed to sign tags, archives, and commits, respectively.

You can use sq-git policy authorize to grant a specific right to a user. For instance, you could run:

$ sq-git policy authorize --sign-commit 'Neal H. Walfield <neal@pep.foundation>' F7173B3C7C685CD9ECC4191B74E445BA0E15C957

This says that the specified certificate can be used to sign commits. The name is purely decorative.

To make assigning rights easier, sq-git policy authorize knows about three roles: the project maintainer (who gets all rights), the release manager (who can sign tags, archives, and commits), and the committer (who can only sign commits). These can be passed to sq-git policy authorized. For instance:

$ sq-git policy authorize --project-maintainer 'Neal H. Walfield <neal@pep.foundation>' F7173B3C7C685CD9ECC4191B74E445BA0E15C957

sq-git policy authorize immediately expands the roles to the corresponding rights; the roles do not appear in the policy file.

You can use the sq-git init subcommand to get a quick overview of who has contributed to the project, and what certificates they used to sign their commits, if any. sq-git init looks at commits from the last half year, or the last 10 commits, whichever is more. As such it focuses on contributors who have been active recently; it doesn't make sense to authorize someone has left the project.

Although sq-git init usually provides a good starting point, you should not trust it. It is essential to verify a contributor's certificate by, e.g., reaching out to them, and asking what their certificate's fingerprint is. As you can always modify the policy later, it is better to only add the certificates that you are certain about, from contributors who are active.

Here's how you might initialize a policy file:

../sequoia-git$ sq-git init
# Examined the 136 commits in the last 183 days.
# Stopped at commit 83ce12f617c9e1dd90f812825707337f8787f69e.

# Encountered 0 unsigned commits

# Neal H. Walfield <neal@walfield.org> added 66 commits (48%).
#
# After checking that they really control the following OpenPGP keys:
#
#   6863C9AD5B4D22D3 (66 commits)
#
# You can make them a project maintainer (someone who can add and
# remove committers) by running:
sq-git policy authorize  --project-maintainer "Neal H. Walfield <neal@walfield.org>" 6863C9AD5B4D22D3

# Justus Winter <justus@sequoia-pgp.org> added 44 commits (32%).
#
# After checking that they really control the following OpenPGP keys:
#
#   686F55B4AB2B3386 (44 commits)
#
# You can make them a committer by running:
sq-git policy authorize --committer "Justus Winter <justus@sequoia-pgp.org>" 686F55B4AB2B3386
...
../sequoia-git$ sq-git policy authorize --project-maintainer "Neal H. Walfield <neal@walfield.org>" 6863C9AD5B4D22D3
  - User "Neal H. Walfield <neal@walfield.org>" was added.
  - User "Neal H. Walfield <neal@walfield.org>" was granted the right sign-commit.
  - User "Neal H. Walfield <neal@walfield.org>" was granted the right sign-tag.
  - User "Neal H. Walfield <neal@walfield.org>" was granted the right sign-archive.
  - User "Neal H. Walfield <neal@walfield.org>" was granted the right add-user.
  - User "Neal H. Walfield <neal@walfield.org>" was granted the right retire-user.
  - User "Neal H. Walfield <neal@walfield.org>" was granted the right audit.
../sequoia-git$ sq-git policy authorize --committer "Justus Winter <justus@sequoia-pgp.org>" 686F55B4AB2B3386
  - User "Justus Winter <justus@sequoia-pgp.org>" was added.
  - User "Justus Winter <justus@sequoia-pgp.org>" was granted the right sign-commit.

sq-git reads the certificates from the user's certificate store. Use sq import < FILE to import certificates in a file, sq keyserver get FINGERPRINT to fetch certificates from a key server, etc. Alternatively, you can provide the certificate to sq-git policy authorize using the --cert-file argument.

The policy file can be viewed as follows:

$ sq-git policy describe
# OpenPGP policy file for git, version 0

## Commit Goodlist


## Authorizations

0. Justus Winter <justus@sequoia-pgp.org>
   - may sign commits
   - has OpenPGP cert: D2F2C5D45BE9FDE6A4EE0AAF31855247603831FD
1. Neal H. Walfield <neal@walfield.org>
   - may sign commits
   - may sign tags
   - may sign archives
   - may add users
   - may retire users
   - may goodlist commits
   - has OpenPGP cert: F7173B3C7C685CD9ECC4191B74E445BA0E15C957

If you are happy, you can add it to your git repository in the usual manner.

Don't forget to tell git to sign commits by adding something like the following to your repository's .git/config file:

[user]
        signingkey = F7173B3C7C685CD9ECC4191B74E445BA0E15C957
        email = 'neal@pep.foundation'
        name = 'Neal H. Walfield'
[commit]
        gpgsign = true

Then run:

../sequoia-git$ git add openpgp-policy.toml
../sequoia-git$ git commit -m 'Add a commit policy.'
[main 911c4eb] Add a commit policy.
 1 file changed, 119 insertions(+), 1831 deletions(-)
 rewrite openpgp-policy.toml (94%)

Create a new commit, and verify the new version:

../sequoia-git$ echo 'hello world' > greeting
../sequoia-git$ git add greeting
../sequoia-git$ git commit -m 'Say hello.'
[main 698876a] Say hello.
 1 file changed, 1 insertion(+)
 create mode 100644 greeting
../sequoia-git$ sq-git log --trust-root 911c4eb1e9832d6df8e733bf103ca4c9f4637eb9
911c4eb1e9832d6df8e733bf103ca4c9f4637eb9..698876a7ff11fff2f8cd0df55bbe8fc5c5d224d9: Neal H. Walfield <neal@walfield.org> [74E445BA0E15C957]

Instead of entering the trust root manually, which is error prone, you can set the trust root in the repository's git config file:

../sequoia-git$ git config sequoia.trust-root 911c4eb1e9832d6df8e733bf103ca4c9f4637eb9
../sequoia-git$ sq-git log
911c4eb1e9832d6df8e733bf103ca4c9f4637eb9..698876a7ff11fff2f8cd0df55bbe8fc5c5d224d9: Cached positive verification

You can also use tags or branches, however, you must be careful as these may be updated when you fetch from a remote repository using, e.g., git fetch.

Rejecting Unauthorized Commits

Insert the following line into hooks/update on a shared git server to make it enforce the policy embedded in the repository starting at the given trust root (<COMMIT>), which is specified as a hash:

sq-git update-hook --trust-root=<COMMIT> "$@"

Using sq-git in CI

sequoia-git is available in an OCI image for ease of use inside of CI pipelines.

Gitlab

To authenticate commits from a Gitlab CI pipeline, there is a script included at scripts/gitlab.sh which may be run as a job inside a project's .gitlab-ci.yml manifest:

authenticate-commits:
  stage: test
  image: registry.gitlab.com/sequoia-pgp/sequoia-git:latest
  script:
    - sq-git policy describe
    - ./scripts/gitlab.sh
  rules:
    # TODO: We currently only authenticate the changes on non-merged
    # branches where we use the default branch as the trust root.  For
    # the default branch, the project needs to set an explicit trust
    # root.
    - if: '$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'

GitHub

To use sq-git to authenticate a pull request in GitHub, you can use the sequoia-pgp/authenticate-commits Action. This action checks that the commits are authorized by the last commit of the merge base. This video shows a demonstration of the action.

Note: GitHub's interface for merging pull requests offers three merge strategies, but unfortunately none of them are appropriate for use with Sequoia git, because they all modify the commits. With Sequoia git, it is necessary to either rebase and fast forward the change, or to add a signed merge commit. It is possible to use the sequoia-pgp/fast-forward action to fast forward pull requests. When enabled for a repository, an authorized user can add a comment containing /fast-forward to the pull request, and the action will fast forward the merge base.

Dependencies

~73MB
~1.5M SLoC