Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

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

Merged
merged 3 commits into from
Oct 1, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,20 @@ WS-Security X509 Certificate support.
var privateKey = fs.readFileSync(privateKeyPath);
var publicKey = fs.readFileSync(publicKeyPath);
var password = ''; // optional password
var options = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add comments in this example to explain if these are optional and what they're for etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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);
```
Expand All @@ -872,8 +886,12 @@ the `options` object is optional and can contain the following properties:
* `hasTimeStamp`: adds Timestamp element (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)
* `additionalReferences` : Array of Soap headers that need to be signed. This need to be added using `client.addSoapHeader('header')`
* `signerOptions`: passes 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' }`)
* `prefix`: Adds this value as a prefix for the generated signature tags.
* `attrs`: A hash of attributes and values attrName: value to add to the signature root node


### 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(' ');
}

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
54 changes: 50 additions & 4 deletions test/security/WSSecurityCert.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ 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"');
});
Expand All @@ -120,7 +123,13 @@ describe('WSSecurityCert', function () {
var instance = new WSSecurityCert(key, cert, '');
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body><Timestamp></Timestamp></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><Timestamp></Timestamp></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: {
Expand All @@ -130,6 +139,43 @@ describe('WSSecurityCert', function () {
});
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body><Timestamp></Timestamp></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><Timestamp></Timestamp></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><Timestamp></Timestamp></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><Timestamp></Timestamp></soap:Body>', 'soap');
xml.should.containEql('<Reference URI="#To">');
xml.should.containEql('<Reference URI="#action-1234">');
});
});