Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Signed-off-by: Philip Harrison <[email protected]>
  • Loading branch information
feelepxyz authored and wraithgar committed Feb 14, 2023
1 parent 53f75a4 commit d20ee2a
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 8 deletions.
1 change: 1 addition & 0 deletions DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
113 changes: 113 additions & 0 deletions node_modules/pacote/lib/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, */*'
Expand Down Expand Up @@ -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
}
Expand Down
7 changes: 4 additions & 3 deletions node_modules/pacote/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pacote",
"version": "15.0.8",
"version": "15.1.0",
"description": "JavaScript package downloader",
"author": "GitHub Inc.",
"bin": {
Expand All @@ -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",
Expand Down Expand Up @@ -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"
},
Expand All @@ -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
}
}
9 changes: 5 additions & 4 deletions package-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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"
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit d20ee2a

Please sign in to comment.