Proposal: cabal-install: verify OpenPGP signatures

Following up on the “cabal-install: Replacing HTTP with HTTPS” thread. I think we can do better. I want to make sure that people will notice if someone compromises the packages on hackage.haskell.org. Here’s a rough plan: 1. Patch ‘hackage-server’ to allow uploading of OpenPGP signatures. 2. Patch ‘cabal-install’ to use GPG for verification. (GPG trust levels could be useful here.) ‘cabal install’ should also support ‘--skip-verification’ or some such to avoid disaster during the adoption stage. In addition, ‘cabal update’ would fetch the list of fingerprints from Hackage and cache each revision. A warning would be raised if a fignerprint cannot be found in the cache. If a maintainer wants to use a new key, it must be signed with the previously used one. If a maintainer loses their private key, for instance, this should be resolved by the admins. For example, an admin (admins?) could sign the new key. After a while, a web of trust would be formed. The fingerprints of active maintainers would be well-known. I’ve been thinking about this for quite a while and don’t see other ways to achive the same level of trust while allowing arbitrary uploads. The proposal also doesn’t require much manual intervention. What do you think? I’m willing to work on this but want to make sure that my time won’t be wasted. Will you accept such a patch?

Hi,
On 30 April 2014 01:15, Nikita Karetnikov
Following up on the “cabal-install: Replacing HTTP with HTTPS” thread. I think we can do better. I want to make sure that people will notice if someone compromises the packages on hackage.haskell.org. [...]
I believe Austin Seipp had some ideas about this. IIRC, his plan was to use ed25519 signatures [1]. [1] https://github.com/thoughtpolice/hs-ed25519

I’ve been told off-list that relying on external tools (such as GPG) may be problematic. Is it the case? And if so, could you elaborate?

Hi,
On 3 May 2014 02:31, Nikita Karetnikov
I’ve been told off-list that relying on external tools (such as GPG) may be problematic. Is it the case? And if so, could you elaborate?
Yes, we want to make cabal-install as self-contained as possible, since it makes installation/distribution easier. E.g. on Windows we can just distribute a single cabal.exe to users.

On Wed, 2014-04-30 at 03:15 +0400, Nikita Karetnikov wrote:
Following up on the “cabal-install: Replacing HTTP with HTTPS” thread. I think we can do better. I want to make sure that people will notice if someone compromises the packages on hackage.haskell.org.
Here’s a rough plan:
1. Patch ‘hackage-server’ to allow uploading of OpenPGP signatures.
2. Patch ‘cabal-install’ to use GPG for verification. (GPG trust levels could be useful here.) ‘cabal install’ should also support ‘--skip-verification’ or some such to avoid disaster during the adoption stage.
In addition, ‘cabal update’ would fetch the list of fingerprints from Hackage and cache each revision. A warning would be raised if a fignerprint cannot be found in the cache.
If a maintainer wants to use a new key, it must be signed with the previously used one. If a maintainer loses their private key, for instance, this should be resolved by the admins. For example, an admin (admins?) could sign the new key.
After a while, a web of trust would be formed. The fingerprints of active maintainers would be well-known.
I’ve been thinking about this for quite a while and don’t see other ways to achive the same level of trust while allowing arbitrary uploads. The proposal also doesn’t require much manual intervention.
What do you think? I’m willing to work on this but want to make sure that my time won’t be wasted. Will you accept such a patch?
I think optional GPG signatures is a good idea, and I think in principle we would accept the patch. However it does have to be opt-in only: both opt-in for authors signing, and opt-in for clients checking. The idea of GPG signing is complementary to another package signing idea that we have been considering (Mikhail's reference to Austin's plan). GPG signing can provide a higher level of security because it can be end to end. However it's much harder with GPG signing to provide broad coverage of packages and users. It's hard because not all users have GPG (e.g. Windows), not all authors have GPG or can be bothered to sign anything. The web of trust is tricky, especially if we want to minimise the effort on the part of end users. Because of these issues with GPG signing, we thought a better use of our time was to work on this complementary plan where we have the server sign all the packages and the client automatically check. This is less secure because it is not end-to-end, but we can cover every package and we should be able to do it on all platforms, and without any knowledge or action needed on the part of authors or users. However, as I've said, these two security measures are complementary, we can have both. I can imagine a situation in which all packages are signed by the server, but some important ones are also signed by the authors, giving us a higher level of assurance of authenticity and integrity for those packages. So yes, you're welcome to help with either GPG signing or this alternative scheme, whichever you'd prefer to hack on. If you go for GPG, here's some issues to consider: * Who is allowed to sign for each package? Do we place any restriction or is any sufficiently trusted user allowed to upload any package? (I know the server will try to prevent this, but I'm talking about crypto level assurance here, not just server permission checks). If we do try to restrict it then we need some equivalent of the maintainer groups so I can allow other certain people to upload "my" package. * How to require some packages to be signed while not requiring all packages to be signed. In particular, once we opt-in to signing a particular package, we want to make sure it's signed from then on, and not allow a non-signed version of the package (which would otherwise be an obvious attack). * How to distribute the web of trust among end users (as opposed to authors) to minimise effort. Good luck! Duncan

Thanks for such a detailed reply, Duncan.
I think optional GPG signatures is a good idea, and I think in principle we would accept the patch. However it does have to be opt-in only: both opt-in for authors signing, and opt-in for clients checking.
Okay.
However, as I've said, these two security measures are complementary, we can have both. I can imagine a situation in which all packages are signed by the server, but some important ones are also signed by the authors, giving us a higher level of assurance of authenticity and integrity for those packages.
Indeed.
So yes, you're welcome to help with either GPG signing or this alternative scheme, whichever you'd prefer to hack on.
I intend to hack on the former. I’ll be sending progress reports, questions, and so forth to this list, so stay tuned. If anyone wants to collaborate, don’t hesitate to contact me.

I created a repository on Gitorious and pushed a couple of commits to the openpgp branch [1]. (There is nothing related to cabal-install yet, so apologies for off-topic.) [1] https://gitorious.org/hackage-server/hackage-server/commits/openpgp

I’ve just pushed a bit more code [1]. Now it’s possible to upload an ASCII-armored OpenPGP signature, which is optional, while uploading a package or a package candidate. If a signature is present, the download link will be shown in the “Downloads” list. Questions: 1. ‘backup’ doesn’t work yet. Should I use symlinks and a shared directory (see ‘Distribution/Server/Framework/BackupDump.hs’)? 2. Is there a need to provide ‘SafeCopy’ instances for the types that have been changed? If so, then which ones should be instantiated? Also, I made a mistake in 328c38a. Public keys must have their own page(s) since ‘name-contact’ requires authorization. (I’ll fix it). Any feedback is appreciated. Note that a development version of hOpenPGP is required for now (see the comment in the cabal file). [1] https://gitorious.org/hackage-server/hackage-server/commits/openpgp

I’ve just pushed the commit [1] allowing to cache OpenPGP public keys on ‘cabal update’. (Note that I haven’t written the needed code for ‘cabal install’ yet, so the rest of this message is only about ‘update’.) After talking to people on #gnupg (thanks!), I decided to abandon the previous idea of relying on a “positive certification of a User ID and Public-Key packet” [2] in order to determine whether a certain key is trusted. Key changes must be treated with care, so now a user must manually mark each key contained in the index tarball [3] as trusted or untrusted. This is tedious, but there are workarounds (like sharing the list of (un)trusted keys with people you trust). I also assume this should happen rarely after the initial update. A short demo follows. First, you need to obtain the source code from this repository [4] and switch to the ‘openpgp’ branch. Configure and install as usual, then run these commands: $ dist/build/hackage-server/hackage-server init $ dist/build/hackage-server/hackage-server run --static-dir=datafiles Register a couple of test accounts [5] (this process requires sendmail). And don’t forget to upload a public key at the temporary page. Keep the server running because it currently cannot recover public keys after shutdown. Clone this repository [6], checkout the ‘openpgp’ branch, and build it. Create a test config file. In mine, these lines differ from the defaults: remote-repo: localhost:http://localhost:8080/packages/archive remote-repo-cache: /home/nikita/cabal/test-cabal-dir/packages world-file: /home/nikita/cabal/test-cabal-dir/world After that you should be able to see something similar: $ dist/build/cabal/cabal \
--config-file=/home/nikita/cabal/test-cabal-dir/test-cabal-config \ --ignore-sandbox update Downloading the latest package list from localhost Do you trust the following key of test01? (yes/no/Skip) Test Key (do not use)
1024 bit RSA key B10D 6514 06C6 9846 F0BF C2E6 1E50 C29A F1EE 591F, created: 2014-06-21 11:38:39 UTC yes Marking the key as trusted. Do you trust the following key of test02? (yes/no/Skip) Test Key2 (do not use) 1024 bit RSA key E2C2 CA45 9442 3F0D 58D3 059A EFAC 5E0D E4B2 0BFE, created: 2014-06-21 23:39:41 UTC s Skipping the key.
(See the code in ‘IndexUtils.hs’ for the list of supported commands.) “Yes” writes the key into ‘test-cabal-dir/packages/localhost/public-keys’, which may later be used by ‘cabal install’ to verify a signature. (This is not implemented yet.) “Skip” (or any unrecognized command) simply prints the message. So the question will be asked again when ‘cabal update’ is run: $ dist/build/cabal/cabal \
--config-file=/home/nikita/cabal/test-cabal-dir/test-cabal-config \ --ignore-sandbox update Downloading the latest package list from localhost Do you trust the following key of test02? (yes/no/Skip) Test Key2 (do not use)
1024 bit RSA key E2C2 CA45 9442 3F0D 58D3 059A EFAC 5E0D E4B2 0BFE, created: 2014-06-21 23:39:41 UTC no Marking the key as untrusted.
“No” adds the fingerprint to ‘test-cabal-dir/packages/localhost/untrusted-fingerprints’. After that a user will never be asked about this key: $ dist/build/cabal/cabal \
--config-file=/home/nikita/cabal/test-cabal-dir/test-cabal-config \ --ignore-sandbox update Downloading the latest package list from localhost
A warning is raised if a trusted key is already present in the cache: $ cd /home/nikita/cabal/test-cabal-dir/packages/localhost/public-keys $ mv test02-public.txt test01-public.txt $ cd - $ dist/build/cabal/cabal \
--config-file=/home/nikita/cabal/test-cabal-dir/test-cabal-config \ --ignore-sandbox update Downloading the latest package list from localhost Warning: the cache already contains the following public key corresponding to test01: Test Key2 (do not use)
1024 bit RSA key E2C2 CA45 9442 3F0D 58D3 059A EFAC 5E0D E4B2 0BFE, created: 2014-06-21 23:39:41 UTC Do you trust the following key of test01? (yes/no/Skip) Test Key (do not use) 1024 bit RSA key B10D 6514 06C6 9846 F0BF C2E6 1E50 C29A F1EE 591F, created: 2014-06-21 11:38:39 UTC C-c C-c
(“Yes” would replace the existing key.) That’s all for now. Any comments are appreciated. Next I’m planning to make this process optional (via a command line flag) and augment ‘cabal install’ to use this. It also bothers me that ‘hackage-server’ and ‘cabal-install’ have nearly the same code in ‘OpenPGP.hs’. I’ll try to contribute it to ‘hOpenPGP’, so it can be imported from there. [1] https://gitorious.org/cabal/cabal/commits/openpgp [2] https://tools.ietf.org/html/rfc4880#section-5.2.1 [3] http://localhost:8080/packages/index.tar.gz [4] https://git.gitorious.org/hackage-server/hackage-server.git [5] http://localhost:8080/users/register-request [6] https://git.gitorious.org/cabal/cabal.git

There is a problem with the current OpenPGP spec: only an 8-octet key id is included in a signature, not the whole fingerprint [1,2]. I’d like to get some feedback on how to address this issue. This branch [3] contains the code that adds available OpenPGP keys and corresponding usernames to the index tarball. This information is used during ‘cabal update’ [4] to establish a set of trusted keys, which is then cached. When a user runs ‘cabal install’, they only get a source tarball and possibly a signature. How would you find the right key in the cache? I see two options: 1. Match on 8-octet key ids. 2. Get an uploader name somehow and match on it instead. The first option is more simple, which is a good thing. But it would require to forbid clashing key ids. I think that’d be too restrictive (fingerprints could be different) and would require querying the cache for every key in the index tarball, which’d probably need a database. The second one means sending an additional web request for each package version during ‘install’, which would also add input validation burden and potential security issues. Since I dislike both options, I’ve talked to Mikhail on IRC who suggested adding an ‘x-hackage-uploader’ field to .cabal files (similar to the already used ‘x-hackage-revision’). That’d be done in the index tarball without changing the original files. I like this idea because it’s simple and would allow to avoid fingerprint collisions [5]. What would you do? [1] https://tools.ietf.org/html/rfc4880#section-5.2.3.5 [2] https://www.ietf.org/mail-archive/web/openpgp/current/msg00405.html [3] https://gitorious.org/hackage-server/hackage-server/commits/openpgp [4] https://gitorious.org/cabal/cabal/commits/openpgp [5] https://www.ietf.org/mail-archive/web/openpgp/current/msg07195.html

I’ve been extremely busy recently, so I only answer the questions for now. Please speak up if you see a possibility for an attack, or if something is not clear or not efficient.
If you go for GPG, here's some issues to consider: * Who is allowed to sign for each package? Do we place any restriction or is any sufficiently trusted user allowed to upload any package? (I know the server will try to prevent this, but I'm talking about crypto level assurance here, not just server permission checks). If we do try to restrict it then we need some equivalent of the maintainer groups so I can allow other certain people to upload "my" package.
Each Hackage user should be able to upload their public key. Each Hackage package must have a “signed?” flag or an equivalent (which will be set automatically). While uploading a package, a user should be able to choose whether to sign it or not. If a package is signed, the maintainers have the permission to sign. If it’s necessary to add a maintainer, the uploader lists a username in a file, signs it, and uploads. (Is there a better way to achieve the same thing?) The server checks the signature, parses the file, and adds the usernames to the maintainers group. Since it’s important to sign the file locally, there should be a convenience tool allowing to select the usernames. An error must be raised if the file is not signed; the server must not allow such uploads. Each user of the said list must have a public key on Hackage. If not, the server must show an error message. If a key is not known, the server must check whether it’s signed by a previously known key corresponding to the package.
* How to require some packages to be signed while not requiring all packages to be signed. In particular, once we opt-in to signing a particular package, we want to make sure it's signed from then on, and not allow a non-signed version of the package (which would otherwise be an obvious attack).
When a user runs ‘cabal update’, it fetches a list of packages along with the “signed?” flags and the keys corresponding to each package. If the cache is empty, the information is written to disk. If not, it is compared with the previous version. If a package that was signed previously is not signed, an error must be raised. People will be able to share such files (if they want) in order to bootstrap securely.
* How to distribute the web of trust among end users (as opposed to authors) to minimise effort.
I’m not sure. Maybe we shouldn’t bother (at least for now). If someone wants to check a fingerprint, for instance, they can ask on haskell-cafe or #haskell.

If a package is signed, the maintainers have the permission to sign. If it’s necessary to add a maintainer, the uploader lists a username in a file, signs it, and uploads. (Is there a better way to achieve the same thing?) The server checks the signature, parses the file, and adds the usernames to the maintainers group.
Since it’s important to sign the file locally, there should be a convenience tool allowing to select the usernames.
This is too complicated and has some drawbacks. I’m currently working on the following: 1. It should be possible to upload a signature while uploading a package. The server must check that the signature corresponds to the uploader’s key. 2. If it’s necessary to add a new maintainer, the initial uploader must sign the public key of the said maintainer. (This will be checked by cabal-install.) For ‘cabal-install’: 1. A warning must be raised if a package that was signed previously is not signed. 2. If a package is signed but was not signed previously, ‘cabal-install’ must check its signature and add the public key to the cache. 3. If a package was signed but now is signed with a different key, ‘cabal-install’ must check whether this key is signed with the previously-known key corresponding to this package, then verify the signature. If not, a warning must be raised.
participants (3)
-
Duncan Coutts
-
Mikhail Glushenkov
-
Nikita Karetnikov