Comparing version 13.3.0 to 13.4.0
@@ -10,2 +10,3 @@ const Fetcher = require('./fetcher.js') | ||
const ssri = require('ssri') | ||
const crypto = require('crypto') | ||
@@ -18,4 +19,2 @@ // Corgis are cute. 🐕🐶 | ||
// TODO: memoize reg requests, so we don't even have to check cache | ||
const _headers = Symbol('_headers') | ||
@@ -44,2 +43,10 @@ class RegistryFetcher extends Fetcher { | ||
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 | ||
@@ -51,13 +58,6 @@ // the resolved URL doesn't go to the same registry. | ||
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( | ||
@@ -67,3 +67,4 @@ new Error('Invalid package manifest: no `dist.tarball` field'), | ||
) | ||
}) | ||
} | ||
return this.resolved | ||
} | ||
@@ -95,9 +96,11 @@ | ||
// 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') | ||
@@ -109,74 +112,108 @@ packument._contentLength = +res.headers.get('content-length') | ||
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 | ||
} | ||
@@ -183,0 +220,0 @@ |
{ | ||
"name": "pacote", | ||
"version": "13.3.0", | ||
"version": "13.4.0", | ||
"description": "JavaScript package downloader", | ||
@@ -29,3 +29,3 @@ "author": "GitHub Inc.", | ||
"@npmcli/eslint-config": "^3.0.1", | ||
"@npmcli/template-oss": "3.4.3", | ||
"@npmcli/template-oss": "3.5.0", | ||
"hosted-git-info": "^5.0.0", | ||
@@ -78,5 +78,5 @@ "mutate-fs": "^2.1.1", | ||
"//@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 | ||
} | ||
} |
@@ -171,2 +171,6 @@ # pacote | ||
when calling `@npmcli/run-script`. | ||
* `verifySignatures` A boolean that will make pacote verify the | ||
integrity signature of a manifest, if present. There must be a | ||
configured `_keys` entry in the config that is scoped to the | ||
registry the manifest is being fetched from. | ||
@@ -173,0 +177,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
69482
1473
277