Wednesday, 18 April, 2018 UTC


Summary

If you’ve recently examined packuments in the Registry, you might have noticed a new npm-signature field in the dist section. It might look to you like a PGP signature, and that in fact is what it is! This field holds the npm registry’s PGP signature of the integrity field.
Read on to find out why we’re signing publications, what exactly we’re signing, and how you can use this signature data.
Some context: how npm verifies package-versions
Every package has a package metadata document. This is a JSON document that describes the package and what versions have been published, and what their dependencies are. We refer to this as a packument—a portmanteau of ‘package’ and 'document’.
A package-version is a specific version of a package, along with its metadata and code tarball. You can uniquely name a package-version with its package-name + version. For example, [email protected] is a package-version.
Every package-version published to the registry has a dist structure in the package’s metadata document. Here’s the dist structure for version 1.4.3 of light-cycle:
{ "integrity": "sha512-sFcuivsDZ99fY0TbvuRC6CDXB8r/ylafjJAMnbSF0y4EMM1/1DtQo40G2WKz1rBbyiz4SLAc3Wa6yZyC4XSGOQ==", "shasum": "c305f0113d81d880f846d84f80c7f3237f197bab", "tarball": "https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.3.tgz", "fileCount": 11, "unpackedSize": 25612, "npm-signature": "..." } 
This structure tells npm clients where to fetch the tarball to install the package-version, and, further, how to verify that the tarball fetched is the same as the one the npm registry intended to serve. The shasum is a SHA-1 sum of the file. We’ve supplemented this to the side with an integrity field, based on the subresource integrity specification, to allow us to migrate to different hashing algorithms. For this publication, the client had sha512 available and used it to hash the contents.
The npm CLI checks tarball integrity when a package-version arrives in your local cache, which is a content-hash addressed cache.
The problem with shasums
The shasums above are invaluable for determining that your npm client received the data it was supposed to receive over the network without corruption. They do not, however, protect against man-in-the-middle attacks.
If an attacker has interposed a proxy between you and the registry, they can tamper with both the package JSON document that advertises the shasum and the tarball itself. This attacker could create a tarball with unexpected content, generate an integrity field for it, then construct a packument advertising this poisoned tarball. An npm client would trust the packument and therefore also trust the tarball.
We offer you a way to detect this kind of tampering by signing package integrity fields along with some data that uniquely identifies the package-version.
Note that verifying this signature only establishes that a middleman has not tampered with the contents or integrity field for that package-version. It does not establish that the npm registry itself hasn’t meddled with package contents at publication time! You still have to trust us. (We intend to offer a solution to that problem in the future.)
PGP and Keybase
The npm registry uses OpenPGP to sign packages. PGP, aka Pretty Good Privacy is an encryption standard that offers ways to authenticate and verify messages. The EFF has a good introduction to public-key cryptography and PGP, which I recommend if you would like a general introduction to the topic.
We’re generating signatures using openpgpjs , a pure JavaScript implementation of OpenPGP.
Our public key is at the end of this post.
We’ve also chosen to use Keybase to publicize our PGP key and give you confidence that the npm registry you install from is the same registry that’s signing packages. Our account on Keybase is npmregistry.
Keybase offers two advantages over the core OpenPGP experience that move us to recommend it to you. First, the Keybase application and CLI provide an excellent user experience for PGP, which can be intimidating for newcomers. Second, Keybase manages and displays social proofs that the entity that controls a specific pgp key also controls accounts on social media and other places. These proofs help you determine whether you can trust an account.
We’ve established proofs on Keybase that we control @npmjs on Twitter, the domain npmjs.com, and the domain npmjs.org. Verifying these proofs won’t tell you who owns those domains, but it does establish that the same entity controls them and the pgp key advertised on Keybase.
You can also observe that npm’s executive and technical team are among the people who’ve chosen to follow npmregistry on Keybase. For example, I (ceej) am following npmregistry, as are Isaac Z. Schlueter (isaacs) and Adam Baldwin (adam_baldwin).
If you install Keybase and create an account, you can follow npmregistry yourself and keep a local copy of the registry’s public key. The verification examples that follow assume you have done so.
What we’re signing
The registry signs the following data for each new package-version published:
<package>@<version>:<integrity>
This string uniquely identifies a package-version in the registry and associates it with a specific package integrity field.
On package publication, we first validate that the data we received matches what the client thinks it published by checking shasums and the integrity field. Next, we construct the package-version + integrity string. We then generate a detached signature for that string—that is, a signature that isn’t part of a full message structure. The detached signature is stored in the npm-signature field in dist.
Here’s an example. When [email protected] was published, the npm registry created a pgp signature for this exact string:
[email protected]:sha512-sFcuivsDZ99fY0TbvuRC6CDXB8r/ylafjJAMnbSF0y4EMM1/1DtQo40G2WKz1rBbyiz4SLAc3Wa6yZyC4XSGOQ== 
Verifying our signatures
The presence of a signature is not enough for you to feel confident that you got the package the npm registry saw. You must verify the signature as having been made by a specific key for a specific message. This process is a little fiddly to do by hand, but I’ll step you through how, so you can do it without relying on third parties or the npm cli if you need to. This example assumes you’ve installed Keybase, but you can also use The GNU Privacy Guard.
Suppose I want to check the signature on version 1.4.3 of light-cycle. First, I fetch the signature for the version I’m interested in and save it in a file:
$ http GET https://registry.npmjs.org/light-cycle | json "versions['1.4.3'].dist.npm-signature" > sig-to-check 
Next, I get the integrity field for that version:
$ http GET https://registry.npmjs.org/light-cycle | json "versions['1.4.3'].dist.integrity" sha512-sFcuivsDZ99fY0TbvuRC6CDXB8r/ylafjJAMnbSF0y4EMM1/1DtQo40G2WKz1rBbyiz4SLAc3Wa6yZyC4XSGOQ== 
You can verify that this integrity field matches the tarball you have by calculating the hash yourself. npm uses the ssri module to calculate integrity fields, and it verifies that the hashes match when it adds a tarball to your local cache.
Now that I have an integrity string for my package, I construct the string that ties the two together: [email protected]:integrity. This is the message I pass to keybase to verify:
$ keybase pgp verify --signed-by npmregistry -d sig-to-check -m '[email protected]:sha512-sFcuivsDZ99fY0TbvuRC6CDXB8r/ylafjJAMnbSF0y4EMM1/1DtQo40G2WKz1rBbyiz4SLAc3Wa6yZyC4XSGOQ==' ▶ INFO Identifying npmregistry ✔ <tracked> public key fingerprint: 0963 1802 8A2B 58C8 4929 D8E1 3D4D 5B12 0276 566A You last followed npmregistry on 2018-04-10 21:21:57 PDT ✔ <tracked> admin of DNS zone npmjs.com: found TXT entry keybase-site-verification=iK3pjpRBkv-CIJ4PHtWL4TTcFXMpPiwPynatKl3oWO4 ✔ <tracked> "npmjs" on twitter: https://twitter.com/npmjs/status/981288548845240320 [cached 2018-04-12 13:18:31 PDT; but got a retryable error (API network error: Get https://twitter.com/npmjs/status/981288548845240320: net/http: request canceled (Client.Timeout exceeded while awaiting headers) (code=170)) this time around] ✔ <tracked> admin of DNS zone npmjs.org: found TXT entry keybase-site-verification=Ls8jN55i6KesjiX91Ck79bUZ17eA-iohmw2jJFM16xc Signature verified. Signed by npmregistry 7 minutes ago (2018-04-13 15:00:37 -0700 PDT). PGP Fingerprint: 096318028a2b58c84929d8e13d4d5b120276566a. 
It’s obviously inconvenient to verify by hand, and we plan to offer you easier ways to check version signatures soon. Note that it’s expensive to verify signatures fully with Keybase, since proofs are rechecked and this requires network activity. This is not intended to be an action you take many thousands of times per second. It would be appropriate action to take when you choose to verify a deploy artifact, however, or when a package is stored in your cache in the first place.
Next steps
You’ll notice that the signatures are only available on newly-published package-versions. We’ll backfill signatures for all packages published earlier than our signing launch, but we’ll do this slowly, so as not to destroy everyone’s registry caches all at once.
We also intend to follow up with verification built into the official npm CLI, so you can check more conveniently.
Finally, we’d like to take the next step and allow package authors to sign integrity fields as part of publication, so you can have a way to verify that the npm registry itself hasn’t meddled with package contents. (I believe we’re a trustworthy steward of your JavaScript code, but I’d like to be able to prove that to you.)
The npm registry public key
Our public key follows. You can also fetch it from Keybase:
http GET https://keybase.io/npmregistry/pgp_keys.asc 
-----BEGIN PGP PUBLIC KEY BLOCK----- Version: Keybase Go 1.0.47 (darwin) Comment: https://keybase.io/download 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 =39YB -----END PGP PUBLIC KEY BLOCK-----