diff --git a/node_modules/pacote/lib/dir.js b/node_modules/pacote/lib/dir.js index 598b029f7ad48..502379810a006 100644 --- a/node_modules/pacote/lib/dir.js +++ b/node_modules/pacote/lib/dir.js @@ -63,10 +63,12 @@ class DirFetcher extends Fetcher { stream.resolved = this.resolved stream.integrity = this.integrity + const { prefix, workspaces } = this.opts + // run the prepare script, get the list of files, and tar it up // pipe to the stream, and proxy errors the chain. this[_prepareDir]() - .then(() => packlist({ path: this.resolved })) + .then(() => packlist({ path: this.resolved, prefix, workspaces })) .then(files => tar.c(tarCreateOptions(this.package), files) .on('error', er => stream.emit('error', er)).pipe(stream)) .catch(er => stream.emit('error', er)) diff --git a/node_modules/pacote/lib/registry.js b/node_modules/pacote/lib/registry.js index e8ca16f596d6f..2eb37bceccebc 100644 --- a/node_modules/pacote/lib/registry.js +++ b/node_modules/pacote/lib/registry.js @@ -7,6 +7,7 @@ const npa = require('npm-package-arg') const rpj = require('read-package-json-fast') const pickManifest = require('npm-pick-manifest') const ssri = require('ssri') +const crypto = require('crypto') // Corgis are cute. 🐕🐶 const corgiDoc = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*' @@ -14,8 +15,6 @@ const fullDoc = 'application/json' const fetch = require('npm-registry-fetch') -// TODO: memoize reg requests, so we don't even have to check cache - const _headers = Symbol('_headers') class RegistryFetcher extends Fetcher { constructor (spec, opts) { @@ -39,28 +38,30 @@ class RegistryFetcher extends Fetcher { this.packumentUrl = removeTrailingSlashes(this.registry) + '/' + this.spec.escapedName + const parsed = new URL(this.registry) + const regKey = `//${parsed.host}${parsed.pathname}` + // unlike the nerf-darted auth keys, this one does *not* allow a mismatch + // of trailing slashes. It must match exactly. + if (this.opts[`${regKey}:_keys`]) { + this.registryKeys = this.opts[`${regKey}:_keys`] + } + // XXX pacote <=9 has some logic to ignore opts.resolved if // the resolved URL doesn't go to the same registry. // Consider reproducing that here, to throw away this.resolved // in that case. } - resolve () { - if (this.resolved) { - return Promise.resolve(this.resolved) - } - - // fetching the manifest sets resolved and (usually) integrity - return this.manifest().then(() => { - if (this.resolved) { - return this.resolved - } - + async resolve () { + // fetching the manifest sets resolved and (if present) integrity + await this.manifest() + if (!this.resolved) { throw Object.assign( new Error('Invalid package manifest: no `dist.tarball` field'), { package: this.spec.toString() } ) - }) + } + return this.resolved } [_headers] () { @@ -87,91 +88,127 @@ class RegistryFetcher extends Fetcher { // npm-registry-fetch the packument // set the appropriate header for corgis if fullMetadata isn't set // return the res.json() promise - const p = fetch(this.packumentUrl, { - ...this.opts, - headers: this[_headers](), - spec: this.spec, - // never check integrity for packuments themselves - integrity: null, - }).then(res => res.json().then(packument => { + try { + const res = await fetch(this.packumentUrl, { + ...this.opts, + headers: this[_headers](), + spec: this.spec, + // never check integrity for packuments themselves + integrity: null, + }) + const packument = await res.json() packument._cached = res.headers.has('x-local-cache') packument._contentLength = +res.headers.get('content-length') if (this.packumentCache) { this.packumentCache.set(this.packumentUrl, packument) } return packument - })).catch(er => { + } catch (err) { if (this.packumentCache) { this.packumentCache.delete(this.packumentUrl) } - if (er.code === 'E404' && !this.fullMetadata) { - // possible that corgis are not supported by this registry - this.fullMetadata = true - return this.packument() + if (err.code !== 'E404' || this.fullMetadata) { + throw err } - throw er - }) - if (this.packumentCache) { - this.packumentCache.set(this.packumentUrl, p) + // possible that corgis are not supported by this registry + this.fullMetadata = true + return this.packument() } - return p } - manifest () { + async manifest () { if (this.package) { - return Promise.resolve(this.package) + return this.package } - return this.packument() - .then(packument => pickManifest(packument, this.spec.fetchSpec, { - ...this.opts, - defaultTag: this.defaultTag, - before: this.before, - }) /* XXX add ETARGET and E403 revalidation of cached packuments here */) - .then(mani => { - // add _resolved and _integrity from dist object - const { dist } = mani - if (dist) { - this.resolved = mani._resolved = dist.tarball - mani._from = this.from - const distIntegrity = dist.integrity ? ssri.parse(dist.integrity) - : dist.shasum ? ssri.fromHex(dist.shasum, 'sha1', { ...this.opts }) - : null - if (distIntegrity) { - if (!this.integrity) { - this.integrity = distIntegrity - } else if (!this.integrity.match(distIntegrity)) { - // only bork if they have algos in common. - // otherwise we end up breaking if we have saved a sha512 - // previously for the tarball, but the manifest only - // provides a sha1, which is possible for older publishes. - // Otherwise, this is almost certainly a case of holding it - // wrong, and will result in weird or insecure behavior - // later on when building package tree. - for (const algo of Object.keys(this.integrity)) { - if (distIntegrity[algo]) { - throw Object.assign(new Error( - `Integrity checksum failed when using ${algo}: ` + - `wanted ${this.integrity} but got ${distIntegrity}.` - ), { code: 'EINTEGRITY' }) - } - } - // made it this far, the integrity is worthwhile. accept it. - // the setter here will take care of merging it into what we - // already had. - this.integrity = distIntegrity + const packument = await this.packument() + const mani = await pickManifest(packument, this.spec.fetchSpec, { + ...this.opts, + defaultTag: this.defaultTag, + before: this.before, + }) + /* XXX add ETARGET and E403 revalidation of cached packuments here */ + + // add _resolved and _integrity from dist object + const { dist } = mani + if (dist) { + this.resolved = mani._resolved = dist.tarball + mani._from = this.from + const distIntegrity = dist.integrity ? ssri.parse(dist.integrity) + : dist.shasum ? ssri.fromHex(dist.shasum, 'sha1', { ...this.opts }) + : null + if (distIntegrity) { + if (this.integrity && !this.integrity.match(distIntegrity)) { + // only bork if they have algos in common. + // otherwise we end up breaking if we have saved a sha512 + // previously for the tarball, but the manifest only + // provides a sha1, which is possible for older publishes. + // Otherwise, this is almost certainly a case of holding it + // wrong, and will result in weird or insecure behavior + // later on when building package tree. + for (const algo of Object.keys(this.integrity)) { + if (distIntegrity[algo]) { + throw Object.assign(new Error( + `Integrity checksum failed when using ${algo}: ` + + `wanted ${this.integrity} but got ${distIntegrity}.` + ), { code: 'EINTEGRITY' }) } } } - if (this.integrity) { - mani._integrity = String(this.integrity) - if (dist.signatures) { + // made it this far, the integrity is worthwhile. accept it. + // the setter here will take care of merging it into what we already + // had. + this.integrity = distIntegrity + } + } + if (this.integrity) { + mani._integrity = String(this.integrity) + if (dist.signatures) { + if (this.opts.verifySignatures) { + if (this.registryKeys) { + // validate and throw on error, then set _signatures + const message = `${mani._id}:${mani._integrity}` + for (const signature of dist.signatures) { + const publicKey = this.registryKeys.filter(key => (key.keyid === signature.keyid))[0] + if (!publicKey) { + throw Object.assign(new Error( + `${mani._id} has a signature with keyid: ${signature.keyid} ` + + 'but no corresponding public key can be found.' + ), { code: 'EMISSINGSIGNATUREKEY' }) + } + const validPublicKey = + !publicKey.expires || (Date.parse(publicKey.expires) > Date.now()) + if (!validPublicKey) { + throw Object.assign(new Error( + `${mani._id} has a signature with keyid: ${signature.keyid} ` + + `but the corresponding public key has expired ${publicKey.expires}` + ), { code: 'EEXPIREDSIGNATUREKEY' }) + } + const verifier = crypto.createVerify('SHA256') + verifier.write(message) + verifier.end() + const valid = verifier.verify( + publicKey.pemkey, + signature.sig, + 'base64' + ) + if (!valid) { + throw Object.assign(new Error( + 'Integrity checksum signature failed: ' + + `key ${publicKey.keyid} signature ${signature.sig}` + ), { code: 'EINTEGRITYSIGNATURE' }) + } + } mani._signatures = dist.signatures } + // if no keys, don't set _signatures + } else { + mani._signatures = dist.signatures } - this.package = rpj.normalize(mani) - return this.package - }) + } + } + this.package = rpj.normalize(mani) + return this.package } [_tarballFromResolved] () { diff --git a/node_modules/pacote/package.json b/node_modules/pacote/package.json index 2f4323c4414ed..f1a86938ff772 100644 --- a/node_modules/pacote/package.json +++ b/node_modules/pacote/package.json @@ -1,6 +1,6 @@ { "name": "pacote", - "version": "13.3.0", + "version": "13.4.1", "description": "JavaScript package downloader", "author": "GitHub Inc.", "bin": { @@ -26,7 +26,7 @@ }, "devDependencies": { "@npmcli/eslint-config": "^3.0.1", - "@npmcli/template-oss": "3.4.3", + "@npmcli/template-oss": "3.5.0", "hosted-git-info": "^5.0.0", "mutate-fs": "^2.1.1", "nock": "^13.2.4", @@ -74,7 +74,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "3.4.3", + "version": "3.5.0", "windowsCI": false } } diff --git a/package-lock.json b/package-lock.json index 3154dc55452cc..b5a784cb3130f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -138,7 +138,7 @@ "npm-user-validate": "^1.0.1", "npmlog": "^6.0.2", "opener": "^1.5.2", - "pacote": "^13.3.0", + "pacote": "^13.4.1", "parse-conflict-json": "^2.0.2", "proc-log": "^2.0.1", "qrcode-terminal": "^0.12.0", @@ -5547,9 +5547,9 @@ } }, "node_modules/pacote": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.3.0.tgz", - "integrity": "sha512-auhJAUlfC2TALo6I0s1vFoPvVFgWGx+uz/PnIojTTgkGwlK3Np8sGJ0ghfFhiuzJXTZoTycMLk8uLskdntPbDw==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.4.1.tgz", + "integrity": "sha512-FqlSWlD8n+ejCE17GF/lf0yasztMGFl4UFzYQk5njaK/qPPWfVDWnfQwqmqeXObWLSmIBew+O+CFD24vxkVDjg==", "inBundle": true, "dependencies": { "@npmcli/git": "^3.0.0", @@ -13877,9 +13877,9 @@ } }, "pacote": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.3.0.tgz", - "integrity": "sha512-auhJAUlfC2TALo6I0s1vFoPvVFgWGx+uz/PnIojTTgkGwlK3Np8sGJ0ghfFhiuzJXTZoTycMLk8uLskdntPbDw==", + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.4.1.tgz", + "integrity": "sha512-FqlSWlD8n+ejCE17GF/lf0yasztMGFl4UFzYQk5njaK/qPPWfVDWnfQwqmqeXObWLSmIBew+O+CFD24vxkVDjg==", "requires": { "@npmcli/git": "^3.0.0", "@npmcli/installed-package-contents": "^1.0.7", diff --git a/package.json b/package.json index a2fd510a77c89..1b0027ba5bb62 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "npm-user-validate": "^1.0.1", "npmlog": "^6.0.2", "opener": "^1.5.2", - "pacote": "^13.3.0", + "pacote": "^13.4.1", "parse-conflict-json": "^2.0.2", "proc-log": "^2.0.1", "qrcode-terminal": "^0.12.0",