From fb4cc249a413af1ecfe6b2be14b1d376f787a03d Mon Sep 17 00:00:00 2001 From: Gar Date: Wed, 1 Jun 2022 14:06:42 -0700 Subject: [PATCH] deps: pacote@13.6.0 (#4969) * allow reuse of external integrity stream * replaceRegistryHost can now be a hostname * error when passing signature without keys --- node_modules/pacote/lib/fetcher.js | 52 ++++++++++--------- node_modules/pacote/lib/registry.js | 77 +++++++++++++++-------------- node_modules/pacote/lib/remote.js | 27 +++++----- node_modules/pacote/package.json | 5 +- package-lock.json | 14 +++--- package.json | 2 +- 6 files changed, 90 insertions(+), 87 deletions(-) diff --git a/node_modules/pacote/lib/fetcher.js b/node_modules/pacote/lib/fetcher.js index d88724c5d1973..5f93e703bca4b 100644 --- a/node_modules/pacote/lib/fetcher.js +++ b/node_modules/pacote/lib/fetcher.js @@ -18,6 +18,7 @@ const removeTrailingSlashes = require('./util/trailing-slashes.js') const getContents = require('@npmcli/installed-package-contents') const readPackageJsonFast = require('read-package-json-fast') const readPackageJson = promisify(require('read-package-json')) +const Minipass = require('minipass') // we only change ownership on unix platforms, and only if uid is 0 const selfOwner = process.getuid && process.getuid() === 0 ? { @@ -105,15 +106,10 @@ class FetcherBase { this[_readPackageJson] = readPackageJsonFast } - // config values: npmjs (default), never, always - // we don't want to mutate the original value - if (opts.replaceRegistryHost !== 'never' - && opts.replaceRegistryHost !== 'always' - ) { - this.replaceRegistryHost = 'npmjs' - } else { - this.replaceRegistryHost = opts.replaceRegistryHost - } + // rrh is a registry hostname or 'never' or 'always' + // defaults to registry.npmjs.org + this.replaceRegistryHost = (!opts.replaceRegistryHost || opts.replaceRegistryHost === 'npmjs') ? + 'registry.npmjs.org' : opts.replaceRegistryHost this.defaultTag = opts.defaultTag || 'latest' this.registry = removeTrailingSlashes(opts.registry || 'https://registry.npmjs.org') @@ -224,18 +220,18 @@ class FetcherBase { } [_istream] (stream) { - // everyone will need one of these, either for verifying or calculating - // We always set it, because we have might only have a weak legacy hex - // sha1 in the packument, and this MAY upgrade it to a stronger algo. - // If we had an integrity, and it doesn't match, then this does not - // override that error; the istream will raise the error before it - // gets to the point of re-setting the integrity. - const istream = ssri.integrityStream(this.opts) - istream.on('integrity', i => this.integrity = i) - stream.on('error', er => istream.emit('error', er)) - - // if not caching this, just pipe through to the istream and return it + // if not caching this, just return it if (!this.opts.cache || !this[_cacheFetches]) { + // instead of creating a new integrity stream, we only piggyback on the + // provided stream's events + if (stream.hasIntegrityEmitter) { + stream.on('integrity', i => this.integrity = i) + return stream + } + + const istream = ssri.integrityStream(this.opts) + istream.on('integrity', i => this.integrity = i) + stream.on('error', err => istream.emit('error', err)) return stream.pipe(istream) } @@ -243,21 +239,23 @@ class FetcherBase { // but then pipe from the original tarball stream into the cache as well. // To do this without losing any data, and since the cacache put stream // is not a passthrough, we have to pipe from the original stream into - // the cache AFTER we pipe into the istream. Since the cache stream + // the cache AFTER we pipe into the middleStream. Since the cache stream // has an asynchronous flush to write its contents to disk, we need to - // defer the istream end until the cache stream ends. - stream.pipe(istream, { end: false }) + // defer the middleStream end until the cache stream ends. + const middleStream = new Minipass() + stream.on('error', err => middleStream.emit('error', err)) + stream.pipe(middleStream, { end: false }) const cstream = cacache.put.stream( this.opts.cache, `pacote:tarball:${this.from}`, this.opts ) + cstream.on('integrity', i => this.integrity = i) + cstream.on('error', err => stream.emit('error', err)) stream.pipe(cstream) - // defer istream end until after cstream - // cache write errors should not crash the fetch, this is best-effort. - cstream.promise().catch(() => {}).then(() => istream.end()) - return istream + cstream.promise().catch(() => {}).then(() => middleStream.end()) + return middleStream } pickIntegrityAlgorithm () { diff --git a/node_modules/pacote/lib/registry.js b/node_modules/pacote/lib/registry.js index 2eb37bceccebc..c8eb6b0290702 100644 --- a/node_modules/pacote/lib/registry.js +++ b/node_modules/pacote/lib/registry.js @@ -122,11 +122,12 @@ class RegistryFetcher extends Fetcher { } const packument = await this.packument() - const mani = await pickManifest(packument, this.spec.fetchSpec, { + let mani = await pickManifest(packument, this.spec.fetchSpec, { ...this.opts, defaultTag: this.defaultTag, before: this.before, }) + mani = rpj.normalize(mani) /* XXX add ETARGET and E403 revalidation of cached packuments here */ // add _resolved and _integrity from dist object @@ -165,49 +166,53 @@ class RegistryFetcher extends Fetcher { 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} ` + + // validate and throw on error, then set _signatures + const message = `${mani._id}:${mani._integrity}` + for (const signature of dist.signatures) { + const publicKey = this.registryKeys && + this.registryKeys.filter(key => (key.keyid === signature.keyid))[0] + if (!publicKey) { + throw Object.assign(new Error( + `${mani._id} has a registry 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 registry 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' }) - } + ), { 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( + `${mani._id} has an invalid registry signature with ` + + `keyid: ${publicKey.keyid} and signature: ${signature.sig}` + ), { + code: 'EINTEGRITYSIGNATURE', + keyid: publicKey.keyid, + signature: signature.sig, + resolved: mani._resolved, + integrity: mani._integrity, + }) } - mani._signatures = dist.signatures } - // if no keys, don't set _signatures + mani._signatures = dist.signatures } else { mani._signatures = dist.signatures } } } - this.package = rpj.normalize(mani) + this.package = mani return this.package } diff --git a/node_modules/pacote/lib/remote.js b/node_modules/pacote/lib/remote.js index 14e687805d47a..6759dbba3ed2f 100644 --- a/node_modules/pacote/lib/remote.js +++ b/node_modules/pacote/lib/remote.js @@ -4,8 +4,6 @@ const _tarballFromResolved = Symbol.for('pacote.Fetcher._tarballFromResolved') const pacoteVersion = require('../package.json').version const fetch = require('npm-registry-fetch') const Minipass = require('minipass') -// The default registry URL is a string of great magic. -const magicHost = 'https://registry.npmjs.org' const _cacheFetches = Symbol.for('pacote.Fetcher._cacheFetches') const _headers = Symbol('_headers') @@ -14,11 +12,9 @@ class RemoteFetcher extends Fetcher { super(spec, opts) this.resolved = this.spec.fetchSpec const resolvedURL = new URL(this.resolved) - if ( - (this.replaceRegistryHost === 'npmjs' - && resolvedURL.origin === magicHost) - || this.replaceRegistryHost === 'always' - ) { + if (this.replaceRegistryHost !== 'never' + && (this.replaceRegistryHost === 'always' + || this.replaceRegistryHost === resolvedURL.host)) { this.resolved = new URL(resolvedURL.pathname, this.registry).href } @@ -35,6 +31,8 @@ class RemoteFetcher extends Fetcher { [_tarballFromResolved] () { const stream = new Minipass() + stream.hasIntegrityEmitter = true + const fetchOpts = { ...this.opts, headers: this[_headers](), @@ -42,16 +40,19 @@ class RemoteFetcher extends Fetcher { integrity: this.integrity, algorithms: [this.pickIntegrityAlgorithm()], } - fetch(this.resolved, fetchOpts).then(res => { - const hash = res.headers.get('x-local-cache-hash') - if (hash) { - this.integrity = decodeURIComponent(hash) - } + fetch(this.resolved, fetchOpts).then(res => { res.body.on('error', /* istanbul ignore next - exceedingly rare and hard to simulate */ er => stream.emit('error', er) - ).pipe(stream) + ) + + res.body.on('integrity', i => { + this.integrity = i + stream.emit('integrity', i) + }) + + res.body.pipe(stream) }).catch(er => stream.emit('error', er)) return stream diff --git a/node_modules/pacote/package.json b/node_modules/pacote/package.json index 5045e3f06a078..af100fa8d6bfa 100644 --- a/node_modules/pacote/package.json +++ b/node_modules/pacote/package.json @@ -1,6 +1,6 @@ { "name": "pacote", - "version": "13.5.0", + "version": "13.6.0", "description": "JavaScript package downloader", "author": "GitHub Inc.", "bin": { @@ -21,8 +21,7 @@ "template-oss-apply": "template-oss-apply --force" }, "tap": { - "timeout": 300, - "coverage-map": "map.js" + "timeout": 300 }, "devDependencies": { "@npmcli/eslint-config": "^3.0.1", diff --git a/package-lock.json b/package-lock.json index ef669eb3a1ca3..5b07bc270fe03 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.4.1", + "pacote": "^13.6.0", "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.5.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.5.0.tgz", - "integrity": "sha512-yekp0ykEsaBH0t0bYA/89R+ywdYV5ZnEdg4YMIfqakSlpIhoF6b8+aEUm8NZpfWRgmy6lxgywcW05URhLRogVQ==", + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.0.tgz", + "integrity": "sha512-zHmuCwG4+QKnj47LFlW3LmArwKoglx2k5xtADiMCivVWPgNRP5QyLDGOIjGjwOe61lhl1rO63m/VxT16pEHLWg==", "inBundle": true, "dependencies": { "@npmcli/git": "^3.0.0", @@ -13877,9 +13877,9 @@ } }, "pacote": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.5.0.tgz", - "integrity": "sha512-yekp0ykEsaBH0t0bYA/89R+ywdYV5ZnEdg4YMIfqakSlpIhoF6b8+aEUm8NZpfWRgmy6lxgywcW05URhLRogVQ==", + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.0.tgz", + "integrity": "sha512-zHmuCwG4+QKnj47LFlW3LmArwKoglx2k5xtADiMCivVWPgNRP5QyLDGOIjGjwOe61lhl1rO63m/VxT16pEHLWg==", "requires": { "@npmcli/git": "^3.0.0", "@npmcli/installed-package-contents": "^1.0.7", diff --git a/package.json b/package.json index fb276d359c29d..778cb83afe644 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.4.1", + "pacote": "^13.6.0", "parse-conflict-json": "^2.0.2", "proc-log": "^2.0.1", "qrcode-terminal": "^0.12.0",