Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: retrieve registry keys via TUF #6418

Merged
merged 1 commit into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
wraithgar marked this conversation as resolved.
Show resolved Hide resolved
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