@@ -10,6 +10,7 @@ import type {
1010 HashAlgorithmType ,
1111 ObjectAttributes ,
1212 Reference ,
13+ AttachmentReference ,
1314 SignatureAlgorithm ,
1415 SignatureAlgorithmType ,
1516 SignedXmlOptions ,
@@ -73,6 +74,11 @@ export class SignedXml {
7374 * @see {@link Reference }
7475 */
7576 private references : Reference [ ] = [ ] ;
77+ /**
78+ * Contains the attachment references that were signed.
79+ * @see {@link Reference }
80+ */
81+ private attachmentReferences : AttachmentReference [ ] = [ ] ;
7682
7783 /**
7884 * Contains the canonicalized XML of the references that were validly signed.
@@ -841,6 +847,56 @@ export class SignedXml {
841847 } ) ;
842848 }
843849
850+ /**
851+ * Adds an attachment reference to the signature.
852+ *
853+ * @param attachment The attachment to be referenced.
854+ * @param transforms An array of transform algorithms to be applied to the selected nodes.
855+ * @param digestAlgorithm The digest algorithm to use for computing the digest value.
856+ * @param uri The URI identifier for the reference. If empty, an empty URI will be used.
857+ * @param digestValue The expected digest value for the reference.
858+ * @param inclusiveNamespacesPrefixList The prefix list for inclusive namespace canonicalization.
859+ * @param isEmptyUri Indicates whether the URI is empty. Defaults to `false`.
860+ * @param id An optional `Id` attribute for the reference.
861+ * @param type An optional `Type` attribute for the reference.
862+ */
863+ addAttachmentReference ( {
864+ attachment,
865+ transforms,
866+ digestAlgorithm,
867+ uri = "" ,
868+ digestValue,
869+ inclusiveNamespacesPrefixList = [ ] ,
870+ isEmptyUri = false ,
871+ id = undefined ,
872+ type = undefined ,
873+ } : Partial < Reference > & Pick < AttachmentReference , "attachment" > ) : void {
874+ if ( digestAlgorithm == null ) {
875+ throw new Error ( "digestAlgorithm is required" ) ;
876+ }
877+
878+ if ( ! utils . isArrayHasLength ( transforms ) ) {
879+ throw new Error ( "transforms must contain at least one transform algorithm" ) ;
880+ }
881+
882+ this . attachmentReferences . push ( {
883+ attachment,
884+ transforms,
885+ digestAlgorithm,
886+ uri,
887+ digestValue,
888+ inclusiveNamespacesPrefixList,
889+ isEmptyUri,
890+ id,
891+ type,
892+ getValidatedNode : ( ) => {
893+ throw new Error (
894+ "Reference has not been validated yet; Did you call `sig.checkSignature()`?" ,
895+ ) ;
896+ } ,
897+ } ) ;
898+ }
899+
844900 /**
845901 * Returns the list of references.
846902 */
@@ -855,6 +911,20 @@ export class SignedXml {
855911 return this . references ;
856912 }
857913
914+ /**
915+ * Returns the list of attachment references.
916+ */
917+ getAttachmentReferences ( ) {
918+ // TODO: Refactor once `getValidatedNode` is removed
919+ /* Once we completely remove the deprecated `getValidatedNode()` method,
920+ we can change this to return a clone to prevent accidental mutations,
921+ e.g.:
922+ return [...this.attachmentReferences];
923+ */
924+
925+ return this . attachmentReferences ;
926+ }
927+
858928 getSignedReferences ( ) {
859929 return [ ...this . signedReferences ] ;
860930 }
@@ -1209,6 +1279,82 @@ export class SignedXml {
12091279 signedInfoNode . appendChild ( referenceElem ) ;
12101280 }
12111281 }
1282+
1283+ // Process each attachment reference
1284+ for ( const ref of this . getAttachmentReferences ( ) ) {
1285+ // Compute the target URI
1286+ let targetUri : string ;
1287+ if ( ref . isEmptyUri ) {
1288+ targetUri = "" ;
1289+ } else {
1290+ targetUri = `#${ ref . uri } ` ;
1291+ }
1292+
1293+ // Create the reference element directly using DOM methods to avoid namespace issues
1294+ const referenceElem = signatureDoc . createElementNS (
1295+ signatureNamespace ,
1296+ `${ currentPrefix } Reference` ,
1297+ ) ;
1298+ referenceElem . setAttribute ( "URI" , targetUri ) ;
1299+
1300+ if ( ref . id ) {
1301+ referenceElem . setAttribute ( "Id" , ref . id ) ;
1302+ }
1303+
1304+ if ( ref . type ) {
1305+ referenceElem . setAttribute ( "Type" , ref . type ) ;
1306+ }
1307+
1308+ const transformsElem = signatureDoc . createElementNS (
1309+ signatureNamespace ,
1310+ `${ currentPrefix } Transforms` ,
1311+ ) ;
1312+
1313+ for ( const trans of ref . transforms || [ ] ) {
1314+ const transform = this . findCanonicalizationAlgorithm ( trans ) ;
1315+ const transformElem = signatureDoc . createElementNS (
1316+ signatureNamespace ,
1317+ `${ currentPrefix } Transform` ,
1318+ ) ;
1319+ transformElem . setAttribute ( "Algorithm" , transform . getAlgorithmName ( ) ) ;
1320+
1321+ if ( utils . isArrayHasLength ( ref . inclusiveNamespacesPrefixList ) ) {
1322+ const inclusiveNamespacesElem = signatureDoc . createElementNS (
1323+ transform . getAlgorithmName ( ) ,
1324+ "InclusiveNamespaces" ,
1325+ ) ;
1326+ inclusiveNamespacesElem . setAttribute (
1327+ "PrefixList" ,
1328+ ref . inclusiveNamespacesPrefixList . join ( " " ) ,
1329+ ) ;
1330+ transformElem . appendChild ( inclusiveNamespacesElem ) ;
1331+ }
1332+
1333+ transformsElem . appendChild ( transformElem ) ;
1334+ }
1335+
1336+ // Get the digest algorithm and compute the digest value
1337+ const digestAlgorithm = this . findHashAlgorithm ( ref . digestAlgorithm ) ;
1338+
1339+ const digestMethodElem = signatureDoc . createElementNS (
1340+ signatureNamespace ,
1341+ `${ currentPrefix } DigestMethod` ,
1342+ ) ;
1343+ digestMethodElem . setAttribute ( "Algorithm" , digestAlgorithm . getAlgorithmName ( ) ) ;
1344+
1345+ const digestValueElem = signatureDoc . createElementNS (
1346+ signatureNamespace ,
1347+ `${ currentPrefix } DigestValue` ,
1348+ ) ;
1349+ digestValueElem . textContent = digestAlgorithm . getHash ( ref . attachment ) ;
1350+
1351+ referenceElem . appendChild ( transformsElem ) ;
1352+ referenceElem . appendChild ( digestMethodElem ) ;
1353+ referenceElem . appendChild ( digestValueElem ) ;
1354+
1355+ // Append the reference element to SignedInfo
1356+ signedInfoNode . appendChild ( referenceElem ) ;
1357+ }
12121358 }
12131359
12141360 private getKeyInfo ( prefix ) {
0 commit comments