Fixing "Incorrect reference digest value" in SOAP XML WS-Security with node-soap
Problem
I'm experiencing an intermittent issue with SOAP XML requests to an external service using the node-soap library (v1.1.10) and xml-crypto (v6.0.1). The server rejects some requests with the following error:
{
"faultcode": "soapenv:Client",
"faultstring": "Incorrect reference digest value",
"detail": {
"IFException": {
"reasonCode": "0x00d30003",
"reasonText": "Incorrect reference digest value"
}
}
}
After examining the logs, I discovered what I think is the root cause: duplicate IDs in the XML document. Specifically, both the Timestamp element and the Body element have the same ID (_1):
<Timestamp xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="_1">
<Created>2025-11-05T13:30:00Z</Created>
<Expires>2025-11-05T13:40:00Z</Expires>
</Timestamp>
<!-- Later in the document -->
<soap:Body Id="_1">
<!-- Body content -->
</soap:Body>
The signature then references this ID twice, with different digest values:
<Reference URI="#_1">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>fSZ3uhjr1D+zGS4eh0yTiW56g3JKiOs+ozpkk0QOu2w=</DigestValue>
</Reference>
<Reference URI="#_1">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>0G8pJmk3ObO7+IerCol9PtWuUllUzLkvZBiztv3jLBk=</DigestValue>
</Reference>
I've tried to fix this by adding a unique ID to the Body element:
const wsSecurityOptions = {
signerOptions: {
// Set a unique ID for the Body element to avoid reference conflicts
attrs: {
Id: `_body_${Date.now()}`,
},
},
};
But this didn't resolve the issue. The problem seems to be that the node-soap library is still generating duplicate IDs.
Detailed Analysis
Looking at the WSSecurityCert.js implementation in the node-soap library, I found that it creates a timestamp with a hardcoded ID of _1:
// From WSSecurityCert.js
if (this.hasTimeStamp) {
timestampStr = `<Timestamp xmlns="${oasisBaseUri}/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="_1">` +
`<Created>${this.created}</Created>` +
`<Expires>${this.expires}</Expires>` +
`</Timestamp>`;
}
Meanwhile, when setting the Body ID, it's using the same ID or a dynamic one that might conflict:
// This is what I tried to customize
const wsSecurityOptions = {
signerOptions: {
attrs: {
Id: `_body_${Date.now()}`, // Still conflicts sometimes
},
},
};
Potential Solutions
1. Modify the XML after generation but before sending
One approach is to modify the XML after it's generated but before it's sent to ensure unique IDs:
// Hook into the request event
client.on('request', (xml) => {
// Replace the Body ID with a unique one that won't conflict with the Timestamp
const modifiedXml = xml.replace(
/<soap:Body Id="_1"/g,
'<soap:Body Id="_body"'
);
// Use the modified XML instead
return modifiedXml;
});
2. Patch the WSSecurityCert class
A more robust solution would be to patch the WSSecurityCert class to use different IDs for the Timestamp and Body:
// Create a custom WSSecurityCert class
class CustomWSSecurityCert extends WSSecurityCert {
postProcess(xml, envelopeKey) {
// Override the timestamp ID
this.timestampId = "_timestamp";
// Generate timestamp with custom ID
if (this.hasTimeStamp) {
timestampStr = `<Timestamp xmlns="${oasisBaseUri}/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="${this.timestampId}">` +
`<Created>${this.created}</Created>` +
`<Expires>${this.expires}</Expires>` +
`</Timestamp>`;
}
// Continue with the rest of the original method
// ...
// Make sure references use the correct IDs
// ...
}
}
3. Use a different WS-Security implementation
If patching is too complex, consider using a different WS-Security implementation:
// Example using a different library
const wsSecurity = require('ws-security-custom');
const securityHandler = new wsSecurity.SecurityHandler({
privateKey: fs.readFileSync('key.pem'),
certificate: fs.readFileSync('cert.pem'),
timestampId: '_timestamp',
bodyId: '_body'
});
4. Modify the XML after signing but before sending
If the above solutions don't work, you could try a more direct approach by modifying the XML after it's signed but before it's sent:
// Hook into the request event
client.on('request', (xml) => {
// First, check if we have duplicate IDs
if ((xml.match(/Id="_1"/g) || []).length > 1) {
// Replace the second occurrence (Body) with a different ID
let count = 0;
const modifiedXml = xml.replace(/Id="_1"/g, (match) => {
count++;
return count === 1 ? match : 'Id="_body"';
});
// Also update references in the signature
// This is tricky and might require more complex XML parsing
return modifiedXml;
}
return xml;
});
Questions
- Has anyone encountered this specific issue with duplicate IDs in node-soap WS-Security?
- Is there a cleaner way to ensure unique IDs for different XML elements when using node-soap?
- Are there any known patches or workarounds for this issue in the latest versions of node-soap?
- Would it be better to fork and modify the node-soap library, or handle this at the application level?
Any insights or solutions would be greatly appreciated!