pacote 13.3.0 → 13.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -168,6 +168,10 @@ resolved, and other properties, as they are determined.
168
168
  is unlikely to change in the span of a single command.
169
169
  * `silent` A boolean that determines whether the banner is displayed
170
170
  when calling `@npmcli/run-script`.
171
+ * `verifySignatures` A boolean that will make pacote verify the
172
+ integrity signature of a manifest, if present. There must be a
173
+ configured `_keys` entry in the config that is scoped to the
174
+ registry the manifest is being fetched from.
171
175
 
172
176
 
173
177
  ### Advanced API
package/lib/dir.js CHANGED
@@ -63,10 +63,12 @@ class DirFetcher extends Fetcher {
63
63
  stream.resolved = this.resolved
64
64
  stream.integrity = this.integrity
65
65
 
66
+ const { prefix, workspaces } = this.opts
67
+
66
68
  // run the prepare script, get the list of files, and tar it up
67
69
  // pipe to the stream, and proxy errors the chain.
68
70
  this[_prepareDir]()
69
- .then(() => packlist({ path: this.resolved }))
71
+ .then(() => packlist({ path: this.resolved, prefix, workspaces }))
70
72
  .then(files => tar.c(tarCreateOptions(this.package), files)
71
73
  .on('error', er => stream.emit('error', er)).pipe(stream))
72
74
  .catch(er => stream.emit('error', er))
package/lib/registry.js CHANGED
@@ -7,6 +7,7 @@ const npa = require('npm-package-arg')
7
7
  const rpj = require('read-package-json-fast')
8
8
  const pickManifest = require('npm-pick-manifest')
9
9
  const ssri = require('ssri')
10
+ const crypto = require('crypto')
10
11
 
11
12
  // Corgis are cute. 🐕🐶
12
13
  const corgiDoc = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'
@@ -14,8 +15,6 @@ const fullDoc = 'application/json'
14
15
 
15
16
  const fetch = require('npm-registry-fetch')
16
17
 
17
- // TODO: memoize reg requests, so we don't even have to check cache
18
-
19
18
  const _headers = Symbol('_headers')
20
19
  class RegistryFetcher extends Fetcher {
21
20
  constructor (spec, opts) {
@@ -39,28 +38,30 @@ class RegistryFetcher extends Fetcher {
39
38
  this.packumentUrl = removeTrailingSlashes(this.registry) + '/' +
40
39
  this.spec.escapedName
41
40
 
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
+
42
49
  // XXX pacote <=9 has some logic to ignore opts.resolved if
43
50
  // the resolved URL doesn't go to the same registry.
44
51
  // Consider reproducing that here, to throw away this.resolved
45
52
  // in that case.
46
53
  }
47
54
 
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) {
59
59
  throw Object.assign(
60
60
  new Error('Invalid package manifest: no `dist.tarball` field'),
61
61
  { package: this.spec.toString() }
62
62
  )
63
- })
63
+ }
64
+ return this.resolved
64
65
  }
65
66
 
66
67
  [_headers] () {
@@ -87,91 +88,127 @@ class RegistryFetcher extends Fetcher {
87
88
  // npm-registry-fetch the packument
88
89
  // set the appropriate header for corgis if fullMetadata isn't set
89
90
  // 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()
97
100
  packument._cached = res.headers.has('x-local-cache')
98
101
  packument._contentLength = +res.headers.get('content-length')
99
102
  if (this.packumentCache) {
100
103
  this.packumentCache.set(this.packumentUrl, packument)
101
104
  }
102
105
  return packument
103
- })).catch(er => {
106
+ } catch (err) {
104
107
  if (this.packumentCache) {
105
108
  this.packumentCache.delete(this.packumentUrl)
106
109
  }
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
111
112
  }
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()
116
116
  }
117
- return p
118
117
  }
119
118
 
120
- manifest () {
119
+ async manifest () {
121
120
  if (this.package) {
122
- return Promise.resolve(this.package)
121
+ return this.package
123
122
  }
124
123
 
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' })
163
155
  }
164
156
  }
165
157
  }
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
+ }
169
202
  mani._signatures = dist.signatures
170
203
  }
204
+ // if no keys, don't set _signatures
205
+ } else {
206
+ mani._signatures = dist.signatures
171
207
  }
172
- this.package = rpj.normalize(mani)
173
- return this.package
174
- })
208
+ }
209
+ }
210
+ this.package = rpj.normalize(mani)
211
+ return this.package
175
212
  }
176
213
 
177
214
  [_tarballFromResolved] () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pacote",
3
- "version": "13.3.0",
3
+ "version": "13.5.0",
4
4
  "description": "JavaScript package downloader",
5
5
  "author": "GitHub Inc.",
6
6
  "bin": {
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@npmcli/eslint-config": "^3.0.1",
29
- "@npmcli/template-oss": "3.4.3",
29
+ "@npmcli/template-oss": "3.5.0",
30
30
  "hosted-git-info": "^5.0.0",
31
31
  "mutate-fs": "^2.1.1",
32
32
  "nock": "^13.2.4",
@@ -54,7 +54,7 @@
54
54
  "minipass": "^3.1.6",
55
55
  "mkdirp": "^1.0.4",
56
56
  "npm-package-arg": "^9.0.0",
57
- "npm-packlist": "^5.0.0",
57
+ "npm-packlist": "^5.1.0",
58
58
  "npm-pick-manifest": "^7.0.0",
59
59
  "npm-registry-fetch": "^13.0.1",
60
60
  "proc-log": "^2.0.0",
@@ -74,7 +74,7 @@
74
74
  },
75
75
  "templateOSS": {
76
76
  "//@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",
78
78
  "windowsCI": false
79
79
  }
80
80
  }