Skip to content

Commit cea32f7

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

File tree

6 files changed

+317
-93
lines changed

6 files changed

+317
-93
lines changed

DEPENDENCIES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ graph LR;
560560
npm-->text-table;
561561
npm-->tiny-relative-date;
562562
npm-->treeverse;
563+
npm-->tufjs-repo-mock["@tufjs/repo-mock"];
563564
npm-->validate-npm-package-name;
564565
npm-->which;
565566
npm-->write-file-atomic;

lib/commands/audit.js

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ const fetch = require('npm-registry-fetch')
33
const localeCompare = require('@isaacs/string-locale-compare')('en')
44
const npa = require('npm-package-arg')
55
const pacote = require('pacote')
6+
const path = require('path')
67
const pMap = require('p-map')
8+
const { sigstore } = require('sigstore')
79

810
const ArboristWorkspaceCmd = require('../arborist-cmd.js')
911
const auditError = require('../utils/audit-error.js')
@@ -188,19 +190,42 @@ class VerifySignatures {
188190
}
189191

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

package-lock.json

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"read-package-json",
6767
"read-package-json-fast",
6868
"semver",
69+
"sigstore",
6970
"ssri",
7071
"tar",
7172
"text-table",
@@ -163,6 +164,7 @@
163164
"@npmcli/mock-registry": "^1.0.0",
164165
"@npmcli/promise-spawn": "^6.0.2",
165166
"@npmcli/template-oss": "4.12.1",
167+
"@tufjs/repo-mock": "^1.3.0",
166168
"licensee": "^10.0.0",
167169
"nock": "^13.3.0",
168170
"npm-packlist": "^7.0.4",
@@ -2555,6 +2557,19 @@
25552557
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
25562558
}
25572559
},
2560+
"node_modules/@tufjs/repo-mock": {
2561+
"version": "1.3.0",
2562+
"resolved": "https://registry.npmjs.org/@tufjs/repo-mock/-/repo-mock-1.3.0.tgz",
2563+
"integrity": "sha512-R2bQ5t9FeZ9qAZuWHg/E/8ixllTI1/fEzJSuVdDo4a9SGfm5wNbUICT7n9Pl9AQCsAkw6evRjLQ/JUwueijTvA==",
2564+
"dev": true,
2565+
"dependencies": {
2566+
"@tufjs/models": "1.0.3",
2567+
"nock": "^13.3.1"
2568+
},
2569+
"engines": {
2570+
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
2571+
}
2572+
},
25582573
"node_modules/@types/debug": {
25592574
"version": "4.1.7",
25602575
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
@@ -8831,9 +8846,9 @@
88318846
"dev": true
88328847
},
88338848
"node_modules/nock": {
8834-
"version": "13.3.0",
8835-
"resolved": "https://registry.npmjs.org/nock/-/nock-13.3.0.tgz",
8836-
"integrity": "sha512-HHqYQ6mBeiMc+N038w8LkMpDCRquCHWeNmN3v6645P3NhN2+qXOBqvPqo7Rt1VyCMzKhJ733wZqw5B7cQVFNPg==",
8849+
"version": "13.3.1",
8850+
"resolved": "https://registry.npmjs.org/nock/-/nock-13.3.1.tgz",
8851+
"integrity": "sha512-vHnopocZuI93p2ccivFyGuUfzjq2fxNyNurp7816mlT5V5HF4SzXu8lvLrVzBbNqzs+ODooZ6OksuSUNM7Njkw==",
88378852
"dev": true,
88388853
"dependencies": {
88398854
"debug": "^4.1.0",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
"@npmcli/mock-registry": "^1.0.0",
198198
"@npmcli/promise-spawn": "^6.0.2",
199199
"@npmcli/template-oss": "4.12.1",
200+
"@tufjs/repo-mock": "^1.3.0",
200201
"licensee": "^10.0.0",
201202
"nock": "^13.3.0",
202203
"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)