Skip to content

Commit f3b0a24

Browse files
wraithgarlukekarrys
authored andcommitted
* pass prefix and workspaces to npm-packlist * add verifySignatures to registry.manifest
1 parent 7b2b77a commit f3b0a24

File tree

5 files changed

+128
-89
lines changed

5 files changed

+128
-89
lines changed

node_modules/pacote/lib/dir.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@ class DirFetcher extends Fetcher {
6363
stream.resolved = this.resolved
6464
stream.integrity = this.integrity
6565

66+
const { prefix, workspaces } = this.opts
67+
6668
// run the prepare script, get the list of files, and tar it up
6769
// pipe to the stream, and proxy errors the chain.
6870
this[_prepareDir]()
69-
.then(() => packlist({ path: this.resolved }))
71+
.then(() => packlist({ path: this.resolved, prefix, workspaces }))
7072
.then(files => tar.c(tarCreateOptions(this.package), files)
7173
.on('error', er => stream.emit('error', er)).pipe(stream))
7274
.catch(er => stream.emit('error', er))

node_modules/pacote/lib/registry.js

+114-77
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ const npa = require('npm-package-arg')
77
const rpj = require('read-package-json-fast')
88
const pickManifest = require('npm-pick-manifest')
99
const ssri = require('ssri')
10+
const crypto = require('crypto')
1011

1112
// Corgis are cute. 🐕🐶
1213
const corgiDoc = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'
1314
const fullDoc = 'application/json'
1415

1516
const fetch = require('npm-registry-fetch')
1617

17-
// TODO: memoize reg requests, so we don't even have to check cache
18-
1918
const _headers = Symbol('_headers')
2019
class RegistryFetcher extends Fetcher {
2120
constructor (spec, opts) {
@@ -39,28 +38,30 @@ class RegistryFetcher extends Fetcher {
3938
this.packumentUrl = removeTrailingSlashes(this.registry) + '/' +
4039
this.spec.escapedName
4140

41+
const parsed = new URL(this.registry)
42+
const regKey = `//${parsed.host}${parsed.pathname}`
43+
// unlike the nerf-darted auth keys, this one does *not* allow a mismatch
44+
// of trailing slashes. It must match exactly.
45+
if (this.opts[`${regKey}:_keys`]) {
46+
this.registryKeys = this.opts[`${regKey}:_keys`]
47+
}
48+
4249
// XXX pacote <=9 has some logic to ignore opts.resolved if
4350
// the resolved URL doesn't go to the same registry.
4451
// Consider reproducing that here, to throw away this.resolved
4552
// in that case.
4653
}
4754

48-
resolve () {
49-
if (this.resolved) {
50-
return Promise.resolve(this.resolved)
51-
}
52-
53-
// fetching the manifest sets resolved and (usually) integrity
54-
return this.manifest().then(() => {
55-
if (this.resolved) {
56-
return this.resolved
57-
}
58-
55+
async resolve () {
56+
// fetching the manifest sets resolved and (if present) integrity
57+
await this.manifest()
58+
if (!this.resolved) {
5959
throw Object.assign(
6060
new Error('Invalid package manifest: no `dist.tarball` field'),
6161
{ package: this.spec.toString() }
6262
)
63-
})
63+
}
64+
return this.resolved
6465
}
6566

6667
[_headers] () {
@@ -87,91 +88,127 @@ class RegistryFetcher extends Fetcher {
8788
// npm-registry-fetch the packument
8889
// set the appropriate header for corgis if fullMetadata isn't set
8990
// return the res.json() promise
90-
const p = fetch(this.packumentUrl, {
91-
...this.opts,
92-
headers: this[_headers](),
93-
spec: this.spec,
94-
// never check integrity for packuments themselves
95-
integrity: null,
96-
}).then(res => res.json().then(packument => {
91+
try {
92+
const res = await fetch(this.packumentUrl, {
93+
...this.opts,
94+
headers: this[_headers](),
95+
spec: this.spec,
96+
// never check integrity for packuments themselves
97+
integrity: null,
98+
})
99+
const packument = await res.json()
97100
packument._cached = res.headers.has('x-local-cache')
98101
packument._contentLength = +res.headers.get('content-length')
99102
if (this.packumentCache) {
100103
this.packumentCache.set(this.packumentUrl, packument)
101104
}
102105
return packument
103-
})).catch(er => {
106+
} catch (err) {
104107
if (this.packumentCache) {
105108
this.packumentCache.delete(this.packumentUrl)
106109
}
107-
if (er.code === 'E404' && !this.fullMetadata) {
108-
// possible that corgis are not supported by this registry
109-
this.fullMetadata = true
110-
return this.packument()
110+
if (err.code !== 'E404' || this.fullMetadata) {
111+
throw err
111112
}
112-
throw er
113-
})
114-
if (this.packumentCache) {
115-
this.packumentCache.set(this.packumentUrl, p)
113+
// possible that corgis are not supported by this registry
114+
this.fullMetadata = true
115+
return this.packument()
116116
}
117-
return p
118117
}
119118

120-
manifest () {
119+
async manifest () {
121120
if (this.package) {
122-
return Promise.resolve(this.package)
121+
return this.package
123122
}
124123

125-
return this.packument()
126-
.then(packument => pickManifest(packument, this.spec.fetchSpec, {
127-
...this.opts,
128-
defaultTag: this.defaultTag,
129-
before: this.before,
130-
}) /* XXX add ETARGET and E403 revalidation of cached packuments here */)
131-
.then(mani => {
132-
// add _resolved and _integrity from dist object
133-
const { dist } = mani
134-
if (dist) {
135-
this.resolved = mani._resolved = dist.tarball
136-
mani._from = this.from
137-
const distIntegrity = dist.integrity ? ssri.parse(dist.integrity)
138-
: dist.shasum ? ssri.fromHex(dist.shasum, 'sha1', { ...this.opts })
139-
: null
140-
if (distIntegrity) {
141-
if (!this.integrity) {
142-
this.integrity = distIntegrity
143-
} else if (!this.integrity.match(distIntegrity)) {
144-
// only bork if they have algos in common.
145-
// otherwise we end up breaking if we have saved a sha512
146-
// previously for the tarball, but the manifest only
147-
// provides a sha1, which is possible for older publishes.
148-
// Otherwise, this is almost certainly a case of holding it
149-
// wrong, and will result in weird or insecure behavior
150-
// later on when building package tree.
151-
for (const algo of Object.keys(this.integrity)) {
152-
if (distIntegrity[algo]) {
153-
throw Object.assign(new Error(
154-
`Integrity checksum failed when using ${algo}: ` +
155-
`wanted ${this.integrity} but got ${distIntegrity}.`
156-
), { code: 'EINTEGRITY' })
157-
}
158-
}
159-
// made it this far, the integrity is worthwhile. accept it.
160-
// the setter here will take care of merging it into what we
161-
// already had.
162-
this.integrity = distIntegrity
124+
const packument = await this.packument()
125+
const mani = await pickManifest(packument, this.spec.fetchSpec, {
126+
...this.opts,
127+
defaultTag: this.defaultTag,
128+
before: this.before,
129+
})
130+
/* XXX add ETARGET and E403 revalidation of cached packuments here */
131+
132+
// add _resolved and _integrity from dist object
133+
const { dist } = mani
134+
if (dist) {
135+
this.resolved = mani._resolved = dist.tarball
136+
mani._from = this.from
137+
const distIntegrity = dist.integrity ? ssri.parse(dist.integrity)
138+
: dist.shasum ? ssri.fromHex(dist.shasum, 'sha1', { ...this.opts })
139+
: null
140+
if (distIntegrity) {
141+
if (this.integrity && !this.integrity.match(distIntegrity)) {
142+
// only bork if they have algos in common.
143+
// otherwise we end up breaking if we have saved a sha512
144+
// previously for the tarball, but the manifest only
145+
// provides a sha1, which is possible for older publishes.
146+
// Otherwise, this is almost certainly a case of holding it
147+
// wrong, and will result in weird or insecure behavior
148+
// later on when building package tree.
149+
for (const algo of Object.keys(this.integrity)) {
150+
if (distIntegrity[algo]) {
151+
throw Object.assign(new Error(
152+
`Integrity checksum failed when using ${algo}: ` +
153+
`wanted ${this.integrity} but got ${distIntegrity}.`
154+
), { code: 'EINTEGRITY' })
163155
}
164156
}
165157
}
166-
if (this.integrity) {
167-
mani._integrity = String(this.integrity)
168-
if (dist.signatures) {
158+
// made it this far, the integrity is worthwhile. accept it.
159+
// the setter here will take care of merging it into what we already
160+
// had.
161+
this.integrity = distIntegrity
162+
}
163+
}
164+
if (this.integrity) {
165+
mani._integrity = String(this.integrity)
166+
if (dist.signatures) {
167+
if (this.opts.verifySignatures) {
168+
if (this.registryKeys) {
169+
// validate and throw on error, then set _signatures
170+
const message = `${mani._id}:${mani._integrity}`
171+
for (const signature of dist.signatures) {
172+
const publicKey = this.registryKeys.filter(key => (key.keyid === signature.keyid))[0]
173+
if (!publicKey) {
174+
throw Object.assign(new Error(
175+
`${mani._id} has a signature with keyid: ${signature.keyid} ` +
176+
'but no corresponding public key can be found.'
177+
), { code: 'EMISSINGSIGNATUREKEY' })
178+
}
179+
const validPublicKey =
180+
!publicKey.expires || (Date.parse(publicKey.expires) > Date.now())
181+
if (!validPublicKey) {
182+
throw Object.assign(new Error(
183+
`${mani._id} has a signature with keyid: ${signature.keyid} ` +
184+
`but the corresponding public key has expired ${publicKey.expires}`
185+
), { code: 'EEXPIREDSIGNATUREKEY' })
186+
}
187+
const verifier = crypto.createVerify('SHA256')
188+
verifier.write(message)
189+
verifier.end()
190+
const valid = verifier.verify(
191+
publicKey.pemkey,
192+
signature.sig,
193+
'base64'
194+
)
195+
if (!valid) {
196+
throw Object.assign(new Error(
197+
'Integrity checksum signature failed: ' +
198+
`key ${publicKey.keyid} signature ${signature.sig}`
199+
), { code: 'EINTEGRITYSIGNATURE' })
200+
}
201+
}
169202
mani._signatures = dist.signatures
170203
}
204+
// if no keys, don't set _signatures
205+
} else {
206+
mani._signatures = dist.signatures
171207
}
172-
this.package = rpj.normalize(mani)
173-
return this.package
174-
})
208+
}
209+
}
210+
this.package = rpj.normalize(mani)
211+
return this.package
175212
}
176213

177214
[_tarballFromResolved] () {

node_modules/pacote/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pacote",
3-
"version": "13.3.0",
3+
"version": "13.4.1",
44
"description": "JavaScript package downloader",
55
"author": "GitHub Inc.",
66
"bin": {
@@ -26,7 +26,7 @@
2626
},
2727
"devDependencies": {
2828
"@npmcli/eslint-config": "^3.0.1",
29-
"@npmcli/template-oss": "3.4.3",
29+
"@npmcli/template-oss": "3.5.0",
3030
"hosted-git-info": "^5.0.0",
3131
"mutate-fs": "^2.1.1",
3232
"nock": "^13.2.4",
@@ -74,7 +74,7 @@
7474
},
7575
"templateOSS": {
7676
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
77-
"version": "3.4.3",
77+
"version": "3.5.0",
7878
"windowsCI": false
7979
}
8080
}

package-lock.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
"npm-user-validate": "^1.0.1",
139139
"npmlog": "^6.0.2",
140140
"opener": "^1.5.2",
141-
"pacote": "^13.3.0",
141+
"pacote": "^13.4.1",
142142
"parse-conflict-json": "^2.0.2",
143143
"proc-log": "^2.0.1",
144144
"qrcode-terminal": "^0.12.0",
@@ -5547,9 +5547,9 @@
55475547
}
55485548
},
55495549
"node_modules/pacote": {
5550-
"version": "13.3.0",
5551-
"resolved": "https://registry.npmjs.org/pacote/-/pacote-13.3.0.tgz",
5552-
"integrity": "sha512-auhJAUlfC2TALo6I0s1vFoPvVFgWGx+uz/PnIojTTgkGwlK3Np8sGJ0ghfFhiuzJXTZoTycMLk8uLskdntPbDw==",
5550+
"version": "13.4.1",
5551+
"resolved": "https://registry.npmjs.org/pacote/-/pacote-13.4.1.tgz",
5552+
"integrity": "sha512-FqlSWlD8n+ejCE17GF/lf0yasztMGFl4UFzYQk5njaK/qPPWfVDWnfQwqmqeXObWLSmIBew+O+CFD24vxkVDjg==",
55535553
"inBundle": true,
55545554
"dependencies": {
55555555
"@npmcli/git": "^3.0.0",
@@ -13877,9 +13877,9 @@
1387713877
}
1387813878
},
1387913879
"pacote": {
13880-
"version": "13.3.0",
13881-
"resolved": "https://registry.npmjs.org/pacote/-/pacote-13.3.0.tgz",
13882-
"integrity": "sha512-auhJAUlfC2TALo6I0s1vFoPvVFgWGx+uz/PnIojTTgkGwlK3Np8sGJ0ghfFhiuzJXTZoTycMLk8uLskdntPbDw==",
13880+
"version": "13.4.1",
13881+
"resolved": "https://registry.npmjs.org/pacote/-/pacote-13.4.1.tgz",
13882+
"integrity": "sha512-FqlSWlD8n+ejCE17GF/lf0yasztMGFl4UFzYQk5njaK/qPPWfVDWnfQwqmqeXObWLSmIBew+O+CFD24vxkVDjg==",
1388313883
"requires": {
1388413884
"@npmcli/git": "^3.0.0",
1388513885
"@npmcli/installed-package-contents": "^1.0.7",

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
"npm-user-validate": "^1.0.1",
108108
"npmlog": "^6.0.2",
109109
"opener": "^1.5.2",
110-
"pacote": "^13.3.0",
110+
"pacote": "^13.4.1",
111111
"parse-conflict-json": "^2.0.2",
112112
"proc-log": "^2.0.1",
113113
"qrcode-terminal": "^0.12.0",

0 commit comments

Comments
 (0)