diff --git a/lib/saml20.js b/lib/saml20.js index 2ada2a7d..fd1c2e02 100644 --- a/lib/saml20.js +++ b/lib/saml20.js @@ -56,14 +56,20 @@ function getNameFormat(name){ } /** -* Gets the complere SAML Response merged with assertion (encrypted optional) and uses the -* saml20 argument options to set parts of the response utilizing the saml20Response.template file. +* Gets the complete SAML20 response embedding the assertion (encrypted optional) and +* options argument to set attributes for response utilizing the saml20Response.template file. * @param assertion - the SAML assertion to add to the SAML response. * @param options - The saml20 class options argument. +* @returns string - SAML20 Full Response with embedded assertion XML. +* @throws assertion argument null or empty error. */ function getSamlResponseXml(assertion, options) { + var issueTime = new Date().toISOString(); + if (!assertion || assertion.length < 1) + throw new ReferenceError('Assertion XML cannot be empty for parsing while creating SAML20 Response.') + var assertionXml = new Parser().parseFromString(assertion); var saml20Response = fs.readFileSync(path.join(__dirname, 'saml20Response.template')).toString(); @@ -77,7 +83,7 @@ function getSamlResponseXml(assertion, options) { issuer[0].textContent = options.issuer; } doc.lastChild.appendChild(assertionXml.documentElement); - + return doc.toString(); } @@ -85,8 +91,14 @@ function getSamlResponseXml(assertion, options) { * Signs the SAML XML at the Assertion level (default) or the Response Level (optional) using private key and cert. * @param xmlToSign - The XML in string form containing the XML assertion or response. * @param options - The saml20 class options argument. +* @returns string - Signed SAML assertion or response depending on option. +* @throws ReferenceError if xml argument sent for signing is null or empty. */ function signXml(xmlToSign, options) { + + if (!xmlToSign || xmlToSign.length < 1) + throw new ReferenceError('XML to sign cannot be null or empty.') + // 0.10.1 added prefix, but we want to name it signatureNamespacePrefix - This is just to keep supporting prefix options.signatureNamespacePrefix = options.signatureNamespacePrefix || options.prefix; options.signatureNamespacePrefix = typeof options.signatureNamespacePrefix === 'string' ? options.signatureNamespacePrefix : '' ; @@ -114,7 +126,7 @@ function signXml(xmlToSign, options) { return "<" + prefix + "X509Data><" + prefix + "X509Certificate>" + cert + ""; } }; - + sig.computeSignature(xmlToSign, opts); return sig.getSignedXml(); @@ -124,22 +136,24 @@ function signXml(xmlToSign, options) { * Encrypts s SAML assertion and formats with EncryptedAssertion wrapper using provided cert. * @param assertionToEncrypt - The SAML assertion to encrypt. * @param options - The saml20 class options argument. -* @param callback - The callback function for ASYNC processing completion. +* @returns Promise (resolve, reject) for the embedded ASYNC encrypt function callback wrapper. */ -function encryptAssertionXml(assertionToEncrypt, options, callback) { - 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(assertionToEncrypt, encryptOptions, function (err, encrypted) { - if (err) return callback(err); - var assertion = '' + encrypted + ''; - return callback(null, assertion); - }) -} +var encryptAssertionXml = (assertionToEncrypt, options) => + new Promise((resolve, reject) => { + 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(assertionToEncrypt, encryptOptions, function (err, encryptedAssertion) { + if (err) reject(err); + encryptedAssertion = `${encryptedAssertion}`; + + resolve(encryptedAssertion); + }); +}); exports.create = function (options, callback) { if (!options.key) @@ -262,44 +276,33 @@ exports.create = function (options, callback) { var assertion = utils.removeWhitespace(doc.toString()); - // NEW: Option: build a complete signed SAML response with embedded (option encrypted) assertion + // NEW: construct a SAML20 response signed at the response with embedded (encrypted option) assertion if (options.createSignedSamlResponse) { - try { - // IF SAML response assertion is set to be encrypted - if (options.encryptionCert) { - encryptAssertionXml(assertion, options, function (err, encryptedAssertion) { - if (err) return callback(err); - var signedResponse = signSamlResponse(encryptedAssertion); - return callback(null, signedResponse); - }); - } else { - // Do not encrypt assertion and send back - var signedPlainResponse = signSamlResponse(assertion); - return (callback) ? callback(null, signedPlainResponse) : signedPlainResponse; - } - } catch (err) { - return (callback) ? callback(err) : err; + + if (options.encryptionCert) { + encryptAssertionXml(assertion, options) + .then(encryptedAssertion => (callback(null, signSamlResponse(encryptedAssertion)))) + .catch((err) => callback(err)); + } else { + // Send saml response back signed if not set for encryption + var signedPlainResponse = signSamlResponse(assertion); + return (callback) ? callback(null, signedPlainResponse) : signedPlainResponse; } } else { - try { - // Sign the assertion always for both options - var signedAssertion = signXml(utils.removeWhitespace(assertion), options); - if (options.encryptionCert) { - // If assertion is set to be encrypted - encryptAssertionXml(signedAssertion, options, function (err, encryptedAssertion) { - if (err) return callback(err); - return callback(null, encryptedAssertion) - }); - } else { - // If assertion encryption not set just send back - return (callback) ? callback(null, signedAssertion) : signedAssertion; - } - } catch (err) { - return (callback) ? callback(err) : err; + // Sign the assertion always for both options + var signedAssertion = signXml(utils.removeWhitespace(assertion), options); + + if (options.encryptionCert) { + encryptAssertionXml(signedAssertion, options) + .then(encryptedAssertion => callback(null, encryptedAssertion)) + .catch((err) => callback(err)); + } else { + // Send back signed if not set for encryption + return (callback) ? callback(null, signedAssertion) : signedAssertion; } } - // Generates response with inserted assertion (or encrypted assertion) and signs + // Sign response with inserted assertion (or encrypted assertion) function signSamlResponse(assertion) { var samlResponse = getSamlResponseXml(assertion, options); return signXml(utils.removeWhitespace(samlResponse), options); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..c14fc296 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,379 @@ +{ + "name": "saml", + "version": "0.15.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "^3.0.0", + "lodash._basecreate": "^3.0.0", + "lodash._isiterateecall": "^3.0.0" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + } + }, + "moment": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.3.tgz", + "integrity": "sha1-vbmdJw1tf9p4zA+6zoVeJ/59pp8=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node-forge": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", + "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "should": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/should/-/should-1.2.2.tgz", + "integrity": "sha1-DwP3dQZtnqJjJpDJF7EoJPzB1YI=", + "dev": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + }, + "valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xml-crypto": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-1.0.2.tgz", + "integrity": "sha512-bDQkgu1yuwl+QoJbi4GBP9MWxpmYkXc8a9iSHbZ7lKqcxzGlDqMRugcl7qK7TsMI0ydU66GG8/eLNvRUk5T2fw==", + "requires": { + "xmldom": "0.1.27", + "xpath.js": ">=0.0.3" + }, + "dependencies": { + "xmldom": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" + } + } + }, + "xml-encryption": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-0.11.2.tgz", + "integrity": "sha512-jVvES7i5ovdO7N+NjgncA326xYKjhqeAnnvIgRnY7ROLCfFqEDLwP0Sxp/30SHG0AXQV1048T5yinOFyvwGFzg==", + "requires": { + "async": "^2.1.5", + "ejs": "^2.5.6", + "node-forge": "^0.7.0", + "xmldom": "~0.1.15", + "xpath": "0.0.27" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "xpath": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", + "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" + } + } + }, + "xml-name-validator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", + "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=" + }, + "xmldom": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.15.tgz", + "integrity": "sha1-swSAYvG91S7cQhQkRZ8G3O6y+U0=" + }, + "xpath": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.5.tgz", + "integrity": "sha1-RUA29u8PPfWvXUukoRn7dWdLPmw=" + }, + "xpath.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz", + "integrity": "sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==" + } + } +} diff --git a/package.json b/package.json index 83c47b14..4fdd11db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "saml", - "version": "0.14.0", + "version": "0.15.0", "devDependencies": { "mocha": "3.5.3", "should": "~1.2.1"