--- 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](https://berlin.ccc.de/~hannes/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 - Testing of opam2 [`repository validation command`](http://opam.ocaml.org/doc/2.0/Manual.html#configfield-repository-validation-command) and `conex_verify` ## 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 repository yet, but opam2 will have 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). To reduce the manual work, we analysed 7000 PRs of the opam repository within the last 4.5 years (more details [here](https://hannes.nqsb.io/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. TODO: remove pin once [PR 8493](https://github.com/ocaml/opam-repository/pull/8493) is merged. ```bash $ git clone -b auth https://github.com/hannesm/opam-repository.git repo $ opam pin add conex https://github.com/hannesm/conex.git $ 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](https://hannes.nqsb.io/Posts/Maintainer). 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. 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://github.com/hannesm/hannes.nqsb.io).