diff --git a/README.md b/README.md index a1674ee..150970d 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,29 @@ var signedAssertion = saml.create(options); Everything except the cert and key is optional. +### Encryption + +SAML assertions can optionally be encrypted, by providing a certificate and public key, as follows: + +```js +var saml = require('saml').Saml20; // or Saml11 + +var options = { + cert: fs.readFileSync(__dirname + '/test-auth0.pem'), + key: fs.readFileSync(__dirname + '/test-auth0.key'), + nameIdentifier: 'foo', + encryptionPublicKey: fs.readFileSync(__dirname + '/encryption-key.pub'), + encryptionCert: fs.readFileSync(__dirname + '/encryption-cert.pem'), + encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc', // Defaults to http://www.w3.org/2009/xmlenc11#aes256-gcm if not specified + disallowEncryptionWithInsecureAlgorithm: true, + warnOnInsecureEncryptionAlgorithm: true +} +``` + +See [node-xml-encryption](https://github.com/auth0/node-xml-encryption) for documentation on the allowed algorithms. If using algorithms treated as insecure by [node-xml-encryption](https://github.com/auth0/node-xml-encryption), you must provide disallowEncryptionWithInsecureAlgorithm option set to false. +A warning will be piped to `stderr` using console.warn() by default when the insecure algorithms are used and above mentioned flag is false. This can be disabled via the `warnOnInsecureEncryptionAlgorithm` flag. + + ## Issue Reporting If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. diff --git a/lib/saml11.js b/lib/saml11.js index 48c5988..9de4b2d 100644 --- a/lib/saml11.js +++ b/lib/saml11.js @@ -56,6 +56,8 @@ function extractSaml11Options(opts) { * @param [options.encryptionPublicKey] {Buffer} * @param [options.encryptionAlgorithm] {string} * @param [options.keyEncryptionAlgorithm] {string} + * @param [options.disallowEncryptionWithInsecureAlgorithm] {boolean} + * @param [options.warnOnInsecureEncryptionAlgorithm] {boolean} * * @param {Function} [callback] required if encrypting * @return {String|*} @@ -89,6 +91,8 @@ exports.create = function(options, callback) { * @param [options.encryptionPublicKey] {Buffer} * @param [options.encryptionAlgorithm] {string} * @param [options.keyEncryptionAlgorithm] {string} + * @param [options.disallowEncryptionWithInsecureAlgorithm] {boolean} + * @param [options.warnOnInsecureEncryptionAlgorithm] {boolean} * * @param {Function} [callback] required if encrypting * @return {String|*} diff --git a/lib/saml20.js b/lib/saml20.js index 9db8141..bfcd1ed 100644 --- a/lib/saml20.js +++ b/lib/saml20.js @@ -95,6 +95,8 @@ function extractSaml20Options(opts) { * @param [options.encryptionPublicKey] {Buffer} * @param [options.encryptionAlgorithm] {string} * @param [options.keyEncryptionAlgorithm] {string} + * @param [options.disallowEncryptionWithInsecureAlgorithm] {boolean} + * @param [options.warnOnInsecureEncryptionAlgorithm] {boolean} * * @param {Function} [callback] required if encrypting * @return {*} @@ -134,6 +136,8 @@ exports.create = function createSignedAssertion(options, callback) { * @param [options.encryptionPublicKey] {Buffer} * @param [options.encryptionAlgorithm] {string} * @param [options.keyEncryptionAlgorithm] {string} + * @param [options.disallowEncryptionWithInsecureAlgorithm] {boolean} + * @param [options.warnOnInsecureEncryptionAlgorithm] {boolean} * * @param {Function} [callback] required if encrypting * @return {*} diff --git a/lib/xml/encrypt.js b/lib/xml/encrypt.js index 7452108..ea6e859 100644 --- a/lib/xml/encrypt.js +++ b/lib/xml/encrypt.js @@ -9,8 +9,10 @@ exports.fromEncryptXmlOptions = function (options) { const encryptOptions = { rsa_pub: options.encryptionPublicKey, pem: options.encryptionCert, - encryptionAlgorithm: options.encryptionAlgorithm || 'http://www.w3.org/2001/04/xmlenc#aes256-cbc', + encryptionAlgorithm: options.encryptionAlgorithm || 'http://www.w3.org/2009/xmlenc11#aes256-gcm', keyEncryptionAlgorithm: options.keyEncryptionAlgorithm || 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p', + disallowEncryptionWithInsecureAlgorithm: options?.disallowEncryptionWithInsecureAlgorithm !== false, + warnInsecureAlgorithm: options?.warnOnInsecureEncryptionAlgorithm !== false, }; // expose the encryptOptions as these are needed when adding the SubjectConfirmation diff --git a/package.json b/package.json index bffb6fc..5a70fb4 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "husky": "^9.1.7", "mocha": "^8.2.0", "semantic-release": "^25.0.2", - "should": "~1.2.1" + "should": "~1.2.1", + "sinon": "^9.0.2" }, "files": [ "lib" @@ -31,7 +32,7 @@ "moment": "^2.29.4", "valid-url": "~1.0.9", "xml-crypto": "^2.1.3", - "xml-encryption": "^2.0.0", + "xml-encryption": "^4.0.0", "xml-name-validator": "~2.0.1", "xpath": "0.0.5" }, diff --git a/test/saml11.tests.js b/test/saml11.tests.js index 6d2841d..de81a27 100644 --- a/test/saml11.tests.js +++ b/test/saml11.tests.js @@ -7,6 +7,7 @@ var xmlenc = require('xml-encryption'); var utils = require('./utils'); var saml11 = require('../lib/saml11'); +var sinon = require('sinon'); describe('saml 1.1', function () { @@ -356,6 +357,14 @@ describe('saml 1.1', function () { }); describe('encryption', function () { + let consoleSpy = null; + beforeEach(function() { + consoleSpy = sinon.spy(console, 'warn'); + }); + + afterEach(function() { + consoleSpy.restore(); + }); it('should create a saml 1.1 encrypted assertion', function (done) { var options = { @@ -483,6 +492,60 @@ describe('saml 1.1', function () { }); }); }); + + it('should use aes256-gcm as the default encryption algorithm', 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'), + }; + + saml11[createAssertion](options, function (err, encrypted) { + if (err) return done(err); + var doc = new xmldom.DOMParser().parseFromString(encrypted); + var encryptionMethod = doc.getElementsByTagName('xenc:EncryptionMethod')[0]; + assert.equal('http://www.w3.org/2009/xmlenc11#aes256-gcm', encryptionMethod.getAttribute('Algorithm')); + done(); + }) + }); + + it('should allow aes256-cbc when disallowEncryptionWithInsecureAlgorithm is false', 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'), + encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc', + disallowEncryptionWithInsecureAlgorithm: false, + warnOnInsecureEncryptionAlgorithm: true, + }; + + saml11[createAssertion](options, function (err, encrypted) { + if (err) return done(err); + var doc = new xmldom.DOMParser().parseFromString(encrypted); + var encryptionMethod = doc.getElementsByTagName('xenc:EncryptionMethod')[0]; + assert.equal('http://www.w3.org/2001/04/xmlenc#aes256-cbc', encryptionMethod.getAttribute('Algorithm')); + assert.equal(consoleSpy.called, true); + done(); + }); + }); + + it('should not allow aes256-cbc when disallowEncryptionWithInsecureAlgorithm is true', 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'), + encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc', + disallowEncryptionWithInsecureAlgorithm: true + }; + + saml11[createAssertion](options, function (err, encrypted) { + assert.ok(err); + done(); + }); + }); }); }); } diff --git a/test/saml20.tests.js b/test/saml20.tests.js index b386341..d7280c8 100644 --- a/test/saml20.tests.js +++ b/test/saml20.tests.js @@ -5,6 +5,7 @@ var moment = require('moment'); var should = require('should'); var xmldom = require('@xmldom/xmldom'); var xmlenc = require('xml-encryption'); +var sinon = require('sinon'); var saml = require('../lib/saml20'); @@ -581,6 +582,14 @@ describe('saml 2.0', function () { }); describe('encryption', function () { + let consoleSpy = null; + beforeEach(function() { + consoleSpy = sinon.spy(console, 'warn'); + }); + + afterEach(function() { + consoleSpy.restore(); + }); it('should create a saml 2.0 signed and encrypted assertion', function (done) { var options = { @@ -682,6 +691,61 @@ describe('saml 2.0', function () { }); }); }); + + it('should use aes256-gcm as the default encryption algorithm', 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'), + }; + + saml[createAssertion](options, function (err, encrypted) { + if (err) return done(err); + var encryptedData = utils.getEncryptedData(encrypted); + var encryptionMethod = encryptedData.getElementsByTagName('xenc:EncryptionMethod')[0]; + assert.equal('http://www.w3.org/2009/xmlenc11#aes256-gcm', encryptionMethod.getAttribute('Algorithm')); + done(); + }) + }); + + it('should allow aes256-cbc when disallowEncryptionWithInsecureAlgorithm is false', 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'), + encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc', + disallowEncryptionWithInsecureAlgorithm: false, + warnOnInsecureEncryptionAlgorithm: true, + }; + + saml[createAssertion](options, function (err, encrypted) { + if (err) return done(err); + var encryptedData = utils.getEncryptedData(encrypted); + var encryptionMethod = encryptedData.getElementsByTagName('xenc:EncryptionMethod')[0]; + assert.equal('http://www.w3.org/2001/04/xmlenc#aes256-cbc', encryptionMethod.getAttribute('Algorithm')); + assert.equal(consoleSpy.called, true); + done(); + }); + }); + + it('should not allow aes256-cbc when disallowEncryptionWithInsecureAlgorithm is true', 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'), + encryptionAlgorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc', + disallowEncryptionWithInsecureAlgorithm: true + }; + + saml[createAssertion](options, function (err, encrypted) { + assert.ok(err); + done(); + }); + }); + }); }); }