-
Notifications
You must be signed in to change notification settings - Fork 120
Package signing detailed propsal
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.
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:
- Key signing parties; i.e. a direct in-person or hangout level of trust with someone you know.
- 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:
- Publishing our FP Complete key on fpcomplete.com (perhaps also other places) which can be accessed by the implicitly trusted TLS connection.
- 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.
We have a Github repository (maybe "the index") of:
- Package signatures: this indicates whether a package archive indeed came from the author.
- 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
The process for signing and uploading is simple:
- I run the
tool sign
in the package directory or whatever, this generates a package dist (or receives the name of a dist, a lacabal upload
) and a.asc
file for the.tar.gz
. - 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.
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).
With all that in place, the user runs e.g. tool install x
or tool verify x
which:
-
Downloads the index from the web server or loads from cache if not modified.
-
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)
-
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
. -
Installs or rejects the package appropriately.
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.