From d20ee2afa0b9c97ed6822cb8e6838ba537dd76a9 Mon Sep 17 00:00:00 2001 From: Philip Harrison Date: Tue, 14 Feb 2023 13:37:10 +0000 Subject: [PATCH] deps: pacote@15.1.0 Signed-off-by: Philip Harrison --- DEPENDENCIES.md | 1 + node_modules/pacote/lib/registry.js | 113 ++++++++++++++++++++++++++++ node_modules/pacote/package.json | 7 +- package-lock.json | 9 ++- package.json | 2 +- 5 files changed, 124 insertions(+), 8 deletions(-) diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index f94175fbbb135..5d1e94d496bef 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -714,6 +714,7 @@ graph LR; pacote-->promise-retry; pacote-->read-package-json-fast; pacote-->read-package-json; + pacote-->sigstore; pacote-->ssri; pacote-->tar; parse-conflict-json-->json-parse-even-better-errors; diff --git a/node_modules/pacote/lib/registry.js b/node_modules/pacote/lib/registry.js index c4c9df8e4096c..625bedc9a7736 100644 --- a/node_modules/pacote/lib/registry.js +++ b/node_modules/pacote/lib/registry.js @@ -7,6 +7,8 @@ const rpj = require('read-package-json-fast') const pickManifest = require('npm-pick-manifest') const ssri = require('ssri') const crypto = require('crypto') +const npa = require('npm-package-arg') +const { sigstore } = require('sigstore') // Corgis are cute. 🐕🐶 const corgiDoc = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*' @@ -203,7 +205,118 @@ class RegistryFetcher extends Fetcher { mani._signatures = dist.signatures } } + + if (dist.attestations) { + if (this.opts.verifyAttestations) { + // Always fetch attestations from the current registry host + const attestationsPath = new URL(dist.attestations.url).pathname + const attestationsUrl = removeTrailingSlashes(this.registry) + attestationsPath + const res = await fetch(attestationsUrl, { + ...this.opts, + // disable integrity check for attestations json payload, we check the + // integrity in the verification steps below + integrity: null, + }) + const { attestations } = await res.json() + const bundles = attestations.map(({ predicateType, bundle }) => { + const statement = JSON.parse( + Buffer.from(bundle.dsseEnvelope.payload, 'base64').toString('utf8') + ) + const keyid = bundle.dsseEnvelope.signatures[0].keyid + const signature = bundle.dsseEnvelope.signatures[0].sig + + return { + predicateType, + bundle, + statement, + keyid, + signature, + } + }) + + const attestationKeyIds = bundles.map((b) => b.keyid).filter((k) => !!k) + const attestationRegistryKeys = (this.registryKeys || []) + .filter(key => attestationKeyIds.includes(key.keyid)) + if (!attestationRegistryKeys.length) { + throw Object.assign(new Error( + `${mani._id} has attestations but no corresponding public key(s) can be found` + ), { code: 'EMISSINGSIGNATUREKEY' }) + } + + for (const { predicateType, bundle, keyid, signature, statement } of bundles) { + const publicKey = attestationRegistryKeys.find(key => key.keyid === keyid) + // Publish attestations have a keyid set and a valid public key must be found + if (keyid) { + if (!publicKey) { + throw Object.assign(new Error( + `${mani._id} has attestations with keyid: ${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 attestations with keyid: ${keyid} ` + + `but the corresponding public key has expired ${publicKey.expires}` + ), { code: 'EEXPIREDSIGNATUREKEY' }) + } + } + + const subject = { + name: statement.subject[0].name, + sha512: statement.subject[0].digest.sha512, + } + + // Only type 'version' can be turned into a PURL + const purl = this.spec.type === 'version' ? npa.toPurl(this.spec) : this.spec + // Verify the statement subject matches the package, version + if (subject.name !== purl) { + throw Object.assign(new Error( + `${mani._id} package name and version (PURL): ${purl} ` + + `doesn't match what was signed: ${subject.name}` + ), { code: 'EATTESTATIONSUBJECT' }) + } + + // Verify the statement subject matches the tarball integrity + const integrityHexDigest = ssri.parse(this.integrity).hexDigest() + if (subject.sha512 !== integrityHexDigest) { + throw Object.assign(new Error( + `${mani._id} package integrity (hex digest): ` + + `${integrityHexDigest} ` + + `doesn't match what was signed: ${subject.sha512}` + ), { code: 'EATTESTATIONSUBJECT' }) + } + + try { + // Provenance attestations are signed with a signing certificate + // (including the key) so we don't need to return a public key. + // + // Publish attestations are signed with a keyid so we need to + // specify a public key from the keys endpoint: `registry-host.tld/-/npm/v1/keys` + const options = { keySelector: publicKey ? () => publicKey.pemkey : undefined } + await sigstore.verify(bundle, null, options) + } catch (e) { + throw Object.assign(new Error( + `${mani._id} failed to verify attestation: ${e.message}` + ), { + code: 'EATTESTATIONVERIFY', + predicateType, + keyid, + signature, + resolved: mani._resolved, + integrity: mani._integrity, + }) + } + } + mani._attestations = dist.attestations + } else { + mani._attestations = dist.attestations + } + } } + this.package = mani return this.package } diff --git a/node_modules/pacote/package.json b/node_modules/pacote/package.json index ae4e871690eaa..c09fbda86aa1d 100644 --- a/node_modules/pacote/package.json +++ b/node_modules/pacote/package.json @@ -1,6 +1,6 @@ { "name": "pacote", - "version": "15.0.8", + "version": "15.1.0", "description": "JavaScript package downloader", "author": "GitHub Inc.", "bin": { @@ -27,7 +27,7 @@ "devDependencies": { "@npmcli/arborist": "^6.0.0 || ^6.0.0-pre.0", "@npmcli/eslint-config": "^4.0.0", - "@npmcli/template-oss": "4.11.0", + "@npmcli/template-oss": "4.11.4", "hosted-git-info": "^6.0.0", "mutate-fs": "^2.1.1", "nock": "^13.2.4", @@ -59,6 +59,7 @@ "promise-retry": "^2.0.1", "read-package-json": "^6.0.0", "read-package-json-fast": "^3.0.0", + "sigstore": "^1.0.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, @@ -71,7 +72,7 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.11.0", + "version": "4.11.4", "windowsCI": false } } diff --git a/package-lock.json b/package-lock.json index f7f2ff595b3fe..7cd605442ce6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -133,7 +133,7 @@ "npm-user-validate": "^2.0.0", "npmlog": "^7.0.1", "p-map": "^4.0.0", - "pacote": "^15.0.8", + "pacote": "^15.1.0", "parse-conflict-json": "^3.0.0", "proc-log": "^3.0.0", "qrcode-terminal": "^0.12.0", @@ -9869,9 +9869,9 @@ } }, "node_modules/pacote": { - "version": "15.0.8", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.0.8.tgz", - "integrity": "sha512-UlcumB/XS6xyyIMwg/WwMAyUmga+RivB5KgkRwA1hZNtrx+0Bt41KxHCvg1kr0pZ/ZeD8qjhW4fph6VaYRCbLw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.1.0.tgz", + "integrity": "sha512-FFcjtIl+BQNfeliSm7MZz5cpdohvUV1yjGnqgVM4UnVF7JslRY0ImXAygdaCDV0jjUADEWu4y5xsDV8brtrTLg==", "inBundle": true, "dependencies": { "@npmcli/git": "^4.0.0", @@ -9889,6 +9889,7 @@ "promise-retry": "^2.0.1", "read-package-json": "^6.0.0", "read-package-json-fast": "^3.0.0", + "sigstore": "^1.0.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, diff --git a/package.json b/package.json index b30be96265fc9..a8252f02d54c4 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "npm-user-validate": "^2.0.0", "npmlog": "^7.0.1", "p-map": "^4.0.0", - "pacote": "^15.0.8", + "pacote": "^15.1.0", "parse-conflict-json": "^3.0.0", "proc-log": "^3.0.0", "qrcode-terminal": "^0.12.0",