@@ -8,6 +8,7 @@ import type {
88 GetKeyInfoContentArgs ,
99 HashAlgorithm ,
1010 HashAlgorithmType ,
11+ IdAttributeType ,
1112 ObjectAttributes ,
1213 Reference ,
1314 SignatureAlgorithm ,
@@ -29,7 +30,7 @@ import * as utils from "./utils";
2930
3031export class SignedXml {
3132 idMode ?: "wssecurity" ;
32- idAttributes : string [ ] ;
33+ idAttributes : IdAttributeType [ ] ;
3334 /**
3435 * A {@link Buffer} or pem encoded {@link String} containing your private key
3536 */
@@ -53,6 +54,7 @@ export class SignedXml {
5354 throw new Error ( "Not implemented" ) ;
5455 } ,
5556 } ;
57+ private maxTransforms : number | null ;
5658 implicitTransforms : ReadonlyArray < CanonicalizationOrTransformAlgorithmType > = [ ] ;
5759 keyInfoAttributes : { [ attrName : string ] : string } = { } ;
5860 getKeyInfoContent = SignedXml . getKeyInfoContent ;
@@ -137,11 +139,13 @@ export class SignedXml {
137139 const {
138140 idMode,
139141 idAttribute,
142+ idAttributes,
140143 privateKey,
141144 publicCert,
142145 signatureAlgorithm,
143146 canonicalizationAlgorithm,
144147 inclusiveNamespacesPrefixList,
148+ maxTransforms,
145149 implicitTransforms,
146150 keyInfoAttributes,
147151 getKeyInfoContent,
@@ -151,7 +155,7 @@ export class SignedXml {
151155
152156 // Options
153157 this . idMode = idMode ;
154- this . idAttributes = [ "Id" , "ID" , "id" ] ;
158+ this . idAttributes = idAttributes ?? [ "Id" , "ID" , "id" ] ;
155159 if ( idAttribute ) {
156160 this . idAttributes . unshift ( idAttribute ) ;
157161 }
@@ -164,6 +168,7 @@ export class SignedXml {
164168 } else if ( utils . isArrayHasLength ( inclusiveNamespacesPrefixList ) ) {
165169 this . inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList ;
166170 }
171+ this . maxTransforms = maxTransforms ?? null ;
167172 this . implicitTransforms = implicitTransforms ?? this . implicitTransforms ;
168173 this . keyInfoAttributes = keyInfoAttributes ?? this . keyInfoAttributes ;
169174 this . getKeyInfoContent = getKeyInfoContent ?? this . getKeyInfoContent ;
@@ -502,11 +507,18 @@ export class SignedXml {
502507 for ( const ref of this . getReferences ( ) ) {
503508 const uri = ref . uri ?. [ 0 ] === "#" ? ref . uri . substring ( 1 ) : ref . uri ;
504509
505- for ( const attr of this . idAttributes ) {
506- const elemId = elem . getAttribute ( attr ) ;
507- if ( uri === elemId ) {
508- ref . xpath = `//*[@*[local-name(.)='${ attr } ']='${ uri } ']` ;
509- break ; // found the correct element, no need to check further
510+ for ( const idAttr of this . idAttributes ) {
511+ if ( typeof idAttr === "string" ) {
512+ if ( uri === elem . getAttribute ( idAttr ) ) {
513+ ref . xpath = `//*[@*[local-name(.)='${ idAttr } ']='${ uri } ']` ;
514+ break ; // found the correct element, no need to check further
515+ }
516+ } else {
517+ const attr = utils . findAttr ( elem , idAttr . localName , idAttr . namespaceUri ) ;
518+ if ( attr && uri === attr . value ) {
519+ ref . xpath = `//*[@*[local-name(.)='${ idAttr . localName } ' and namespace-uri(.)='${ idAttr . namespaceUri } ']='${ uri } ']` ;
520+ break ; // found the correct element, no need to check further
521+ }
510522 }
511523 }
512524
@@ -533,8 +545,19 @@ export class SignedXml {
533545 throw new Error ( "Cannot validate a uri with quotes inside it" ) ;
534546 } else {
535547 let num_elements_for_id = 0 ;
536- for ( const attr of this . idAttributes ) {
537- const tmp_elemXpath = `//*[@*[local-name(.)='${ attr } ']='${ uri } ']` ;
548+ for ( const idAttr of this . idAttributes ) {
549+ let tmp_elemXpath : string ;
550+
551+ if ( typeof idAttr === "string" ) {
552+ tmp_elemXpath = `//*[@*[local-name(.)='${ idAttr } ']='${ uri } ']` ;
553+ } else {
554+ if ( idAttr . namespaceUri ) {
555+ tmp_elemXpath = `//*[@*[local-name(.)='${ idAttr . localName } ' and namespace-uri(.)='${ idAttr . namespaceUri } ']='${ uri } ']` ;
556+ } else {
557+ tmp_elemXpath = `//*[@*[local-name(.)='${ idAttr . localName } ']='${ uri } ']` ;
558+ }
559+ }
560+
538561 const tmp_elem = xpath . select ( tmp_elemXpath , doc ) ;
539562 if ( utils . isArrayHasLength ( tmp_elem ) ) {
540563 num_elements_for_id += tmp_elem . length ;
@@ -781,6 +804,14 @@ export class SignedXml {
781804 ? refNode . getAttribute ( "URI" ) || undefined
782805 : undefined ;
783806
807+ if ( this . maxTransforms ) {
808+ if ( transforms . length > this . maxTransforms ) {
809+ throw new Error (
810+ `Number of transforms (${ transforms . length } ) exceeds the maximum allowed (${ this . maxTransforms } )` ,
811+ ) ;
812+ }
813+ }
814+
784815 this . addReference ( {
785816 transforms,
786817 digestAlgorithm : digestAlgo ,
@@ -1305,7 +1336,11 @@ export class SignedXml {
13051336 ) ;
13061337 } else {
13071338 this . idAttributes . some ( ( idAttribute ) => {
1308- attr = utils . findAttr ( node , idAttribute ) ;
1339+ if ( typeof idAttribute === "string" ) {
1340+ attr = utils . findAttr ( node , idAttribute ) ;
1341+ } else {
1342+ attr = utils . findAttr ( node , idAttribute . localName , idAttribute . namespaceUri ) ;
1343+ }
13091344 return ! ! attr ; // This will break the loop as soon as a truthy attr is found.
13101345 } ) ;
13111346 }
@@ -1329,7 +1364,26 @@ export class SignedXml {
13291364 id ,
13301365 ) ;
13311366 } else {
1332- node . setAttribute ( "Id" , id ) ;
1367+ // Use the first idAttribute to set the new ID
1368+ const firstIdAttr = this . idAttributes [ 0 ] ;
1369+ if ( typeof firstIdAttr === "string" ) {
1370+ node . setAttribute ( firstIdAttr , id ) ;
1371+ } else {
1372+ if ( firstIdAttr . prefix && firstIdAttr . namespaceUri ) {
1373+ node . setAttributeNS (
1374+ "http://www.w3.org/2000/xmlns/" ,
1375+ `xmlns:${ firstIdAttr . prefix } ` ,
1376+ firstIdAttr . namespaceUri ,
1377+ ) ;
1378+ node . setAttributeNS (
1379+ firstIdAttr . namespaceUri ,
1380+ `${ firstIdAttr . prefix } :${ firstIdAttr . localName } ` ,
1381+ id ,
1382+ ) ;
1383+ } else {
1384+ node . setAttribute ( firstIdAttr . localName , id ) ;
1385+ }
1386+ }
13331387 }
13341388
13351389 return id ;
0 commit comments