# 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. [the specification]: https://sequoia-pgp.gitlab.io/sequoia-git/ ## 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](https://toml.io), 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 ' 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 ' 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: ```shell ../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 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 " 6863C9AD5B4D22D3 # Justus Winter 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 " 686F55B4AB2B3386 ... ../sequoia-git$ sq-git policy authorize --project-maintainer "Neal H. Walfield " 6863C9AD5B4D22D3 - User "Neal H. Walfield " was added. - User "Neal H. Walfield " was granted the right sign-commit. - User "Neal H. Walfield " was granted the right sign-tag. - User "Neal H. Walfield " was granted the right sign-archive. - User "Neal H. Walfield " was granted the right add-user. - User "Neal H. Walfield " was granted the right retire-user. - User "Neal H. Walfield " was granted the right audit. ../sequoia-git$ sq-git policy authorize --committer "Justus Winter " 686F55B4AB2B3386 - User "Justus Winter " was added. - User "Justus Winter " 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: ```shell $ sq-git policy describe # OpenPGP policy file for git, version 0 ## Commit Goodlist ## Authorizations 0. Justus Winter - may sign commits - has OpenPGP cert: D2F2C5D45BE9FDE6A4EE0AAF31855247603831FD 1. Neal H. Walfield - 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: ```text [user] signingkey = F7173B3C7C685CD9ECC4191B74E445BA0E15C957 email = 'neal@pep.foundation' name = 'Neal H. Walfield' [commit] gpgsign = true ``` Then run: ```shell ../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: ```shell ../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 [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: ```shell ../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 (``), which is specified as a hash: ```text sq-git update-hook --trust-root= "$@" ``` ## 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](https://github.com/sequoia-pgp/authenticate-commits). This action checks that the commits are authorized by the last commit of the merge base. [This video](https://www.youtube.com/watch?v=KdDbU9u5X-Q) 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](https://github.com/sequoia-pgp/fast-forward) 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.