Recently I looked at the state of 2FA support across package managers. 2FA adds a layer of security by requiring two sources of authentication from maintainers when publishing packages. This helps open source communities avoid supply-chain attacks by protecting packages from their author to their repository.
2FA is great, but hinges upon the package repository being secure, and isn’t an end-to-end verification that a package came from its maintainer.
But there’s another way that isn’t as dependent upon the package repository: cryptographic signing of packages. Let’s take a look at which platforms support package signing.
But first, what is package signing?
Package signing is the act of an open source package (repo, binary, recipe, etc.) being cryptographically signed with a private key so that downstream users can verify the package with a public key.
Across language ecosystems, there are generally two types of package signing:
- Signed-by-repository: the repository signs uploaded packages, and users verify them after downloading.
- Signed-by-author: the author signs packages before uploading them to a repository, and users verify them after downloading from the repository. This is an end-to-end guard to ensure the package was uploaded by its maintainers.
Why is package signing useful?
Whether you’re setting up a new codebase on your developer machine or deploying a webapp to your servers, you’re probably downloading dozens—or hundreds—of open source packages. It’s impractical to comb through every line of code to make sure the package you received was not tampered with. Package signing offers a way to say I trust this maintainer and I am guaranteed that this code was uploaded by them.
Which programming language package managers support package signing?
Let’s try to classify what each package manager does currently:
Author-signing:
- Nuget: as of nuget cli 4.6.0 (March 2018), packages can be signed with certificates from a list of trusted Certificate Authorities, and verified against those CAs or a specific set of key fingerprints. Nuget also supports repository-signing.
- Maven/Gradle/Ant: all packages uploaded to Maven Central are required to be PGP-signed, and all three package managers have tooling to sign and verify.
- Rubygems: authors can sign packages using SSL certificates based on RSA keys. Verification offers several levels of signature checks.
- Caveat: supports Certificate Authorities but doesn’t make any CA recommendations for the ecosystem, so there’s no central CA.
- Usage: we found that, as of March 2020, only 1.4% (2,216 of 157,640 gems) of latest-version gems on Rubygems.org were signed.
Repository-signing:
-
npm: npm signs packages with its own PGP key, which is publicized on Keybase.
- Caveat: although npm doesn’t have native author-signing, there is some 3rd-party tooling available via the pkgsign library.
- More: some interesting discussions about signing have happened on node-forward, and npm.
In progress:
- Pypi: Python 2.5 added support for author-signing with GPG (via python setup.py --sign upload), but there is no built-in support to verify those signatures yet.
- Caveat: there are active discussions and PEPs around signing packages—PEP 458 for repository-signing and PEP 480 for author-signing—using The Update Framework.
- Wordpress: discussion of signing happened here, and signing of Wordpress itself was added here. As of 5.2, WordPress updates are now signed, but plugins and themes are still unsigned.
- Caveat: there’s an open discussion about implementing author-signing via a PKI called Gossamer.
Partial or no signing:
- Go Modules (Go): as of Go 1.13, Go Modules verifies downloaded packages (which are usually git repos) against a checksum database.
- Composer (PHP): there have been discussions of built in signing/verification here.
- Cargo (Rust): crates are hosted on GitHub*, and the Rust community has discussed package signing here, here and here.
- CPAN (Perl): no built-in support, but author-signing is available via 3rd party package manager pp (or the underlying cpansign cli)
- Carthage (Cocoa): packages are hosted on GitHub*, GitLab**, Bitbucket***, etc
- Julia Pkg (Julia): packages are hosted on GitHub*, GitLab**, Bitbucket***, and registered with the Julia Package Registry on github.
- Bower (JS): packages are hosted on GitHub*, GitLab**, Bitbucket***, etc
- PDK (Puppet)
- Meteor (Meteor)
- Cabal (Haskell)
- Mix (Erlang)
- R (R)
Does package signing matter?
If you read through the discussions linked to above, package signing is indeed a lofty goal and challenging to get right. So is it worth it, and could it prevent any classes of exploits? You can find some examples of supply chain attacks from the past decade documented here and here. Those lists contain some examples of repository account takeovers and other exploits that could be mitigated using package signing.
More reading
Here are some more writeups on package signing that are useful or that we found interesting:
- The Update Framework, for “securing software update systems”
- How NuGet package signing was planned and Why NuGet Package Signing Is Not (Yet) for Me (Nuget)
- Signing and Verifying Packages with PGP (Pypi)
- Nobody cares about signed gems (Rubygems)
Footnotes:
* GitHub allows commits and tag signing and will label them as verified-or-not, although there are no signatures/checksums available for release zip/tar archives.
** GitLab allows commit signing and labels them as verified or not, but not for builds.
*** BitBucket Server allows commit and tag signing -- while Bitbucket Cloud does not yet -- but not for builds.