Skip to content

Commit

Permalink
Allow WSSecurity and WSSecurityCert to be used together (#1195)
Browse files Browse the repository at this point in the history
* feat(WSSecurity): allow using WSSecurity and WSSecurityCert together

* docs: add idMode option for WSSecurityCert to Readme

Co-authored-by: Florian Schmid <florian@schmid.hamburg>
  • Loading branch information
flowpl and Florian Schmid committed Aug 17, 2022
1 parent aac37bd commit ceb2599
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 15 deletions.
12 changes: 12 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,18 @@ The `options` object is optional and can contain the following properties:
* `existingPrefixes`: (optional) A hash of prefixes and namespaces prefix: namespace that shouldn't be in the signature because they already exist in the xml (default: `{ 'wsse': 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' }`)
* `prefix`: (optional) Adds this value as a prefix for the generated signature tags.
* `attrs`: (optional) A hash of attributes and values attrName: value to add to the signature root node
* `idMode`: (optional) either 'wssecurity' to generate wsse-scoped reference Id on <Body> or undefined for an unscoped reference Id

### WSSecurityPlusCert

Use WSSecurity and WSSecurityCert together.

``` javascript
var wsSecurity = new soap.WSSecurity(/* see WSSecurity above */);
var wsSecurityCert = new soap.WSSecurityCert(/* see WSSecurityCert above */);
var wsSecurityPlusCert = new soap.WSSecurityPlusCert(wsSecurity, wsSecurityCert);
client.setSecurity(wsSecurityPlusCert);
```

#### Option examples

Expand Down
22 changes: 20 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
],
"license": "MIT",
"devDependencies": {
"@types/axios": "^0.14.0",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.13",
"@types/formidable": "^2.0.4",
Expand Down
1 change: 0 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,6 @@ export class Client extends EventEmitter {
) +
'<' + envelopeKey + ':Body' +
(this.bodyAttributes ? this.bodyAttributes.join(' ') : '') +
(this.security && this.security.postProcess ? ' Id="_0"' : '') +
'>' +
message +
'</' + envelopeKey + ':Body>' +
Expand Down
50 changes: 39 additions & 11 deletions src/security/WSSecurityCert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface IXmlSignerOptions {
prefix?: string;
attrs?: { [key: string]: string };
existingPrefixes?: { [key: string]: string };
idMode?: 'wssecurity';
}

export class WSSecurityCert implements ISecurity {
Expand All @@ -70,7 +71,7 @@ export class WSSecurityCert implements ISecurity {
.replace('-----END CERTIFICATE-----', '')
.replace(/(\r\n|\n|\r)/gm, '');

this.signer = new SignedXml();
this.signer = new SignedXml(options?.signerOptions?.idMode);
if (options.signatureAlgorithm === 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') {
this.signer.signatureAlgorithm = options.signatureAlgorithm;
this.signer.addReference(
Expand Down Expand Up @@ -127,18 +128,43 @@ export class WSSecurityCert implements ISecurity {
`</Timestamp>`;
}

const secHeader =
`<wsse:Security xmlns:wsse="${oasisBaseUri}/oasis-200401-wss-wssecurity-secext-1.0.xsd" ` +
`xmlns:wsu="${oasisBaseUri}/oasis-200401-wss-wssecurity-utility-1.0.xsd" ` +
`${envelopeKey}:mustUnderstand="1">` +
`<wsse:BinarySecurityToken ` +
const binarySecurityToken = `<wsse:BinarySecurityToken ` +
`EncodingType="${oasisBaseUri}/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ` +
`ValueType="${oasisBaseUri}/oasis-200401-wss-x509-token-profile-1.0#X509v3" ` +
`wsu:Id="${this.x509Id}">${this.publicP12PEM}</wsse:BinarySecurityToken>` +
timestampStr +
`</wsse:Security>`;

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

let xmlWithSec;
const secExt = `xmlns:wsse="${oasisBaseUri}/oasis-200401-wss-wssecurity-secext-1.0.xsd"`;
const secUtility = `xmlns:wsu="${oasisBaseUri}/oasis-200401-wss-wssecurity-utility-1.0.xsd"`;
const endOfSecurityHeader = xml.indexOf('</wsse:Security>');
if (endOfSecurityHeader > -1) {
const securityHeaderRegexp = /<wsse:Security( [^>]*)?>/;
const match = xml.match(securityHeaderRegexp);
let insertHeaderAttributes = '';
if (!match[0].includes(` ${envelopeKey}:mustUnderstand="`)) {
insertHeaderAttributes += `${envelopeKey}:mustUnderstand="1" `;
}
if (!match[0].includes(secExt.substring(0, secExt.indexOf('=')))) {
insertHeaderAttributes += `${secExt} `;
}
if (!match[0].includes(secUtility.substring(0, secExt.indexOf('=')))) {
insertHeaderAttributes += `${secUtility} `;
}
const headerMarker = '<wsse:Security ';
const startOfSecurityHeader = xml.indexOf(headerMarker);
xml = insertStr(binarySecurityToken, xml, endOfSecurityHeader);
xmlWithSec = insertStr(insertHeaderAttributes, xml, startOfSecurityHeader + headerMarker.length);
} else {
const secHeader =
`<wsse:Security ${secExt} ` +
secUtility +
`${envelopeKey}:mustUnderstand="1">` +
binarySecurityToken +
`</wsse:Security>`;

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

const references = this.signatureTransformations;

Expand All @@ -163,6 +189,8 @@ export class WSSecurityCert implements ISecurity {

this.signer.computeSignature(xmlWithSec, this.signerOptions);

return insertStr(this.signer.getSignatureXml(), xmlWithSec, xmlWithSec.indexOf('</wsse:Security>'));
const originalXmlWithIds = this.signer.getOriginalXmlWithIds();
const signatureXml = this.signer.getSignatureXml();
return insertStr(signatureXml, originalXmlWithIds, originalXmlWithIds.indexOf('</wsse:Security>'));
}
}
15 changes: 15 additions & 0 deletions src/security/WSSecurityPlusCert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {ISecurity} from '../types';
import {WSSecurity} from './WSSecurity';
import {WSSecurityCert} from './WSSecurityCert';

export class WSSecurityPlusCert implements ISecurity {
constructor(private readonly wsSecurity: WSSecurity, private readonly wsSecurityCert: WSSecurityCert) {}

public postProcess(xml: string, envelopeKey: string) {
const securityXml = this.wsSecurity.toXML();
const endOfHeader = xml.indexOf(`</${envelopeKey}:Header>`);
xml = [xml.slice(0, endOfHeader), securityXml, xml.slice(endOfHeader)].join('');

return this.wsSecurityCert.postProcess(xml, envelopeKey);
}
}
1 change: 1 addition & 0 deletions src/security/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './ClientSSLSecurityPFX';
export * from './NTLMSecurity';
export * from './WSSecurity';
export * from './WSSecurityCert';
export * from './WSSecurityPlusCert';
2 changes: 1 addition & 1 deletion src/soap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const debug = debugBuilder('node-soap:soap');
export const security = _security;
export { Client } from './client';
export { HttpClient } from './http';
export { BasicAuthSecurity, BearerSecurity, ClientSSLSecurity, ClientSSLSecurityPFX, NTLMSecurity, WSSecurity, WSSecurityCert } from './security';
export { BasicAuthSecurity, BearerSecurity, ClientSSLSecurity, ClientSSLSecurityPFX, NTLMSecurity, WSSecurity, WSSecurityCert, WSSecurityPlusCert } from './security';
export { Server } from './server';
export { passwordDigest } from './utils';
export * from './types';
Expand Down
19 changes: 19 additions & 0 deletions test/security/WSSecurityCert.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,25 @@ describe('WSSecurityCert', function () {
xml.should.containEql(instance.signer.getSignatureXml());
});

it('should extend an existing Security block', function () {
var instance = new WSSecurityCert(keyWithPassword, cert, 'soap');
var xml1 = instance.postProcess('<soap:Header><wsse:Security someAttribute="someValue"></wsse:Security></soap:Header><soap:Body></soap:Body>', 'soap');
var matches1 = xml1.match(/<wsse:Security [^>]*>/);
matches1[0].should.containEql('soap:mustUnderstand="1"');
matches1[0].should.containEql('someAttribute="someValue"');
matches1[0].should.containEql('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd');
matches1[0].should.containEql('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd');

var xml2 = instance.postProcess('<soap:Header><wsse:Security xmlns:wsu="wsu" xmlns:wsse="wsse" soap:mustUnderstand="true" someAttribute="someValue"></wsse:Security></soap:Header><soap:Body></soap:Body>', 'soap');
var matches2 = xml2.match(/<wsse:Security [^>]*>/);
matches2[0].should.not.containEql('soap:mustUnderstand="1"');
matches2[0].should.containEql('soap:mustUnderstand="true"');
matches2[0].should.not.containEql('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd');
matches2[0].should.containEql('xmlns:wsse="wsse"');
matches2[0].should.not.containEql('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd');
matches2[0].should.containEql('xmlns:wsu="wsu"');
});

it('should only add two Reference elements, for Soap Body and Timestamp inside wsse:Security element', function () {
var instance = new WSSecurityCert(key, cert, '');
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body><Timestamp></Timestamp></soap:Body>', 'soap');
Expand Down

0 comments on commit ceb2599

Please sign in to comment.