Skip to content

Commit

Permalink
New XML Signing Options, extra tags to sign and small bug fix (vpulim…
Browse files Browse the repository at this point in the history
…#1091)

* New XML Signing Options, extra tags to sign and small bug fix

Added new Signer Options to add prefix to signature tags added
Added new Signer Options to add Attributes to the Signature tag
Updated the Readme file
Added tests
change the join from `/n` to `' '` in client _invoke soapheaders.
The new line character stopped the XML from being decoded by a WSE system

* Fixed small lint issues

* Added README examples

Added Readme examples for the option added
Removed some extra XML tags added from a cut and paste error
  • Loading branch information
LorneCurrie authored and Danail-Irinkov committed Jan 19, 2020
1 parent 358abd3 commit 0f422d6
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 24 deletions.
154 changes: 150 additions & 4 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -864,16 +864,162 @@ WS-Security X509 Certificate support.
var privateKey = fs.readFileSync(privateKeyPath);
var publicKey = fs.readFileSync(publicKeyPath);
var password = ''; // optional password
var options = {
hasTimeStamp: true,
additionalReferences: [
'wsa:Action',
'wsa:ReplyTo',
'wsa:To',
],
signerOptions: {
prefix: 'ds',
attrs: { Id: 'Signature' },
existingPrefixes: {
wsse: 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd',
}
}
var wsSecurity = new soap.WSSecurityCert(privateKey, publicKey, password, options);
client.setSecurity(wsSecurity);
```
the `options` object is optional and can contain the following properties:
* `hasTimeStamp`: adds Timestamp element (default: `true`)
The `options` object is optional and can contain the following properties:
* `hasTimeStamp`: Includes Timestamp tags (default: `true`)
* `signatureTransformations`: sets the Reference Transforms Algorithm (default ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#']). Type is a string array
* `signatureAlgorithm`: set to `http://www.w3.org/2001/04/xmldsig-more#rsa-sha256` to use sha256
* `signerOptions`: passed options to the XML Signer package - from (https://github.com/yaronn/xml-crypto)
* `existingPrefixes`: 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' }`)
* `additionalReferences` : (optional) Array of Soap headers that need to be signed. This need to be added using `client.addSoapHeader('header')`
* `signerOptions`: (optional) passes options to the XML Signer package - from (https://github.com/yaronn/xml-crypto)
* `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

#### Option examples

`hasTimeStamp:true`

``` xml
<soap:Header>
<wsse:Security soap:mustUnderstand="1">
<wsse:BinarySecurityToken>XXX</wsse:BinarySecurityToken>
<!-- The Timestamp group of tags are added and signed -->
<Timestamp xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="Timestamp">
<Created>2019-10-01T08:17:50Z</Created>
<Expires>2019-10-01T08:27:50Z</Expires>
</Timestamp>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
...
<Reference URI="#Timestamp">
<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/2000/09/xmldsig#sha1"/>
<DigestValue>XyZ=</DigestValue>
</Reference>
</SignedInfo>
</Signature>
</wsse:Security>
</soap:Header>
```

`additionalReferences: ['To']`
``` XML
<soap:Header>
<To Id="To">localhost.com</To>
<wsse:Security soap:mustUnderstand="1">
<wsse:BinarySecurityToken>XXX</wsse:BinarySecurityToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<!-- The "To" tag is signed and added as a reference -->
<Reference URI="#To">
<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/2000/09/xmldsig#sha1"/>
<DigestValue>XYZ</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>
Rf6M4F4puQuQHJIPtJz1CZIVvF3qOdpEEcuAiooWkX5ecnAHSf3RW3sOIzFUWW7VOOncJcts/3xr8DuN4+8Wm9hx1MoOcWJ6kyRIdVNbQWLseIcAhxYCntRY57T2TBXzpb0UPA56pry1+TEcnIQXhdIzG5YT+tTVTp+SZHHcnlP5Y+yqnIOH9wzgRvAovbydTYPCODF7Ana9K/7CSGDe7vpVT85CUYUcJE4DfTxaRa9gKkKrBdPN9vFVi0WfxtMF4kv23cZRCZzS5+CoLfPlx3mq65gVXsqH01RLbktNJq9VaQKcZUgapmUCMzrYhqyzUQJ8HrSHqe+ya2GsjlB0VQ==
</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:Reference URI="#x509-c5c0d213676f4a6ba5e6fa58074eb57a"
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference>
</KeyInfo>
</Signature>
</wsse:Security>
</soap:Header>
```

`signerOptions.prefix:'ds'`

``` XML
<soap:Header>
<To Id="To">localhost.com</To>
<wsse:Security soap:mustUnderstand="1">
<wsse:BinarySecurityToken>XXX</wsse:BinarySecurityToken>
<!-- Signature and children tags are given the prefix defined. -->
<ds:Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#To">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<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>XYZ</DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
Rf6M4F4puQuQHJIPtJz1CZIVvF3qOdpEEcuAiooWkX5ecnAHSf3RW3sOIzFUWW7VOOncJcts/3xr8DuN4+8Wm9hx1MoOcWJ6kyRIdVNbQWLseIcAhxYCntRY57T2TBXzpb0UPA56pry1+TEcnIQXhdIzG5YT+tTVTp+SZHHcnlP5Y+yqnIOH9wzgRvAovbydTYPCODF7Ana9K/7CSGDe7vpVT85CUYUcJE4DfTxaRa9gKkKrBdPN9vFVi0WfxtMF4kv23cZRCZzS5+CoLfPlx3mq65gVXsqH01RLbktNJq9VaQKcZUgapmUCMzrYhqyzUQJ8HrSHqe+ya2GsjlB0VQ==
</ds:SignatureValue>
<ds:KeyInfo>
<wsse:SecurityTokenReference
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:Reference URI="#x509-c5c0d213676f4a6ba5e6fa58074eb57a"
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</soap:Header>
```

`signerOptions.attrs:{ Id: 'signature-100', foo:'bar'}`

``` xml
<soap:Header>
<wsse:Security soap:mustUnderstand="1">
<wsse:BinarySecurityToken>XXX</wsse:BinarySecurityToken>
<!-- The Timestamp group of tags are added and signed -->
<Timestamp xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="Timestamp">
<Created>2019-10-01T08:17:50Z</Created>
<Expires>2019-10-01T08:27:50Z</Expires>
</Timestamp>
<Signature Id="signature-100" foo="bar" xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
...
<Reference URI="#Timestamp">
<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/2000/09/xmldsig#sha1"/>
<DigestValue>XyZ=</DigestValue>
</Reference>
</SignedInfo>
</Signature>
</wsse:Security>
</soap:Header>
```

### NTLMSecurity

Expand Down
2 changes: 1 addition & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export class Client extends EventEmitter {
} else {
return header;
}
}).join('\n');
}).join(' ');
}
// ADDED TO FIT RAPIDO SOAP API
xml = '<?xml version="1.0" encoding="utf-8"?>' +
Expand Down
50 changes: 37 additions & 13 deletions src/security/WSSecurityCert.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { v4 as uuid4 } from 'uuid';
import { SignedXml } from 'xml-crypto';
import { ISecurity } from '../types';
Expand All @@ -22,7 +21,7 @@ function generateExpires(): string {
}

function insertStr(src: string, dst: string, pos: number): string {
return [dst.slice(0, pos), src, dst.slice(pos)].join('');
return [ dst.slice(0, pos), src, dst.slice(pos) ].join('');
}

function generateId(): string {
Expand All @@ -35,10 +34,13 @@ export interface IWSSecurityCertOptions {
hasTimeStamp?: boolean;
signatureTransformations?: string[];
signatureAlgorithm?: string;
additionalReferences?: string[];
signerOptions?: IXmlSignerOptions;
}

export interface IXmlSignerOptions {
prefix?: string;
attrs?: { [key: string]: string };
existingPrefixes?: { [key: string]: string };
}

Expand All @@ -51,6 +53,7 @@ export class WSSecurityCert implements ISecurity {
private signatureTransformations: string[];
private created: string;
private expires: string;
private additionalReferences: string[] = [];

constructor(privatePEM: any, publicP12PEM: any, password: any, options: IWSSecurityCertOptions = {}) {
this.publicP12PEM = publicP12PEM.toString()
Expand All @@ -63,13 +66,27 @@ export class WSSecurityCert implements ISecurity {
this.signer.signatureAlgorithm = options.signatureAlgorithm;
this.signer.addReference(
'//*[name(.)="soap:Body"]',
['http://www.w3.org/2001/10/xml-exc-c14n#'],
[ 'http://www.w3.org/2001/10/xml-exc-c14n#' ],
'http://www.w3.org/2001/04/xmlenc#sha256',
);
}

this.signerOptions = (options.signerOptions) ? this.signerOptions = options.signerOptions
: this.signerOptions = { existingPrefixes: { wsse: `${oasisBaseUri}/oasis-200401-wss-wssecurity-secext-1.0.xsd` } };
if (options.additionalReferences && options.additionalReferences.length > 0) {
this.additionalReferences = options.additionalReferences;
}

if (options.signerOptions) {
const { signerOptions } = options;
this.signerOptions = signerOptions;
if (!this.signerOptions.existingPrefixes) {
this.signerOptions.existingPrefixes = {};
}
if (this.signerOptions.existingPrefixes && !this.signerOptions.existingPrefixes.wsse) {
this.signerOptions.existingPrefixes.wsse = `${oasisBaseUri}/oasis-200401-wss-wssecurity-secext-1.0.xsd`;
}
} else {
this.signerOptions = { existingPrefixes: { wsse: `${oasisBaseUri}/oasis-200401-wss-wssecurity-secext-1.0.xsd` } };
}

this.signer.signingKey = {
key: privatePEM,
Expand All @@ -78,7 +95,7 @@ export class WSSecurityCert implements ISecurity {
this.x509Id = `x509-${generateId()}`;
this.hasTimeStamp = typeof options.hasTimeStamp === 'undefined' ? true : !!options.hasTimeStamp;
this.signatureTransformations = Array.isArray(options.signatureTransformations) ? options.signatureTransformations
: ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'];
: [ 'http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#' ];

this.signer.keyInfoProvider = {};
this.signer.keyInfoProvider.getKeyInfo = (key) => {
Expand All @@ -96,19 +113,19 @@ export class WSSecurityCert implements ISecurity {
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>` +
`<Created>${this.created}</Created>` +
`<Expires>${this.expires}</Expires>` +
`</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" ` +
`soap:mustUnderstand="1">` +
`xmlns:wsu="${oasisBaseUri}/oasis-200401-wss-wssecurity-utility-1.0.xsd" ` +
`soap:mustUnderstand="1">` +
`<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>` +
`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>`;

Expand All @@ -121,6 +138,13 @@ export class WSSecurityCert implements ISecurity {
this.signer.addReference(bodyXpath, references);
}

for (const name of this.additionalReferences) {
const xpath = `//*[name(.)='${name}']`;
if (!(this.signer.references.filter((ref) => (ref.xpath === xpath)).length > 0)) {
this.signer.addReference(xpath, references);
}
}

const timestampXpath = `//*[name(.)='wsse:Security']/*[local-name(.)='Timestamp']`;
if (this.hasTimeStamp && !(this.signer.references.filter((ref) => (ref.xpath === timestampXpath)).length > 0)) {
this.signer.addReference(timestampXpath, references);
Expand Down
58 changes: 52 additions & 6 deletions test/security/WSSecurityCert.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,25 +111,71 @@ describe('WSSecurityCert', function () {
});

it('should use rsa-sha256 signature method when the signatureAlgorithm option is set to WSSecurityCert', function () {
var instance = new WSSecurityCert(key, cert, '', { hasTimeStamp: false, signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' });
var instance = new WSSecurityCert(key, cert, '', {
hasTimeStamp: false,
signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
});
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body></soap:Body>', 'soap');
xml.should.containEql('SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"');
});

it('should use default xmlns:wsse if no signerOptions.existingPrefixes is provided', function () {
var instance = new WSSecurityCert(key, cert, '');
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body><Timestamp></Timestamp></soap:Body>', 'soap')
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body></soap:Body>', 'soap')
xml.should.containEql('xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"');
});
it('should still add wsse if another signerOption attribute is passed through ', function(){
var instance = new WSSecurityCert(key, cert, '', { signerOptions: { prefix: 'ds'} });
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body></soap:Body>', 'soap')
xml.should.containEql('xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"');
})
xml.should.containEql('<ds:SignedInfo>');
});
it('should contain a provided prefix when signerOptions.existingPrefixes is provided', function () {
var instance = new WSSecurityCert(key, cert, '', {
signerOptions: {
location: { action: 'after' },
existingPrefixes: { wsse: 'https://localhost/node-soap.xsd' }
}
});
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body><Timestamp></Timestamp></soap:Body>', 'soap')
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body></soap:Body>', 'soap')
xml.should.containEql('<wsse:SecurityTokenReference xmlns:wsse="https://localhost/node-soap.xsd">');
})

});
it('should contain the prefix to the generated Signature tags', function () {
var instance = new WSSecurityCert(key, cert, '', {
signerOptions: {
prefix: 'ds',
}
});
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body></soap:Body>', 'soap');
xml.should.containEql('<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">');
xml.should.containEql('<ds:SignedInfo>');
xml.should.containEql('<ds:CanonicalizationMethod');
xml.should.containEql('<ds:SignatureMethod ');
xml.should.containEql('<ds:Reference URI="#_1">');
xml.should.containEql('<ds:Transforms>');
xml.should.containEql('<ds:Transform');
xml.should.containEql('<ds:DigestMethod');
xml.should.containEql('<ds:DigestValue>');
xml.should.containEql('</ds:DigestValue>');
});
it('should add attributes to the security tag', function () {
var instance = new WSSecurityCert(key, cert, '', {
signerOptions: {
attrs: { Id: 'security_123' },
}
});
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body></soap:Body>', 'soap');
xml.should.containEql('<Signature Id="security_123" xmlns="http://www.w3.org/2000/09/xmldsig#">');
});
it('should sign additional headers that are added via additionalReferences', function () {
var instance = new WSSecurityCert(key, cert, '', {
additionalReferences: [
'To',
'Action'
],
});
var xml = instance.postProcess('<soap:Header><To Id="To">localhost.com</To><Action Id="action-1234">testing</Action></soap:Header><soap:Body><Body></Body></soap:Body>', 'soap');
xml.should.containEql('<Reference URI="#To">');
xml.should.containEql('<Reference URI="#action-1234">');
});
});

0 comments on commit 0f422d6

Please sign in to comment.