From 1b175bb230fadfe821812c1010c8fd973096c870 Mon Sep 17 00:00:00 2001
From: Troy <5209556+troyfactor4@users.noreply.github.com>
Date: Wed, 3 Mar 2021 04:08:15 -0400
Subject: [PATCH 1/2] feat: add external async signing and encryption
strategies
---
lib/saml11.js | 44 ++++++++++++++++++++++++----
lib/saml20.js | 69 +++++++++++++++++++++++++++++++++++---------
test/saml11.tests.js | 36 +++++++++++++++++++++++
test/saml20.tests.js | 36 +++++++++++++++++++++++
4 files changed, 166 insertions(+), 19 deletions(-)
diff --git a/lib/saml11.js b/lib/saml11.js
index bc3512a..bb73197 100644
--- a/lib/saml11.js
+++ b/lib/saml11.js
@@ -57,17 +57,34 @@ function extractSaml11Options(opts) {
* @param [options.encryptionAlgorithm] {string}
* @param [options.keyEncryptionAlgorithm] {string}
*
+ * @param [strategies]
+ *
+ * // Signing strategy
+ * @param {Function} [strategies.signXml]
+ *
+ * // Encryption strategy
+ * @param {Function} [strategies.encryptXml]
+ *
* @param {Function} [callback] required if encrypting
* @return {String|*}
*/
-exports.create = function(options, callback) {
- return createAssertion(extractSaml11Options(options), {
+exports.create = function(options, strategies, callback) {
+ var defaultStrategy = {
signXml: SignXml.fromSignXmlOptions(Object.assign({
xpathToNodeBeforeSignature: "//*[local-name(.)='AuthenticationStatement']",
signatureIdAttribute: 'AssertionID'
}, options)),
encryptXml: EncryptXml.fromEncryptXmlOptions(options)
- }, callback);
+ }
+
+ if (typeof strategies === 'function' && callback == null) {
+ callback = strategies
+ strategies = defaultStrategy
+ } else {
+ strategies = strategies || defaultStrategy
+ }
+
+ return createAssertion(extractSaml11Options(options), strategies, callback);
}
/**
@@ -177,9 +194,19 @@ function createAssertion(options, strategies, callback) {
nameIDs[1].setAttribute('Format', options.nameIdentifierFormat);
}
+ if (callback) {
+ signAndEncryptAsync(doc, strategies, options, callback)
+ } else {
+ return signAndEncryptSync(doc, strategies)
+ }
+}
+
+function signAndEncryptAsync(doc, strategies, options, callback) {
if (strategies.encryptXml === EncryptXml.unencrypted) {
- var signed = strategies.signXml(doc);
- return strategies.encryptXml(signed, callback);
+ strategies.signXml(doc, function(err, signed){
+ strategies.encryptXml(signed, callback);
+ });
+ return
}
// encryption is turned on,
@@ -206,6 +233,13 @@ function createAssertion(options, strategies, callback) {
});
}
+function signAndEncryptSync(doc, strategies) {
+ if (strategies.encryptXml === EncryptXml.unencrypted) {
+ var signed = strategies.signXml(doc);
+ return strategies.encryptXml(signed);
+ }
+}
+
function addSubjectConfirmation(encryptOptions, doc, randomBytes, callback) {
xmlenc.encryptKeyInfo(randomBytes, encryptOptions, function(err, keyinfo) {
if (err) return callback(err);
diff --git a/lib/saml20.js b/lib/saml20.js
index 9db8141..7a9d1b0 100644
--- a/lib/saml20.js
+++ b/lib/saml20.js
@@ -96,17 +96,34 @@ function extractSaml20Options(opts) {
* @param [options.encryptionAlgorithm] {string}
* @param [options.keyEncryptionAlgorithm] {string}
*
+ * @param [strategies]
+ *
+ * // Signing strategy
+ * @param {Function} [strategies.signXml]
+ *
+ * // Encryption strategy
+ * @param {Function} [strategies.encryptXml]
+ *
* @param {Function} [callback] required if encrypting
* @return {*}
*/
-exports.create = function createSignedAssertion(options, callback) {
- return createAssertion(extractSaml20Options(options), {
+exports.create = function createSignedAssertion(options, strategies, callback) {
+ var defaultStrategy = {
signXml: SignXml.fromSignXmlOptions(Object.assign({
xpathToNodeBeforeSignature: "//*[local-name(.)='Issuer']",
signatureIdAttribute: 'ID'
}, options)),
encryptXml: EncryptXml.fromEncryptXmlOptions(options)
- }, callback);
+ }
+
+ if (typeof strategies === 'function' && callback == null) {
+ callback = strategies
+ strategies = defaultStrategy
+ } else {
+ strategies = strategies || defaultStrategy
+ }
+
+ return createAssertion(extractSaml20Options(options), strategies, callback);
};
/**
@@ -251,6 +268,40 @@ function createAssertion(options, strategies, callback) {
authnCtxClassRef.textContent = options.authnContextClassRef;
}
+ if (callback) {
+ signAndEncryptAsync(doc, strategies, callback)
+ } else {
+ return signAndEncryptSync(doc, strategies)
+ }
+}
+
+function signAndEncryptAsync(doc, strategies, callback) {
+ try {
+ strategies.signXml(doc, function(err, signed){
+ if (err || !signed) {
+ return utils.reportError(err, callback);
+ }
+
+ if (strategies.encryptXml === EncryptXml.unencrypted) {
+ return strategies.encryptXml(signed, callback);
+ }
+
+ async.waterfall([
+ function (cb) {
+ strategies.encryptXml(signed, cb)
+ },
+ function (encrypted, cb) {
+ var assertion = '' + encrypted + '';
+ cb(null, utils.removeWhitespace(assertion));
+ },
+ ], callback);
+ });
+ } catch(err) {
+ utils.reportError(err, callback);
+ }
+}
+
+function signAndEncryptSync(doc, strategies) {
var signed;
try {
signed = strategies.signXml(doc);
@@ -259,16 +310,6 @@ function createAssertion(options, strategies, callback) {
}
if (strategies.encryptXml === EncryptXml.unencrypted) {
- return strategies.encryptXml(signed, callback);
+ return strategies.encryptXml(signed);
}
-
- async.waterfall([
- function (cb) {
- strategies.encryptXml(signed, cb)
- },
- function (encrypted, cb) {
- var assertion = '' + encrypted + '';
- cb(null, utils.removeWhitespace(assertion));
- },
- ], callback);
}
diff --git a/test/saml11.tests.js b/test/saml11.tests.js
index 0e7a3c8..d37dada 100644
--- a/test/saml11.tests.js
+++ b/test/saml11.tests.js
@@ -8,6 +8,9 @@ var xmlenc = require('xml-encryption');
var utils = require('./utils');
var saml11 = require('../lib/saml11');
+var EncryptXml = require('../lib/xml/encrypt');
+var SignXml = require('../lib/xml/sign');
+
describe('saml 1.1', function () {
saml11TestSuite({
@@ -347,6 +350,39 @@ describe('saml 1.1', function () {
});
});
+ it('should create a saml 1.1 encrypted assertion using async strategy', function (done) {
+ var options = {
+ cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
+ key: fs.readFileSync(__dirname + '/test-auth0.key'),
+ encryptionPublicKey: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
+ encryptionCert: fs.readFileSync(__dirname + '/test-auth0.pem')
+ };
+
+ var strategies = {
+ signXml: SignXml.fromSignXmlOptions(Object.assign({
+ xpathToNodeBeforeSignature: "//*[local-name(.)='AuthenticationStatement']",
+ signatureIdAttribute: 'AssertionID'
+ }, options)),
+ encryptXml: EncryptXml.fromEncryptXmlOptions(options)
+ }
+
+ var callback = function(err, encrypted) {
+ if (err) return done(err);
+
+ xmlenc.decrypt(encrypted, { key: fs.readFileSync(__dirname + '/test-auth0.key')}, function(err, decrypted) {
+ if (err) return done(err);
+ assertSignature(decrypted, options);
+ done();
+ });
+ }
+
+ if (createAssertion === 'create'){
+ saml11[createAssertion](options, strategies, callback);
+ } else {
+ saml11[createAssertion](options, callback);
+ }
+ });
+
it('should support holder-of-key suject confirmationmethod', function (done) {
var options = {
cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
diff --git a/test/saml20.tests.js b/test/saml20.tests.js
index b55a7b6..dd51e23 100644
--- a/test/saml20.tests.js
+++ b/test/saml20.tests.js
@@ -7,6 +7,8 @@ var xmldom = require('xmldom');
var xmlenc = require('xml-encryption');
var saml = require('../lib/saml20');
+var EncryptXml = require('../lib/xml/encrypt');
+var SignXml = require('../lib/xml/sign');
describe('saml 2.0', function () {
saml20TestSuite({
@@ -513,6 +515,40 @@ describe('saml 2.0', function () {
});
});
+ it('should create a saml 2.0 signed and encrypted assertion with async strategy', function (done) {
+ var options = {
+ cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
+ key: fs.readFileSync(__dirname + '/test-auth0.key'),
+ encryptionPublicKey: fs.readFileSync(__dirname + '/test-auth0_rsa.pub'),
+ encryptionCert: fs.readFileSync(__dirname + '/test-auth0.pem')
+ };
+
+ var strategies = {
+ signXml: SignXml.fromSignXmlOptions(Object.assign({
+ xpathToNodeBeforeSignature: "//*[local-name(.)='Issuer']",
+ signatureIdAttribute: 'ID'
+ }, options)),
+ encryptXml: EncryptXml.fromEncryptXmlOptions(options)
+ }
+
+ var callback = function(err, encrypted){
+ if (err) return done(err);
+ var encryptedData = utils.getEncryptedData(encrypted);
+
+ xmlenc.decrypt(encryptedData.toString(), { key: fs.readFileSync(__dirname + '/test-auth0.key') }, function (err, decrypted) {
+ if (err) return done(err);
+ assertSignature(decrypted, options);
+ done();
+ });
+ }
+
+ if (createAssertion === 'create'){
+ saml[createAssertion](options, strategies, callback)
+ } else {
+ saml[createAssertion](options, callback)
+ }
+ });
+
it('should set attributes', function (done) {
var options = {
cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
From da5e66c55b3d95f24e1067713097619e23e6695e Mon Sep 17 00:00:00 2001
From: D1plo1d
Date: Fri, 29 Apr 2022 14:21:49 -0400
Subject: [PATCH 2/2] fix(sign): passing callback to compute signature
---
lib/xml/sign.js | 69 ++++++++++++++++++++++++++++++++++---------------
1 file changed, 48 insertions(+), 21 deletions(-)
diff --git a/lib/xml/sign.js b/lib/xml/sign.js
index 7c01fd0..d3de3a3 100644
--- a/lib/xml/sign.js
+++ b/lib/xml/sign.js
@@ -39,7 +39,7 @@ exports.fromSignXmlOptions = function (options) {
* @return {string}
*/
return function signXmlDocument(doc, callback) {
- function sign(key) {
+ function sign(key, signCallback) {
const unsigned = exports.unsigned(doc);
const cert = utils.pemToCert(pem);
@@ -60,32 +60,59 @@ exports.fromSignXmlOptions = function (options) {
}
};
- sig.computeSignature(unsigned, {
- location: {reference: xpathToNodeBeforeSignature, action: 'after'},
- prefix: signatureNamespacePrefix
- });
+ if (signCallback == null) {
+ sig.computeSignature(unsigned, {
+ location: {reference: xpathToNodeBeforeSignature, action: 'after'},
+ prefix: signatureNamespacePrefix
+ });
- return sig.getSignedXml();
+ return sig.getSignedXml();
+ } else {
+ sig.computeSignature(unsigned, {
+ location: {reference: xpathToNodeBeforeSignature, action: 'after'},
+ prefix: signatureNamespacePrefix
+ }, function (err) {
+ if (err != null) {
+ signCallback(err, null)
+ } else {
+ signCallback(null, sig.getSignedXml())
+ }
+ });
+ }
}
- let signed
- try {
- try {
- signed = sign(key)
- } catch (err) {
- signed = sign(utils.fixPemFormatting(key))
- }
+ if (callback != null) {
+ sign(key, function(err, signed) {
+ if (err != null) {
+ sign(utils.fixPemFormatting(key), function(err, signed) {
+ setImmediate(callback, err, signed);
+ })
+ return;
+ }
- if (callback) {
setImmediate(callback, null, signed);
- } else {
- return signed;
- }
- } catch (e) {
- if (callback) {
- setImmediate(callback, e)
+ })
+ } else {
+ let signed
+
+ try {
+ try {
+ signed = sign(key)
+ } catch (err) {
+ signed = sign(utils.fixPemFormatting(key))
+ }
+
+ if (callback) {
+ setImmediate(callback, null, signed);
+ } else {
+ return signed;
+ }
+ } catch (e) {
+ if (callback) {
+ setImmediate(callback, e)
+ }
+ throw e
}
- throw e
}
};
};