@@ -252,26 +252,72 @@ export class SignedXml {
252
252
this . signedXml = xml ;
253
253
254
254
const doc = new xmldom . DOMParser ( ) . parseFromString ( xml ) ;
255
+ // Reset the references as only references from our re-parsed signedInfo node can be trusted
256
+ this . references = [ ] ;
257
+
258
+ const unverifiedSignedInfoCanon = this . getCanonSignedInfoXml ( doc ) ;
259
+ if ( ! unverifiedSignedInfoCanon ) {
260
+ if ( callback ) {
261
+ callback ( new Error ( "Canonical signed info cannot be empty" ) , false ) ;
262
+ return ;
263
+ }
264
+
265
+ throw new Error ( "Canonical signed info cannot be empty" ) ;
266
+ }
267
+
268
+ // unsigned, verify later to keep with consistent callback behavior
269
+ const parsedUnverifiedSignedInfo = new xmldom . DOMParser ( ) . parseFromString (
270
+ unverifiedSignedInfoCanon ,
271
+ "text/xml" ,
272
+ ) ;
273
+
274
+ const unverifiedSignedInfoDoc = parsedUnverifiedSignedInfo . documentElement ;
275
+ if ( ! unverifiedSignedInfoDoc ) {
276
+ if ( callback ) {
277
+ callback ( new Error ( "Could not parse unverifiedSignedInfoCanon into a document" ) , false ) ;
278
+ return ;
279
+ }
280
+
281
+ throw new Error ( "Could not parse unverifiedSignedInfoCanon into a document" ) ;
282
+ }
283
+
284
+ const references = utils . findChildren ( unverifiedSignedInfoDoc , "Reference" ) ;
285
+ if ( ! utils . isArrayHasLength ( references ) ) {
286
+ if ( callback ) {
287
+ callback ( new Error ( "could not find any Reference elements" ) , false ) ;
288
+ return ;
289
+ }
290
+
291
+ throw new Error ( "could not find any Reference elements" ) ;
292
+ }
293
+
294
+ // TODO: In a future release we'd like to load the Signature and its References at the same time,
295
+ // however, in the `.loadSignature()` method we don't have the entire document,
296
+ // which we need to to keep the inclusive namespaces
297
+ for ( const reference of references ) {
298
+ this . loadReference ( reference ) ;
299
+ }
255
300
256
301
if ( ! this . getReferences ( ) . every ( ( ref ) => this . validateReference ( ref , doc ) ) ) {
257
302
if ( callback ) {
258
- callback ( new Error ( "Could not validate all references" ) ) ;
303
+ callback ( new Error ( "Could not validate all references" ) , false ) ;
259
304
return ;
260
305
}
261
306
262
307
return false ;
263
308
}
264
309
265
- const signedInfoCanon = this . getCanonSignedInfoXml ( doc ) ;
310
+ // Stage B: Take the signature algorithm and key and verify the SignatureValue against the canonicalized SignedInfo
266
311
const signer = this . findSignatureAlgorithm ( this . signatureAlgorithm ) ;
267
312
const key = this . getCertFromKeyInfo ( this . keyInfo ) || this . publicCert || this . privateKey ;
268
313
if ( key == null ) {
269
314
throw new Error ( "KeyInfo or publicCert or privateKey is required to validate signature" ) ;
270
315
}
316
+
271
317
if ( callback ) {
272
- signer . verifySignature ( signedInfoCanon , key , this . signatureValue , callback ) ;
318
+ signer . verifySignature ( unverifiedSignedInfoCanon , key , this . signatureValue , callback ) ;
273
319
} else {
274
- const verified = signer . verifySignature ( signedInfoCanon , key , this . signatureValue ) ;
320
+ const verified = signer . verifySignature ( unverifiedSignedInfoCanon , key , this . signatureValue ) ;
275
321
276
322
if ( verified === false ) {
277
323
throw new Error (
@@ -295,6 +341,11 @@ export class SignedXml {
295
341
if ( signedInfo . length === 0 ) {
296
342
throw new Error ( "could not find SignedInfo element in the message" ) ;
297
343
}
344
+ if ( signedInfo . length > 1 ) {
345
+ throw new Error (
346
+ "could not get canonicalized signed info for a signature that contains multiple SignedInfo nodes" ,
347
+ ) ;
348
+ }
298
349
299
350
if (
300
351
this . canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ||
@@ -522,11 +573,43 @@ export class SignedXml {
522
573
this . signatureAlgorithm = signatureAlgorithm . value as SignatureAlgorithmType ;
523
574
}
524
575
525
- this . references = [ ] ;
526
- const references = xpath . select (
527
- ".//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference']" ,
528
- signatureNode ,
576
+ const signedInfoNodes = utils . findChildren ( this . signatureNode , "SignedInfo" ) ;
577
+ if ( ! utils . isArrayHasLength ( signedInfoNodes ) ) {
578
+ throw new Error ( "no signed info node found" ) ;
579
+ }
580
+ if ( signedInfoNodes . length > 1 ) {
581
+ throw new Error ( "could not load signature that contains multiple SignedInfo nodes" ) ;
582
+ }
583
+
584
+ // Try to operate on the c14n version of `signedInfo`. This forces the initial `getReferences()`
585
+ // API call to always return references that are loaded under the canonical `SignedInfo`
586
+ // in the case that the client access the `.references` **before** signature verification.
587
+
588
+ // Ensure canonicalization algorithm is exclusive, otherwise we'd need the entire document
589
+ let canonicalizationAlgorithmForSignedInfo = this . canonicalizationAlgorithm ;
590
+ if (
591
+ ! canonicalizationAlgorithmForSignedInfo ||
592
+ canonicalizationAlgorithmForSignedInfo ===
593
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ||
594
+ canonicalizationAlgorithmForSignedInfo ===
595
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
596
+ ) {
597
+ canonicalizationAlgorithmForSignedInfo = "http://www.w3.org/2001/10/xml-exc-c14n#" ;
598
+ }
599
+
600
+ const temporaryCanonSignedInfo = this . getCanonXml (
601
+ [ canonicalizationAlgorithmForSignedInfo ] ,
602
+ signedInfoNodes [ 0 ] ,
603
+ ) ;
604
+ const temporaryCanonSignedInfoXml = new xmldom . DOMParser ( ) . parseFromString (
605
+ temporaryCanonSignedInfo ,
606
+ "text/xml" ,
529
607
) ;
608
+ const signedInfoDoc = temporaryCanonSignedInfoXml . documentElement ;
609
+
610
+ this . references = [ ] ;
611
+ const references = utils . findChildren ( signedInfoDoc , "Reference" ) ;
612
+
530
613
if ( ! utils . isArrayHasLength ( references ) ) {
531
614
throw new Error ( "could not find any Reference elements" ) ;
532
615
}
@@ -572,11 +655,15 @@ export class SignedXml {
572
655
if ( nodes . length === 0 ) {
573
656
throw new Error ( `could not find DigestValue node in reference ${ refNode . toString ( ) } ` ) ;
574
657
}
575
- const firstChild = nodes [ 0 ] . firstChild ;
576
- if ( ! firstChild || ! ( "data" in firstChild ) ) {
577
- throw new Error ( `could not find the value of DigestValue in ${ nodes [ 0 ] . toString ( ) } ` ) ;
658
+ if ( nodes . length > 1 ) {
659
+ throw new Error (
660
+ `could not load reference for a node that contains multiple DigestValue nodes: ${ refNode . toString ( ) } ` ,
661
+ ) ;
662
+ }
663
+ const digestValue = nodes [ 0 ] . textContent ;
664
+ if ( ! digestValue ) {
665
+ throw new Error ( `could not find the value of DigestValue in ${ refNode . toString ( ) } ` ) ;
578
666
}
579
- const digestValue = firstChild . data ;
580
667
581
668
const transforms : string [ ] = [ ] ;
582
669
let inclusiveNamespacesPrefixList : string [ ] = [ ] ;
@@ -626,11 +713,14 @@ export class SignedXml {
626
713
) {
627
714
transforms . push ( "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ) ;
628
715
}
716
+ const refUri = isDomNode . isElementNode ( refNode )
717
+ ? refNode . getAttribute ( "URI" ) || undefined
718
+ : undefined ;
629
719
630
720
this . addReference ( {
631
721
transforms,
632
722
digestAlgorithm : digestAlgo ,
633
- uri : isDomNode . isElementNode ( refNode ) ? utils . findAttr ( refNode , "URI" ) ?. value : undefined ,
723
+ uri : refUri ,
634
724
digestValue,
635
725
inclusiveNamespacesPrefixList,
636
726
isEmptyUri : false ,
0 commit comments