Skip to content

Commit 1cb620a

Browse files
committed
chore: retrieve npm keys via TUF
Signed-off-by: Brian DeHamer <[email protected]>
1 parent 3d5bbcc commit 1cb620a

File tree

7 files changed

+314
-90
lines changed

7 files changed

+314
-90
lines changed

DEPENDENCIES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@ graph LR;
574574
npm-->text-table;
575575
npm-->tiny-relative-date;
576576
npm-->treeverse;
577+
npm-->tufjs-repo-mock["@tufjs/repo-mock"];
577578
npm-->validate-npm-package-name;
578579
npm-->which;
579580
npm-->write-file-atomic;

lib/commands/audit.js

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const localeCompare = require('@isaacs/string-locale-compare')('en')
44
const npa = require('npm-package-arg')
55
const pacote = require('pacote')
66
const pMap = require('p-map')
7+
const { sigstore } = require('sigstore')
78

89
const ArboristWorkspaceCmd = require('../arborist-cmd.js')
910
const auditError = require('../utils/audit-error.js')
@@ -188,19 +189,41 @@ class VerifySignatures {
188189
}
189190

190191
async setKeys ({ registry }) {
191-
const keys = await fetch.json('/-/npm/v1/keys', {
192-
...this.npm.flatOptions,
193-
registry,
194-
}).then(({ keys: ks }) => ks.map((key) => ({
195-
...key,
196-
pemkey: `-----BEGIN PUBLIC KEY-----\n${key.key}\n-----END PUBLIC KEY-----`,
197-
}))).catch(err => {
198-
if (err.code === 'E404' || err.code === 'E400') {
199-
return null
200-
} else {
201-
throw err
202-
}
203-
})
192+
const { host, pathname } = new URL(registry)
193+
// Strip any trailing slashes from pathname
194+
const regKey = `${host}${pathname.replace(/\/$/, '')}/keys.json`
195+
let keys = await sigstore.tuf.getTarget(regKey, { tufCachePath: this.opts.tufCache })
196+
.then((target) => JSON.parse(target))
197+
.then(({ keys: ks }) => ks.map((key) => ({
198+
...key,
199+
keyid: key.keyId,
200+
pemkey: `-----BEGIN PUBLIC KEY-----\n${key.publicKey.rawBytes}\n-----END PUBLIC KEY-----`,
201+
expires: key.publicKey.validFor.end || null,
202+
}))).catch(err => {
203+
if (err.code === 'TUF_FIND_TARGET_ERROR') {
204+
return null
205+
} else {
206+
throw err
207+
}
208+
})
209+
210+
// If keys not found in Sigstore TUF repo, fallback to registry keys API
211+
if (!keys) {
212+
keys = await fetch.json('/-/npm/v1/keys', {
213+
...this.npm.flatOptions,
214+
registry,
215+
}).then(({ keys: ks }) => ks.map((key) => ({
216+
...key,
217+
pemkey: `-----BEGIN PUBLIC KEY-----\n${key.key}\n-----END PUBLIC KEY-----`,
218+
}))).catch(err => {
219+
if (err.code === 'E404' || err.code === 'E400') {
220+
return null
221+
} else {
222+
throw err
223+
}
224+
})
225+
}
226+
204227
if (keys) {
205228
this.keys.set(registry, keys)
206229
}

lib/utils/config/definitions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ define('cache', {
331331
flatten (key, obj, flatOptions) {
332332
flatOptions.cache = join(obj.cache, '_cacache')
333333
flatOptions.npxCache = join(obj.cache, '_npx')
334+
flatOptions.tufCache = join(obj.cache, '_tuf')
334335
},
335336
})
336337

package-lock.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
"read-package-json": "^6.0.3",
142142
"read-package-json-fast": "^3.0.2",
143143
"semver": "^7.5.0",
144+
"sigstore": "^1.4.0",
144145
"ssri": "^10.0.4",
145146
"tar": "^6.1.14",
146147
"text-table": "~0.2.0",
@@ -162,6 +163,7 @@
162163
"@npmcli/mock-registry": "^1.0.0",
163164
"@npmcli/promise-spawn": "^6.0.2",
164165
"@npmcli/template-oss": "4.14.1",
166+
"@tufjs/repo-mock": "^1.3.1",
165167
"licensee": "^10.0.0",
166168
"nock": "^13.3.0",
167169
"npm-packlist": "^7.0.4",
@@ -2639,6 +2641,19 @@
26392641
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
26402642
}
26412643
},
2644+
"node_modules/@tufjs/repo-mock": {
2645+
"version": "1.3.1",
2646+
"resolved": "https://registry.npmjs.org/@tufjs/repo-mock/-/repo-mock-1.3.1.tgz",
2647+
"integrity": "sha512-7IDezQbPGReWD3xmgR2pAfG61BZpvW51XnB87OfuiJOe5mkGnziCTTGITtUC3A6htQr9shkk5qIKrhpoMXBwpQ==",
2648+
"dev": true,
2649+
"dependencies": {
2650+
"@tufjs/models": "1.0.4",
2651+
"nock": "^13.3.1"
2652+
},
2653+
"engines": {
2654+
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
2655+
}
2656+
},
26422657
"node_modules/@types/debug": {
26432658
"version": "4.1.7",
26442659
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
"read-package-json": "^6.0.3",
111111
"read-package-json-fast": "^3.0.2",
112112
"semver": "^7.5.0",
113+
"sigstore": "^1.4.0",
113114
"ssri": "^10.0.4",
114115
"tar": "^6.1.14",
115116
"text-table": "~0.2.0",
@@ -195,6 +196,7 @@
195196
"@npmcli/mock-registry": "^1.0.0",
196197
"@npmcli/promise-spawn": "^6.0.2",
197198
"@npmcli/template-oss": "4.14.1",
199+
"@tufjs/repo-mock": "^1.3.1",
198200
"licensee": "^10.0.0",
199201
"nock": "^13.3.0",
200202
"npm-packlist": "^7.0.4",

tap-snapshots/test/lib/commands/audit.js.test.cjs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,20 @@ audited 1 package in xxx
175175
176176
`
177177

178+
exports[`test/lib/commands/audit.js TAP audit signatures third-party registry with sub-path (trailing slash) > must match snapshot 1`] = `
179+
audited 1 package in xxx
180+
181+
1 package has a verified registry signature
182+
183+
`
184+
185+
exports[`test/lib/commands/audit.js TAP audit signatures third-party registry with sub-path > must match snapshot 1`] = `
186+
audited 1 package in xxx
187+
188+
1 package has a verified registry signature
189+
190+
`
191+
178192
exports[`test/lib/commands/audit.js TAP audit signatures with both invalid and missing signatures > must match snapshot 1`] = `
179193
audited 2 packages in xxx
180194
@@ -230,6 +244,13 @@ Someone might have tampered with this package since it was published on the regi
230244
231245
`
232246

247+
exports[`test/lib/commands/audit.js TAP audit signatures with key fallback to legacy API > must match snapshot 1`] = `
248+
audited 1 package in xxx
249+
250+
1 package has a verified registry signature
251+
252+
`
253+
233254
exports[`test/lib/commands/audit.js TAP audit signatures with keys but missing signature > must match snapshot 1`] = `
234255
audited 1 package in xxx
235256

0 commit comments

Comments
 (0)