pacote 15.0.8 → 15.1.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 +3 -1
- package/lib/registry.js +113 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -172,7 +172,9 @@ resolved, and other properties, as they are determined.
|
|
|
172
172
|
integrity signature of a manifest, if present. There must be a
|
|
173
173
|
configured `_keys` entry in the config that is scoped to the
|
|
174
174
|
registry the manifest is being fetched from.
|
|
175
|
-
|
|
175
|
+
* `verifyAttestations` A boolean that will make pacote verify Sigstore
|
|
176
|
+
attestations, if present. There must be a configured `_keys` entry in the
|
|
177
|
+
config that is scoped to the registry the manifest is being fetched from.
|
|
176
178
|
|
|
177
179
|
### Advanced API
|
|
178
180
|
|
package/lib/registry.js
CHANGED
|
@@ -7,6 +7,8 @@ const rpj = require('read-package-json-fast')
|
|
|
7
7
|
const pickManifest = require('npm-pick-manifest')
|
|
8
8
|
const ssri = require('ssri')
|
|
9
9
|
const crypto = require('crypto')
|
|
10
|
+
const npa = require('npm-package-arg')
|
|
11
|
+
const { sigstore } = require('sigstore')
|
|
10
12
|
|
|
11
13
|
// Corgis are cute. 🐕🐶
|
|
12
14
|
const corgiDoc = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'
|
|
@@ -203,7 +205,118 @@ class RegistryFetcher extends Fetcher {
|
|
|
203
205
|
mani._signatures = dist.signatures
|
|
204
206
|
}
|
|
205
207
|
}
|
|
208
|
+
|
|
209
|
+
if (dist.attestations) {
|
|
210
|
+
if (this.opts.verifyAttestations) {
|
|
211
|
+
// Always fetch attestations from the current registry host
|
|
212
|
+
const attestationsPath = new URL(dist.attestations.url).pathname
|
|
213
|
+
const attestationsUrl = removeTrailingSlashes(this.registry) + attestationsPath
|
|
214
|
+
const res = await fetch(attestationsUrl, {
|
|
215
|
+
...this.opts,
|
|
216
|
+
// disable integrity check for attestations json payload, we check the
|
|
217
|
+
// integrity in the verification steps below
|
|
218
|
+
integrity: null,
|
|
219
|
+
})
|
|
220
|
+
const { attestations } = await res.json()
|
|
221
|
+
const bundles = attestations.map(({ predicateType, bundle }) => {
|
|
222
|
+
const statement = JSON.parse(
|
|
223
|
+
Buffer.from(bundle.dsseEnvelope.payload, 'base64').toString('utf8')
|
|
224
|
+
)
|
|
225
|
+
const keyid = bundle.dsseEnvelope.signatures[0].keyid
|
|
226
|
+
const signature = bundle.dsseEnvelope.signatures[0].sig
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
predicateType,
|
|
230
|
+
bundle,
|
|
231
|
+
statement,
|
|
232
|
+
keyid,
|
|
233
|
+
signature,
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const attestationKeyIds = bundles.map((b) => b.keyid).filter((k) => !!k)
|
|
238
|
+
const attestationRegistryKeys = (this.registryKeys || [])
|
|
239
|
+
.filter(key => attestationKeyIds.includes(key.keyid))
|
|
240
|
+
if (!attestationRegistryKeys.length) {
|
|
241
|
+
throw Object.assign(new Error(
|
|
242
|
+
`${mani._id} has attestations but no corresponding public key(s) can be found`
|
|
243
|
+
), { code: 'EMISSINGSIGNATUREKEY' })
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (const { predicateType, bundle, keyid, signature, statement } of bundles) {
|
|
247
|
+
const publicKey = attestationRegistryKeys.find(key => key.keyid === keyid)
|
|
248
|
+
// Publish attestations have a keyid set and a valid public key must be found
|
|
249
|
+
if (keyid) {
|
|
250
|
+
if (!publicKey) {
|
|
251
|
+
throw Object.assign(new Error(
|
|
252
|
+
`${mani._id} has attestations with keyid: ${keyid} ` +
|
|
253
|
+
'but no corresponding public key can be found'
|
|
254
|
+
), { code: 'EMISSINGSIGNATUREKEY' })
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const validPublicKey =
|
|
258
|
+
!publicKey.expires || (Date.parse(publicKey.expires) > Date.now())
|
|
259
|
+
if (!validPublicKey) {
|
|
260
|
+
throw Object.assign(new Error(
|
|
261
|
+
`${mani._id} has attestations with keyid: ${keyid} ` +
|
|
262
|
+
`but the corresponding public key has expired ${publicKey.expires}`
|
|
263
|
+
), { code: 'EEXPIREDSIGNATUREKEY' })
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const subject = {
|
|
268
|
+
name: statement.subject[0].name,
|
|
269
|
+
sha512: statement.subject[0].digest.sha512,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Only type 'version' can be turned into a PURL
|
|
273
|
+
const purl = this.spec.type === 'version' ? npa.toPurl(this.spec) : this.spec
|
|
274
|
+
// Verify the statement subject matches the package, version
|
|
275
|
+
if (subject.name !== purl) {
|
|
276
|
+
throw Object.assign(new Error(
|
|
277
|
+
`${mani._id} package name and version (PURL): ${purl} ` +
|
|
278
|
+
`doesn't match what was signed: ${subject.name}`
|
|
279
|
+
), { code: 'EATTESTATIONSUBJECT' })
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Verify the statement subject matches the tarball integrity
|
|
283
|
+
const integrityHexDigest = ssri.parse(this.integrity).hexDigest()
|
|
284
|
+
if (subject.sha512 !== integrityHexDigest) {
|
|
285
|
+
throw Object.assign(new Error(
|
|
286
|
+
`${mani._id} package integrity (hex digest): ` +
|
|
287
|
+
`${integrityHexDigest} ` +
|
|
288
|
+
`doesn't match what was signed: ${subject.sha512}`
|
|
289
|
+
), { code: 'EATTESTATIONSUBJECT' })
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
// Provenance attestations are signed with a signing certificate
|
|
294
|
+
// (including the key) so we don't need to return a public key.
|
|
295
|
+
//
|
|
296
|
+
// Publish attestations are signed with a keyid so we need to
|
|
297
|
+
// specify a public key from the keys endpoint: `registry-host.tld/-/npm/v1/keys`
|
|
298
|
+
const options = { keySelector: publicKey ? () => publicKey.pemkey : undefined }
|
|
299
|
+
await sigstore.verify(bundle, null, options)
|
|
300
|
+
} catch (e) {
|
|
301
|
+
throw Object.assign(new Error(
|
|
302
|
+
`${mani._id} failed to verify attestation: ${e.message}`
|
|
303
|
+
), {
|
|
304
|
+
code: 'EATTESTATIONVERIFY',
|
|
305
|
+
predicateType,
|
|
306
|
+
keyid,
|
|
307
|
+
signature,
|
|
308
|
+
resolved: mani._resolved,
|
|
309
|
+
integrity: mani._integrity,
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
mani._attestations = dist.attestations
|
|
314
|
+
} else {
|
|
315
|
+
mani._attestations = dist.attestations
|
|
316
|
+
}
|
|
317
|
+
}
|
|
206
318
|
}
|
|
319
|
+
|
|
207
320
|
this.package = mani
|
|
208
321
|
return this.package
|
|
209
322
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pacote",
|
|
3
|
-
"version": "15.0
|
|
3
|
+
"version": "15.1.0",
|
|
4
4
|
"description": "JavaScript package downloader",
|
|
5
5
|
"author": "GitHub Inc.",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@npmcli/arborist": "^6.0.0 || ^6.0.0-pre.0",
|
|
29
29
|
"@npmcli/eslint-config": "^4.0.0",
|
|
30
|
-
"@npmcli/template-oss": "4.11.
|
|
30
|
+
"@npmcli/template-oss": "4.11.4",
|
|
31
31
|
"hosted-git-info": "^6.0.0",
|
|
32
32
|
"mutate-fs": "^2.1.1",
|
|
33
33
|
"nock": "^13.2.4",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"promise-retry": "^2.0.1",
|
|
60
60
|
"read-package-json": "^6.0.0",
|
|
61
61
|
"read-package-json-fast": "^3.0.0",
|
|
62
|
+
"sigstore": "^1.0.0",
|
|
62
63
|
"ssri": "^10.0.0",
|
|
63
64
|
"tar": "^6.1.11"
|
|
64
65
|
},
|
|
@@ -71,7 +72,7 @@
|
|
|
71
72
|
},
|
|
72
73
|
"templateOSS": {
|
|
73
74
|
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
|
|
74
|
-
"version": "4.11.
|
|
75
|
+
"version": "4.11.4",
|
|
75
76
|
"windowsCI": false
|
|
76
77
|
}
|
|
77
78
|
}
|