diff --git a/.gitignore b/.gitignore
index 28f1ba75..f854f3fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
node_modules
-.DS_Store
\ No newline at end of file
+.DS_Store
+package-lock.json
diff --git a/lib/saml20.js b/lib/saml20.js
index 21c1f278..acd501f4 100644
--- a/lib/saml20.js
+++ b/lib/saml20.js
@@ -74,7 +74,13 @@ exports.create = function(options, callback) {
var cert = utils.pemToCert(options.cert);
- var sig = new SignedXml(null, { signatureAlgorithm: algorithms.signature[options.signatureAlgorithm], idAttribute: 'ID' });
+ var signatureAlgorithm = algorithms.signature[options.signatureAlgorithm];
+ if (typeof (options.signatureFunction) === 'function') {
+ signatureAlgorithm = (new options.signatureFunction()).getAlgorithmName();
+ SignedXml.SignatureAlgorithms[signatureAlgorithm] = options.signatureFunction;
+ }
+ var sig = new SignedXml(null, { signatureAlgorithm: signatureAlgorithm, idAttribute: 'ID'});
+
sig.addReference("//*[local-name(.)='Assertion']",
["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/2001/10/xml-exc-c14n#"],
algorithms.digest[options.digestAlgorithm]);
@@ -200,32 +206,39 @@ exports.create = function(options, callback) {
},
prefix: options.signatureNamespacePrefix
};
-
- sig.computeSignature(token, opts);
- signed = sig.getSignedXml();
- } catch(err){
- return utils.reportError(err, callback);
- }
- if (!options.encryptionCert) {
- if (callback)
- return callback(null, signed);
- else
- return signed;
- }
+ if (callback) {
+ sig.computeSignature(token, opts, function(err, sigXML){
+ if (err || !sigXML) return callback(err)
+ signed = sigXML.getSignedXml();
+ if (!options.encryptionCert) {
+ return callback(null, signed);
+ }
- var encryptOptions = {
- rsa_pub: options.encryptionPublicKey,
- pem: options.encryptionCert,
- encryptionAlgorithm: options.encryptionAlgorithm || 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
- keyEncryptionAlgorighm: options.keyEncryptionAlgorighm || 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
- };
+ var encryptOptions = {
+ rsa_pub: options.encryptionPublicKey,
+ pem: options.encryptionCert,
+ encryptionAlgorithm: options.encryptionAlgorithm || 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
+ keyEncryptionAlgorighm: options.keyEncryptionAlgorighm || 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
+ };
+
+ xmlenc.encrypt(signed, encryptOptions, function(err, encrypted) {
+ if (err) return callback(err);
+ encrypted = '' + encrypted + '';
+ callback(null, utils.removeWhitespace(encrypted));
+ });
+ });
+ } else {
+ sig.computeSignature(token, opts);
+ signed = sig.getSignedXml();
- xmlenc.encrypt(signed, encryptOptions, function(err, encrypted) {
- if (err) return callback(err);
- encrypted = '' + encrypted + '';
- callback(null, utils.removeWhitespace(encrypted));
- });
+ if (!options.encryptionCert) {
+ return signed;
+ }
+ }
+ } catch(err){
+ return utils.reportError(err, callback);
+ }
};
diff --git a/package.json b/package.json
index 83c47b14..cab72b9d 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"async": "~0.2.9",
"moment": "2.19.3",
"valid-url": "~1.0.9",
- "xml-crypto": "~1.0.1",
+ "xml-crypto": "~1.5.3",
"xml-encryption": "0.11.2",
"xml-name-validator": "~2.0.1",
"xmldom": "=0.1.15",
diff --git a/test/saml20.tests.js b/test/saml20.tests.js
index e351cfa3..29684430 100644
--- a/test/saml20.tests.js
+++ b/test/saml20.tests.js
@@ -9,6 +9,74 @@ var assert = require('assert'),
describe('saml 2.0', function () {
+ it('testing all using async and custom signing algo', function () {
+ var options = {
+ cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
+ key: fs.readFileSync(__dirname + '/test-auth0.key'),
+ issuer: 'urn:issuer',
+ lifetimeInSeconds: 600,
+ audiences: 'urn:myapp',
+ attributes: {
+ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress': 'foo@bar.com',
+ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name': 'Foo Bar'
+ },
+ nameIdentifier: 'foo',
+ nameIdentifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
+ signingFunction: function RSASHA256() {
+ this.getSignature = function(signedInfo, signingKey, callback) {
+ var signer = crypto.createSign("RSA-SHA256")
+ signer.update(signedInfo)
+ var res = signer.sign(signingKey, 'base64')
+ //or do async signing from key server or HSM
+ callback(null, res)
+ }
+
+ this.verifySignature = function(str, key, signatureValue, callback) {
+ var verifier = crypto.createVerify("RSA-SHA256")
+ verifier.update(str)
+ var res = verifier.verify(key, signatureValue, 'base64')
+ //verify async as well
+ callback(null, res)
+ }
+
+ this.getAlgorithmName = function() {
+ return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
+ }
+ }
+ };
+
+ saml.create(options, function(err, signedAssertion){
+ var isValid = utils.isValidSignature(signedAssertion, options.cert);
+ assert.equal(true, isValid);
+
+ var nameIdentifier = utils.getNameID(signedAssertion);
+ assert.equal('foo', nameIdentifier.textContent);
+ assert.equal('urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified', nameIdentifier.getAttribute('Format'));
+
+ var attributes = utils.getAttributes(signedAssertion);
+ assert.equal(2, attributes.length);
+ assert.equal('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', attributes[0].getAttribute('Name'));
+ assert.equal('foo@bar.com', attributes[0].textContent);
+ assert.equal('http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name', attributes[1].getAttribute('Name'));
+ assert.equal('Foo Bar', attributes[1].textContent);
+
+ assert.equal('urn:issuer', utils.getSaml2Issuer(signedAssertion).textContent);
+
+ var conditions = utils.getConditions(signedAssertion);
+ assert.equal(1, conditions.length);
+ var notBefore = conditions[0].getAttribute('NotBefore');
+ var notOnOrAfter = conditions[0].getAttribute('NotOnOrAfter');
+ should.ok(notBefore);
+ should.ok(notOnOrAfter);
+
+ var lifetime = Math.round((moment(notOnOrAfter).utc() - moment(notBefore).utc()) / 1000);
+ assert.equal(600, lifetime);
+
+ var authnContextClassRef = utils.getAuthnContextClassRef(signedAssertion);
+ assert.equal('urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified', authnContextClassRef.textContent);
+ });
+ });
+
it('whole thing with default authnContextClassRef', function () {
var options = {
cert: fs.readFileSync(__dirname + '/test-auth0.pem'),