@@ -252,26 +252,72 @@ export class SignedXml {
252252 this . signedXml = xml ;
253253
254254 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+ }
255300
256301 if ( ! this . getReferences ( ) . every ( ( ref ) => this . validateReference ( ref , doc ) ) ) {
257302 if ( callback ) {
258- callback ( new Error ( "Could not validate all references" ) ) ;
303+ callback ( new Error ( "Could not validate all references" ) , false ) ;
259304 return ;
260305 }
261306
262307 return false ;
263308 }
264309
265- const signedInfoCanon = this . getCanonSignedInfoXml ( doc ) ;
310+ // Stage B: Take the signature algorithm and key and verify the SignatureValue against the canonicalized SignedInfo
266311 const signer = this . findSignatureAlgorithm ( this . signatureAlgorithm ) ;
267312 const key = this . getCertFromKeyInfo ( this . keyInfo ) || this . publicCert || this . privateKey ;
268313 if ( key == null ) {
269314 throw new Error ( "KeyInfo or publicCert or privateKey is required to validate signature" ) ;
270315 }
316+
271317 if ( callback ) {
272- signer . verifySignature ( signedInfoCanon , key , this . signatureValue , callback ) ;
318+ signer . verifySignature ( unverifiedSignedInfoCanon , key , this . signatureValue , callback ) ;
273319 } else {
274- const verified = signer . verifySignature ( signedInfoCanon , key , this . signatureValue ) ;
320+ const verified = signer . verifySignature ( unverifiedSignedInfoCanon , key , this . signatureValue ) ;
275321
276322 if ( verified === false ) {
277323 throw new Error (
@@ -295,6 +341,11 @@ export class SignedXml {
295341 if ( signedInfo . length === 0 ) {
296342 throw new Error ( "could not find SignedInfo element in the message" ) ;
297343 }
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+ }
298349
299350 if (
300351 this . canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ||
@@ -522,11 +573,43 @@ export class SignedXml {
522573 this . signatureAlgorithm = signatureAlgorithm . value as SignatureAlgorithmType ;
523574 }
524575
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" ,
529607 ) ;
608+ const signedInfoDoc = temporaryCanonSignedInfoXml . documentElement ;
609+
610+ this . references = [ ] ;
611+ const references = utils . findChildren ( signedInfoDoc , "Reference" ) ;
612+
530613 if ( ! utils . isArrayHasLength ( references ) ) {
531614 throw new Error ( "could not find any Reference elements" ) ;
532615 }
@@ -572,11 +655,15 @@ export class SignedXml {
572655 if ( nodes . length === 0 ) {
573656 throw new Error ( `could not find DigestValue node in reference ${ refNode . toString ( ) } ` ) ;
574657 }
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 ( ) } ` ) ;
578666 }
579- const digestValue = firstChild . data ;
580667
581668 const transforms : string [ ] = [ ] ;
582669 let inclusiveNamespacesPrefixList : string [ ] = [ ] ;
@@ -626,11 +713,14 @@ export class SignedXml {
626713 ) {
627714 transforms . push ( "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ) ;
628715 }
716+ const refUri = isDomNode . isElementNode ( refNode )
717+ ? refNode . getAttribute ( "URI" ) || undefined
718+ : undefined ;
629719
630720 this . addReference ( {
631721 transforms,
632722 digestAlgorithm : digestAlgo ,
633- uri : isDomNode . isElementNode ( refNode ) ? utils . findAttr ( refNode , "URI" ) ?. value : undefined ,
723+ uri : refUri ,
634724 digestValue,
635725 inclusiveNamespacesPrefixList,
636726 isEmptyUri : false ,
0 commit comments