UNPKG

pnpm

Version:

Fast, disk space efficient package manager

52 lines (50 loc) • 2.68 kB
import { createVerify } from 'node:crypto'; import { NPM_SIGNING_KEYS } from './npmSigningKeys.js'; /** * Checks the registry's signature over a package's identity and checksum. * * The registry serves both the tarball and the checksum, so a checksum taken * from it proves nothing on its own. This is what makes it worth anything: the * signature is made with a key the registry publishes but the download host * cannot mint, and the trusted copy of that key ships inside this package. * * @throws if the package is unsigned, signed with a key that isn't trusted or * has expired, or the signature does not verify. */ export function verifyRegistrySignature(opts) { const pkg = `${opts.name}@${opts.version}`; const signatures = opts.signatures ?? []; if (signatures.length === 0) { throw new Error(`${pkg} carries no npm registry signature, so it cannot be verified.`); } // Pick the signature by key rather than by position. A package can carry // several, in no guaranteed order, and across a rotation one of them can be // from a key this installer does not pin — which is not a reason to refuse a // package that another, pinned key also signed. const keys = opts.keys ?? NPM_SIGNING_KEYS; const match = signatures .map((signature) => ({ signature, key: keys.find(({ keyid }) => keyid === signature.keyid) })) .find((candidate) => candidate.key != null); if (match?.key == null) { throw new Error(`${pkg} is signed with an unexpected npm key (${signatures.map(({ keyid }) => keyid).join(', ')}). If npm has rotated its signing key, this installer needs updating. Until then, install pnpm another way: https://pnpm.io/installation`); } const { signature, key } = match; if (key.expires != null) { const expires = new Date(key.expires).getTime(); // An unreadable date must not read as "never expires". if (Number.isNaN(expires)) { throw new Error(`${pkg} is signed with an npm key whose expiry date cannot be read (${key.expires}).`); } if (expires < (opts.now ?? new Date()).getTime()) { throw new Error(`${pkg} is signed with an npm key that expired on ${key.expires}.`); } } // Registry signatures cover the package identity and its content hash. const message = `${pkg}:${opts.integrity}`; const publicKey = `-----BEGIN PUBLIC KEY-----\n${key.key}\n-----END PUBLIC KEY-----`; if (!createVerify('SHA256').update(message).verify(publicKey, signature.sig, 'base64')) { throw new Error(`The npm registry signature for ${pkg} is not valid. Refusing to install.`); } }