@@ -56,6 +56,7 @@ export class SignedXml {
56
56
keyInfoAttributes : { [ attrName : string ] : string } = { } ;
57
57
getKeyInfoContent = SignedXml . getKeyInfoContent ;
58
58
getCertFromKeyInfo = SignedXml . getCertFromKeyInfo ;
59
+ getObjectContent = SignedXml . getObjectContent ;
59
60
60
61
// Internal state
61
62
private id = 0 ;
@@ -126,6 +127,16 @@ export class SignedXml {
126
127
127
128
static noop = ( ) => null ;
128
129
130
+ /**
131
+ * Default implementation for getObjectContent that returns null (no Objects)
132
+ */
133
+ static getObjectContent ( ) : Array < {
134
+ content : string ;
135
+ attributes ?: Record < string , string | undefined > ;
136
+ } > | null {
137
+ return null ;
138
+ }
139
+
129
140
/**
130
141
* The SignedXml constructor provides an abstraction for sign and verify xml documents. The object is constructed using
131
142
* @param options {@link SignedXmlOptions }
@@ -143,6 +154,7 @@ export class SignedXml {
143
154
keyInfoAttributes,
144
155
getKeyInfoContent,
145
156
getCertFromKeyInfo,
157
+ getObjectContent,
146
158
} = options ;
147
159
148
160
// Options
@@ -164,6 +176,7 @@ export class SignedXml {
164
176
this . keyInfoAttributes = keyInfoAttributes ?? this . keyInfoAttributes ;
165
177
this . getKeyInfoContent = getKeyInfoContent ?? this . getKeyInfoContent ;
166
178
this . getCertFromKeyInfo = getCertFromKeyInfo ?? SignedXml . noop ;
179
+ this . getObjectContent = getObjectContent ?? this . getObjectContent ;
167
180
this . CanonicalizationAlgorithms ;
168
181
this . HashAlgorithms ;
169
182
this . SignatureAlgorithms ;
@@ -796,6 +809,7 @@ export class SignedXml {
796
809
* @param digestValue The expected digest value for the reference.
797
810
* @param inclusiveNamespacesPrefixList The prefix list for inclusive namespace canonicalization.
798
811
* @param isEmptyUri Indicates whether the URI is empty. Defaults to `false`.
812
+ * @param isSignatureReference Indicates whether this reference points to an element in the signature itself (like an Object element).
799
813
*/
800
814
addReference ( {
801
815
xpath,
@@ -805,6 +819,7 @@ export class SignedXml {
805
819
digestValue,
806
820
inclusiveNamespacesPrefixList = [ ] ,
807
821
isEmptyUri = false ,
822
+ isSignatureReference = false ,
808
823
} : Partial < Reference > & Pick < Reference , "xpath" > ) : void {
809
824
if ( digestAlgorithm == null ) {
810
825
throw new Error ( "digestAlgorithm is required" ) ;
@@ -822,6 +837,7 @@ export class SignedXml {
822
837
digestValue,
823
838
inclusiveNamespacesPrefixList,
824
839
isEmptyUri,
840
+ isSignatureReference,
825
841
getValidatedNode : ( ) => {
826
842
throw new Error (
827
843
"Reference has not been validated yet; Did you call `sig.checkSignature()`?" ,
@@ -965,6 +981,7 @@ export class SignedXml {
965
981
966
982
signatureXml += this . createSignedInfo ( doc , prefix ) ;
967
983
signatureXml += this . getKeyInfo ( prefix ) ;
984
+ signatureXml += this . getObjects ( prefix ) ;
968
985
signatureXml += `</${ currentPrefix } Signature>` ;
969
986
970
987
this . originalXmlWithIds = doc . toString ( ) ;
@@ -979,6 +996,9 @@ export class SignedXml {
979
996
const dummySignatureWrapper = `<Dummy ${ existingPrefixesString } >${ signatureXml } </Dummy>` ;
980
997
const nodeXml = new xmldom . DOMParser ( ) . parseFromString ( dummySignatureWrapper ) ;
981
998
999
+ // Process any signature references after the signature has been created
1000
+ this . processSignatureReferences ( nodeXml , prefix ) ;
1001
+
982
1002
// Because we are using a dummy wrapper hack described above, we know there will be a `firstChild`
983
1003
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
984
1004
const signatureDoc = nodeXml . documentElement . firstChild ! ;
@@ -1070,6 +1090,39 @@ export class SignedXml {
1070
1090
return "" ;
1071
1091
}
1072
1092
1093
+ /**
1094
+ * Creates XML for Object elements to be included in the signature
1095
+ *
1096
+ * @param prefix Optional namespace prefix
1097
+ * @returns XML string with Object elements or empty string if none
1098
+ */
1099
+ private getObjects ( prefix ?: string ) {
1100
+ const currentPrefix = prefix ? `${ prefix } :` : "" ;
1101
+ const objects = this . getObjectContent ?.( ) ;
1102
+
1103
+ if ( ! objects || objects . length === 0 ) {
1104
+ return "" ;
1105
+ }
1106
+
1107
+ let result = "" ;
1108
+
1109
+ for ( const obj of objects ) {
1110
+ let objectAttrs = "" ;
1111
+ if ( obj . attributes ) {
1112
+ Object . keys ( obj . attributes ) . forEach ( ( name ) => {
1113
+ const value = obj . attributes ?. [ name ] ;
1114
+ if ( value !== undefined ) {
1115
+ objectAttrs += ` ${ name } ="${ value } "` ;
1116
+ }
1117
+ } ) ;
1118
+ }
1119
+
1120
+ result += `<${ currentPrefix } Object${ objectAttrs } >${ obj . content } </${ currentPrefix } Object>` ;
1121
+ }
1122
+
1123
+ return result ;
1124
+ }
1125
+
1073
1126
/**
1074
1127
* Generate the Reference nodes (as part of the signature process)
1075
1128
*
@@ -1082,6 +1135,10 @@ export class SignedXml {
1082
1135
1083
1136
/* eslint-disable-next-line deprecation/deprecation */
1084
1137
for ( const ref of this . getReferences ( ) ) {
1138
+ if ( ref . isSignatureReference ) {
1139
+ // For signature references, we'll handle them separately after the signature is created
1140
+ continue ;
1141
+ }
1085
1142
const nodes = xpath . selectWithResolver ( ref . xpath ?? "" , doc , this . namespaceResolver ) ;
1086
1143
1087
1144
if ( ! utils . isArrayHasLength ( nodes ) ) {
@@ -1262,6 +1319,115 @@ export class SignedXml {
1262
1319
return doc . documentElement . firstChild ! ;
1263
1320
}
1264
1321
1322
+ /**
1323
+ * Process references that point to elements within the Signature element
1324
+ * This is called after the initial signature has been created
1325
+ */
1326
+ private processSignatureReferences ( signatureDoc : Document , prefix ?: string ) {
1327
+ // Get signature references
1328
+ const signatureReferences = this . references . filter ( ( ref ) => ref . isSignatureReference ) ;
1329
+ if ( signatureReferences . length === 0 ) {
1330
+ return ;
1331
+ }
1332
+
1333
+ prefix = prefix || "" ;
1334
+ prefix = prefix ? `${ prefix } :` : prefix ;
1335
+ const signatureNamespace = "http://www.w3.org/2000/09/xmldsig#" ;
1336
+
1337
+ // Find the SignedInfo element to append to
1338
+ const signedInfoNode = xpath . select1 (
1339
+ `.//*[local-name(.)='SignedInfo']` ,
1340
+ signatureDoc ,
1341
+ ) as Element ;
1342
+ if ( ! signedInfoNode ) {
1343
+ throw new Error ( "Could not find SignedInfo element in signature" ) ;
1344
+ }
1345
+
1346
+ // Process each signature reference
1347
+ for ( const ref of signatureReferences ) {
1348
+ const nodes = xpath . selectWithResolver ( ref . xpath ?? "" , signatureDoc , this . namespaceResolver ) ;
1349
+
1350
+ if ( ! utils . isArrayHasLength ( nodes ) ) {
1351
+ throw new Error (
1352
+ `the following xpath cannot be signed because it was not found: ${ ref . xpath } ` ,
1353
+ ) ;
1354
+ }
1355
+
1356
+ // Process the reference
1357
+ for ( const node of nodes ) {
1358
+ // Create the reference element directly using DOM methods to avoid namespace issues
1359
+ const referenceElem = signatureDoc . createElementNS (
1360
+ signatureNamespace ,
1361
+ `${ prefix } Reference` ,
1362
+ ) ;
1363
+ if ( ref . isEmptyUri ) {
1364
+ referenceElem . setAttribute ( "URI" , "" ) ;
1365
+ } else {
1366
+ const id = this . ensureHasId ( node ) ;
1367
+ ref . uri = id ;
1368
+ referenceElem . setAttribute ( "URI" , `#${ id } ` ) ;
1369
+ }
1370
+
1371
+ const transformsElem = signatureDoc . createElementNS (
1372
+ signatureNamespace ,
1373
+ `${ prefix } Transforms` ,
1374
+ ) ;
1375
+
1376
+ for ( const trans of ref . transforms || [ ] ) {
1377
+ const transform = this . findCanonicalizationAlgorithm ( trans ) ;
1378
+ const transformElem = signatureDoc . createElementNS (
1379
+ signatureNamespace ,
1380
+ `${ prefix } Transform` ,
1381
+ ) ;
1382
+ transformElem . setAttribute ( "Algorithm" , transform . getAlgorithmName ( ) ) ;
1383
+
1384
+ if ( utils . isArrayHasLength ( ref . inclusiveNamespacesPrefixList ) ) {
1385
+ const inclusiveNamespacesElem = signatureDoc . createElementNS (
1386
+ transform . getAlgorithmName ( ) ,
1387
+ "InclusiveNamespaces" ,
1388
+ ) ;
1389
+ inclusiveNamespacesElem . setAttribute (
1390
+ "PrefixList" ,
1391
+ ref . inclusiveNamespacesPrefixList . join ( " " ) ,
1392
+ ) ;
1393
+ transformElem . appendChild ( inclusiveNamespacesElem ) ;
1394
+ }
1395
+
1396
+ transformsElem . appendChild ( transformElem ) ;
1397
+ }
1398
+
1399
+ // Get the canonicalized XML
1400
+ const canonXml = this . getCanonReferenceXml ( signatureDoc , ref , node ) ;
1401
+
1402
+ // Calculate the digest
1403
+ const digestAlgorithm = this . findHashAlgorithm ( ref . digestAlgorithm ) ;
1404
+ const digestValue = digestAlgorithm . getHash ( canonXml ) ;
1405
+
1406
+ // Store the digest value for later validation
1407
+ ref . digestValue = digestValue ;
1408
+
1409
+ const digestMethodElem = signatureDoc . createElementNS (
1410
+ signatureNamespace ,
1411
+ `${ prefix } DigestMethod` ,
1412
+ ) ;
1413
+ digestMethodElem . setAttribute ( "Algorithm" , digestAlgorithm . getAlgorithmName ( ) ) ;
1414
+
1415
+ const digestValueElem = signatureDoc . createElementNS (
1416
+ signatureNamespace ,
1417
+ `${ prefix } DigestValue` ,
1418
+ ) ;
1419
+ digestValueElem . textContent = digestValue ;
1420
+
1421
+ referenceElem . appendChild ( transformsElem ) ;
1422
+ referenceElem . appendChild ( digestMethodElem ) ;
1423
+ referenceElem . appendChild ( digestValueElem ) ;
1424
+
1425
+ // Append the reference element to SignedInfo
1426
+ signedInfoNode . appendChild ( referenceElem ) ;
1427
+ }
1428
+ }
1429
+ }
1430
+
1265
1431
/**
1266
1432
* Returns just the signature part, must be called only after {@link computeSignature}
1267
1433
*
0 commit comments