Skip to content

Commit

Permalink
Added Options object for signer.computeSignature (#1066)
Browse files Browse the repository at this point in the history
* Added Options object for signer.computeSignature

XML-crypto vers 1.4.0 broke the WSSecuriityCert functionality
Added the optons needed to correctly add the headers

* Fixed a linting issues

* Small changes

Updated the Reademe with options details for WSSecurityCert
Reverted the Tests
Locked XML-crypto to version ~1.2.0
Reverted the Package-lock.json file
Reworked the signerOptions to be park of the WSS options.

* Added tests

Simplied the XML SignerOpetions to only include the attribute that is currently causing the issues
Added tests
Updated Readme.
  • Loading branch information
LorneCurrie authored and jsdevel committed Jul 20, 2019
1 parent a329800 commit 48d0e01
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 26 deletions.
9 changes: 8 additions & 1 deletion Readme.md
Expand Up @@ -851,10 +851,17 @@ WS-Security X509 Certificate support.
var privateKey = fs.readFileSync(privateKeyPath);
var publicKey = fs.readFileSync(publicKeyPath);
var password = ''; // optional password
var wsSecurity = new soap.WSSecurityCert(privateKey, publicKey, password);
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`)
* `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' }`)

### NTLMSecurity

Parameter invocation:
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -7,6 +7,7 @@
},
"author": "Vinay Pulim <v@pulim.com>",
"dependencies": {
"@types/request": "^2.48.1",
"bluebird": "^3.5.0",
"concat-stream": "^2.0.0",
"debug": "^4.1.1",
Expand All @@ -16,9 +17,8 @@
"sax": ">=0.6",
"serve-static": "^1.11.1",
"strip-bom": "^3.0.0",
"@types/request": "^2.48.1",
"uuid": "^3.1.0",
"xml-crypto": "^1.2.0"
"xml-crypto": "^1.4.0"
},
"repository": {
"type": "git",
Expand Down
15 changes: 12 additions & 3 deletions src/security/WSSecurityCert.ts
Expand Up @@ -35,19 +35,24 @@ export interface IWSSecurityCertOptions {
hasTimeStamp?: boolean;
signatureTransformations?: string[];
signatureAlgorithm?: string;
signerOptions?: IXmlSignerOptions;
}

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

export class WSSecurityCert implements ISecurity {
private publicP12PEM: string;
private signer: any;
private signerOptions: IXmlSignerOptions = {};
private x509Id: string;
private hasTimeStamp: boolean;
private signatureTransformations: string[];
private created: string;
private expires: string;

constructor(privatePEM: any, publicP12PEM: any, password: any, options?: IWSSecurityCertOptions) {
options = options || {};
constructor(privatePEM: any, publicP12PEM: any, password: any, options: IWSSecurityCertOptions = {}) {
this.publicP12PEM = publicP12PEM.toString()
.replace('-----BEGIN CERTIFICATE-----', '')
.replace('-----END CERTIFICATE-----', '')
Expand All @@ -62,6 +67,10 @@ export class WSSecurityCert implements ISecurity {
'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` } };

this.signer.signingKey = {
key: privatePEM,
passphrase: password,
Expand Down Expand Up @@ -117,7 +126,7 @@ export class WSSecurityCert implements ISecurity {
this.signer.addReference(timestampXpath, references);
}

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

return insertStr(this.signer.getSignatureXml(), xmlWithSec, xmlWithSec.indexOf('</wsse:Security>'));
}
Expand Down
51 changes: 34 additions & 17 deletions test/security/WSSecurityCert.js
Expand Up @@ -3,30 +3,30 @@
var fs = require('fs'),
join = require('path').join;

describe('WSSecurityCert', function() {
describe('WSSecurityCert', function () {
var WSSecurityCert = require('../../').WSSecurityCert;
var cert = fs.readFileSync(join(__dirname, '..', 'certs', 'agent2-cert.pem'));
var key = fs.readFileSync(join(__dirname, '..', 'certs', 'agent2-key.pem'));
var keyWithPassword = fs.readFileSync(join(__dirname, '..', 'certs', 'agent2-key-with-password.pem')); // The passphrase protecting the private key is "soap"

it('is a function', function() {
it('is a function', function () {
WSSecurityCert.should.be.type('function');
});

it('should accept valid constructor variables', function() {
it('should accept valid constructor variables', function () {
var instance = new WSSecurityCert(key, cert, '');
instance.should.have.property('publicP12PEM');
instance.should.have.property('signer');
instance.should.have.property('x509Id');
});

it('should fail at computing signature when the private key is invalid', function() {
it('should fail at computing signature when the private key is invalid', function () {
var passed = true;

try {
var instance = new WSSecurityCert('*****', cert, '');
instance.postProcess('<soap:Header></soap:Header><soap:Body></soap:Body>', 'soap');
} catch(e) {
} catch (e) {
passed = false;
}

Expand All @@ -35,7 +35,7 @@ describe('WSSecurityCert', function() {
}
});

it('should insert a WSSecurity signing block when postProcess is called (private key is raw)', function() {
it('should insert a WSSecurity signing block when postProcess is called (private key is raw)', function () {
var instance = new WSSecurityCert(key, cert, '');
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body></soap:Body>', 'soap');

Expand All @@ -52,14 +52,14 @@ describe('WSSecurityCert', function() {
xml.should.containEql('<Created>' + instance.created);
xml.should.containEql('<Expires>' + instance.expires);
xml.should.containEql('<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">');
xml.should.containEql('<wsse:SecurityTokenReference>');
xml.should.containEql('<wsse:SecurityTokenReference');
xml.should.containEql('<wsse:Reference URI="#' + instance.x509Id);
xml.should.containEql('ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>');
xml.should.containEql(instance.publicP12PEM);
xml.should.containEql(instance.signer.getSignatureXml());
});
it('should insert a WSSecurity signing block when postProcess is called (private key is protected by a passphrase)', function() {

it('should insert a WSSecurity signing block when postProcess is called (private key is protected by a passphrase)', function () {
var instance = new WSSecurityCert(keyWithPassword, cert, 'soap');
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body></soap:Body>', 'soap');

Expand All @@ -76,43 +76,60 @@ describe('WSSecurityCert', function() {
xml.should.containEql('<Created>' + instance.created);
xml.should.containEql('<Expires>' + instance.expires);
xml.should.containEql('<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">');
xml.should.containEql('<wsse:SecurityTokenReference>');
xml.should.containEql('<wsse:SecurityTokenReference');
xml.should.containEql('<wsse:Reference URI="#' + instance.x509Id);
xml.should.containEql('ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>');
xml.should.containEql(instance.publicP12PEM);
xml.should.containEql(instance.signer.getSignatureXml());
});

it('should only add two Reference elements, for Soap Body and Timestamp inside wsse:Security element', function() {
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');
xml.match(/<Reference URI="#/g).should.have.length(2);
});

it('should only add one Reference elements, for Soap Body wsse:Security element when addTimestamp is false', function() {
var instance = new WSSecurityCert(key, cert, '', {hasTimeStamp: false});
it('should only add one Reference elements, for Soap Body wsse:Security element when addTimestamp is false', function () {
var instance = new WSSecurityCert(key, cert, '', { hasTimeStamp: false });
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body><Timestamp></Timestamp></soap:Body>', 'soap');
xml.match(/<Reference URI="#/g).should.have.length(1);
});

it('double post process should not add extra alments', function() {
it('double post process should not add extra alments', function () {
var instance = new WSSecurityCert(key, cert, '');
var _ = 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><Timestamp></Timestamp></soap:Body>', 'soap');
xml.match(/<Reference URI="#/g).should.have.length(2);
});

it('should have no timestamp when addTimestamp is false', function() {
var instance = new WSSecurityCert(key, cert, '', {hasTimeStamp: false});
it('should have no timestamp when addTimestamp is false', function () {
var instance = new WSSecurityCert(key, cert, '', { hasTimeStamp: false });
var xml = instance.postProcess('<soap:Header></soap:Header><soap:Body><Body></Body><Timestamp></Timestamp></soap:Body>', 'soap');
xml.should.not.containEql('<Timestamp xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="_1">');
xml.should.not.containEql('<Created>' + instance.created);
xml.should.not.containEql('<Expires>' + instance.expires);
});

it('should use rsa-sha256 signature method when the signatureAlgorithm option is set to 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 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')
xml.should.containEql('xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"');
})
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')
xml.should.containEql('<wsse:SecurityTokenReference xmlns:wsse="https://localhost/node-soap.xsd">');
})

});
42 changes: 42 additions & 0 deletions test/security/tmp.xml
@@ -0,0 +1,42 @@
<soap:Header>
<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">
<wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="x509-6df39b9626c0466e960f866109756560">MIID8zCCAtugAwIBAgIUfaJJaqBa7P+81aiqjFdwled4g3EwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdBcml6b25hMRIwEAYDVQQHDAlGbGFnc3RhZmYxDTALBgNVBAoMBEFDTUUxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAlub2RlLXNvYXAxIzAhBgkqhkiG9w0BCQEWFHNvYXBtYXN0ZXJzQGFjbWUuY29tMB4XDTE5MDEyNDE3MDUxMFoXDTIwMDEyNDE3MDUxMFowgYgxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdBcml6b25hMRIwEAYDVQQHDAlGbGFnc3RhZmYxDTALBgNVBAoMBEFDTUUxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAlub2RlLXNvYXAxIzAhBgkqhkiG9w0BCQEWFHNvYXBtYXN0ZXJzQGFjbWUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1AmnP/IPSpocMHYJQ5FE1an5eQmfkg+4QyaKq6ECXCv4VhK5N2aD71OL7IKKnW5qqxzXGAGy1vQjCGyUGmPa4/LnXBvM67ED5irFy00djXqc/pNn7FCACxgrg9+6bIWlItSkVkGn/D4VPGiA8xTC05FxJkUXp8be/hYs9EQ0+9WqZ7ZM6W5SMFYNW0CE9G9dQK9b0NrS9Rx7wrNTzwMZp9u1PdY4HtYsS5WHVMOOGXN+ds4v3dP8Ym6sW1rbe2LeYfTB7dM8aZyS4Zn38vpCiMzrqO1/govPgz/6Bw4DhW0FoXLtDgx0giTGEy5GBqZf6+U6z5ZCosKKdD8KZoxrNwIDAQABo1MwUTAdBgNVHQ4EFgQUxbsTaT+279hFiJmplXfBwAfUGy0wHwYDVR0jBBgwFoAUxbsTaT+279hFiJmplXfBwAfUGy0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAO04vm1mWY1IagQ+fxH8HGxE8OOwxSVTmmkNz+DZVXG4/8wwMJxCEoLY2CvJH5o/ZdeZPTd9MhmMxTwBNHAM9vmj3AmbD65EukMV76RIATBzavEb6NzTLugar2xco3MMHR945kLJmk1uEyusCJXgxzK0lzk6oGp2OAtlRUT7DvR3AkMvEYia3AHSY1M0Dtsf3r7DsoFIpWvVj9+WDeeoxI19ZIuJ6fzFQnEIX4M4hcfmCTtOlICowqpMCg1JIWryBBUr3KizDrq2Ad5I2i8E4WanGz4pDVShd6Y1XTNLR1xv3Is3Jlhusk9UpTbATyMxOVnfj9OgS1/BK41xRmasgPg==</wsse:BinarySecurityToken>
<Timestamp xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="_1">
<Created>2019-06-24T09:16:27Z</Created>
<Expires>2019-06-24T09:26:27Z</Expires>
</Timestamp>
<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"/>
<Reference URI="#_0">
<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>+a/MnIamE2MEuaDxCQq8WpiQbFg=</DigestValue>
</Reference>
<Reference URI="#_1">
<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>+AOj65Xa46P6/xRj5KyAgDHjP1Q=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>npN8Ly8YtIyodpQhcn4M17FJdz11pFrXWGwtO5v6KgNxUcrX6LDIzeeOQRVSzXbK6X2AyIdPKUOG2m/vThx1wXDCD68L8xx3i2WW3r6Hkw+FzgC4y/GncszqTrlPd0BlWtOAWxKoHYSOS+RkYMtC58iMR3I8gysYJ8xC30UtSm7CYP2CQNPVdPFEN5SdTb7334xHB6ifTlqZ92FrdTnwExOt127jf9xqwZruHNoXlBKkLSjanr+uTs38Hf9IkxSS149ExtNsiybgaiccRh5+qagGgb5aAsMeuY4+mvQb/ZpJFF1BQuChpx1fLq5WVmbkzEAUzug61zfoKSv4HGoPMA==</SignatureValue>
<KeyInfo>
<wsse:SecurityTokenReference xmlns:wsse="">
<wsse:Reference URI="#x509-6df39b9626c0466e960f866109756560" 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>
<soap:Body>
<Body></Body>
<Timestamp></Timestamp>
</soap:Body>

0 comments on commit 48d0e01

Please sign in to comment.