Skip to content

Package signing detailed propsal

Chris Done edited this page Apr 9, 2015 · 3 revisions

Overview

Here we lay out our proposed solution to the package signing problem end-to-end, including help users with maintaining a web of trust, signing packages and uploading signatures and finally verifying signatures with automated installation. Additionally, the problem of revocation of trust is addressed at the end.

Key trust

The hard part about signing via a web of trust is getting users educated on the process, getting buy-in and following of proper procedures to acquire keys.

There are two options for acquiring keys that can be trusted:

  1. Key signing parties; i.e. a direct in-person or hangout level of trust with someone you know.
  2. Web of trust; going via someone you know.

The former, we believe, is enough of a pain that people won't bother to do it on a large enough scale to get the thing off the ground.

We will provide a bootstrapping process for (2) by:

  1. Publishing our FP Complete key on fpcomplete.com (perhaps also other places) which can be accessed by the implicitly trusted TLS connection.
  2. The FP Complete key will sign the keys of maintainers of popular or many-in-number packages, and upload those signatures to key servers.

Now the end-user of this process can (1) download this key and sign it and (2) use that key to verify all the other keys of package authors which can be downloaded from key servers or from that author's own publication (homepage, email, etc.).

We will acknowledge that this shifts trust to the TLS CAs and un-hacked hosting, but that this lays down the foundation for the infrastructure and can be strengthened at any time; you can re-verify public keys via key signing parties or by strengthening your web of trust.

Maintenance of package signatures

We have a Github repository (maybe "the index") of:

  1. Package signatures: this indicates whether a package archive indeed came from the author.
  2. Package-signer mapping files: will be a JSON/YAML file simply listing which key fingerprint can sign which package. There will be many mapping files, each of which has an associated signature itself

So the file structure may look like:

signatures/
signatures/yesod/4.1.0/0D4F46E1.asc
signatures/yesod-base/1.2.3/0D4F46E1.asc
signatures/conduit/4.5.6/0D4F46E1.asc
signatures/lens/1.2.3/0D4F46E1.asc
signatures/lens/1.2.3/34JKA8GD.asc
mappings/
mappings/fpco.yaml
mappings/fpco.yaml.asc
mappings/blah.yaml
mappings/blah.yaml.asc

(The signature filename is the fingerprint of the signer.)

The structure allows for multiple versions of packages to be signed in the index, and also for multiple signatures to be provided for a given version.

The yesod, conduit and lens packages are signed by various people but fpco.yaml contains a mapping from packages to signers like this:

package:
  name: lens
  signers:
    - 34JKA8GD ekmett@gmail.com
package:
  name: yesod
  signers:
    - 0D4F46E1 michael@snoyman.com
    - 5DSHJ634 gregwebs@gmail.com

This fpco.yaml, together with the fact that it's signed by FP Complete (fpco.yaml.asc, with key 0D4F46E1), means: FP Complete (michael@snoyman.com) (0D4F46E1) trusts that the email ekmett@gmail.com and finger print of 34JKA8GD is authorative for the lens package, and that 0D4F46E1 and 34JKA8GD are authorative for the yesod package. A user can now decide to trust FP Complete (0D4F46E1) by adding them to trusted-mapping-signers (see below), which implies that they trust the the statements made by fpco.yaml, which implies that the user trusts that ekmett's signature is authorative over lens and that Michael Snoyman and Greg Weber are authorative over yesod.

Whether to include the fingerprint or just the email address is unclear.

If we wanted to be complicated we could have version ranges so that signers of packages could later be revoked.

Maybe this should be inverted given that there is a one-to-many relationship between author and packages:

signer:
  fingerprint: 34JKA8GD
  email: ekmett@gmail.com
  packages:
    - lens
    - semigroups
    - comonads

Uploading of signatures

The process for signing and uploading is simple:

  1. I run the tool sign in the package directory or whatever, this generates a package dist (or receives the name of a dist, a la cabal upload) and a .asc file for the .tar.gz.
  2. The tool then uploads this signature to the index.

We have some web server which accepts signatures (.asc) of packages and automatically includes them in the repo in the appropriate organization. We may or may not decide to reject signatures which are not in a mapping anywhere. I'd vote not, because: (1) the validation can happen later, (2) it may discourage people from signing their packages just because they're not in a mapping yet.

To provide assistance with getting this setup, we can provide a tool for signing many historical packages from the Hackage archive, which are implicitly trusted at the moment anyway. E.g. as an author I could run tool sign-old-packages yesod and this would download and sign all versions of yesod and upload my signatures.

Trusting mappings

A user has a .tool/config.yaml configuration file like:

trusted-mapping-signers:
  - 0D4F46E1 michael@snoyman.com

This indicates that I trust this file from the earlier hierarchy:

mappings/fpco.yaml
mappings/fpco.asc

Because it is signed by 0D4F46E1 (michael@snoyman.com).

Verifying and installing packages

With all that in place, the user runs e.g. tool install x or tool verify x which:

  1. Downloads the index from the web server or loads from cache if not modified.

  2. Loads up all mapping files that are signed by trusted-mapping-signers; which is like

    type TrustedMappingSigners = Set Signer

    and whose signatures are verified by running gpg in the user's environment, finally producing one big user-mapping. E.g.

    type UserMapping = Map PackageName (Set Signer)
  3. There is an existence check of whether package x exists in the user-mapping, if so, we will have a list of signers that are allowed for that package. From that list, we see if any signatures (.asc) exist which are signed by any of those signers. If so, we verify them. Only one of these signatures have to verify. The code might be like:

    verify :: Map PackageIdentifier Signature
            -> Map PackageName (Set Signer)
            -> PackageIdentifier
            -> Either Problem Signature
    verify signatures validSigners ident =
        case M.lookup ident signatures of
           Nothing -> ... -- no signature for foo-1.2.3
           Just sig ->
               case M.lookup (pkgName ident) validSigners of
                   Nothing -> ... -- no valid signers for package
                   Just signers ->
                       if S.elem (sigSigner sig) signers
                          then Right sig
                          else ... -- creator of this signature is not valid

    After that it's a matter of checking that the signature verifies with gpg --verify.

  4. Installs or rejects the package appropriately.

Revocation of trust

If a package or author is discovered to be untrustworthy for whatever reason, we should revoke trust in their packages. This involves updating any mappings files related to the offending packages.

For this, we can put the current date into the mapping file when it is updated and signed, so that when a package is flagged as being untrustworthy, users can be sent a notice that they should update their index by putting in .tool/config.yaml e.g.

mappings:
  fpco:
      minimum: 2015-04-16

Now, the tool will reject any mappings which have not been updated since this date. Previous indexes can be considered compromised. FP Complete can go and remove the offending packages from their mapping, and any other owners of mapping files.

Due to mirroring, archives and compromised servers, one cannot always be sure that users are getting a (up to date) trustworthy index. By adding a specific, signed, version identifier, we can give users a way to force this.