NPM's signatures allow you to check that when you have a copy of the package, that it has the same contents as the package in the NPM registry. Thus, you can prove that a downloaded copy was not tampered with later. Signing packages is important in particular when mirrors or proxies are involved, and is done similarly by other package management systems.
This does not prove that it's the same contents as were uploaded by the package author, i.e. this signature does not defend against a malicious registry.
You correctly point out that the security of this approach hinges on knowing which keys are valid.
- For PGP signatures, NPM has published its key on Keybase
- For ECDSA signatures, NPM publishes its keys through the NPM API
For example, the NPM documentation lists light-cycle as an example of a package with a signature. Its metadata contains:
"dist": {
"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": "...",
"signatures": [
{
"keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA",
"sig": "MEUCIQCX/49atNeSDYZP8betYWEqB0G8zZnIyB7ibC7nRNyMiQIgHosOKHhVTVNBI/6iUNSpDokOc44zsZ7TfybMKj8YdfY="
}
]
}
(I removed the PGP signature since it is very long.)
Using https://registry.npmjs.org/-/npm/v1/keys, we can fetch NPM's keys:
{
"keys": [
{
"expires": null,
"keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA",
"keytype": "ecdsa-sha2-nistp256",
"scheme": "ecdsa-sha2-nistp256",
"key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg=="
}
]
}
We can see that the keyid in the signature matches the ID of one of NPM's keys, so that we could proceed to verify the signature.