samlify 2.11.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +1 -1
  2. package/build/src/api.js +52 -3
  3. package/build/src/api.js.map +1 -1
  4. package/build/src/binding-post.js +236 -182
  5. package/build/src/binding-post.js.map +1 -1
  6. package/build/src/binding-redirect.js +303 -215
  7. package/build/src/binding-redirect.js.map +1 -1
  8. package/build/src/binding-simplesign.js +285 -137
  9. package/build/src/binding-simplesign.js.map +1 -1
  10. package/build/src/entity-idp.js +130 -47
  11. package/build/src/entity-idp.js.map +1 -1
  12. package/build/src/entity-sp.js +81 -39
  13. package/build/src/entity-sp.js.map +1 -1
  14. package/build/src/entity.js +100 -62
  15. package/build/src/entity.js.map +1 -1
  16. package/build/src/extractor.js +119 -155
  17. package/build/src/extractor.js.map +1 -1
  18. package/build/src/flow.js +100 -96
  19. package/build/src/flow.js.map +1 -1
  20. package/build/src/libsaml.js +318 -261
  21. package/build/src/libsaml.js.map +1 -1
  22. package/build/src/metadata-idp.js +60 -30
  23. package/build/src/metadata-idp.js.map +1 -1
  24. package/build/src/metadata-sp.js +51 -41
  25. package/build/src/metadata-sp.js.map +1 -1
  26. package/build/src/metadata.js +47 -43
  27. package/build/src/metadata.js.map +1 -1
  28. package/build/src/options.js +73 -0
  29. package/build/src/options.js.map +1 -0
  30. package/build/src/urn.js +28 -1
  31. package/build/src/urn.js.map +1 -1
  32. package/build/src/utility.js +165 -83
  33. package/build/src/utility.js.map +1 -1
  34. package/build/src/validator.js +27 -10
  35. package/build/src/validator.js.map +1 -1
  36. package/package.json +17 -7
  37. package/types/src/api.d.ts +33 -3
  38. package/types/src/binding-post.d.ts +67 -34
  39. package/types/src/binding-redirect.d.ts +58 -31
  40. package/types/src/binding-simplesign.d.ts +77 -21
  41. package/types/src/entity-idp.d.ts +40 -31
  42. package/types/src/entity-sp.d.ts +37 -27
  43. package/types/src/entity.d.ts +71 -77
  44. package/types/src/extractor.d.ts +31 -22
  45. package/types/src/flow.d.ts +24 -2
  46. package/types/src/libsaml.d.ts +172 -118
  47. package/types/src/metadata-idp.d.ts +27 -11
  48. package/types/src/metadata-sp.d.ts +29 -19
  49. package/types/src/metadata.d.ts +59 -34
  50. package/types/src/options.d.ts +37 -0
  51. package/types/src/types.d.ts +250 -24
  52. package/types/src/urn.d.ts +7 -0
  53. package/types/src/utility.d.ts +144 -89
  54. package/types/src/validator.d.ts +21 -0
  55. package/.circleci/config.yml +0 -98
  56. package/.editorconfig +0 -19
  57. package/.github/FUNDING.yml +0 -1
  58. package/.github/workflows/deploy-docs.yml +0 -56
  59. package/.pre-commit.sh +0 -15
  60. package/.snyk +0 -4
  61. package/Makefile +0 -25
  62. package/index.ts +0 -28
  63. package/src/api.ts +0 -36
  64. package/src/binding-post.ts +0 -336
  65. package/src/binding-redirect.ts +0 -335
  66. package/src/binding-simplesign.ts +0 -231
  67. package/src/entity-idp.ts +0 -145
  68. package/src/entity-sp.ts +0 -114
  69. package/src/entity.ts +0 -243
  70. package/src/extractor.ts +0 -399
  71. package/src/flow.ts +0 -469
  72. package/src/libsaml.ts +0 -777
  73. package/src/metadata-idp.ts +0 -146
  74. package/src/metadata-sp.ts +0 -203
  75. package/src/metadata.ts +0 -166
  76. package/src/types.ts +0 -127
  77. package/src/urn.ts +0 -210
  78. package/src/utility.ts +0 -231
  79. package/src/validator.ts +0 -44
  80. package/tsconfig.json +0 -41
  81. package/tslint.json +0 -35
  82. package/types.d.ts +0 -2
  83. package/vitest.config.ts +0 -12
@@ -1,9 +1,21 @@
1
1
  "use strict";
2
2
  /**
3
- * @file SamlLib.js
4
- * @author tngan
5
- * @desc A simple library including some common functions
6
- */
3
+ * @file libsaml.ts
4
+ * @author tngan
5
+ * @desc SAML primitives: templates, XML signing/verification, assertion
6
+ * encryption/decryption, and XPath helpers used by the higher-level flows.
7
+ */
8
+ var __assign = (this && this.__assign) || function () {
9
+ __assign = Object.assign || function(t) {
10
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
11
+ s = arguments[i];
12
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
13
+ t[p] = s[p];
14
+ }
15
+ return t;
16
+ };
17
+ return __assign.apply(this, arguments);
18
+ };
7
19
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
20
  if (k2 === undefined) k2 = k;
9
21
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -91,30 +103,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
91
103
  var utility_1 = __importStar(require("./utility"));
92
104
  var urn_1 = require("./urn");
93
105
  var xpath_1 = require("xpath");
94
- function toNodeArray(result) {
95
- if (Array.isArray(result))
96
- return result;
97
- if (result != null && typeof result === 'object' && 'nodeType' in result)
98
- return [result];
99
- return [];
100
- }
101
106
  var node_rsa_1 = __importDefault(require("node-rsa"));
102
107
  var xml_crypto_1 = require("xml-crypto");
103
108
  var xmlenc = __importStar(require("@authenio/xml-encryption"));
104
- var camelcase_1 = __importDefault(require("camelcase"));
105
109
  var api_1 = require("./api");
106
110
  var xml_escape_1 = __importDefault(require("xml-escape"));
111
+ var crypto = __importStar(require("crypto"));
107
112
  var fs = __importStar(require("fs"));
108
- var xmldom_1 = require("@xmldom/xmldom");
109
113
  var signatureAlgorithms = urn_1.algorithms.signature;
110
114
  var digestAlgorithms = urn_1.algorithms.digest;
111
115
  var certUse = urn_1.wording.certUse;
112
116
  var urlParams = urn_1.wording.urlParams;
117
+ /** Coerce the heterogeneous return of `xpath.select` into a Node array. */
118
+ function toNodeArray(result) {
119
+ if (Array.isArray(result))
120
+ return result;
121
+ if (result != null && typeof result === 'object' && 'nodeType' in result) {
122
+ return [result];
123
+ }
124
+ return [];
125
+ }
113
126
  var libSaml = function () {
114
127
  /**
115
- * @desc helper function to get back the query param for redirect binding for SLO/SSO
116
- * @type {string}
117
- */
128
+ * Map a SAML URL parameter type onto its canonical query-string key
129
+ * (`SAMLRequest` or `SAMLResponse`).
130
+ *
131
+ * @param type SAML URL parameter name
132
+ * @returns `SAMLRequest` or `SAMLResponse`
133
+ */
118
134
  function getQueryParamByType(type) {
119
135
  if ([urlParams.logoutRequest, urlParams.samlRequest].indexOf(type) !== -1) {
120
136
  return 'SAMLRequest';
@@ -125,113 +141,187 @@ var libSaml = function () {
125
141
  throw new Error('ERR_UNDEFINED_QUERY_PARAMS');
126
142
  }
127
143
  /**
144
+ * Mapping from XML-DSig signature algorithm URIs to node-rsa schemes.
128
145
  *
146
+ * The PSS entry covers the redirect-binding detached signature path:
147
+ * `node-rsa` accepts `pss-sha256` directly as a `signingScheme` value.
148
+ * The `xmldsig-more#sha256-rsa-MGF1` URI (W3C Note, 2007-05) is listed
149
+ * in `xmldsig-core §6.4.2` as the recommended successor to PKCS#1 v1.5
150
+ * RSA-SHA256 (`saml-sec-consider §6.5`).
129
151
  */
130
152
  var nrsaAliasMapping = {
131
153
  'http://www.w3.org/2000/09/xmldsig#rsa-sha1': 'pkcs1-sha1',
132
154
  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'pkcs1-sha256',
133
155
  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': 'pkcs1-sha512',
156
+ 'http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1': 'pss-sha256',
157
+ };
158
+ /**
159
+ * RSASSA-PSS plugin class for `xml-crypto`'s `SignedXml.SignatureAlgorithms`
160
+ * registry (`xmldsig-core §6.4.2`).
161
+ *
162
+ * **Temporary shim.** xml-crypto already implements PSS-SHA256 on its
163
+ * master branch (PR node-saml/xml-crypto#488, merged 2025-10-17), but
164
+ * the latest published npm version (6.1.2, 2024-08) predates that
165
+ * commit. Once xml-crypto cuts a release containing #488, delete this
166
+ * class and the `registerPssAlgorithms` helper below — the URI alone
167
+ * in `algorithms.signature` is sufficient and the constructor will
168
+ * seed `SignatureAlgorithms` with the upstream implementation.
169
+ *
170
+ * `RSA_PKCS1_PSS_PADDING` with `RSA_PSS_SALTLEN_DIGEST` matches the
171
+ * MGF1+SHA-256 convention referenced by the `sha256-rsa-MGF1` URI
172
+ * (xmldsig-more, W3C Note 2007-05).
173
+ */
174
+ var pssPaddingOptions = {
175
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
176
+ saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST,
134
177
  };
178
+ var RsaSha256Mgf1 = /** @class */ (function () {
179
+ function RsaSha256Mgf1() {
180
+ this.getSignature = function (signedInfo, privateKey) {
181
+ var signOpts = {
182
+ key: privateKey,
183
+ padding: pssPaddingOptions.padding,
184
+ saltLength: pssPaddingOptions.saltLength,
185
+ };
186
+ return crypto.sign('RSA-SHA256', Buffer.from(signedInfo), signOpts).toString('base64');
187
+ };
188
+ this.verifySignature = function (material, key, signatureValue) {
189
+ var verifyOpts = {
190
+ key: key,
191
+ padding: pssPaddingOptions.padding,
192
+ saltLength: pssPaddingOptions.saltLength,
193
+ };
194
+ return crypto.verify('RSA-SHA256', Buffer.from(material), verifyOpts, Buffer.from(signatureValue, 'base64'));
195
+ };
196
+ this.getAlgorithmName = function () { return 'http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1'; };
197
+ }
198
+ return RsaSha256Mgf1;
199
+ }());
200
+ /**
201
+ * Register the RSASSA-PSS SHA-256 signature class on a fresh `SignedXml`
202
+ * instance so the algorithm-agility surface from `xmldsig-core §6.4` is
203
+ * available for both signing and verification. PKCS#1 v1.5 entries are
204
+ * preserved untouched.
205
+ */
206
+ function registerPssAlgorithms(sig) {
207
+ sig.SignatureAlgorithms = __assign(__assign({}, sig.SignatureAlgorithms), { 'http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1': RsaSha256Mgf1 });
208
+ }
135
209
  /**
136
- * @desc Default login request template
137
- * @type {LoginRequestTemplate}
138
- */
210
+ * Default AuthnRequest XML template.
211
+ *
212
+ * Per saml-core §3.4.1, `ProtocolBinding`, `AssertionConsumerServiceURL`,
213
+ * and `AssertionConsumerServiceIndex` are all `use="optional"` and
214
+ * mutually exclusive — when the index is set, neither URL nor
215
+ * ProtocolBinding may be present. All three are placeholders here so
216
+ * that `replaceTagsByValue` drops whichever the caller leaves
217
+ * undefined (closes #437).
218
+ */
139
219
  var defaultLoginRequestTemplate = {
140
- context: '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="{AssertionConsumerServiceURL}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:NameIDPolicy Format="{NameIDFormat}" AllowCreate="{AllowCreate}"/></samlp:AuthnRequest>',
220
+ context: '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" ForceAuthn="{ForceAuthn}" IssueInstant="{IssueInstant}" Destination="{Destination}" ProtocolBinding="{ProtocolBinding}" AssertionConsumerServiceURL="{AssertionConsumerServiceURL}" AssertionConsumerServiceIndex="{AssertionConsumerServiceIndex}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:NameIDPolicy Format="{NameIDFormat}" AllowCreate="{AllowCreate}"/></samlp:AuthnRequest>',
141
221
  };
142
222
  /**
143
- * @desc Default logout request template
144
- * @type {LogoutRequestTemplate}
145
- */
223
+ * Default LogoutRequest XML template.
224
+ *
225
+ * The optional `<samlp:SessionIndex>` element (saml-core §3.7.1) is
226
+ * included with a placeholder body. When the caller leaves
227
+ * `user.sessionIndex` undefined, `replaceTagsByValue` drops the whole
228
+ * element from the rendered XML, matching the schema's
229
+ * `minOccurs="0"` declaration.
230
+ */
146
231
  var defaultLogoutRequestTemplate = {
147
- context: '<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}"><saml:Issuer>{Issuer}</saml:Issuer><saml:NameID Format="{NameIDFormat}">{NameID}</saml:NameID></samlp:LogoutRequest>',
232
+ context: '<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}"><saml:Issuer>{Issuer}</saml:Issuer><saml:NameID Format="{NameIDFormat}">{NameID}</saml:NameID><samlp:SessionIndex>{SessionIndex}</samlp:SessionIndex></samlp:LogoutRequest>',
148
233
  };
149
- /**
150
- * @desc Default AttributeStatement template
151
- * @type {AttributeStatementTemplate}
152
- */
234
+ /** Default AttributeStatement XML fragment template. */
153
235
  var defaultAttributeStatementTemplate = {
154
236
  context: '<saml:AttributeStatement>{Attributes}</saml:AttributeStatement>',
155
237
  };
156
- /**
157
- * @desc Default Attribute template
158
- * @type {AttributeTemplate}
159
- */
238
+ /** Default Attribute XML fragment template. */
160
239
  var defaultAttributeTemplate = {
161
240
  context: '<saml:Attribute Name="{Name}" NameFormat="{NameFormat}"><saml:AttributeValue xmlns:xs="{ValueXmlnsXs}" xmlns:xsi="{ValueXmlnsXsi}" xsi:type="{ValueXsiType}">{Value}</saml:AttributeValue></saml:Attribute>',
162
241
  };
163
- /**
164
- * @desc Default login response template
165
- * @type {LoginResponseTemplate}
166
- */
242
+ /** Default LoginResponse XML template. */
167
243
  var defaultLoginResponseTemplate = {
168
244
  context: '<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" InResponseTo="{InResponseTo}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="{StatusCode}"/></samlp:Status><saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{AssertionID}" Version="2.0" IssueInstant="{IssueInstant}"><saml:Issuer>{Issuer}</saml:Issuer><saml:Subject><saml:NameID Format="{NameIDFormat}">{NameID}</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="{SubjectConfirmationDataNotOnOrAfter}" Recipient="{SubjectRecipient}" InResponseTo="{InResponseTo}"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="{ConditionsNotBefore}" NotOnOrAfter="{ConditionsNotOnOrAfter}"><saml:AudienceRestriction><saml:Audience>{Audience}</saml:Audience></saml:AudienceRestriction></saml:Conditions>{AuthnStatement}{AttributeStatement}</saml:Assertion></samlp:Response>',
169
245
  attributes: [],
170
246
  additionalTemplates: {
171
- 'attributeStatementTemplate': defaultAttributeStatementTemplate,
172
- 'attributeTemplate': defaultAttributeTemplate
173
- }
247
+ attributeStatementTemplate: defaultAttributeStatementTemplate,
248
+ attributeTemplate: defaultAttributeTemplate,
249
+ },
174
250
  };
175
- /**
176
- * @desc Default logout response template
177
- * @type {LogoutResponseTemplate}
178
- */
251
+ /** Default LogoutResponse XML template. */
179
252
  var defaultLogoutResponseTemplate = {
180
253
  context: '<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" InResponseTo="{InResponseTo}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="{StatusCode}"/></samlp:Status></samlp:LogoutResponse>',
181
254
  };
182
255
  /**
183
- * @private
184
- * @desc Get the signing scheme alias by signature algorithms, used by the node-rsa module
185
- * @param {string} sigAlg signature algorithm
186
- * @return {string/null} signing algorithm short-hand for the module node-rsa
187
- */
256
+ * Map a SAML signature algorithm URI to its node-rsa signing scheme.
257
+ *
258
+ * - When `sigAlg` is omitted, the default RSA-SHA256 scheme is used
259
+ * (per `saml-bindings §3.4.4.1` recommendation).
260
+ * - When `sigAlg` is supplied but does not match a known URI, the
261
+ * function throws. Silently downgrading to RSA-SHA1 (the previous
262
+ * behaviour) was a verification-time vulnerability: an attacker
263
+ * could supply an unknown `SigAlg` query parameter to coerce
264
+ * verification onto SHA-1, which is collision-broken
265
+ * (`saml-sec-consider §6.5`, `xmldsig-core §6.4`).
266
+ *
267
+ * @param sigAlg signature algorithm URI
268
+ * @returns node-rsa signing scheme string
269
+ * @throws when `sigAlg` is supplied and does not match a supported URI
270
+ */
188
271
  function getSigningScheme(sigAlg) {
189
- if (sigAlg) {
190
- var algAlias = nrsaAliasMapping[sigAlg];
191
- if (!(algAlias === undefined)) {
192
- return algAlias;
193
- }
272
+ if (sigAlg === undefined) {
273
+ return nrsaAliasMapping[signatureAlgorithms.RSA_SHA256];
194
274
  }
195
- return nrsaAliasMapping[signatureAlgorithms.RSA_SHA1];
275
+ var algAlias = nrsaAliasMapping[sigAlg];
276
+ if (algAlias === undefined) {
277
+ throw new Error('ERR_UNSUPPORTED_SIGNATURE_ALGORITHM');
278
+ }
279
+ return algAlias;
196
280
  }
197
281
  /**
198
- * @private
199
- * @desc Get the digest algorithms by signature algorithms
200
- * @param {string} sigAlg signature algorithm
201
- * @return {string/undefined} digest algorithm
202
- */
282
+ * Return the companion digest URI for a given signature algorithm URI.
283
+ *
284
+ * @param sigAlg signature algorithm URI
285
+ * @returns digest algorithm URI or undefined when unsupported
286
+ */
203
287
  function getDigestMethod(sigAlg) {
204
288
  return digestAlgorithms[sigAlg];
205
289
  }
206
290
  /**
207
- * @public
208
- * @desc Create XPath
209
- * @param {string/object} local parameters to create XPath
210
- * @param {boolean} isExtractAll define whether returns whole content according to the XPath
211
- * @return {string} xpath
212
- */
291
+ * Build an XPath expression that matches either a named element or one of
292
+ * its attributes.
293
+ *
294
+ * @param local element name, or `{ name, attr }` for an attribute selector
295
+ * @param isExtractAll when true the element selector resolves to its text()
296
+ * @returns XPath expression
297
+ */
213
298
  function createXPath(local, isExtractAll) {
214
299
  if ((0, utility_1.isString)(local)) {
215
- return isExtractAll === true ? "//*[local-name(.)='" + local + "']/text()" : "//*[local-name(.)='" + local + "']";
300
+ var escaped = (0, utility_1.escapeXPathValue)(local);
301
+ return isExtractAll === true
302
+ ? '//*[local-name(.)=' + escaped + ']/text()'
303
+ : '//*[local-name(.)=' + escaped + ']';
216
304
  }
217
- return "//*[local-name(.)='" + local.name + "']/@" + local.attr;
305
+ var _a = local, name = _a.name, attr = _a.attr;
306
+ return '//*[local-name(.)=' + (0, utility_1.escapeXPathValue)(name) + ']/@' + attr;
218
307
  }
219
308
  /**
220
- * @private
221
- * @desc Tag normalization
222
- * @param {string} prefix prefix of the tag
223
- * @param {content} content normalize it to capitalized camel case
224
- * @return {string}
309
+ * Capitalise a content string after camel-casing and optionally prefix it.
225
310
  */
226
311
  function tagging(prefix, content) {
227
- var camelContent = (0, camelcase_1.default)(content, { locale: 'en-us' });
312
+ var camelContent = (0, utility_1.camelCase)(content);
228
313
  return prefix + camelContent.charAt(0).toUpperCase() + camelContent.slice(1);
229
314
  }
315
+ /**
316
+ * Replacer for {@link replaceTagsByValue}. Always XML-escapes the
317
+ * replacement text, in both attribute and element-text contexts, to
318
+ * prevent SAML attribute/element injection through user-controlled
319
+ * template values.
320
+ */
230
321
  function escapeTag(replacement) {
231
322
  return function (_match, quote) {
232
- var text = (replacement === null || replacement === undefined) ? '' : String(replacement);
233
- // not having a quote means this interpolation isn't for an attribute, and so does not need escaping
234
- return quote ? "".concat(quote).concat((0, xml_escape_1.default)(text)) : text;
323
+ var text = replacement === null || replacement === undefined ? '' : String(replacement);
324
+ return quote ? "".concat(quote).concat((0, xml_escape_1.default)(text)) : (0, xml_escape_1.default)(text);
235
325
  };
236
326
  }
237
327
  return {
@@ -244,24 +334,55 @@ var libSaml = function () {
244
334
  defaultLogoutRequestTemplate: defaultLogoutRequestTemplate,
245
335
  defaultLogoutResponseTemplate: defaultLogoutResponseTemplate,
246
336
  /**
247
- * @desc Replace the tag (e.g. {tag}) inside the raw XML
248
- * @param {string} rawXML raw XML string used to do keyword replacement
249
- * @param {array} tagValues tag values
250
- * @return {string}
251
- */
337
+ * Substitute `{Tag}` placeholders inside an XML template with the given
338
+ * replacement map. Replacement text is XML-escaped in both attribute and
339
+ * element-text positions to prevent SAML element/attribute injection.
340
+ *
341
+ * When a tag's value is `null` or `undefined`:
342
+ * - in **attribute position** (`name="{Tag}"`) the entire attribute is
343
+ * omitted from the rendered XML, per `saml-core §3.4.1` (every
344
+ * `<AuthnRequest>` attribute is `use="optional"`); previously samlify
345
+ * emitted `name=""` or the literal `name="undefined"`, which is
346
+ * invalid for typed attributes (e.g. `AssertionConsumerServiceURL`
347
+ * declared as `xs:anyURI`).
348
+ * - in **element-only-body position** (`<X>{Tag}</X>` or
349
+ * `<X attr="...">{Tag}</X>`) the entire element is dropped — per
350
+ * `saml-core §3.7.1` for `<samlp:SessionIndex>` and any other
351
+ * `minOccurs="0"` element where the body is solely a placeholder.
352
+ * - in **mixed-text position** (`<X>prefix{Tag}suffix</X>`) the
353
+ * placeholder is replaced with the empty string (legacy behaviour).
354
+ *
355
+ * @param rawXML template with `{Tag}` placeholders
356
+ * @param tagValues replacement map keyed by tag name
357
+ * @returns XML with placeholders resolved
358
+ */
252
359
  replaceTagsByValue: function (rawXML, tagValues) {
253
360
  Object.keys(tagValues).forEach(function (t) {
254
- rawXML = rawXML.replace(new RegExp("(\"?)\\{".concat(t, "\\}"), 'g'), escapeTag(tagValues[t]));
361
+ var value = tagValues[t];
362
+ if (value === null || value === undefined) {
363
+ // Drop the entire `\s+name="{Tag}"` (or single-quoted) attribute.
364
+ rawXML = rawXML.replace(new RegExp("\\s+[A-Za-z_:][\\w:.-]*=(\"|')\\{".concat(t, "\\}\\1"), 'g'), '');
365
+ // Drop the entire `<X ...>{Tag}</X>` element when the placeholder
366
+ // is the only body content (allows optional elements to be omitted
367
+ // rather than rendered empty).
368
+ rawXML = rawXML.replace(new RegExp("<([A-Za-z_:][\\w:.-]*)((?:\\s+[^>]*)?)>\\{".concat(t, "\\}</\\1>"), 'g'), '');
369
+ // Replace any remaining `{Tag}` occurrences with empty string.
370
+ rawXML = rawXML.replace(new RegExp("\\{".concat(t, "\\}"), 'g'), '');
371
+ return;
372
+ }
373
+ rawXML = rawXML.replace(new RegExp("(\"?)\\{".concat(t, "\\}"), 'g'), escapeTag(value));
255
374
  });
256
375
  return rawXML;
257
376
  },
258
377
  /**
259
- * @desc Helper function to build the AttributeStatement tag
260
- * @param {LoginResponseAttribute} attributes an array of attribute configuration
261
- * @param {AttributeTemplate} attributeTemplate the attribute tag template to be used
262
- * @param {AttributeStatementTemplate} attributeStatementTemplate the attributeStatement tag template to be used
263
- * @return {string}
264
- */
378
+ * Build a serialized `<AttributeStatement>` from attribute descriptors
379
+ * by applying the attribute and statement templates.
380
+ *
381
+ * @param attributes attribute descriptors (name, format, value)
382
+ * @param attributeTemplate per-attribute template
383
+ * @param attributeStatementTemplate wrapping statement template
384
+ * @returns serialized XML fragment
385
+ */
265
386
  attributeStatementBuilder: function (attributes, attributeTemplate, attributeStatementTemplate) {
266
387
  if (attributeTemplate === void 0) { attributeTemplate = defaultAttributeTemplate; }
267
388
  if (attributeStatementTemplate === void 0) { attributeStatementTemplate = defaultAttributeStatementTemplate; }
@@ -281,37 +402,37 @@ var libSaml = function () {
281
402
  return attributeStatementTemplate.context.replace('{Attributes}', attr);
282
403
  },
283
404
  /**
284
- * @desc Construct the XML signature for POST binding
285
- * @param {string} rawSamlMessage request/response xml string
286
- * @param {string} referenceTagXPath reference uri
287
- * @param {string} privateKey declares the private key
288
- * @param {string} passphrase passphrase of the private key [optional]
289
- * @param {string|buffer} signingCert signing certificate
290
- * @param {string} signatureAlgorithm signature algorithm
291
- * @param {string[]} transformationAlgorithms canonicalization and transformation Algorithms
292
- * @return {string} base64 encoded string
293
- */
405
+ * Compute an XML-DSig signature over the supplied SAML message. Can
406
+ * sign the message root (`isMessageSigned`), a referenced subtree
407
+ * (`referenceTagXPath`), or both.
408
+ *
409
+ * @param opts signature inputs and layout options
410
+ * @returns base64 (default) or raw signed XML string
411
+ */
294
412
  constructSAMLSignature: function (opts) {
295
413
  var rawSamlMessage = opts.rawSamlMessage, referenceTagXPath = opts.referenceTagXPath, privateKey = opts.privateKey, privateKeyPass = opts.privateKeyPass, _a = opts.signatureAlgorithm, signatureAlgorithm = _a === void 0 ? signatureAlgorithms.RSA_SHA256 : _a, _b = opts.transformationAlgorithms, transformationAlgorithms = _b === void 0 ? [
296
414
  'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
297
415
  'http://www.w3.org/2001/10/xml-exc-c14n#',
298
416
  ] : _b, signingCert = opts.signingCert, signatureConfig = opts.signatureConfig, _c = opts.isBase64Output, isBase64Output = _c === void 0 ? true : _c, _d = opts.isMessageSigned, isMessageSigned = _d === void 0 ? false : _d;
299
417
  var sig = new xml_crypto_1.SignedXml();
300
- // Add assertion sections as reference
418
+ // xmldsig-core §6.4.2 make PSS variants available alongside the
419
+ // PKCS#1 v1.5 set built into xml-crypto v6.x. No-op for callers
420
+ // staying on RSA-SHA*; required when `signatureAlgorithm` is one of
421
+ // the `xmldsig-more 2007-05` PSS URIs.
422
+ registerPssAlgorithms(sig);
301
423
  var digestAlgorithm = getDigestMethod(signatureAlgorithm);
302
424
  if (referenceTagXPath) {
303
425
  sig.addReference({
304
426
  xpath: referenceTagXPath,
305
427
  transforms: transformationAlgorithms,
306
- digestAlgorithm: digestAlgorithm
428
+ digestAlgorithm: digestAlgorithm,
307
429
  });
308
430
  }
309
431
  if (isMessageSigned) {
310
432
  sig.addReference({
311
- // reference to the root node
312
433
  xpath: '/*',
313
434
  transforms: transformationAlgorithms,
314
- digestAlgorithm: digestAlgorithm
435
+ digestAlgorithm: digestAlgorithm,
315
436
  });
316
437
  }
317
438
  sig.signatureAlgorithm = signatureAlgorithm;
@@ -325,46 +446,50 @@ var libSaml = function () {
325
446
  else {
326
447
  sig.computeSignature(rawSamlMessage);
327
448
  }
328
- return isBase64Output !== false ? utility_1.default.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
449
+ return isBase64Output !== false
450
+ ? utility_1.default.base64Encode(sig.getSignedXml())
451
+ : sig.getSignedXml();
329
452
  },
330
453
  /**
331
- * @desc Verify the XML signature
332
- * @param {string} xml xml
333
- * @param {SignatureVerifierOptions} opts cert declares the X509 certificate
334
- * @return {[boolean, string | null]} - A tuple where:
335
- * - The first element is `true` if the signature is valid, `false` otherwise.
336
- * - The second element is the cryptographically authenticated assertion node as a string, or `null` if not found.
454
+ * Verify an XML-DSig signature on a SAML payload and, on success, return
455
+ * the cryptographically authenticated assertion node.
456
+ *
457
+ * Defends against classic wrapping attacks by rejecting assertions that
458
+ * appear inside a `SubjectConfirmationData` subtree.
459
+ *
460
+ * @param xml SAML message XML
461
+ * @param opts metadata or key file plus signature algorithm
462
+ * @returns tuple `[verified, authenticatedAssertion | null]`
337
463
  */
338
464
  verifySignature: function (xml, opts) {
339
465
  var e_1, _a;
340
466
  var _b;
341
467
  var dom = (0, api_1.getContext)().dom;
342
468
  var doc = dom.parseFromString(xml);
343
- var docParser = new xmldom_1.DOMParser();
344
- // In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element
345
- // message signature (logout response / saml response)
469
+ var contextDom = (0, api_1.getContext)().dom;
470
+ var docParser = contextDom;
471
+ // Absolute XPaths defend against signature-wrapping attacks.
346
472
  var messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
347
- // assertion signature (logout response / saml response)
348
473
  var assertionSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']/*[local-name(.)='Signature']";
349
- // check if there is a potential malicious wrapping signature
350
474
  var wrappingElementsXPath = "/*[contains(local-name(), 'Response')]/*[local-name(.)='Assertion']/*[local-name(.)='Subject']/*[local-name(.)='SubjectConfirmation']/*[local-name(.)='SubjectConfirmationData']//*[local-name(.)='Assertion' or local-name(.)='Signature']";
351
- // select the signature node
352
475
  var selection = [];
353
476
  var messageSignatureNode = toNodeArray((0, xpath_1.select)(messageSignatureXpath, doc));
354
477
  var assertionSignatureNode = toNodeArray((0, xpath_1.select)(assertionSignatureXpath, doc));
355
478
  var wrappingElementNode = toNodeArray((0, xpath_1.select)(wrappingElementsXPath, doc));
356
479
  selection = selection.concat(messageSignatureNode);
357
480
  selection = selection.concat(assertionSignatureNode);
358
- // try to catch potential wrapping attack
359
481
  if (wrappingElementNode.length !== 0) {
360
482
  throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
361
483
  }
362
- // guarantee to have a signature in saml response
363
484
  if (selection.length === 0) {
364
- return [false, null]; // we return false now
485
+ return [false, null];
365
486
  }
366
487
  var _loop_1 = function (signatureNode) {
367
488
  var sig = new xml_crypto_1.SignedXml();
489
+ // Register PSS plugins on the verifier instance so the algorithm
490
+ // declared inside the signed XML can resolve to a SignatureAlgorithm
491
+ // class (xmldsig-core §6.4.2; see `registerPssAlgorithms`).
492
+ registerPssAlgorithms(sig);
368
493
  var verified = false;
369
494
  sig.signatureAlgorithm = opts.signatureAlgorithm;
370
495
  if (!opts.keyFile && !opts.metadata) {
@@ -375,79 +500,59 @@ var libSaml = function () {
375
500
  }
376
501
  if (opts.metadata) {
377
502
  var certificateNode = toNodeArray((0, xpath_1.select)(".//*[local-name(.)='X509Certificate']", signatureNode));
378
- // certificate in metadata
379
503
  var metadataCert = opts.metadata.getX509Certificate(certUse.signing);
380
- // flattens the nested array of Certificates from each KeyDescriptor
381
504
  if (Array.isArray(metadataCert)) {
382
505
  metadataCert = (0, utility_1.flattenDeep)(metadataCert);
383
506
  }
384
507
  else if (typeof metadataCert === 'string') {
385
508
  metadataCert = [metadataCert];
386
509
  }
387
- // normalise the certificate string
388
510
  metadataCert = metadataCert.map(utility_1.default.normalizeCerString);
389
- // no certificate in node response nor metadata
390
511
  if (certificateNode.length === 0 && metadataCert.length === 0) {
391
512
  throw new Error('NO_SELECTED_CERTIFICATE');
392
513
  }
393
- // certificate node in response
394
514
  if (certificateNode.length !== 0) {
395
515
  var certEl = certificateNode[0];
396
516
  var x509CertificateData = (_b = certEl.textContent) !== null && _b !== void 0 ? _b : '';
397
517
  var x509Certificate_1 = utility_1.default.normalizeCerString(x509CertificateData);
398
518
  if (metadataCert.length >= 1 &&
399
519
  !metadataCert.find(function (cert) { return cert.trim() === x509Certificate_1.trim(); })) {
400
- // keep this restriction for rolling certificate usage
401
- // to make sure the response certificate is one of those specified in metadata
402
520
  throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
403
521
  }
404
522
  sig.publicCert = this_1.getKeyInfo(x509Certificate_1).getKey();
405
523
  }
406
524
  else {
407
- // Select first one from metadata
408
525
  sig.publicCert = this_1.getKeyInfo(metadataCert[0]).getKey();
409
526
  }
410
527
  }
411
528
  sig.loadSignature(signatureNode);
412
529
  verified = sig.checkSignature(doc.toString());
413
- // immediately throw error when any one of the signature is failed to get verified
414
530
  if (!verified) {
415
531
  return "continue";
416
- // throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
417
532
  }
418
- // Require there to be at least one reference that was signed
419
533
  if (!(sig.getSignedReferences().length >= 1)) {
420
534
  throw new Error('NO_SIGNATURE_REFERENCES');
421
535
  }
422
536
  var signedVerifiedXML = sig.getSignedReferences()[0];
423
537
  var rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement;
424
- // process the verified signature:
425
- // case 1, rootSignedDoc is a response:
426
538
  if (rootNode.localName === 'Response') {
427
- // try getting the Xml from the first assertion
428
539
  var assertions = toNodeArray((0, xpath_1.select)("./*[local-name()='Assertion']", rootNode));
429
540
  var encryptedAssertions = toNodeArray((0, xpath_1.select)("./*[local-name()='EncryptedAssertion']", rootNode));
430
- // now we can process the assertion as an assertion
431
541
  if (assertions.length === 1) {
432
542
  return { value: [true, assertions[0].toString()] };
433
543
  }
434
544
  else if (encryptedAssertions.length >= 1) {
435
545
  return { value: [true, rootNode.toString()] };
436
546
  }
437
- else {
438
- return { value: [true, null] };
439
- }
547
+ return { value: [true, null] };
440
548
  }
441
549
  else if (rootNode.localName === 'Assertion') {
442
550
  return { value: [true, rootNode.toString()] };
443
551
  }
444
- else {
445
- return { value: [true, null] };
446
- }
552
+ return { value: [true, null] };
447
553
  };
448
554
  var this_1 = this;
449
555
  try {
450
- // need to refactor later on
451
556
  for (var selection_1 = __values(selection), selection_1_1 = selection_1.next(); !selection_1_1.done; selection_1_1 = selection_1.next()) {
452
557
  var signatureNode = selection_1_1.value;
453
558
  var state_1 = _loop_1(signatureNode);
@@ -462,108 +567,65 @@ var libSaml = function () {
462
567
  }
463
568
  finally { if (e_1) throw e_1.error; }
464
569
  }
465
- ;
466
- return [false, null]; // we didn't verify anything, none of the signatures are valid
467
- /*
468
- // response must be signed, either entire document or assertion
469
- // default we will take the assertion section under root
470
- if (messageSignatureNode.length === 1) {
471
- const node = select("/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']", doc);
472
- if (node.length === 1) {
473
- assertionNode = node[0].toString();
474
- }
475
- }
476
-
477
- if (assertionSignatureNode.length === 1) {
478
- const verifiedAssertionInfo = extract(assertionSignatureNode[0].toString(), [{
479
- key: 'refURI',
480
- localPath: ['Signature', 'SignedInfo', 'Reference'],
481
- attributes: ['URI']
482
- }]);
483
- // get the assertion supposed to be the one should be verified
484
- const desiredAssertionInfo = extract(doc.toString(), [{
485
- key: 'id',
486
- localPath: ['~Response', 'Assertion'],
487
- attributes: ['ID']
488
- }]);
489
- // 5.4.2 References
490
- // SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of
491
- // the assertion or protocol message being signed. The assertion’s or protocol message's root element may
492
- // or may not be the root element of the actual XML document containing the signed assertion or protocol
493
- // message (e.g., it might be contained within a SOAP envelope).
494
- // Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID
495
- // attribute value of the root element of the assertion or protocol message being signed. For example, if the
496
- // ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo".
497
- if (verifiedAssertionInfo.refURI !== `#${desiredAssertionInfo.id}`) {
498
- throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
499
- }
500
- const verifiedDoc = extract(doc.toString(), [{
501
- key: 'assertion',
502
- localPath: ['~Response', 'Assertion'],
503
- attributes: [],
504
- context: true
505
- }]);
506
- assertionNode = verifiedDoc.assertion.toString();
507
- }
508
-
509
- return [verified, assertionNode];*/
570
+ return [false, null];
510
571
  },
511
572
  /**
512
- * @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
513
- * @param {string} use type of certificate (e.g. signing, encrypt)
514
- * @param {string} certString declares the certificate String
515
- * @return {object} object used in xml module
516
- */
573
+ * Build the metadata `<KeyDescriptor>` fragment for a certificate use.
574
+ *
575
+ * @param use `signing` or `encryption`
576
+ * @param certString PEM certificate body or Buffer
577
+ * @returns element tree consumable by the `xml` module
578
+ */
517
579
  createKeySection: function (use, certString) {
518
- var _a, _b, _c;
519
- return _a = {},
520
- _a['KeyDescriptor'] = [
580
+ return {
581
+ KeyDescriptor: [
521
582
  {
522
583
  _attr: { use: use },
523
584
  },
524
- (_b = {},
525
- _b['ds:KeyInfo'] = [
585
+ {
586
+ 'ds:KeyInfo': [
526
587
  {
527
588
  _attr: {
528
589
  'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
529
590
  },
530
591
  },
531
- (_c = {},
532
- _c['ds:X509Data'] = [{
592
+ {
593
+ 'ds:X509Data': [{
533
594
  'ds:X509Certificate': utility_1.default.normalizeCerString(certString),
534
595
  }],
535
- _c),
596
+ },
536
597
  ],
537
- _b)
598
+ },
538
599
  ],
539
- _a;
600
+ };
540
601
  },
541
602
  /**
542
- * @desc Constructs SAML message
543
- * @param {string} octetString see "Bindings for the OASIS Security Assertion Markup Language (SAML V2.0)" P.17/46
544
- * @param {string} key declares the pem-formatted private key
545
- * @param {string} passphrase passphrase of private key [optional]
546
- * @param {string} signingAlgorithm signing algorithm
547
- * @return {string} message signature
548
- */
603
+ * Produce a detached RSA signature over a SAML redirect-binding octet
604
+ * string. See SAML bindings spec §3.4.4.1.
605
+ *
606
+ * @param octetString canonical query-string to sign
607
+ * @param key PEM private key
608
+ * @param passphrase optional passphrase for the key
609
+ * @param isBase64 when true (default), base64-encode the signature
610
+ * @param signingAlgorithm signature algorithm URI
611
+ * @returns base64 string (default) or raw Buffer signature
612
+ */
549
613
  constructMessageSignature: function (octetString, key, passphrase, isBase64, signingAlgorithm) {
550
- // Default returning base64 encoded signature
551
- // Embed with node-rsa module
552
614
  var decryptedKey = new node_rsa_1.default(utility_1.default.readPrivateKey(key, passphrase), undefined, {
553
615
  signingScheme: getSigningScheme(signingAlgorithm),
554
616
  });
555
617
  var signature = decryptedKey.sign(octetString);
556
- // Use private key to sign data
557
618
  return isBase64 !== false ? signature.toString('base64') : signature;
558
619
  },
559
620
  /**
560
- * @desc Verifies message signature
561
- * @param {Metadata} metadata metadata object of identity provider or service provider
562
- * @param {string} octetString see "Bindings for the OASIS Security Assertion Markup Language (SAML V2.0)" P.17/46
563
- * @param {string} signature context of XML signature
564
- * @param {string} verifyAlgorithm algorithm used to verify
565
- * @return {boolean} verification result
566
- */
621
+ * Verify a detached RSA signature over a redirect-binding octet string.
622
+ *
623
+ * @param metadata peer metadata carrying the signing certificate
624
+ * @param octetString canonical query-string that was signed
625
+ * @param signature signature bytes
626
+ * @param verifyAlgorithm signature algorithm URI (optional)
627
+ * @returns true when the signature verifies
628
+ */
567
629
  verifyMessageSignature: function (metadata, octetString, signature, verifyAlgorithm) {
568
630
  var signCert = metadata.getX509Certificate(certUse.signing);
569
631
  var signingScheme = getSigningScheme(verifyAlgorithm);
@@ -571,10 +633,11 @@ var libSaml = function () {
571
633
  return key.verify(Buffer.from(octetString), Buffer.from(signature));
572
634
  },
573
635
  /**
574
- * @desc Get the public key in string format
575
- * @param {string} x509Certificate certificate
576
- * @return {string} public key
577
- */
636
+ * Build the KeyInfo XML fragment and PEM public key for a certificate.
637
+ *
638
+ * @param x509Certificate certificate body (no PEM wrappers)
639
+ * @param signatureConfig optional prefix/location for the KeyInfo element
640
+ */
578
641
  getKeyInfo: function (x509Certificate, signatureConfig) {
579
642
  if (signatureConfig === void 0) { signatureConfig = {}; }
580
643
  var prefix = signatureConfig.prefix ? "".concat(signatureConfig.prefix, ":") : '';
@@ -588,14 +651,16 @@ var libSaml = function () {
588
651
  };
589
652
  },
590
653
  /**
591
- * @desc Encrypt the assertion section in Response
592
- * @param {Entity} sourceEntity source entity
593
- * @param {Entity} targetEntity target entity
594
- * @param {string} xml response in xml string format
595
- * @return {Promise} a promise to resolve the finalized xml
596
- */
654
+ * Encrypt the `<Assertion>` inside a SAML response using the target
655
+ * entity's encryption certificate. Returns the base64-encoded XML
656
+ * containing the `<EncryptedAssertion>` element in place of the plaintext.
657
+ *
658
+ * @param sourceEntity entity initiating the encryption (its settings drive the algorithms)
659
+ * @param targetEntity entity whose certificate is used
660
+ * @param xml response XML containing a single `<Assertion>`
661
+ * @returns promise resolving to base64-encoded XML
662
+ */
597
663
  encryptAssertion: function (sourceEntity, targetEntity, xml) {
598
- // Implement encryption after signature if it has
599
664
  return new Promise(function (resolve, reject) {
600
665
  if (!xml) {
601
666
  return reject(new Error('ERR_UNDEFINED_ASSERTION'));
@@ -612,16 +677,16 @@ var libSaml = function () {
612
677
  throw new Error('ERR_MULTIPLE_ASSERTION');
613
678
  }
614
679
  var rawAssertionNode = assertions[0];
615
- // Perform encryption depends on the setting, default is false
616
680
  if (sourceEntitySetting.isAssertionEncrypted) {
617
- var publicKeyPem = utility_1.default.getPublicKeyPemFromCertificate(targetEntityMetadata.getX509Certificate(certUse.encrypt));
681
+ var encryptCert = targetEntityMetadata.getX509Certificate(certUse.encrypt);
682
+ var publicKeyPem = utility_1.default.getPublicKeyPemFromCertificate(encryptCert);
618
683
  xmlenc.encrypt(rawAssertionNode.toString(), {
619
- // use xml-encryption module
620
- rsa_pub: Buffer.from(publicKeyPem), // public key from certificate
621
- pem: Buffer.from("-----BEGIN CERTIFICATE-----".concat(targetEntityMetadata.getX509Certificate(certUse.encrypt), "-----END CERTIFICATE-----")),
684
+ rsa_pub: Buffer.from(publicKeyPem),
685
+ pem: Buffer.from("-----BEGIN CERTIFICATE-----".concat(encryptCert, "-----END CERTIFICATE-----")),
622
686
  encryptionAlgorithm: sourceEntitySetting.dataEncryptionAlgorithm,
623
687
  keyEncryptionAlgorithm: sourceEntitySetting.keyEncryptionAlgorithm,
624
688
  }, function (err, res) {
689
+ /* v8 ignore start */
625
690
  if (err) {
626
691
  console.error(err);
627
692
  return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_ENCRYPTION'));
@@ -629,6 +694,7 @@ var libSaml = function () {
629
694
  if (!res) {
630
695
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
631
696
  }
697
+ /* v8 ignore stop */
632
698
  var encAssertionPrefix = sourceEntitySetting.tagPrefix.encryptedAssertion;
633
699
  var encryptAssertionDoc = dom.parseFromString("<".concat(encAssertionPrefix, ":EncryptedAssertion xmlns:").concat(encAssertionPrefix, "=\"").concat(urn_1.namespace.names.assertion, "\">").concat(res, "</").concat(encAssertionPrefix, ":EncryptedAssertion>"));
634
700
  doc.documentElement.replaceChild(encryptAssertionDoc.documentElement, rawAssertionNode);
@@ -636,25 +702,24 @@ var libSaml = function () {
636
702
  });
637
703
  }
638
704
  else {
639
- return resolve(utility_1.default.base64Encode(xml)); // No need to do encryption
705
+ return resolve(utility_1.default.base64Encode(xml));
640
706
  }
641
707
  });
642
708
  },
643
709
  /**
644
- * @desc Decrypt the assertion section in Response
645
- * @param {string} type only accept SAMLResponse to proceed decryption
646
- * @param {Entity} here this entity
647
- * @param {Entity} from from the entity where the message is sent
648
- * @param {string} entireXML response in xml string format
649
- * @return {function} a promise to get back the entire xml with decrypted assertion
650
- */
710
+ * Decrypt the `<EncryptedAssertion>` inside a SAML response using the
711
+ * local entity's private key. Returns both the decrypted document XML
712
+ * and the raw assertion fragment for downstream extraction.
713
+ *
714
+ * @param here local entity performing decryption
715
+ * @param entireXML SAML response XML containing `<EncryptedAssertion>`
716
+ * @returns tuple `[decryptedDocumentXml, rawAssertionXml]`
717
+ */
651
718
  decryptAssertion: function (here, entireXML) {
652
719
  return new Promise(function (resolve, reject) {
653
- // Implement decryption first then check the signature
654
720
  if (!entireXML) {
655
721
  return reject(new Error('ERR_UNDEFINED_ASSERTION'));
656
722
  }
657
- // Perform encryption depends on the setting of where the message is sent, default is false
658
723
  var hereSetting = here.entitySetting;
659
724
  var dom = (0, api_1.getContext)().dom;
660
725
  var doc = dom.parseFromString(entireXML);
@@ -669,6 +734,7 @@ var libSaml = function () {
669
734
  return xmlenc.decrypt(encAssertionNode.toString(), {
670
735
  key: utility_1.default.readPrivateKey(hereSetting.encPrivateKey, hereSetting.encPrivateKeyPass),
671
736
  }, function (err, res) {
737
+ /* v8 ignore start */
672
738
  if (err) {
673
739
  console.error(err);
674
740
  return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION'));
@@ -676,6 +742,7 @@ var libSaml = function () {
676
742
  if (!res) {
677
743
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
678
744
  }
745
+ /* v8 ignore stop */
679
746
  var rawAssertionDoc = dom.parseFromString(res);
680
747
  doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
681
748
  return resolve([doc.toString(), res]);
@@ -683,34 +750,24 @@ var libSaml = function () {
683
750
  });
684
751
  },
685
752
  /**
686
- * @desc Check if the xml string is valid and bounded
753
+ * Validate the SAML XML against the registered schema validator. Throws
754
+ * when no validator has been configured via {@link setSchemaValidator}
755
+ * so consumers can't silently ship without schema checks.
756
+ *
757
+ * @param input SAML XML string
687
758
  */
688
759
  isValidXml: function (input) {
689
760
  return __awaiter(this, void 0, void 0, function () {
690
- var validate, e_2;
761
+ var validate;
691
762
  return __generator(this, function (_a) {
692
763
  switch (_a.label) {
693
764
  case 0:
694
765
  validate = (0, api_1.getContext)().validate;
695
- /**
696
- * user can write a validate function that always returns
697
- * a resolved promise and skip the validator even in
698
- * production, user will take the responsibility if
699
- * they intend to skip the validation
700
- */
701
766
  if (!validate) {
702
- // otherwise, an error will be thrown
703
- return [2 /*return*/, Promise.reject('Your application is potentially vulnerable because no validation function found. Please read the documentation on how to setup the validator. (https://github.com/tngan/samlify#installation)')];
767
+ return [2 /*return*/, Promise.reject(new Error('Your application is potentially vulnerable because no validation function found. Please read the documentation on how to setup the validator. (https://github.com/tngan/samlify#installation)'))];
704
768
  }
705
- _a.label = 1;
706
- case 1:
707
- _a.trys.push([1, 3, , 4]);
708
769
  return [4 /*yield*/, validate(input)];
709
- case 2: return [2 /*return*/, _a.sent()];
710
- case 3:
711
- e_2 = _a.sent();
712
- throw e_2;
713
- case 4: return [2 /*return*/];
770
+ case 1: return [2 /*return*/, _a.sent()];
714
771
  }
715
772
  });
716
773
  });