Skip to content

Commit ed1c2f3

Browse files
Abdullah-03w666
andauthored
Add new 'addElement' option to WSSE Security that adds custom xml to <wsse> element (#1362)
* Add additional optional option to WSSE Security classes: `appendElement`. This allows user to pass a custom xml string that will be added to the WSSE Security element. * Add tests --------- Co-authored-by: Vasily Martynov <[email protected]>
1 parent 987377f commit ed1c2f3

File tree

7 files changed

+148
-22
lines changed

7 files changed

+148
-22
lines changed

Readme.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,7 @@ the `options` object is optional and can contain the following properties:
10251025
- `hasNonce`: adds Nonce element (default: `false`)
10261026
- `mustUnderstand`: adds mustUnderstand=1 attribute to security tag (default: `false`)
10271027
- `actor`: if set, adds Actor attribute with given value to security tag (default: `''`)
1028+
- `appendElement`: A string containing XML element to append to the end of the WSSecurity element. This can be used to add custom elements like certificates or other security tokens (default: `''`)
10281029

10291030
### WSSecurityCert
10301031

@@ -1062,6 +1063,7 @@ The `options` object is optional and can contain the following properties:
10621063
- `prefix`: (optional) Adds this value as a prefix for the generated signature tags.
10631064
- `attrs`: (optional) A hash of attributes and values attrName: value to add to the signature root node
10641065
- `idMode`: (optional) either 'wssecurity' to generate wsse-scoped reference Id on <Body> or undefined for an unscoped reference Id
1066+
- `appendElement`: (optional) A string containing XML element to append to the end of the WSSecurity element. This can be used to add custom elements like certificates or other security tokens.
10651067

10661068
### WSSecurityPlusCert
10671069

@@ -1204,6 +1206,25 @@ client.setSecurity(wsSecurityPlusCert);
12041206
</soap:Header>
12051207
```
12061208

1209+
`appendElement: '<custom:Element>test</custom:Element>'`
1210+
1211+
```xml
1212+
<soap:Header>
1213+
<wsse:Security soap:mustUnderstand="1">
1214+
<wsse:BinarySecurityToken>XXX</wsse:BinarySecurityToken>
1215+
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
1216+
<SignedInfo>
1217+
...
1218+
</SignedInfo>
1219+
</Signature>
1220+
<!-- Custom element is appended to the end of the security block -->
1221+
<custom:MyCustomElement xmlns:custom="http://example.com/custom">
1222+
foo
1223+
</custom:MyCustomElement>
1224+
</wsse:Security>
1225+
</soap:Header>
1226+
```
1227+
12071228
### WSSecurityCertWithToken
12081229

12091230
WS-Security X509 Certificate support. Just like WSSecurityCert, except that it accepts the input properties as a single object, with two properties added `username` and `password`. Which if added, will add a UsernameToken Element to the xml security element.

src/security/WSSecurity.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface IWSSecurityOptions {
1212
actor?: string;
1313
mustUnderstand?;
1414
envelopeKey?: string;
15+
appendElement?: string;
1516
}
1617

1718
export class WSSecurity implements ISecurity {
@@ -24,12 +25,14 @@ export class WSSecurity implements ISecurity {
2425
private _actor: string;
2526
private _mustUnderstand: boolean;
2627
private _envelopeKey: string;
28+
private _appendElement: string;
2729

2830
constructor(username: string, password: string, options?: string | IWSSecurityOptions) {
2931
options = options || {};
3032
this._username = username;
3133
this._password = password;
3234
this._envelopeKey = 'soap';
35+
this._appendElement = '';
3336
// must account for backward compatibility for passwordType String param as well as object options defaults: passwordType = 'PasswordText', hasTimeStamp = true
3437
if (typeof options === 'string') {
3538
this._passwordType = options ? options : 'PasswordText';
@@ -57,6 +60,9 @@ export class WSSecurity implements ISecurity {
5760
if (options.envelopeKey) {
5861
this._envelopeKey = options.envelopeKey;
5962
}
63+
if (options.appendElement) {
64+
this._appendElement = options.appendElement;
65+
}
6066
}
6167

6268
public toXML(): string {
@@ -115,6 +121,7 @@ export class WSSecurity implements ISecurity {
115121
password +
116122
(this._hasTokenCreated ? '<wsu:Created>' + created + '</wsu:Created>' : '') +
117123
'</wsse:UsernameToken>' +
124+
this._appendElement +
118125
'</wsse:Security>'
119126
);
120127
}

src/security/WSSecurityCert.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export interface IWSSecurityCertOptions {
5858
additionalReferences?: string[];
5959
signerOptions?: IXmlSignerOptions;
6060
excludeReferencesFromSigning?: string[];
61+
appendElement?: string;
6162
}
6263

6364
export interface IXmlSignerOptions {
@@ -78,6 +79,7 @@ export class WSSecurityCert implements ISecurity {
7879
private expires: string;
7980
private additionalReferences: string[] = [];
8081
private excludeReferencesFromSigning: string[] = [];
82+
private appendElement: string;
8183

8284
constructor(privatePEM: any, publicP12PEM: any, password: any, options: IWSSecurityCertOptions = {}) {
8385
this.publicP12PEM = publicP12PEM
@@ -92,6 +94,10 @@ export class WSSecurityCert implements ISecurity {
9294
});
9395

9496
this.signer.digestAlgorithm = options.digestAlgorithm ?? 'http://www.w3.org/2001/04/xmlenc#sha256';
97+
this.appendElement = '';
98+
if (options.appendElement) {
99+
this.appendElement = options.appendElement;
100+
}
95101
if (options.signatureAlgorithm === 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') {
96102
this.signer.signatureAlgorithm = options.signatureAlgorithm;
97103
this.signer.addReference({
@@ -155,7 +161,8 @@ export class WSSecurityCert implements ISecurity {
155161
`EncodingType="${oasisBaseUri}/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ` +
156162
`ValueType="${oasisBaseUri}/oasis-200401-wss-x509-token-profile-1.0#X509v3" ` +
157163
`wsu:Id="${this.x509Id}">${this.publicP12PEM}</wsse:BinarySecurityToken>` +
158-
timestampStr;
164+
timestampStr +
165+
this.appendElement;
159166

160167
let xmlWithSec: string;
161168
const secExt = `xmlns:wsse="${oasisBaseUri}/oasis-200401-wss-wssecurity-secext-1.0.xsd"`;

src/security/WSSecurityCertWithToken.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export class WSSecurityCertWithToken implements ISecurity {
6363
private additionalReferences: string[] = [];
6464
private username: string;
6565
private password: string;
66+
private appendElement: string;
6667

6768
constructor(props: { privateKey: Buffer; publicKey: string; keyPassword?: string; username: string; password: string; options?: IWSSecurityCertOptions }) {
6869
this.publicP12PEM = props.publicKey
@@ -75,6 +76,12 @@ export class WSSecurityCertWithToken implements ISecurity {
7576

7677
this.signer = new SignedXml();
7778
const opts = props.options || {};
79+
80+
this.appendElement = '';
81+
if (opts.appendElement) {
82+
this.appendElement = opts.appendElement;
83+
}
84+
7885
if (opts.signatureAlgorithm === 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') {
7986
this.signer.signatureAlgorithm = opts.signatureAlgorithm;
8087
this.signer.addReference({
@@ -148,6 +155,7 @@ export class WSSecurityCertWithToken implements ISecurity {
148155
`wsu:Id="${this.x509Id}">${this.publicP12PEM}</wsse:BinarySecurityToken>` +
149156
usernameToken +
150157
timestampStr +
158+
this.appendElement +
151159
`</wsse:Security>`;
152160

153161
const xmlWithSec = insertStr(secHeader, xml, xml.indexOf(`</${envelopeKey}:Header>`));

test/security/WSSecurity.js

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
'use strict';
22

3-
var fs = require('fs'),
4-
join = require('path').join;
3+
const { equal } = require('should');
4+
const sinon = require('sinon');
55

66
describe('WSSecurity', function () {
77
var WSSecurity = require('../../').WSSecurity;
88

9+
let clock;
10+
11+
before(() => {
12+
const fixedDate = new Date('2025-10-06T00:00:00Z');
13+
clock = sinon.useFakeTimers(fixedDate.getTime());
14+
});
15+
16+
after(() => {
17+
clock.restore();
18+
});
19+
920
it('is a function', function () {
1021
WSSecurity.should.be.type('function');
1122
});
@@ -43,31 +54,26 @@ describe('WSSecurity', function () {
4354
var password = 'my&Pass';
4455
var options = {
4556
passwordType: 'PassWordText',
46-
hasNonce: true,
57+
hasNonce: false,
4758
actor: 'urn:sample',
4859
};
4960
var instance = new WSSecurity(username, password, options);
5061
var xml = instance.toXML();
5162

52-
xml.should.containEql('<wsse:Security soap:actor="urn:sample" ');
53-
xml.should.containEql('xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" ');
54-
xml.should.containEql('xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">');
55-
xml.should.containEql('<wsu:Timestamp wsu:Id="Timestamp-');
56-
xml.should.containEql('<wsu:Created>');
57-
xml.should.containEql('<wsu:Expires>');
58-
xml.should.containEql('</wsu:Timestamp>');
59-
xml.should.containEql('<wsse:UsernameToken ');
60-
xml.should.containEql('xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" ');
61-
xml.should.containEql('wsu:Id="SecurityToken-');
62-
xml.should.containEql('<wsse:Username>my&amp;User</wsse:Username>');
63-
xml.should.containEql('<wsse:Password ');
64-
xml.should.containEql('Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">');
65-
xml.should.containEql('my&amp;Pass</wsse:Password>');
66-
xml.should.containEql('<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">');
67-
xml.should.containEql('</wsse:Nonce>');
68-
xml.should.containEql('<wsu:Created>');
69-
xml.should.containEql('</wsse:UsernameToken></wsse:Security>');
63+
equal(
64+
xml,
65+
`<wsse:Security soap:actor="urn:sample" 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">` +
66+
`<wsu:Timestamp wsu:Id="Timestamp-2025-10-06T00:00:00Z">` +
67+
`<wsu:Created>2025-10-06T00:00:00Z</wsu:Created>` +
68+
`<wsu:Expires>2025-10-06T00:10:00Z</wsu:Expires>` +
69+
`</wsu:Timestamp><wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-2025-10-06T00:00:00Z">` +
70+
`<wsse:Username>my&amp;User</wsse:Username>` +
71+
`<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">my&amp;Pass</wsse:Password>` +
72+
`<wsu:Created>2025-10-06T00:00:00Z</wsu:Created>` +
73+
`</wsse:UsernameToken></wsse:Security>`,
74+
);
7075
});
76+
7177
it('should add envelopeKey to properties in Security block', function () {
7278
var username = 'myUser';
7379
var password = 'myPass';
@@ -82,4 +88,27 @@ describe('WSSecurity', function () {
8288
xml.should.containEql('<wsse:Security soapenv:actor="urn:sample" ');
8389
xml.should.containEql('soapenv:mustUnderstand="1"');
8490
});
91+
92+
it('should add appendElement when provided', function () {
93+
var username = 'myUser';
94+
var password = 'myPass';
95+
var options = {
96+
hasTimeStamp: false,
97+
appendElement: '<custom:MyCustomElement xmlns:custom="http://example.com/custom">foo</custom:MyCustomElement>',
98+
};
99+
var instance = new WSSecurity(username, password, options);
100+
var xml = instance.toXML();
101+
102+
equal(
103+
xml,
104+
`<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">` +
105+
`<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-2025-10-06T00:00:00Z">` +
106+
`<wsse:Username>myUser</wsse:Username>` +
107+
`<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">myPass</wsse:Password>` +
108+
`<wsu:Created>2025-10-06T00:00:00Z</wsu:Created>` +
109+
`</wsse:UsernameToken>` +
110+
`<custom:MyCustomElement xmlns:custom="http://example.com/custom">foo</custom:MyCustomElement>` +
111+
`</wsse:Security>`,
112+
);
113+
});
85114
});

test/security/WSSecurityCert.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,33 @@ describe('WSSecurityCert', function () {
296296
var xml = instance.postProcess('<soap:Envelope><soap:Header></soap:Header><soap:Body><Body></Body></soap:Body></soap:Envelope>', 'soap');
297297
xml.should.containEql('DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"');
298298
});
299+
300+
it('should add appendElement when provided', function () {
301+
var instance = new WSSecurityCert(key, cert, '', {
302+
appendElement: '<Certificate>Mfg...1+</Certificate>',
303+
});
304+
var xml = instance.postProcess('<soap:Envelope><soap:Header></soap:Header><soap:Body></soap:Body></soap:Envelope>', 'soap');
305+
306+
xml.should.containEql('<wsse:Security');
307+
xml.should.containEql('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd');
308+
xml.should.containEql('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd');
309+
xml.should.containEql('soap:mustUnderstand="1"');
310+
xml.should.containEql('<wsse:BinarySecurityToken');
311+
xml.should.containEql('EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary');
312+
xml.should.containEql('ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"');
313+
xml.should.containEql('wsu:Id="' + instance.x509Id);
314+
xml.should.containEql('</wsse:BinarySecurityToken>');
315+
xml.should.containEql('<Timestamp xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="_1">');
316+
xml.should.containEql('<Created>' + instance.created);
317+
xml.should.containEql('<Expires>' + instance.expires);
318+
xml.should.containEql('<Certificate>Mfg...1+</Certificate>');
319+
xml.should.containEql('<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">');
320+
xml.should.containEql('<wsse:SecurityTokenReference');
321+
xml.should.containEql('<wsse:Reference URI="#' + instance.x509Id);
322+
xml.should.containEql('<KeyInfo>');
323+
xml.should.containEql('</KeyInfo>');
324+
xml.should.containEql('ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>');
325+
xml.should.containEql(instance.publicP12PEM);
326+
xml.should.containEql(instance.signer.getSignatureXml());
327+
});
299328
});

test/security/WSSecurityCertWithToken.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,5 +266,30 @@ describe('WSSecurityCertWithToken', function () {
266266
}
267267
should(xml).not.be.ok();
268268
});
269+
270+
it('should add appendElement when provided', function () {
271+
var instance = new WSSecurityCertWithToken({ privateKey: key, publicKey: cert, keyPassword: '', options: { appendElement: '<custom:MyCustomElement xmlns:custom="http://example.com/custom">foo</custom:MyCustomElement>' } });
272+
var xml = instance.postProcess('<soap:Envelope><soap:Header></soap:Header><soap:Body></soap:Body></soap:Envelope>', 'soap');
273+
274+
xml.should.containEql('<wsse:Security');
275+
xml.should.containEql('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd');
276+
xml.should.containEql('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd');
277+
xml.should.containEql('soap:mustUnderstand="1"');
278+
xml.should.containEql('<wsse:BinarySecurityToken');
279+
xml.should.containEql('EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary');
280+
xml.should.containEql('ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"');
281+
xml.should.containEql('wsu:Id="' + instance.x509Id);
282+
xml.should.containEql('</wsse:BinarySecurityToken>');
283+
xml.should.containEql('<Timestamp xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="_1">');
284+
xml.should.containEql('<custom:MyCustomElement xmlns:custom="http://example.com/custom">foo</custom:MyCustomElement>');
285+
xml.should.containEql('<Created>' + instance.created);
286+
xml.should.containEql('<Expires>' + instance.expires);
287+
xml.should.containEql('<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">');
288+
xml.should.containEql('<wsse:SecurityTokenReference');
289+
xml.should.containEql('<wsse:Reference URI="#' + instance.x509Id);
290+
xml.should.containEql('ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>');
291+
xml.should.containEql(instance.publicP12PEM);
292+
xml.should.containEql(instance.signer.getSignatureXml());
293+
});
269294
});
270295
});

0 commit comments

Comments
 (0)