pnpm
Version:
52 lines (50 loc) • 2.68 kB
JavaScript
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.`);
}
}