Skip to content

Commit d20ee2a

Browse files
feelepxyzwraithgar
authored andcommitted
deps: pacote@15.1.0
Signed-off-by: Philip Harrison <philip@mailharrison.com>
1 parent 53f75a4 commit d20ee2a

File tree

5 files changed

+124
-8
lines changed

5 files changed

+124
-8
lines changed

DEPENDENCIES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,7 @@ graph LR;
714714
pacote-->promise-retry;
715715
pacote-->read-package-json-fast;
716716
pacote-->read-package-json;
717+
pacote-->sigstore;
717718
pacote-->ssri;
718719
pacote-->tar;
719720
parse-conflict-json-->json-parse-even-better-errors;

node_modules/pacote/lib/registry.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const rpj = require('read-package-json-fast')
77
const pickManifest = require('npm-pick-manifest')
88
const ssri = require('ssri')
99
const crypto = require('crypto')
10+
const npa = require('npm-package-arg')
11+
const { sigstore } = require('sigstore')
1012

1113
// Corgis are cute. 🐕🐶
1214
const corgiDoc = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'
@@ -203,7 +205,118 @@ class RegistryFetcher extends Fetcher {
203205
mani._signatures = dist.signatures
204206
}
205207
}
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+
}
206318
}
319+
207320
this.package = mani
208321
return this.package
209322
}

node_modules/pacote/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pacote",
3-
"version": "15.0.8",
3+
"version": "15.1.0",
44
"description": "JavaScript package downloader",
55
"author": "GitHub Inc.",
66
"bin": {
@@ -27,7 +27,7 @@
2727
"devDependencies": {
2828
"@npmcli/arborist": "^6.0.0 || ^6.0.0-pre.0",
2929
"@npmcli/eslint-config": "^4.0.0",
30-
"@npmcli/template-oss": "4.11.0",
30+
"@npmcli/template-oss": "4.11.4",
3131
"hosted-git-info": "^6.0.0",
3232
"mutate-fs": "^2.1.1",
3333
"nock": "^13.2.4",
@@ -59,6 +59,7 @@
5959
"promise-retry": "^2.0.1",
6060
"read-package-json": "^6.0.0",
6161
"read-package-json-fast": "^3.0.0",
62+
"sigstore": "^1.0.0",
6263
"ssri": "^10.0.0",
6364
"tar": "^6.1.11"
6465
},
@@ -71,7 +72,7 @@
7172
},
7273
"templateOSS": {
7374
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
74-
"version": "4.11.0",
75+
"version": "4.11.4",
7576
"windowsCI": false
7677
}
7778
}

package-lock.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
"npm-user-validate": "^2.0.0",
134134
"npmlog": "^7.0.1",
135135
"p-map": "^4.0.0",
136-
"pacote": "^15.0.8",
136+
"pacote": "^15.1.0",
137137
"parse-conflict-json": "^3.0.0",
138138
"proc-log": "^3.0.0",
139139
"qrcode-terminal": "^0.12.0",
@@ -9869,9 +9869,9 @@
98699869
}
98709870
},
98719871
"node_modules/pacote": {
9872-
"version": "15.0.8",
9873-
"resolved": "https://registry.npmjs.org/pacote/-/pacote-15.0.8.tgz",
9874-
"integrity": "sha512-UlcumB/XS6xyyIMwg/WwMAyUmga+RivB5KgkRwA1hZNtrx+0Bt41KxHCvg1kr0pZ/ZeD8qjhW4fph6VaYRCbLw==",
9872+
"version": "15.1.0",
9873+
"resolved": "https://registry.npmjs.org/pacote/-/pacote-15.1.0.tgz",
9874+
"integrity": "sha512-FFcjtIl+BQNfeliSm7MZz5cpdohvUV1yjGnqgVM4UnVF7JslRY0ImXAygdaCDV0jjUADEWu4y5xsDV8brtrTLg==",
98759875
"inBundle": true,
98769876
"dependencies": {
98779877
"@npmcli/git": "^4.0.0",
@@ -9889,6 +9889,7 @@
98899889
"promise-retry": "^2.0.1",
98909890
"read-package-json": "^6.0.0",
98919891
"read-package-json-fast": "^3.0.0",
9892+
"sigstore": "^1.0.0",
98929893
"ssri": "^10.0.0",
98939894
"tar": "^6.1.11"
98949895
},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
"npm-user-validate": "^2.0.0",
103103
"npmlog": "^7.0.1",
104104
"p-map": "^4.0.0",
105-
"pacote": "^15.0.8",
105+
"pacote": "^15.1.0",
106106
"parse-conflict-json": "^3.0.0",
107107
"proc-log": "^3.0.0",
108108
"qrcode-terminal": "^0.12.0",

0 commit comments

Comments
 (0)