@@ -6,14 +6,14 @@ const fetch = require('npm-registry-fetch')
66const localeCompare = require ( '@isaacs/string-locale-compare' ) ( 'en' )
77const npa = require ( 'npm-package-arg' )
88const pacote = require ( 'pacote' )
9- // const pickManifest = require('npm-pick-manifest')
109
1110const ArboristWorkspaceCmd = require ( '../arborist-cmd.js' )
1211const auditError = require ( '../utils/audit-error.js' )
1312const {
1413 registry : { default : defaultRegistry } ,
1514} = require ( '../utils/config/definitions.js' )
16- // const log = require('../utils/log-shim.js')
15+ const log = require ( '../utils/log-shim.js' )
16+ const pulseTillDone = require ( '../utils/pulse-till-done.js' )
1717const reifyFinish = require ( '../utils/reify-finish.js' )
1818
1919const validateSignature = async ( { message, signature, publicKey } ) => {
@@ -45,8 +45,10 @@ class VerifySignatures {
4545 const nodes = this . tree . inventory . values ( )
4646 this . getEdges ( nodes , 'edgesOut' )
4747 const edges = Array . from ( this . edges )
48+ if ( edges . length === 0 ) {
49+ throw new Error ( 'No dependencies found' )
50+ }
4851
49- // QUESTION: Do we need to get the registry host from the resolved url to handle proxies?
5052 // Prefetch and cache public keys from used registries
5153 const registries = this . findAllRegistryUrls ( edges , this . npm . flatOptions )
5254 for ( const registry of registries ) {
@@ -75,11 +77,13 @@ class VerifySignatures {
7577 this . appendOutput ( this . makeJSON ( { invalid, missing } ) )
7678 } else {
7779 const timing = `audited ${ edges . length } packages in ${ Math . floor ( Number ( elapsed ) / 1e9 ) } s`
78- const verifiedPrefix = verified ? 'verified signatures, ' : ''
80+ const verifiedPrefix = verified ? 'verified registry signatures, ' : ''
7981 this . appendOutput ( `${ verifiedPrefix } ${ timing } \n` )
8082
8183 if ( this . verified && ! verified ) {
82- this . appendOutput ( `${ this . verified } ${ chalk . bold ( 'verified' ) } packages\n` )
84+ this . appendOutput (
85+ `${ this . verified } packages have ${ chalk . bold ( 'verified' ) } registry signatures\n`
86+ )
8387 }
8488
8589 if ( missing . length ) {
@@ -191,16 +195,6 @@ class VerifySignatures {
191195 }
192196 }
193197
194- // TODO: Remove this once we can get time from pacote.manifest
195- async getPackument ( spec ) {
196- const packument = await pacote . packument ( spec , {
197- ...this . npm . flatOptions ,
198- fullMetadata : this . npm . config . get ( 'long' ) ,
199- preferOffline : true ,
200- } )
201- return packument
202- }
203-
204198 async getKeys ( { registry } ) {
205199 return await fetch . json ( '/-/npm/v1/keys' , {
206200 ...this . npm . flatOptions ,
@@ -260,95 +254,75 @@ class VerifySignatures {
260254 return null
261255 }
262256
263- try {
264- const name = alias ? edge . spec . replace ( 'npm' , edge . name ) : edge . name
265- // QUESTION: Is name@version the right way to get the manifest?
266- const manifest = await pacote . manifest ( `${ name } @${ version } ` , this . npm . flatOptions )
267- const registry = fetch . pickRegistry ( spec , this . npm . flatOptions )
268-
269- const { _integrity : integrity , _signatures } = manifest
270- const message = `${ name } @${ version } :${ integrity } `
271- const signatures = _signatures || [ ]
272-
273- // TODO: Get version created time from manifest
274- //
275- // const packument = await this.getPackument(spec)
276- // const versionCreated = packument.time && packument.time[version]
277- const keys = this . keys . get ( registry ) || [ ]
278- const validKeys = keys . filter ( ( publicKey ) => {
279- if ( ! publicKey . expires ) {
280- return true
281- }
282- // return Date.parse(versionCreated) < Date.parse(publicKey.expires)
283- return Date . parse ( publicKey . expires ) > Date . now ( )
257+ const name = alias ? edge . spec . replace ( 'npm' , edge . name ) : edge . name
258+ const manifest = await pacote . manifest ( `${ name } @${ version } ` , this . npm . flatOptions )
259+ const registry = fetch . pickRegistry ( spec , this . npm . flatOptions )
260+
261+ const { _integrity : integrity , _signatures } = manifest
262+ const message = `${ name } @${ version } :${ integrity } `
263+ const signatures = _signatures || [ ]
264+
265+ const keys = this . keys . get ( registry ) || [ ]
266+ const validKeys = keys . filter ( ( publicKey ) => {
267+ if ( ! publicKey . expires ) {
268+ return true
269+ }
270+ return Date . parse ( publicKey . expires ) > Date . now ( )
271+ } )
272+
273+ // Currently we only care about missing signatures on registries that provide a public key
274+ // We could make this configurable in the future with a strict/paranoid mode
275+ if ( ! signatures . length && validKeys . length ) {
276+ this . missing . add ( {
277+ name,
278+ path,
279+ version,
280+ location,
281+ registry,
282+ } )
283+
284+ return
285+ }
286+
287+ await Promise . all ( signatures . map ( async ( signature ) => {
288+ const publicKey = keys . filter ( key => key . keyid === signature . keyid ) [ 0 ]
289+ const validPublicKey = validKeys . filter ( key => key . keyid === signature . keyid ) [ 0 ]
290+
291+ if ( ! publicKey && ! validPublicKey ) {
292+ throw new Error (
293+ `${ name } has a signature with keyid: ${ signature . keyid } ` +
294+ `but not corresponding public key can be found on ${ registry } -/npm/v1/keys`
295+ )
296+ } else if ( publicKey && ! validPublicKey ) {
297+ throw new Error (
298+ `${ name } has a signature with keyid: ${ signature . keyid } ` +
299+ `but the corresponding public key on ${ registry } -/npm/v1/keys has expired ` +
300+ `(${ publicKey . expires } )`
301+ )
302+ }
303+
304+ const valid = await validateSignature ( {
305+ message,
306+ signature : signature . sig ,
307+ publicKey : validPublicKey . pemKey ,
284308 } )
285309
286- // Currently we only care about missing signatures on registries that provide a public key
287- // We could make this configurable in the future with a strict/paranoid mode
288- if ( ! signatures . length && validKeys . length ) {
289- this . missing . add ( {
310+ if ( ! valid ) {
311+ this . invalid . add ( {
290312 name,
291313 path,
314+ type,
292315 version,
293316 location,
294317 registry,
295- } )
296-
297- return
298- }
299-
300- await Promise . all ( signatures . map ( async ( signature ) => {
301- const publicKey = keys . filter ( key => key . keyid === signature . keyid ) [ 0 ]
302- const validPublicKey = validKeys . filter ( key => key . keyid === signature . keyid ) [ 0 ]
303-
304- if ( ! publicKey && ! validPublicKey ) {
305- throw new Error (
306- `${ name } has a signature with keyid: ${ signature . keyid } ` +
307- `but not corresponding public key can be found on ${ registry } -/npm/v1/keys`
308- )
309- } else if ( publicKey && ! validPublicKey ) {
310- throw new Error (
311- `${ name } has a signature with keyid: ${ signature . keyid } ` +
312- `but the corresponding public key on ${ registry } -/npm/v1/keys has expired ` +
313- `(${ publicKey . expires } )`
314- )
315- }
316-
317- const valid = await validateSignature ( {
318- message,
318+ integrity,
319319 signature : signature . sig ,
320- publicKey : validPublicKey . pemKey ,
320+ keyid : signature . keyid ,
321321 } )
322-
323- if ( ! valid ) {
324- this . invalid . add ( {
325- name,
326- path,
327- type,
328- version,
329- location,
330- registry,
331- integrity,
332- signature : signature . sig ,
333- keyid : signature . keyid ,
334- } )
335- } else {
336- this . verified ++
337- }
338- } ) )
339- } catch ( err ) {
340- // QUESTION: Is this the right way to handle these errors?
341- //
342- // silently catch and ignore ETARGET, E403 &
343- // E404 errors, deps are just skipped
344- if ( ! (
345- err . code === 'ETARGET' ||
346- err . code === 'E403' ||
347- err . code === 'E404' )
348- ) {
349- throw err
322+ } else {
323+ this . verified ++
350324 }
351- }
325+ } ) )
352326 }
353327
354328 humanOutput ( list ) {
@@ -471,6 +445,7 @@ class Audit extends ArboristWorkspaceCmd {
471445 }
472446
473447 async auditSignatures ( ) {
448+ log . newItem ( 'loading intalled packages' )
474449 const reporter = this . npm . config . get ( 'json' ) ? 'json' : 'detail'
475450 const opts = {
476451 ...this . npm . flatOptions ,
@@ -494,8 +469,9 @@ class Audit extends ArboristWorkspaceCmd {
494469 arb . excludeWorkspacesDependencySet ( tree )
495470 }
496471
472+ log . newItem ( 'verifying registry signatures' )
497473 const verify = new VerifySignatures ( tree , filterSet , this . npm , { ...opts } )
498- await verify . run ( )
474+ await pulseTillDone . withPromise ( verify . run ( ) )
499475 const result = verify . report ( )
500476 process . exitCode = process . exitCode || result . exitCode
501477 this . npm . output ( result . report )
0 commit comments