Skip to content

Support public/private download signing and verification #38

Closed
damianb opened this Issue Sep 30, 2011 · 26 comments
@damianb
damianb commented Sep 30, 2011

As some package maintaners may wish to sign the downloads (tar/phar, etc.) that they make available, there probably should be some method of easily distributing the public key or keys that are used to sign the packages, or at least working with locally stored package keys.

Storage of the public key(s) may or may not be something for composer.json itself, but it needs addressed regardless; seamless verification of packages with already-provided keys is going to be demanded for higher security platforms.

And obviously, downloading of public keys from a remote source should be out of the question. That would defeat the purpose of code signing and should not be made possible.

@Seldaek
Composer member
Seldaek commented Sep 30, 2011

I agree we need to address this eventually, but for now all packages are basically public github repos that don't have "dist" (tar/phar/..) versions per-se.

@damianb
damianb commented Sep 30, 2011

Yep. It's one for the back burner, but if accounted for earlier now with changes in design it should be much easier to work in than later on.

The biggest problem I see, however, is that of relating keys to packages seamlessly.

Anyways, semi-relevant: I'm playing with an idea right now for a specific security model of package/addon loading [link], but I don't expect it to be completely applicable due to its ultra-paranoid design. It'd end up being quite similar to how browser vendors have CA signing set up, but with a bit less trust involved in third parties; either you're whitelisted for by the vendor, you are whitelisted specifically by the user, or GTFO.
And while I think it should be possible to lock down source packages using composer this far, I would be against it being default behavior. I imagine it being frustrating as hell!

@Seldaek
Composer member
Seldaek commented Sep 30, 2011

Well once you do finish thinking about that (feel free to stop by on irc.freenode.org #composer-dev if you need to discuss with others), please let us know if you think there's any blocking issues that should be addressed before it's too late.

@damianb
damianb commented Jan 2, 2012

I've been thinking about this one for a while, I'm still stumped as to how distribution of public keys can be accomplished without opening up the "poisoned key via MITM" can of worms - the only thing I see being workable is having an encrypted (HTTPS) method of key distribution, but that introduces an environment where the end user has to place all their trust in one individual/organization.
While this is what's already in place with the current DNS structure of the internet, it's not exactly the most ideal setup and it centralizes power in an unstable fashion; in my opinion, for a project like this, it takes too much freedom and flexibility away from the end user and the package developers.

Alternative ideas, anyone? I'm stumped.

@beberlei

Bump.

Packagist.org should have SSL itself, currently the packages.json is unsecure, so there is no trust that the returned data is right. One way would be to distribute the packagist SSL key with composer, and only allow secure connections to the packages.json.

That would also be a way to verify the that this is a trusted source to grab public keys for additional repositories from.

@bantu
bantu commented Jul 16, 2012

For git repositories, some authentication could be archieved by allowing the specification of commit-SHA1s in composer.json. Composer would then verify that the expected commit has been checked out from the specified repository. If such a commit does not exist, it would have to throw a fatal error. Security follows from SHA1 because it is infeasible to come up with another tree/commit that has the same hash. It would then be possible to verify that "I got what I wanted".

@sstok
sstok commented Jul 17, 2012

@bantu I was thinking about this exact same thing yesterday.
Changing the history of a repository will change its SHA1 commit hash, and there is just no way to spoof this.

Direct downloads however should be verified.
Most RPM and deb files use a key-server for this, so using something like this is a possibility.
Signing the file with a private-key, storing the signature with with the file-location, then when downloading verifying the signature to the public-key (provided by 3rth party key-server if not on current system) and then verifying the downloaded file using the signature.

@bantu
bantu commented Jul 17, 2012

@sstok
With git you can also already cryptographically sign tags. A signed git tag is basically a secure mapping from the tag name to a tree SHA1. The rest of the security follows from what has been said. By looking at the history, it should also be possible to securely update to a later release (i.e. another signed tag) and even within the version constraints specified.

To sign packages or executables, phpseclib could be used which besides support for common crypto extensions contains a PHP-native RSA signature implementation. On update, it has to be ensured that progress is made (i.e. the new version is actually newer than the installed version) so attackers can not give you an older (buggy) version that also has a valid signature.
A first step in this direction would be making the self-update function of composer secure.

Key servers provide public-keys but public-keys should never be trusted automatically. Unknown public keys should always be confirmed by the user or manually added.

@sstok
sstok commented Jul 17, 2012

Like I said, a 'loose download' ;) like a zip or tar.

"Key servers provide public-keys but public-keys should never be trusted automatically. Unknown public keys should
always be confirmed by the user or manually added."

True, I meant manually importing them from the server. Like is done in most cases.
I cant remember the correct command :) But you know what I mean.

". On update, it has to be ensured that progress is made (i.e. the new version is actually newer than the installed version) so attackers can not give you an older (buggy) version that also has a valid signature."
Good point. But sometimes one wants to use an older version as the current one is having issues.
Or is this not what you meant?

@bantu
bantu commented Jul 17, 2012

@sstok If I specify "2.0.x" as the version and have "2.0.5" installed, an attacker should be unable to feed me "2.0.0" (which would also have a valid signature) when composer updates, because that version might (and in general will) contain security problems he might then be able to exploit. Of course, if you manually specify "2.0.4" because "2.0.5" contains other bugs, the "2.0.4" version should be used.

@Seldaek
Composer member
Seldaek commented Jul 17, 2012

@bantu This might be very hard to implement because a package downgrade could happen naturally because some of your other dependencies requires that first dependency in v2.0.4 instead of 2.0.5 suddenly because they detected an incompatibility. That said, I don't think it's really necessary. The version choosing is done by the solver, and then if it installs the 2.0.4 you just need to check you are really getting this. At the moment for tags if you install from a zip packagist contains the md5 of the archive from github, so the downloader checks that the md5 matches. That does not cover all sources yet, but it's a start. For git the SHA1 idea is probably a good one. Again packagist sends you the latest known hash of the branch, so it will update to that exact hash and if the hash isn't there anymore it warns you that it rolled back to an older hash (because sometimes hashes disappear on legit repos due to force pushes). You get the warning so you can assess the situation. I guess we could have a stricter security mode that just fails hard on those occasions.

@bantu
bantu commented Jul 17, 2012

@Seldaek Regarding the dependencies: Indeed, I haven't consided that. On the other side, again, you do not want 2.0.4 if it has security issues. So in that case you also do not want to downgrade but fail (or at least warn). This seems to boil down to the following: Instead of assuming that only the latest existing version is secure, each of the available versions need their own secure/insecure flag. I don't know whether the existing functionality for "broken releases" already addresses that.

"At the moment for tags if you install from a zip packagist contains the md5 of the archive from github, so the downloader checks that the md5 matches." for authentication this only works if you a) have a "secure" connection to packagist which you probably do not have right now b) packagist knows the md5 beforehand and c) you trust packagist (which I would generally prefer not to assume). The same applies to "Again packagist sends you the latest known hash of the branch, so it will update to that exact hash and if the hash isn't there anymore it warns you that it rolled back to an older hash (because sometimes hashes disappear on legit repos due to force pushes)." The MD5 hash probably only provides integrity of transfered data but not authenticity.

@Seldaek
Composer member
Seldaek commented Jul 17, 2012

The problem with marking releases as insecure is that when you find a problem you may have to mark 20 releases as affected if it's some obscure problem. If suddenly all those people start getting warnings when they install the affected versions you might raise awareness but you will also piss people off I think. Especially in large frameworks what is a security issue to some might just not be a problem at all for the majority of users depending on how they are used. So I think we might want to do that on packagist itself some news feed of patched vulnerabilities or something. But it's another discussion IMO.

Regarding packagist, a) we don't yet, but should be doable already and we will try to enable that in a bit. b) packagist gets the md5 from github when updating the package metadata, so as long as the packagist - github link is secure this can be trusted. c) Fair enough. But you gotta get the information from somewhere. If you mirror packagist you're trusting it too, if you don't mirror it you can replicate it and recrawl all packages but that's gonna be a pain to manage IMO for your organization.

The MD5 .. well actually it's a SHA1, so it's not foolproof but if you get it from github and get the same SHA1 that packagist computed, it's reasonably safe to assume it's legit I would say.

@psliwa
psliwa commented Jan 22, 2014

I want to dig out this issue because I have had recently some reflection about packagist and composer security.

Checksum checking, as it was mentioned, ensures only data integrity - so we now that downloaded data has not been changed in the fly (MITM attack) and it is not corrupted. But there are no any author identity checking (e.g. by pgp signature). In my opinion packagist should support package signatures and it should enforce signing of package because this is very serious security issue.

Lets look on this scenario: hacker has broken into github account of developer one of symfony bundle. The hacker is able to do commits. This package affected by the hacker is available in packagist.org automatically, so the hacker is able to inject any malicious code to thousands of server and break into those servers or databases (Cross build injection vulnerability). If there had been support for package signing, this scenario wouldn't have been possible because hacker wouldn't have known private key so he wouldn't be able to sign "package" (e.g. checksum signing - details are not important now) and packagist wouldn't have accepted new package version.

@soatok
soatok commented Aug 5, 2014

Is there any traction on this initiative? I'd like to be able to automatically wget, gpg --verify, and then run composer all in one simple, human-readable shell script.

@alphapapa

Rather than reinventing the wheel, it would probably be a good idea to use http://theupdateframework.com/ if possible.

@julienfastre

Hi,

I am interested in this issue: we would like to be sure that the code which is executed on our server is the code we have written and approved :-) .

We use Symfony and we began to use the checksum published by sensiolabs, but this does not works : sensiolabs/checksums#3 . It seems that calculating the zip files are auto-generated by github, and the sha1sum might change (but I did not investigated further).

It is also difficult (but not impossible) to compute the verification by a machine.

I am thinking about another way to sign package, using OpenPGP :

  • add in the project root package the trusted keys, and for which package they are trusted, using extra, like :
{
"extra":
     "package-signatures": [
          "myvendor/my-package": "BDEF085DDES85DE",
          "myvendor2/*": "DDEFBCBC123456",  /* to sign all package from the same vendors */
     ]
}
  • add in the signed package a file composer.sha1.json (or something more obvious) which will list all packages present in the files, their sha1sum (individually) and eventually their lenght (in bytes ?)
  • sign the file composer.sha1.json with OpenPGP (eventually by adding a new file composer.sha1.json.asc or with an inline signature, I do not know which is the more obvious).
  • add a plugin to listen on post-package-install and post-package-update event to :
    • check the signature of the composer.sha1.json file according to the trusted signatures given in composer.json 's root package;
    • compute the sha1 and the length of installed files, and compare them with the sha1 given in composer.sha1.json
    • add a warning or stop the installation process if the script detects a difference

It seems to be more robust than relying on sha1's dist file (zip, tar, ...). It will also works with proxies.

In the future, it would be great to generate the file composer.sha1.json with a composer's command, but it seems that the api does not allow to expose commands yet.

In an advanced future, it might also be useful to grab trusted signatures from packagist...

Do you think it might work ? Do you have any suggestions ?

Do you think it will be an improvement on security ?

@sstok
sstok commented Sep 7, 2015

Adding the PGP trusted keys in the composer root package? GENIUS! 👍

@padraic I'm no security expert, any comments on this suggestion?

@julienfastre

I have written some "proof of concept" of my idea in the gist here : https://gist.github.com/julienfastre/e380f19a1e31b737418c

The php code is quite ugly (no function, no classes, ... this is pure script php). The aim of this script was to test gnupg extension on PHP.

It seems to work on different machines and, on the desktop I could test, the installation was quite easy.

If you are willing to help me, jump to this part : https://gist.github.com/julienfastre/e380f19a1e31b737418c#i-want-to-test- and let me know if it worked for you. It should last 10 minutes to test it on a Debian/Ubuntu.

An example of composer.sha1.json is here : https://github.com/Chill-project/Main/blob/add_ui_permission/composer.sha1.json

Writing this test, I discovered that I had to ignore some folders (.git, bin/, others vendors, ...).

I am going to write some specifications (usage, command line, ...) of the plugin I am planning to write in a repo soon. I don't plan to ask for the integration of this feature in composer before having experienced it in some plugin.

Thanks for your answers and comments.

@stof
stof commented Sep 22, 2015

the fact that it requires an uncommon pecl extension makes this POC a no-go for usage in Composer though:

  • most people won't have it installed, making it much harder to use Composer
  • the extension is not compatible with Windows, and dropping Windows support in Composer is simply not an option
@serbanghita

@stof what if supporting composer.sha1.json (package signing) is made optional and looks like there is a support for Windows https://github.com/maryo/php-5.5-windows-extensions/tree/master/php_gnupg-1.3.3-5.5-vc11-x86

@stof
stof commented Sep 22, 2015

well, at least there is no official support for it: the extension source code does not have the necessary config files to be compiled on Windows, which means pecl.php.net cannot compile it and we would have to depend on custom shipping of the extension for Windows, which is decoupled from the actual development of the library.
And this repo only provides the extension for PHP 5.5 x86, which means it cannot be linked as the way to get the extension on Windows (PHP 5.5 is not the current PHP version).
And the extension does not support PHP 7 either, which is coming soon (and will probably be there approximately at the time such a feature can become available in Composer as some time is needed for the implementation).

An optional package signing system which would work only for a very small subset of Composer users (people with this extension installed already) would not increase the security of Composer, as most people using Composer will just bypass your optional system because they don't have this requirement.

@julienfastre

@stop Thanks for this information about not having gnupg supported on Windows.

In my mind, this would have been only a composer-plugin. But if you think it would be nice to have in Composer itself, I would be happy to contribute.

Another way of signing package could be using the openssl functions. If a user would like to check the package signature integrity he goes to the developers website and copy/paste the public key used to signed the package, and put it into the composer.json file :

{
"extra":
     "package-signatures": [
          "myvendor/my-package": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB",
          "myvendor2/*": "... another key here ..."
     ]
}

We keep the composer.sha1.json file, without GPG signature, and add a composer.sha1.json.signature in the same directory which contains the signature.

After installation / upgrade, the script will check the file integrity using the public key provided in the root's composer.json file.

This, we could ensure that the user will install code that was, most probably, signed by the original developer. (It does not help in other matters, but using TLS on downloading packagist's packages.json does a good job, no? )

I think the advantage of using GnuPG is the ease to revoke keys. Here, to perform a key revocation, the user must regularly watch on the developer website looking for a key compromission...

If you do think it worth to be experienced, I am ready to re-write the "Proof of concept".

@sstok
sstok commented Sep 23, 2015

In my mind, this would have been only a composer-plugin.
Uhh? :) if the 'core' of Composer is not checking the file integrity then how could you possible trust any plug-in you install? :-)

@julienfastre

@sstok That's a good point...

But solutions exists : we can check the sha1sum of the archive, check a gpg signature on the archive, but this require installing the plugin manually, I admit (and I also admit I do not know if this is possible...)

But I would be happy to see this feature included in composer, if it works well.

Reading the installer script, it seems that openssl is required to run composer (I wasn't sure before :-) ). So openssl might be used, isn't it ?

@Seldaek
Composer member
Seldaek commented Apr 18, 2016

Closing in favor of #4022 as it has more relevant info IMO.

@Seldaek Seldaek closed this Apr 18, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.