@@ -7,15 +7,14 @@ const npa = require('npm-package-arg')
77const  rpj  =  require ( 'read-package-json-fast' ) 
88const  pickManifest  =  require ( 'npm-pick-manifest' ) 
99const  ssri  =  require ( 'ssri' ) 
10+ const  crypto  =  require ( 'crypto' ) 
1011
1112// Corgis are cute. 🐕🐶 
1213const  corgiDoc  =  'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*' 
1314const  fullDoc  =  'application/json' 
1415
1516const  fetch  =  require ( 'npm-registry-fetch' ) 
1617
17- // TODO: memoize reg requests, so we don't even have to check cache 
18- 
1918const  _headers  =  Symbol ( '_headers' ) 
2019class  RegistryFetcher  extends  Fetcher  { 
2120  constructor  ( spec ,  opts )  { 
@@ -39,28 +38,30 @@ class RegistryFetcher extends Fetcher {
3938    this . packumentUrl  =  removeTrailingSlashes ( this . registry )  +  '/'  + 
4039      this . spec . escapedName 
4140
41+     const  parsed  =  new  URL ( this . registry ) 
42+     const  regKey  =  `//${ parsed . host } ${ parsed . pathname }  
43+     // unlike the nerf-darted auth keys, this one does *not* allow a mismatch 
44+     // of trailing slashes.  It must match exactly. 
45+     if  ( this . opts [ `${ regKey }  ] )  { 
46+       this . registryKeys  =  this . opts [ `${ regKey }  ] 
47+     } 
48+ 
4249    // XXX pacote <=9 has some logic to ignore opts.resolved if 
4350    // the resolved URL doesn't go to the same registry. 
4451    // Consider reproducing that here, to throw away this.resolved 
4552    // in that case. 
4653  } 
4754
48-   resolve  ( )  { 
49-     if  ( this . resolved )  { 
50-       return  Promise . resolve ( this . resolved ) 
51-     } 
52- 
53-     // fetching the manifest sets resolved and (usually) integrity 
54-     return  this . manifest ( ) . then ( ( )  =>  { 
55-       if  ( this . resolved )  { 
56-         return  this . resolved 
57-       } 
58- 
55+   async  resolve  ( )  { 
56+     // fetching the manifest sets resolved and (if present) integrity 
57+     await  this . manifest ( ) 
58+     if  ( ! this . resolved )  { 
5959      throw  Object . assign ( 
6060        new  Error ( 'Invalid package manifest: no `dist.tarball` field' ) , 
6161        {  package : this . spec . toString ( )  } 
6262      ) 
63-     } ) 
63+     } 
64+     return  this . resolved 
6465  } 
6566
6667  [ _headers ]  ( )  { 
@@ -87,91 +88,127 @@ class RegistryFetcher extends Fetcher {
8788    // npm-registry-fetch the packument 
8889    // set the appropriate header for corgis if fullMetadata isn't set 
8990    // return the res.json() promise 
90-     const  p  =  fetch ( this . packumentUrl ,  { 
91-       ...this . opts , 
92-       headers : this [ _headers ] ( ) , 
93-       spec : this . spec , 
94-       // never check integrity for packuments themselves 
95-       integrity : null , 
96-     } ) . then ( res  =>  res . json ( ) . then ( packument  =>  { 
91+     try  { 
92+       const  res  =  await  fetch ( this . packumentUrl ,  { 
93+         ...this . opts , 
94+         headers : this [ _headers ] ( ) , 
95+         spec : this . spec , 
96+         // never check integrity for packuments themselves 
97+         integrity : null , 
98+       } ) 
99+       const  packument  =  await  res . json ( ) 
97100      packument . _cached  =  res . headers . has ( 'x-local-cache' ) 
98101      packument . _contentLength  =  + res . headers . get ( 'content-length' ) 
99102      if  ( this . packumentCache )  { 
100103        this . packumentCache . set ( this . packumentUrl ,  packument ) 
101104      } 
102105      return  packument 
103-     } ) ) . catch ( er   =>  { 
106+     }   catch   ( err )  { 
104107      if  ( this . packumentCache )  { 
105108        this . packumentCache . delete ( this . packumentUrl ) 
106109      } 
107-       if  ( er . code  ===  'E404'  &&  ! this . fullMetadata )  { 
108-         // possible that corgis are not supported by this registry 
109-         this . fullMetadata  =  true 
110-         return  this . packument ( ) 
110+       if  ( err . code  !==  'E404'  ||  this . fullMetadata )  { 
111+         throw  err 
111112      } 
112-       throw  er 
113-     } ) 
114-     if  ( this . packumentCache )  { 
115-       this . packumentCache . set ( this . packumentUrl ,  p ) 
113+       // possible that corgis are not supported by this registry 
114+       this . fullMetadata  =  true 
115+       return  this . packument ( ) 
116116    } 
117-     return  p 
118117  } 
119118
120-   manifest  ( )  { 
119+   async   manifest  ( )  { 
121120    if  ( this . package )  { 
122-       return  Promise . resolve ( this . package ) 
121+       return  this . package 
123122    } 
124123
125-     return  this . packument ( ) 
126-       . then ( packument  =>  pickManifest ( packument ,  this . spec . fetchSpec ,  { 
127-         ...this . opts , 
128-         defaultTag : this . defaultTag , 
129-         before : this . before , 
130-       } )  /* XXX add ETARGET and E403 revalidation of cached packuments here */ ) 
131-       . then ( mani  =>  { 
132-         // add _resolved and _integrity from dist object 
133-         const  {  dist }  =  mani 
134-         if  ( dist )  { 
135-           this . resolved  =  mani . _resolved  =  dist . tarball 
136-           mani . _from  =  this . from 
137-           const  distIntegrity  =  dist . integrity  ? ssri . parse ( dist . integrity ) 
138-             : dist . shasum  ? ssri . fromHex ( dist . shasum ,  'sha1' ,  {  ...this . opts  } ) 
139-             : null 
140-           if  ( distIntegrity )  { 
141-             if  ( ! this . integrity )  { 
142-               this . integrity  =  distIntegrity 
143-             }  else  if  ( ! this . integrity . match ( distIntegrity ) )  { 
144-               // only bork if they have algos in common. 
145-               // otherwise we end up breaking if we have saved a sha512 
146-               // previously for the tarball, but the manifest only 
147-               // provides a sha1, which is possible for older publishes. 
148-               // Otherwise, this is almost certainly a case of holding it 
149-               // wrong, and will result in weird or insecure behavior 
150-               // later on when building package tree. 
151-               for  ( const  algo  of  Object . keys ( this . integrity ) )  { 
152-                 if  ( distIntegrity [ algo ] )  { 
153-                   throw  Object . assign ( new  Error ( 
154-                     `Integrity checksum failed when using ${ algo }   + 
155-                     `wanted ${ this . integrity } ${ distIntegrity }  
156-                   ) ,  {  code : 'EINTEGRITY'  } ) 
157-                 } 
158-               } 
159-               // made it this far, the integrity is worthwhile.  accept it. 
160-               // the setter here will take care of merging it into what we 
161-               // already had. 
162-               this . integrity  =  distIntegrity 
124+     const  packument  =  await  this . packument ( ) 
125+     const  mani  =  await  pickManifest ( packument ,  this . spec . fetchSpec ,  { 
126+       ...this . opts , 
127+       defaultTag : this . defaultTag , 
128+       before : this . before , 
129+     } ) 
130+     /* XXX add ETARGET and E403 revalidation of cached packuments here */ 
131+ 
132+     // add _resolved and _integrity from dist object 
133+     const  {  dist }  =  mani 
134+     if  ( dist )  { 
135+       this . resolved  =  mani . _resolved  =  dist . tarball 
136+       mani . _from  =  this . from 
137+       const  distIntegrity  =  dist . integrity  ? ssri . parse ( dist . integrity ) 
138+         : dist . shasum  ? ssri . fromHex ( dist . shasum ,  'sha1' ,  {  ...this . opts  } ) 
139+         : null 
140+       if  ( distIntegrity )  { 
141+         if  ( this . integrity  &&  ! this . integrity . match ( distIntegrity ) )  { 
142+           // only bork if they have algos in common. 
143+           // otherwise we end up breaking if we have saved a sha512 
144+           // previously for the tarball, but the manifest only 
145+           // provides a sha1, which is possible for older publishes. 
146+           // Otherwise, this is almost certainly a case of holding it 
147+           // wrong, and will result in weird or insecure behavior 
148+           // later on when building package tree. 
149+           for  ( const  algo  of  Object . keys ( this . integrity ) )  { 
150+             if  ( distIntegrity [ algo ] )  { 
151+               throw  Object . assign ( new  Error ( 
152+                 `Integrity checksum failed when using ${ algo }   + 
153+                 `wanted ${ this . integrity } ${ distIntegrity }  
154+               ) ,  {  code : 'EINTEGRITY'  } ) 
163155            } 
164156          } 
165157        } 
166-         if  ( this . integrity )  { 
167-           mani . _integrity  =  String ( this . integrity ) 
168-           if  ( dist . signatures )  { 
158+         // made it this far, the integrity is worthwhile.  accept it. 
159+         // the setter here will take care of merging it into what we already 
160+         // had. 
161+         this . integrity  =  distIntegrity 
162+       } 
163+     } 
164+     if  ( this . integrity )  { 
165+       mani . _integrity  =  String ( this . integrity ) 
166+       if  ( dist . signatures )  { 
167+         if  ( this . opts . verifySignatures )  { 
168+           if  ( this . registryKeys )  { 
169+             // validate and throw on error, then set _signatures 
170+             const  message  =  `${ mani . _id } ${ mani . _integrity }  
171+             for  ( const  signature  of  dist . signatures )  { 
172+               const  publicKey  =  this . registryKeys . filter ( key  =>  ( key . keyid  ===  signature . keyid ) ) [ 0 ] 
173+               if  ( ! publicKey )  { 
174+                 throw  Object . assign ( new  Error ( 
175+                   `${ mani . _id } ${ signature . keyid }   + 
176+                   'but no corresponding public key can be found.' 
177+                 ) ,  {  code : 'EMISSINGSIGNATUREKEY'  } ) 
178+               } 
179+               const  validPublicKey  = 
180+                 ! publicKey . expires  ||  ( Date . parse ( publicKey . expires )  >  Date . now ( ) ) 
181+               if  ( ! validPublicKey )  { 
182+                 throw  Object . assign ( new  Error ( 
183+                   `${ mani . _id } ${ signature . keyid }   + 
184+                   `but the corresponding public key has expired ${ publicKey . expires }  
185+                 ) ,  {  code : 'EEXPIREDSIGNATUREKEY'  } ) 
186+               } 
187+               const  verifier  =  crypto . createVerify ( 'SHA256' ) 
188+               verifier . write ( message ) 
189+               verifier . end ( ) 
190+               const  valid  =  verifier . verify ( 
191+                 publicKey . pemkey , 
192+                 signature . sig , 
193+                 'base64' 
194+               ) 
195+               if  ( ! valid )  { 
196+                 throw  Object . assign ( new  Error ( 
197+                   'Integrity checksum signature failed: '  + 
198+                   `key ${ publicKey . keyid } ${ signature . sig }  
199+                 ) ,  {  code : 'EINTEGRITYSIGNATURE'  } ) 
200+               } 
201+             } 
169202            mani . _signatures  =  dist . signatures 
170203          } 
204+           // if no keys, don't set _signatures 
205+         }  else  { 
206+           mani . _signatures  =  dist . signatures 
171207        } 
172-         this . package  =  rpj . normalize ( mani ) 
173-         return  this . package 
174-       } ) 
208+       } 
209+     } 
210+     this . package  =  rpj . normalize ( mani ) 
211+     return  this . package 
175212  } 
176213
177214  [ _tarballFromResolved ]  ( )  { 
0 commit comments