@@ -17,14 +17,21 @@ import { Dictionary, parseHeader, quoteString } from '../structured-header';
17
17
import {
18
18
Request ,
19
19
Response ,
20
+ SignatureParameters ,
20
21
SignConfig ,
21
22
VerifyConfig ,
22
23
defaultParams ,
23
24
isRequest ,
24
25
CommonConfig ,
25
26
VerifyingKey ,
26
27
} from '../types' ;
27
- import { UnsupportedAlgorithmError } from '../errors' ;
28
+ import {
29
+ ExpiredError ,
30
+ MalformedSignatureError ,
31
+ UnacceptableSignatureError ,
32
+ UnknownKeyError ,
33
+ UnsupportedAlgorithmError ,
34
+ } from '../errors' ;
28
35
29
36
export function deriveComponent ( component : string , params : Map < string , string | number | boolean > , res : Response , req ?: Request ) : string [ ] ;
30
37
export function deriveComponent ( component : string , params : Map < string , string | number | boolean > , req : Request ) : string [ ] ;
@@ -49,7 +56,7 @@ export function deriveComponent(component: string, params: Map<string, string |
49
56
return [ context . method . toUpperCase ( ) ] ;
50
57
case '@target-uri' : {
51
58
if ( ! isRequest ( context ) ) {
52
- throw new Error ( 'Cannot derive @target-url on response' ) ;
59
+ throw new Error ( 'Cannot derive @target-uri on response' ) ;
53
60
}
54
61
return [ context . url . toString ( ) ] ;
55
62
}
@@ -248,7 +255,7 @@ export function createSigningParameters(config: SignConfig): Parameters {
248
255
}
249
256
default :
250
257
if ( config . paramValues ?. [ paramName ] instanceof Date ) {
251
- value = Math . floor ( ( config . paramValues [ paramName ] as Date ) . getTime ( ) / 1000 ) . toString ( ) ;
258
+ value = Math . floor ( ( config . paramValues [ paramName ] as Date ) . getTime ( ) / 1000 ) ;
252
259
} else if ( config . paramValues ?. [ paramName ] ) {
253
260
value = config . paramValues [ paramName ] as string ;
254
261
}
@@ -287,7 +294,7 @@ export function augmentHeaders(headers: Record<string, string | string[]>, signa
287
294
let signatureName = name ?? 'sig' ;
288
295
if ( signatureHeader . has ( signatureName ) || inputHeader . has ( signatureName ) ) {
289
296
let count = 0 ;
290
- while ( signatureHeader ? .has ( `${ signatureName } ${ count } ` ) || inputHeader ? .has ( `${ signatureName } ${ count } ` ) ) {
297
+ while ( signatureHeader . has ( `${ signatureName } ${ count } ` ) || inputHeader . has ( `${ signatureName } ${ count } ` ) ) {
291
298
count ++ ;
292
299
}
293
300
signatureName += count . toString ( ) ;
@@ -308,7 +315,7 @@ export async function signMessage<T extends Request = Request>(config: SignConfi
308
315
export async function signMessage < T extends Request | Response = Request | Response , U extends Request = Request > ( config : SignConfig , message : T , req ?: U ) : Promise < T > {
309
316
const signingParameters = createSigningParameters ( config ) ;
310
317
const signatureBase = createSignatureBase ( {
311
- fields : config ? .fields ?? [ ] ,
318
+ fields : config . fields ?? [ ] ,
312
319
componentParser : config . componentParser ,
313
320
} , message as Response , req ) ;
314
321
const signatureInput = serializeList ( [
@@ -323,7 +330,7 @@ export async function signMessage<T extends Request | Response = Request | Respo
323
330
const signature = await config . key . sign ( Buffer . from ( base ) ) ;
324
331
return {
325
332
...message ,
326
- headers : augmentHeaders ( { ...message . headers } , signature , signatureInput , config . name ) ,
333
+ headers : augmentHeaders ( { ...message . headers } , signature , signatureInput , config . name ) ,
327
334
} ;
328
335
}
329
336
@@ -360,70 +367,68 @@ export async function verifyMessage(config: VerifyConfig, message: Request | Res
360
367
const requiredParams = config . requiredParams ?? [ ] ;
361
368
const requiredFields = config . requiredFields ?? [ ] ;
362
369
return Array . from ( signatureInputs . entries ( ) ) . reduce < Promise < boolean | null > > ( async ( prev , [ name , input ] ) => {
363
- const [ result , key ] : [ Error | boolean | null , VerifyingKey ] = await Promise . all ( [
370
+ const signatureParams : SignatureParameters = Array . from ( input [ 1 ] . entries ( ) ) . reduce ( ( params , [ key , value ] ) => {
371
+ if ( value instanceof ByteSequence ) {
372
+ Object . assign ( params , {
373
+ [ key ] : value . toBase64 ( ) ,
374
+ } ) ;
375
+ } else if ( value instanceof Token ) {
376
+ Object . assign ( params , {
377
+ [ key ] : value . toString ( ) ,
378
+ } ) ;
379
+ } else if ( key === 'created' || key === 'expired' ) {
380
+ Object . assign ( params , {
381
+ [ key ] : new Date ( ( value as number ) * 1000 ) ,
382
+ } ) ;
383
+ } else {
384
+ Object . assign ( params , {
385
+ [ key ] : value ,
386
+ } ) ;
387
+ }
388
+ return params ;
389
+ } , { } ) ;
390
+ const [ result , key ] : [ Error | boolean | null , VerifyingKey | null ] = await Promise . all ( [
364
391
prev . catch ( ( e ) => e ) ,
365
- config . keyLookup ( Array . from ( input [ 1 ] . entries ( ) ) . reduce ( ( params , [ key , value ] ) => {
366
- if ( value instanceof ByteSequence ) {
367
- Object . assign ( params , {
368
- [ key ] : value . toBase64 ( ) ,
369
- } ) ;
370
- } else if ( value instanceof Token ) {
371
- Object . assign ( params , {
372
- [ key ] : value . toString ( ) ,
373
- } ) ;
374
- } else {
375
- Object . assign ( params , {
376
- [ key ] : value ,
377
- } ) ;
378
- }
379
- return params ;
380
- } , { } ) ) ,
392
+ config . keyLookup ( signatureParams ) ,
381
393
] ) ;
382
- if ( input [ 1 ] . has ( 'alg' ) && key . algs ?. includes ( input [ 1 ] . get ( 'alg' ) as string ) === false ) {
383
- throw new UnsupportedAlgorithmError ( 'Unsupported key algorithm' ) ;
384
- }
385
394
// @todo - confirm this is all working as expected
386
- if ( ! config . all && ! key ) {
387
- return null ;
388
- }
389
- if ( ! config . all && result === true ) {
390
- return result ;
395
+ if ( config . all && ! key ) {
396
+ throw new UnknownKeyError ( 'Unknown key' ) ;
391
397
}
392
- if ( config . all && result !== true && result !== null ) {
398
+ if ( ! key ) {
393
399
if ( result instanceof Error ) {
394
400
throw result ;
395
401
}
396
402
return result ;
397
403
}
404
+ if ( input [ 1 ] . has ( 'alg' ) && key . algs ?. includes ( input [ 1 ] . get ( 'alg' ) as string ) === false ) {
405
+ throw new UnsupportedAlgorithmError ( 'Unsupported key algorithm' ) ;
406
+ }
398
407
if ( ! isInnerList ( input ) ) {
399
- throw new Error ( 'Malformed signature input' ) ;
408
+ throw new MalformedSignatureError ( 'Malformed signature input' ) ;
400
409
}
401
410
const hasRequiredParams = requiredParams . every ( ( param ) => input [ 1 ] . has ( param ) ) ;
402
411
if ( ! hasRequiredParams ) {
403
- return false ;
412
+ throw new UnacceptableSignatureError ( 'Missing required signature parameters' ) ;
404
413
}
405
414
// this could be tricky, what if we say "@method" but there is "@method;req"
406
415
const hasRequiredFields = requiredFields . every ( ( field ) => input [ 0 ] . some ( ( [ fieldName ] ) => fieldName === field ) ) ;
407
416
if ( ! hasRequiredFields ) {
408
- return false ;
417
+ throw new UnacceptableSignatureError ( 'Missing required signed fields' ) ;
409
418
}
410
419
if ( input [ 1 ] . has ( 'created' ) ) {
411
420
const created = input [ 1 ] . get ( 'created' ) as number - tolerance ;
412
421
// maxAge overrides expires.
413
422
// signature is older than maxAge
414
- if ( maxAge && created - now > maxAge ) {
415
- return false ;
416
- }
417
- // created after the allowed time (ie: created in the future)
418
- if ( created > notAfter ) {
419
- return false ;
423
+ if ( ( maxAge && now - created > maxAge ) || created > notAfter ) {
424
+ throw new ExpiredError ( 'Signature is too old' ) ;
420
425
}
421
426
}
422
427
if ( input [ 1 ] . has ( 'expires' ) ) {
423
428
const expires = input [ 1 ] . get ( 'expires' ) as number + tolerance ;
424
429
// expired signature
425
- if ( expires > now ) {
426
- return false ;
430
+ if ( now > expires ) {
431
+ throw new ExpiredError ( 'Signature has expired' ) ;
427
432
}
428
433
}
429
434
@@ -434,29 +439,11 @@ export async function verifyMessage(config: VerifyConfig, message: Request | Res
434
439
const base = formatSignatureBase ( signingBase ) ;
435
440
const signature = signatures . get ( name ) ;
436
441
if ( ! signature ) {
437
- throw new Error ( 'No signature found for inputs ' ) ;
442
+ throw new MalformedSignatureError ( 'No corresponding signature for input ' ) ;
438
443
}
439
444
if ( ! isByteSequence ( signature [ 0 ] as BareItem ) ) {
440
- throw new Error ( 'Malformed signature' ) ;
445
+ throw new MalformedSignatureError ( 'Malformed signature' ) ;
441
446
}
442
- return key . verify ( Buffer . from ( base ) , Buffer . from ( ( signature [ 0 ] as ByteSequence ) . toBase64 ( ) , 'base64' ) , Array . from ( input [ 1 ] . entries ( ) ) . reduce ( ( params , [ key , value ] ) => {
443
- let val : Date | number | string ;
444
- switch ( key . toLowerCase ( ) ) {
445
- case 'created' :
446
- case 'expires' :
447
- val = new Date ( ( value as number ) * 1000 ) ;
448
- break ;
449
- default : {
450
- if ( typeof value === 'string' || typeof value === 'number' ) {
451
- val = value ;
452
- } else {
453
- val = value . toString ( ) ;
454
- }
455
- }
456
- }
457
- return Object . assign ( params , {
458
- [ key ] : val ,
459
- } ) ;
460
- } , { } ) ) ;
447
+ return key . verify ( Buffer . from ( base ) , Buffer . from ( ( signature [ 0 ] as ByteSequence ) . toBase64 ( ) , 'base64' ) , signatureParams ) ;
461
448
} , Promise . resolve ( null ) ) ;
462
449
}
0 commit comments