Skip to content

Commit

Permalink
fix: retrieve registry keys via TUF (#6418)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdehamer authored May 18, 2023
1 parent c3638ce commit 37cc797
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 92 deletions.
2 changes: 2 additions & 0 deletions DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,13 +569,15 @@ graph LR;
npm-->remark-github;
npm-->remark;
npm-->semver;
npm-->sigstore;
npm-->spawk;
npm-->ssri;
npm-->tap;
npm-->tar;
npm-->text-table;
npm-->tiny-relative-date;
npm-->treeverse;
npm-->tufjs-repo-mock["@tufjs/repo-mock"];
npm-->validate-npm-package-name;
npm-->which;
npm-->write-file-atomic;
Expand Down
58 changes: 43 additions & 15 deletions lib/commands/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const localeCompare = require('@isaacs/string-locale-compare')('en')
const npa = require('npm-package-arg')
const pacote = require('pacote')
const pMap = require('p-map')
const { sigstore } = require('sigstore')

const ArboristWorkspaceCmd = require('../arborist-cmd.js')
const auditError = require('../utils/audit-error.js')
Expand Down Expand Up @@ -37,7 +38,12 @@ class VerifySignatures {
throw new Error('found no installed dependencies to audit')
}

await Promise.all([...registries].map(registry => this.setKeys({ registry })))
const tuf = await sigstore.tuf.client({
tufCachePath: this.opts.tufCache,
retry: this.opts.retry,
timeout: this.opts.timeout,
})
await Promise.all([...registries].map(registry => this.setKeys({ registry, tuf })))

const progress = log.newItem('verifying registry signatures', edges.size)
const mapper = async (edge) => {
Expand Down Expand Up @@ -187,20 +193,42 @@ class VerifySignatures {
return { edges, registries }
}

async setKeys ({ registry }) {
const keys = await fetch.json('/-/npm/v1/keys', {
...this.npm.flatOptions,
registry,
}).then(({ keys: ks }) => ks.map((key) => ({
...key,
pemkey: `-----BEGIN PUBLIC KEY-----\n${key.key}\n-----END PUBLIC KEY-----`,
}))).catch(err => {
if (err.code === 'E404' || err.code === 'E400') {
return null
} else {
throw err
}
})
async setKeys ({ registry, tuf }) {
const { host, pathname } = new URL(registry)
// Strip any trailing slashes from pathname
const regKey = `${host}${pathname.replace(/\/$/, '')}/keys.json`
let keys = await tuf.getTarget(regKey)
.then((target) => JSON.parse(target))
.then(({ keys: ks }) => ks.map((key) => ({
...key,
keyid: key.keyId,
pemkey: `-----BEGIN PUBLIC KEY-----\n${key.publicKey.rawBytes}\n-----END PUBLIC KEY-----`,
expires: key.publicKey.validFor.end || null,
}))).catch(err => {
if (err.code === 'TUF_FIND_TARGET_ERROR') {
return null
} else {
throw err
}
})

// If keys not found in Sigstore TUF repo, fallback to registry keys API
if (!keys) {
keys = await fetch.json('/-/npm/v1/keys', {
...this.npm.flatOptions,
registry,
}).then(({ keys: ks }) => ks.map((key) => ({
...key,
pemkey: `-----BEGIN PUBLIC KEY-----\n${key.key}\n-----END PUBLIC KEY-----`,
}))).catch(err => {
if (err.code === 'E404' || err.code === 'E400') {
return null
} else {
throw err
}
})
}

if (keys) {
this.keys.set(registry, keys)
}
Expand Down
1 change: 1 addition & 0 deletions lib/utils/config/definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ define('cache', {
flatten (key, obj, flatOptions) {
flatOptions.cache = join(obj.cache, '_cacache')
flatOptions.npxCache = join(obj.cache, '_npx')
flatOptions.tufCache = join(obj.cache, '_tuf')
},
})

Expand Down
16 changes: 16 additions & 0 deletions package-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"read-package-json",
"read-package-json-fast",
"semver",
"sigstore",
"ssri",
"tar",
"text-table",
Expand Down Expand Up @@ -141,6 +142,7 @@
"read-package-json": "^6.0.3",
"read-package-json-fast": "^3.0.2",
"semver": "^7.5.1",
"sigstore": "^1.5.0",
"ssri": "^10.0.4",
"tar": "^6.1.14",
"text-table": "~0.2.0",
Expand All @@ -162,6 +164,7 @@
"@npmcli/mock-registry": "^1.0.0",
"@npmcli/promise-spawn": "^6.0.2",
"@npmcli/template-oss": "4.14.1",
"@tufjs/repo-mock": "^1.3.1",
"licensee": "^10.0.0",
"nock": "^13.3.0",
"npm-packlist": "^7.0.4",
Expand Down Expand Up @@ -2642,6 +2645,19 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/@tufjs/repo-mock": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@tufjs/repo-mock/-/repo-mock-1.3.1.tgz",
"integrity": "sha512-7IDezQbPGReWD3xmgR2pAfG61BZpvW51XnB87OfuiJOe5mkGnziCTTGITtUC3A6htQr9shkk5qIKrhpoMXBwpQ==",
"dev": true,
"dependencies": {
"@tufjs/models": "1.0.4",
"nock": "^13.3.1"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/@types/debug": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"read-package-json": "^6.0.3",
"read-package-json-fast": "^3.0.2",
"semver": "^7.5.1",
"sigstore": "^1.5.0",
"ssri": "^10.0.4",
"tar": "^6.1.14",
"text-table": "~0.2.0",
Expand Down Expand Up @@ -178,6 +179,7 @@
"read-package-json",
"read-package-json-fast",
"semver",
"sigstore",
"ssri",
"tar",
"text-table",
Expand All @@ -195,6 +197,7 @@
"@npmcli/mock-registry": "^1.0.0",
"@npmcli/promise-spawn": "^6.0.2",
"@npmcli/template-oss": "4.14.1",
"@tufjs/repo-mock": "^1.3.1",
"licensee": "^10.0.0",
"nock": "^13.3.0",
"npm-packlist": "^7.0.4",
Expand Down
21 changes: 21 additions & 0 deletions tap-snapshots/test/lib/commands/audit.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,20 @@ audited 1 package in xxx
`

exports[`test/lib/commands/audit.js TAP audit signatures third-party registry with sub-path (trailing slash) > must match snapshot 1`] = `
audited 1 package in xxx
1 package has a verified registry signature
`

exports[`test/lib/commands/audit.js TAP audit signatures third-party registry with sub-path > must match snapshot 1`] = `
audited 1 package in xxx
1 package has a verified registry signature
`

exports[`test/lib/commands/audit.js TAP audit signatures with both invalid and missing signatures > must match snapshot 1`] = `
audited 2 packages in xxx
Expand Down Expand Up @@ -230,6 +244,13 @@ Someone might have tampered with this package since it was published on the regi
`

exports[`test/lib/commands/audit.js TAP audit signatures with key fallback to legacy API > must match snapshot 1`] = `
audited 1 package in xxx
1 package has a verified registry signature
`

exports[`test/lib/commands/audit.js TAP audit signatures with keys but missing signature > must match snapshot 1`] = `
audited 1 package in xxx
Expand Down
Loading

0 comments on commit 37cc797

Please sign in to comment.