hannes.robur.coop/Posts/Conex
2023-11-20 17:58:35 +01:00

392 lines
15 KiB
Text

---
title: Conex, establish trust in community repositories
author: hannes
tags: package signing, security, overview
abstract: Conex is a library to verify and attest package release integrity and authenticity through the use of cryptographic signatures.
---
Less than two years after the initial proposal, we're happy to present conex
0.9.2. Pleas note that this is still work in progress, to be deployed with opam
2.0 and the [opam repository](https://github.com/ocaml/opam-repository).
![screenshot](/static/img/conex.png)
[Conex](https://github.com/hannesm/conex) is a library to verify and attest release integrity and
authenticity of a community repository through the use of cryptographic signatures.
Packages are collected in a community repository to provide an index and
allowing cross-references. Authors submit their packages to the repository. which
is curated by a team of janitors. Information
about a package stored in a repository includes: license, author, releases,
their dependencies, build instructions, url, tarball checksum. When someone
publishes a new package, the janitors integrate it into the repository, if it
compiles and passes some validity checks. For example, its name must not be misleading,
nor may it be too general.
Janitors keep an eye on the repository and fix emergent failures. A new
compiler release, or a release of a package on which other packages depend, might break the compilation of
a package. Janitors usually fix these problems by adding a patch to the build script, or introducing
a version constraint in the repository.
*Conex* ensures that every release of each package has been approved by its author or a quorum of janitors.
A conex-aware client initially verifies the repository using janitor key fingerprints as anchor.
Afterwards, the on-disk repository is trusted, and every update is verified (as a patch) individually.
This incremental verification is accomplished by ensuring all resources
that the patch modifies result in a valid repository with
sufficient approvals. Additionally, monotonicity is preserved by
embedding counters in each resource, and enforcing a counter
increment after modification.
This mechanism avoids rollback attacks, when an
attacker presents you an old version of the repository.
A timestamping service (NYI) will periodically approve a global view of the
verified repository, together with a timestamp. This is then used by the client
to prevent mix-and-match attacks, where an attacker mixes some old packages and
some new ones. Also, the client is able to detect freeze attacks, since at
least every day there should be a new signature done by the timestamping service.
The trust is rooted in digital signatures by package authors. The server which
hosts the repository does not need to be trusted. Neither does the host serving
release tarballs.
If a single janitor would be powerful enough to approve a key for any author,
compromising one janitor would be sufficient to enroll any new identities,
modify dependencies, build scripts, etc. In conex, a quorum of janitors (let's
say 3) have to approve such changes. This is different from current workflows,
where a single janitor with access to the repository can merge fixes.
Conex adds metadata, in form of resources, to the repository to ensure integrity and
authenticity. There are different kinds of resources:
- *Authors*, consisting of a unique identifier, public key(s), accounts.
- *Teams*, sharing the same namespace as authors, containing a set of members.
- *Authorisation*, one for each package, describing which identities are authorised for the package.
- *Package index*, for each package, listing all releases.
- *Release*, for each release, listing checksums of all data files.
Modifications to identities and authorisations need to be approved by a quorum
of janitors, package index and release files can be modified either by an authorised
id or by a quorum of janitors.
## Documentation
[API documentation](https://hannesm.github.io/conex/doc/) is
available online, also a [coverage
report](https://hannesm.github.io/conex/coverage/).
We presented an [abstract at OCaml
2016](https://github.com/hannesm/conex-paper/raw/master/paper.pdf) about an
earlier design.
Another article on an [earlier design (from
2015)](http://opam.ocaml.org/blog/Signing-the-opam-repository/) is also
available.
Conex is inspired by [the update
framework](https://theupdateframework.github.io/), especially on their [CCS 2010
paper](https://isis.poly.edu/~jcappos/papers/samuel_tuf_ccs_2010.pdf), and
adapted to the opam repository.
The [TUF
spec](https://github.com/theupdateframework/tuf/blob/develop/docs/tuf-spec.txt)
has a good overview of attacks and threat model, both of which are shared by conex.
## What's missing
- See [issue 7](https://github.com/hannesm/conex/issues/7) for a laundry list
- Timestamping service
- Key revocation and rollover
- Tool to approve a PR (for janitors)
- Camelus like opam-repository check bot
- Integration into release management systems
## Getting started
At the moment, our [opam repository](https://github.com/ocaml/opam-repository)
does not include any metadata needed for signing. We're in a bootstrap phase:
we need you to generate a keypair, claim your packages, and approve your releases.
We cannot verify the main opam repository yet, but opam2 has support for a
[`repository validation command`](http://opam.ocaml.org/doc/2.0/Manual.html#configfield-repository-validation-command),
builtin, which should then call out to `conex_verify` (there is a `--nostrict`
flag for the impatient). There is also an [example repository](https://github.com/hannesm/testrepo) which uses the opam validation command.
To reduce the manual work, we analysed 7000 PRs of the opam repository within
the last 4.5 years (more details [here](/Posts/Maintainers).
This resulted in an educated guess who are the people
modifying each package, which we use as a basis whom to authorise for
which packages. Please check with `conex_author status` below whether your team
membership and authorised packages were inferred correctly.
Each individual author - you - need to generate their private key, submit
their public key and starts approving releases (and old ones after careful
checking that the build script, patches, and tarball checksum are valid).
Each resource can be approved in multiple versions at the same time.
### Installation
TODO: remove clone once [PR 8494](https://github.com/ocaml/opam-repository/pull/8494) is merged.
```bash
$ git clone -b auth https://github.com/hannesm/opam-repository.git repo
$ opam install conex
$ cd repo
```
This will install conex, namely command line utilities, `conex_author` and
`conex_verify_nocrypto`/`conex_verify_openssl`. All files read and written by conex are in the usual
opam file format. This means can always manually modify them (but be careful,
modifications need to increment counters, add checksums, and be signed). Conex
does not deal with git, you have to manually `git add` files and open pull
requests.
### Author enrollment
For the opam repository, we will use GitHub ids as conex ids. Thus, your conex
id and your GitHub id should match up.
```bash
repo$ conex_author init --repo ~/repo --id hannesm
Created keypair hannesm. Join teams, claim your packages, sign your approved resources and open a PR :)
```
This attempts to parse `~/repo/id/hannesm`, errors if it is a team or an author
with a publickey. Otherwise it generates a keypair, writes the private part as
`home.hannes.repo.hannesm.private` (the absolute path separated by dots,
followed by your id, and `private` - if you move your repository, rename your
private key) into `~/.conex/`, the checksums of the public part and your
accounts into `~/repo/id/hannesm`. See `conex_author help init` for more
options (esp. additional verbosity `-v` can be helpful).
```bash
repo$ git status -s
M id/hannesm
repo$ git diff //abbreviated output
- ["counter" 0x0]
+ ["counter" 0x1]
- ["resources" []]
+ [
+ "resources"
+ [
+ [
+ ["typ" "key"]
+ ["name" "hannesm"]
+ ["index" 0x1]
+ ["digest" ["SHA256" "ht9ztjjDwWwD/id6LSVi7nKqVyCHQuQu9ORpr8Zo2aY="]]
+ ]
+ [
+ ["typ" "account"]
+ ["name" "hannesm"]
+ ["index" 0x2]
+ ["digest" ["SHA256" "aCsktJ5M9PI6T+m1NIQtuIFYILFkqoHKwBxwvuzpuzg="]]
+ ]
+
+keys: [
+ [
+ [
+ "RSA"
+ """
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyUhArwt4XcxLanARyH9S
...
+9KQdg6QnLsQh/j74QKLOZacCAwEAAQ==
+-----END PUBLIC KEY-----"""
+ 0x58A3419F
+ ]
+ [
+ 0x58A79A1D
+ "RSA-PSS-SHA256"
+ "HqqicsDx4hG9pFM5E7"
+ ]
+ ]
+]
```
### Status
If you have a single identity and contribute to a single signed opam repository,
you don't need to specify `--id` or `--repo` from now on.
The `status` subcommand presents an author-specific view on the repository. It
lists the own public keys, team membership, queued resources, and authorised
packages.
The opam repository is in a transitionary state, we explicitly pass `--quorum
0`, which means that every checksum is valid (approved by a quorum of 0
janitors).
```bash
repo$ conex_author status --quorum 0 arp
author hannesm #1 (created 0) verified 3 resources, 0 queued
4096 bit RSA key created 1487094175 approved, SHA256: ht9ztjjDwWwD/id6LSVi7nKqVyCHQuQu9ORpr8Zo2aY=
account GitHub hannesm approved
account email hannes@mehnert.org approved
package arp authorisation approved
conex_author: [ERROR] package index arp was not found in repository
```
This shows your key material and accounts, team membership and packages you are
authorised to modify (inferred as described
[here](/Posts/Maintainers).
The `--noteam` argument limits the package list to only these you are personally
authorised for. The `--id` argument presents you with a view of another author,
or from a team perspective. The positional argument is a prefix matching on
package names (leave empty for all).
### Resource approval
Each resource needs to be approved individually. Each author has a local queue
for to-be-signed resources, which is extended with `authorisation`, `init`,
`key`, `release`, and `team` (all have a `--dry-run` flag). The queue can be
dropped using `conex_author reset`. Below shown is `conex_author sign`, which
let's you interactively approve queued resources and cryptopgraphically signs
your approved resources afterwards.
The output of `conex_author status` listed an authorisation for `conf-gsl`,
which I don't feel responsible for. Let's drop my privileges:
```bash
repo$ conex_author authorisation conf-gsl --remove -m hannesm
modified authorisation and added resource to your queue.
```
I checked my arp release careful (checksums of tarballs are correct, opam files
do not execute arbitrary shell code, etc.), and approve this package and its
single release:
```bash
repo$ conex_author release arp
conex_author.native: [WARNING] package index arp was not found in repository
conex_author.native: [WARNING] release arp.0.1.1 was not found in repository
wrote release and added resources to your queue.
```
Once finished with joining and leaving teams (using the `team` subcommand),
claiming packages (using the `authorisation` subcommand), and approve releases
(using the `release` subcommand), you have to cryprographically sign your queued
resource modifications:
```bash
repo$ conex_author sign
release arp.0.1.1 #1 (created 1487269425)
[descr: SHA256: aCsNvcj3cBKO0GESWG4r3AzoUEnI0pHGSyEDYNPouoE=;
opam: SHA256: nqy6lD1UP+kXj3+oPXLt2VMUIENEuHMVlVaG2V4z3p0=;
url: SHA256: FaUPievda6cEMjNkWdi0kGVK7t6EpWGfQ4q2NTSTcy0=]
approved (yes/No)?
package arp #1 (created 1487269425) [arp.0.1.1]
approved (yes/No)?y
authorisation conf-gsl #1 (created 0) empty
approved (yes/No)?y
wrote hannesm to disk
repo$ conex_author status --quorum 0 arp
author hannesm #1 (created 0) verified 7 resources, 0 queued
4096 bit RSA key created 1487094175 approved, SHA256: ht9ztjjDwWwD/id6LSVi7nKqVyCHQuQu9ORpr8Zo2aY=
account GitHub hannesm approved
account email hannes@mehnert.org approved
package arp authorisation approved package index approved
release arp.0.1.1: approved
```
If you now modify anything in `packages/arp` (add subdirectories, modify opam,
etc.), this will not be automatically approved (see below for how to do this).
You manually need to `git add` some created files.
```bash
repo$ git status -s
M id/hannesm
M packages/conf-gsl/authorisation
?? packages/arp/arp.0.1.1/release
?? packages/arp/package
repo$ git add packages/arp/arp.0.1.1/release packages/arp/package
repo$ git commit -m "hannesm key enrollment and some fixes" id packages
```
Now push this to your fork, and open a PR on opam-repository!
### Editing a package
If you need to modify a released package, you modify the opam file (as before,
e.g. introducing a conflict with a dependency), and then approve the
modifications. After your local modifications, `conex_author status` will
complain:
```bash
repo$ conex_author status arp --quorum 0
package arp authorisation approved package index approved
release arp.0.1.1: checksums for arp.0.1.1 differ, missing on disk: empty, missing in checksums file: empty, checksums differ: [have opam: SHA256: QSGUU9HdPOrwoRs6XJka4cZpd8h+8NN1Auu5IMN8ew4= want opam: SHA256: nqy6lD1UP+kXj3+oPXLt2VMUIENEuHMVlVaG2V4z3p0=]
repo$ conex_author release arp.0.1.1
released and added resources to your resource list.
repo$ conex_author sign
release arp.0.1.1 #1 (created 1487269943)
[descr: SHA256: aCsNvcj3cBKO0GESWG4r3AzoUEnI0pHGSyEDYNPouoE=;
opam: SHA256: QSGUU9HdPOrwoRs6XJka4cZpd8h+8NN1Auu5IMN8ew4=;
url: SHA256: FaUPievda6cEMjNkWdi0kGVK7t6EpWGfQ4q2NTSTcy0=]
approved (yes/No)? y
wrote hannesm to disk
```
The `release` subcommand recomputed the checksums, incremented the counter, and
added it to your queue. The `sign` command signed the approved resource.
```bash
repo$ git status -s
M id/hannesm
M packages/arp/arp.0.1.1/opam
M packages/arp/arp.0.1.1/package
repo$ git commit -m "fixed broken arp package" id packages
```
### Janitor tools
Janitors need to approve teams, keys, accounts, and authorisations.
To approve resources which are already in the repository on disk,
the `key` subcommand queues approval of keys and accounts of the provided author:
```bash
repo$ conex_author key avsm
added keys and accounts to your resource list.
```
The `authorisation` subcommand, and `team` subcommand behave similarly for
authorisations and teams.
Bulk operations are supported as well:
```bash
conex_author authorisation all
```
This will approve all authorisations of the repository which are not yet
approved by you. Similar for the `key` and `team` subcommands, which also
accept `all`.
Don't forget to `conex_author sign` afterwards (or `yes | conex_author sign`).
### Verification
The two command line utlities, `conex_verify_openssl` and
`conex_verify_nocrypto` contain the same logic and same command line arguments.
For bootstrapping purposes (`nocrypto` is an opam package with dependencies),
`conex_verify_openssl` relies on the openssl command line tool (version 1.0.0
and above) for digest computation and verification of the RSA-PSS signature.
The goal is to use the opam2 provided hooks, but before we have signatures we
cannot enable them.
See the [example repository](https://github.com/hannesm/testrepo) for initial
verification experiments, and opam2 integration.
I'm interested in feedback, please open an issue on the [conex
repository](https://github.com/hannesm/conex). This article itself is stored as
Markdown [in a different repository](https://git.robur.coop/hannes/hannes.robur.coop).