Octavian
Octavian

Reputation: 279

How to change node-soap timestamp duration and prefix?

I am trying to consume a SOAP service using node-soap using WSSecurityCert. The service requires me to set the timestamp in such a way that the SOAP request has a validity of 5 minutes and wsu prefix. The node-soap library has 10 minutes of validity "hard-coded", with no obvious way to override it. I don't know how or if I can just modify the timestamp before it is sent, because the WSSecurityCert signature might be invalidated.

My code:

const client = await soap.createClientAsync(url);

const securityOptions = {
  hasTimeStamp: true,
}

const wsSecurity = new soap.WSSecurityCert(PRIVATE_KEY, PUBLIC_CERT, '', securityOptions);

client.setSecurity(wsSecurity);

const result = await client.method(args);

The generated timestamp looks like this:

<Timestamp
  xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
  Id="_1">
  <Created>2024-05-08T13:20:09Z</Created>
  <Expires>2024-05-08T13:30:09Z</Expires>
</Timestamp>

I need to make the timestamp look something like this:

<wsu:Timestamp wsu:Id="TS-7C14BF4AA3E26845E015637928928701">
  <wsu:Created>2024-05-08T13:20:09Z</wsu:Created>
  <wsu:Expires>2024-05-08T13:25:09Z</wsu:Expires>
</wsu:Timestamp>

I tried to add created and expires among other things to securityOptions, to no avail.

Is it possible to achieve this with the node-soap library without forking it?

Upvotes: 2

Views: 282

Answers (2)

josephdpurcell
josephdpurcell

Reputation: 1482

soap doesn't offer any way to customize the timestamp header, see the hardcoded lines: https://github.com/vpulim/node-soap/blob/master/src/security/WSSecurityCert.ts#L124.

But you can disable the library's timestamp generation and add your own.

Step 1: Disable hasTimestamp, add signing reference

const options: IWSSecurityCertOptions = {
  hasTimeStamp: false,
  additionalReferences: [
    'wsu:Timestamp',
    'wsa:To',
  ],
  signerOptions: {
    prefix: 'ds',
    attrs: { Id: 'Signature' },
    existingPrefixes: {
      wsse11: 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd',
    },
  },
};
const wsSecurity = new WSSecurityCert(privateKey, publicKey, password, options);
soapClient.setSecurity(wsSecurity);

Step 2: Add your own timestamp

const expiry = '2100-05-08T00:00:00Z'; // TODO: compute this
const id = 'TS-7C14BF4AA3E26845E015637928928701'; // TODO: compute this
soapClient.addSoapHeader((methodName, location, soapAction, args) => {
  return '<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1">' +
    `<wsu:Timestamp wsu:Id="${id}">` +
    `<wsu:Created>${new Date().toISOString()}</wsu:Created>` +
    `<wsu:Expires>${expiry}</wsu:Expires>` +
    '</wsu:Timestamp>' +
    '</wsse:Security>';
});

Note that toISOString() will have the timestamp with miliseconds, this wasn't an issue for the SOAP service I'm working with.

Notice that the security header is included in here. This works because there is logic within soap to adjust accordingly, see https://github.com/vpulim/node-soap/blob/master/src/security/WSSecurityCert.ts#L141.

Side note: I had to write my import statement like the following for the security classes, not sure if there's a better way:

import { IWSSecurityCertOptions, WSSecurityCert } from 'soap/lib/security/WSSecurityCert';

RESULT

Your SOAP security header will have:

<wsu:Timestamp wsu:Id="TS-7C14BF4AA3E26845E015637928928701">
    <wsu:Created>
        2024-05-10T17:23:56.937Z
    </wsu:Created>
    <wsu:Expires>
        2100-05-08T00:00:00Z
    </wsu:Expires>
</wsu:Timestamp>

And a signed reference like:

<ds:Reference URI="#TS-7C14BF4AA3E26845E015637928928701">
    <ds:Transforms>
        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
    </ds:Transforms>
    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
    <ds:DigestValue>
        GR__REDACTED__e24=
    </ds:DigestValue>
</ds:Reference>

Upvotes: 1

josephdpurcell
josephdpurcell

Reputation: 1482

Not sure if I should edit my other answer, but there is a completely OTHER approach you could take:

Create your own WSSecurityCert class and modify the timestamp as you wish.

Step 1: Copy the class into your own WSSecurityCert

See https://github.com/vpulim/node-soap/blob/master/src/security/WSSecurityCert.ts for the latest, but make sure you switch to the tagged version of soap you're using.

Step 2: Modify the class

Change Line 140 to be like:

if (this.hasTimeStamp) {
  timestampStr =
    `<wsu:Timestamp wsu:Id="TS-${generateId()}">` +
    `<wsu:Created>${this.created}</wsu:Created>` +
    `<wsu:Expires>${this.expires}</wsu:Expires>` +
    `</wsu:Timestamp>`;
}

Step 3: Wire up your custom security class

Do exactly as you would normally:

// Where WSSecurityCert is an import of your own version of the class
const wsSecurity = new WSSecurityCert(/** params */);
soapClient.setSecurity(wsSecurity);

RESULT

Your SOAP security header will have:

<wsu:Timestamp wsu:Id="TS-43dfbc1c4e8046f7b0065acdee3a2ad1">
    <wsu:Created>
        2024-05-10T17:01:27Z
    </wsu:Created>
    <wsu:Expires>
        2024-05-10T17:11:27Z
    </wsu:Expires>
</wsu:Timestamp>

And a signed reference like:

<ds:Reference URI="#TS-43dfbc1c4e8046f7b0065acdee3a2ad1">
    <ds:Transforms>
        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
    </ds:Transforms>
    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
    <ds:DigestValue>
        gZe__REDACTED__8g=
    </ds:DigestValue>
</ds:Reference>

Upvotes: 1

Related Questions