samlesa 2.12.113 → 2.14.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.

Potentially problematic release.


This version of samlesa might be problematic. Click here for more details.

Files changed (145) hide show
  1. package/build/index.js +18 -54
  2. package/build/index.js.map +1 -1
  3. package/build/src/api.js +18 -24
  4. package/build/src/api.js.map +1 -1
  5. package/build/src/binding-post.js +337 -365
  6. package/build/src/binding-post.js.map +1 -1
  7. package/build/src/binding-redirect.js +312 -340
  8. package/build/src/binding-redirect.js.map +1 -1
  9. package/build/src/binding-simplesign.js +201 -229
  10. package/build/src/binding-simplesign.js.map +1 -1
  11. package/build/src/entity-idp.js +119 -127
  12. package/build/src/entity-idp.js.map +1 -1
  13. package/build/src/entity-sp.js +88 -96
  14. package/build/src/entity-sp.js.map +1 -1
  15. package/build/src/entity.js +193 -225
  16. package/build/src/entity.js.map +1 -1
  17. package/build/src/extractor.js +361 -369
  18. package/build/src/extractor.js.map +1 -1
  19. package/build/src/flow.js +313 -320
  20. package/build/src/flow.js.map +1 -1
  21. package/build/src/libsaml.js +693 -721
  22. package/build/src/libsaml.js.map +1 -1
  23. package/build/src/metadata-idp.js +119 -127
  24. package/build/src/metadata-idp.js.map +1 -1
  25. package/build/src/metadata-sp.js +223 -231
  26. package/build/src/metadata-sp.js.map +1 -1
  27. package/build/src/metadata.js +138 -166
  28. package/build/src/metadata.js.map +1 -1
  29. package/build/src/types.js +4 -11
  30. package/build/src/types.js.map +1 -1
  31. package/build/src/urn.js +204 -212
  32. package/build/src/urn.js.map +1 -1
  33. package/build/src/utility.js +277 -292
  34. package/build/src/utility.js.map +1 -1
  35. package/build/src/validator.js +24 -27
  36. package/build/src/validator.js.map +1 -1
  37. package/package.json +13 -7
  38. package/types/api.d.ts +15 -0
  39. package/types/api.d.ts.map +1 -0
  40. package/types/binding-post.d.ts +48 -0
  41. package/types/binding-post.d.ts.map +1 -0
  42. package/types/binding-redirect.d.ts +54 -0
  43. package/types/binding-redirect.d.ts.map +1 -0
  44. package/types/binding-simplesign.d.ts +41 -0
  45. package/types/binding-simplesign.d.ts.map +1 -0
  46. package/types/entity-idp.d.ts +38 -0
  47. package/types/entity-idp.d.ts.map +1 -0
  48. package/types/entity-sp.d.ts +38 -0
  49. package/types/entity-sp.d.ts.map +1 -0
  50. package/types/entity.d.ts +100 -0
  51. package/types/entity.d.ts.map +1 -0
  52. package/types/extractor.d.ts +26 -0
  53. package/types/extractor.d.ts.map +1 -0
  54. package/types/flow.d.ts +7 -0
  55. package/types/flow.d.ts.map +1 -0
  56. package/types/index.d.ts +11 -10
  57. package/types/index.d.ts.map +1 -0
  58. package/types/libsaml.d.ts +208 -0
  59. package/types/libsaml.d.ts.map +1 -0
  60. package/types/metadata-idp.d.ts +25 -0
  61. package/types/metadata-idp.d.ts.map +1 -0
  62. package/types/metadata-sp.d.ts +37 -0
  63. package/types/metadata-sp.d.ts.map +1 -0
  64. package/types/metadata.d.ts +58 -0
  65. package/types/metadata.d.ts.map +1 -0
  66. package/types/src/api.d.ts +15 -13
  67. package/types/src/api.d.ts.map +1 -0
  68. package/types/src/binding-post.d.ts +48 -47
  69. package/types/src/binding-post.d.ts.map +1 -0
  70. package/types/src/binding-redirect.d.ts +54 -53
  71. package/types/src/binding-redirect.d.ts.map +1 -0
  72. package/types/src/binding-simplesign.d.ts +41 -40
  73. package/types/src/binding-simplesign.d.ts.map +1 -0
  74. package/types/src/entity-idp.d.ts +38 -37
  75. package/types/src/entity-idp.d.ts.map +1 -0
  76. package/types/src/entity-sp.d.ts +38 -36
  77. package/types/src/entity-sp.d.ts.map +1 -0
  78. package/types/src/entity.d.ts +100 -101
  79. package/types/src/entity.d.ts.map +1 -0
  80. package/types/src/extractor.d.ts +26 -25
  81. package/types/src/extractor.d.ts.map +1 -0
  82. package/types/src/flow.d.ts +7 -6
  83. package/types/src/flow.d.ts.map +1 -0
  84. package/types/src/libsaml.d.ts +208 -209
  85. package/types/src/libsaml.d.ts.map +1 -0
  86. package/types/src/metadata-idp.d.ts +25 -24
  87. package/types/src/metadata-idp.d.ts.map +1 -0
  88. package/types/src/metadata-sp.d.ts +37 -36
  89. package/types/src/metadata-sp.d.ts.map +1 -0
  90. package/types/src/metadata.d.ts +58 -59
  91. package/types/src/metadata.d.ts.map +1 -0
  92. package/types/src/types.d.ts +128 -129
  93. package/types/src/types.d.ts.map +1 -0
  94. package/types/src/urn.d.ts +195 -194
  95. package/types/src/urn.d.ts.map +1 -0
  96. package/types/src/utility.d.ts +133 -134
  97. package/types/src/utility.d.ts.map +1 -0
  98. package/types/src/validator.d.ts +4 -3
  99. package/types/src/validator.d.ts.map +1 -0
  100. package/types/types.d.ts +128 -0
  101. package/types/types.d.ts.map +1 -0
  102. package/types/urn.d.ts +195 -0
  103. package/types/urn.d.ts.map +1 -0
  104. package/types/utility.d.ts +133 -0
  105. package/types/utility.d.ts.map +1 -0
  106. package/types/validator.d.ts +4 -0
  107. package/types/validator.d.ts.map +1 -0
  108. package/.editorconfig +0 -19
  109. package/.github/FUNDING.yml +0 -1
  110. package/.idea/inspectionProfiles/Project_Default.xml +0 -6
  111. package/.idea/modules.xml +0 -8
  112. package/.idea/samlify.iml +0 -12
  113. package/.idea/vcs.xml +0 -6
  114. package/.pre-commit.sh +0 -15
  115. package/.snyk +0 -8
  116. package/.travis.yml +0 -29
  117. package/Makefile +0 -25
  118. package/index.d.ts +0 -10
  119. package/index.js +0 -19
  120. package/index.js.map +0 -1
  121. package/index.ts +0 -28
  122. package/qodana.yaml +0 -29
  123. package/src/.idea/modules.xml +0 -8
  124. package/src/.idea/src.iml +0 -12
  125. package/src/.idea/vcs.xml +0 -6
  126. package/src/api.ts +0 -36
  127. package/src/binding-post.ts +0 -348
  128. package/src/binding-redirect.ts +0 -356
  129. package/src/binding-simplesign.ts +0 -238
  130. package/src/entity-idp.ts +0 -153
  131. package/src/entity-sp.ts +0 -114
  132. package/src/entity.ts +0 -243
  133. package/src/extractor.ts +0 -392
  134. package/src/flow.ts +0 -467
  135. package/src/libsaml.ts +0 -895
  136. package/src/metadata-idp.ts +0 -146
  137. package/src/metadata-sp.ts +0 -268
  138. package/src/metadata.ts +0 -166
  139. package/src/types.ts +0 -153
  140. package/src/urn.ts +0 -211
  141. package/src/utility.ts +0 -319
  142. package/src/validator.ts +0 -39
  143. package/tsconfig.json +0 -38
  144. package/tslint.json +0 -35
  145. package/types.d.ts +0 -2
@@ -1,722 +1,694 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- var __importDefault = (this && this.__importDefault) || function (mod) {
26
- return (mod && mod.__esModule) ? mod : { "default": mod };
27
- };
28
- Object.defineProperty(exports, "__esModule", { value: true });
29
- /**
30
- * @file SamlLib.js
31
- * @author tngan
32
- * @desc A simple library including some common functions
33
- */
34
- const xml_1 = __importDefault(require("xml"));
35
- const node_crypto_1 = require("node:crypto");
36
- const utility_js_1 = __importStar(require("./utility.js"));
37
- const urn_js_1 = require("./urn.js");
38
- const xpath_1 = require("xpath");
39
- const xml_crypto_1 = require("xml-crypto");
40
- const xmlenc = __importStar(require("xml-encryption"));
41
- const camelcase_1 = __importDefault(require("camelcase"));
42
- const api_js_1 = require("./api.js");
43
- const xml_escape_1 = __importDefault(require("xml-escape"));
44
- const fs = __importStar(require("fs"));
45
- const xmldom_1 = require("@xmldom/xmldom");
46
- const signatureAlgorithms = urn_js_1.algorithms.signature;
47
- const digestAlgorithms = urn_js_1.algorithms.digest;
48
- const certUse = urn_js_1.wording.certUse;
49
- const urlParams = urn_js_1.wording.urlParams;
50
- /**
51
- * 算法名称映射表 (兼容 X.509 和 SAML 规范)
52
- */
53
- function mapSignAlgorithm(algorithm) {
54
- const algorithmMap = {
55
- 'rsa-sha1': 'RSA-SHA1',
56
- 'rsa-sha256': 'RSA-SHA256',
57
- 'rsa-sha384': 'RSA-SHA384',
58
- 'rsa-sha512': 'RSA-SHA512',
59
- 'ecdsa-sha256': 'ECDSA-SHA256',
60
- 'ecdsa-sha384': 'ECDSA-SHA384',
61
- 'ecdsa-sha512': 'ECDSA-SHA512'
62
- };
63
- return algorithmMap[algorithm.toLowerCase()] || algorithm;
64
- }
65
- const libSaml = () => {
66
- /**
67
- * @desc helper function to get back the query param for redirect binding for SLO/SSO
68
- * @type {string}
69
- */
70
- function getQueryParamByType(type) {
71
- if ([urlParams.logoutRequest, urlParams.samlRequest].indexOf(type) !== -1) {
72
- return 'SAMLRequest';
73
- }
74
- if ([urlParams.logoutResponse, urlParams.samlResponse].indexOf(type) !== -1) {
75
- return 'SAMLResponse';
76
- }
77
- throw new Error('ERR_UNDEFINED_QUERY_PARAMS');
78
- }
79
- /**
80
- *
81
- */
82
- const nrsaAliasMapping = {
83
- 'http://www.w3.org/2000/09/xmldsig#rsa-sha1': 'pkcs1-sha1',
84
- 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'pkcs1-sha256',
85
- 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': 'pkcs1-sha512',
86
- };
87
- const nrsaAliasMappingForNode = {
88
- 'http://www.w3.org/2000/09/xmldsig#rsa-sha1': 'RSA-SHA1',
89
- 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'RSA-SHA256',
90
- 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': 'RSA-SHA512',
91
- };
92
- /**
93
- * @desc Default login request template
94
- * @type {LoginRequestTemplate}
95
- */
96
- const defaultLoginRequestTemplate = {
97
- 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>',
98
- };
99
- /**
100
- * @desc Default logout request template
101
- * @type {LogoutRequestTemplate}
102
- */
103
- const defaultLogoutRequestTemplate = {
104
- 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>',
105
- };
106
- /**
107
- * @desc Default AttributeStatement template
108
- * @type {AttributeStatementTemplate}
109
- */
110
- const defaultAttributeStatementTemplate = {
111
- context: '<saml:AttributeStatement>{Attributes}</saml:AttributeStatement>',
112
- };
113
- /**
114
- * @desc Default Attribute template
115
- * @type {AttributeTemplate}
116
- */
117
- const defaultAttributeTemplate = {
118
- context: '<saml:Attribute Name="{Name}" NameFormat="{NameFormat}">{AttributeValues}</saml:Attribute>',
119
- };
120
- /**
121
- * @desc Default AttributeValue template
122
- * @type {AttributeTemplate}
123
- */
124
- const defaultAttributeValueTemplate = {
125
- context: '<saml:AttributeValue xmlns:xs="{ValueXmlnsXs}" xmlns:xsi="{ValueXmlnsXsi}" xsi:type="{ValueXsiType}">{Value}</saml:AttributeValue>',
126
- };
127
- /**
128
- * @desc Default login response template
129
- * @type {LoginResponseTemplate}
130
- */
131
- const defaultLoginResponseTemplate = {
132
- 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>',
133
- attributes: [],
134
- additionalTemplates: {
135
- 'attributeStatementTemplate': defaultAttributeStatementTemplate,
136
- 'attributeTemplate': defaultAttributeTemplate
137
- }
138
- };
139
- /**
140
- * @desc Default logout response template
141
- * @type {LogoutResponseTemplate}
142
- */
143
- const defaultLogoutResponseTemplate = {
144
- 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>',
145
- };
146
- function getSigningSchemeForNode(sigAlg) {
147
- if (sigAlg) {
148
- const algAlias = nrsaAliasMappingForNode[sigAlg];
149
- if (!(algAlias === undefined)) {
150
- return algAlias;
151
- }
152
- }
153
- return nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA256];
154
- }
155
- /**
156
- * @private
157
- * @desc Get the digest algorithms by signature algorithms
158
- * @param {string} sigAlg signature algorithm
159
- * @return {string/undefined} digest algorithm
160
- */
161
- function getDigestMethod(sigAlg) {
162
- return digestAlgorithms[sigAlg];
163
- }
164
- /**
165
- * @public
166
- * @desc Create XPath
167
- * @param {string/object} local parameters to create XPath
168
- * @param {boolean} isExtractAll define whether returns whole content according to the XPath
169
- * @return {string} xpath
170
- */
171
- function createXPath(local, isExtractAll) {
172
- if ((0, utility_js_1.isString)(local)) {
173
- return isExtractAll === true ? "//*[local-name(.)='" + local + "']/text()" : "//*[local-name(.)='" + local + "']";
174
- }
175
- return "//*[local-name(.)='" + local.name + "']/@" + local.attr;
176
- }
177
- /**
178
- * @private
179
- * @desc Tag normalization
180
- * @param {string} prefix prefix of the tag
181
- * @param {content} content normalize it to capitalized camel case
182
- * @return {string}
183
- */
184
- function tagging(prefix, content) {
185
- const camelContent = (0, camelcase_1.default)(content, { locale: 'en-us' });
186
- return prefix + camelContent.charAt(0).toUpperCase() + camelContent.slice(1);
187
- }
188
- function escapeTag(replacement) {
189
- return (_match, quote) => {
190
- const text = (replacement === null || replacement === undefined) ? '' : String(replacement);
191
- // not having a quote means this interpolation isn't for an attribute, and so does not need escaping
192
- return quote ? `${quote}${(0, xml_escape_1.default)(text)}` : text;
193
- };
194
- }
195
- return {
196
- createXPath,
197
- getQueryParamByType,
198
- defaultLoginRequestTemplate,
199
- defaultLoginResponseTemplate,
200
- defaultAttributeStatementTemplate,
201
- defaultAttributeTemplate,
202
- defaultLogoutRequestTemplate,
203
- defaultLogoutResponseTemplate,
204
- defaultAttributeValueTemplate,
205
- /**
206
- * @desc Replace the tag (e.g. {tag}) inside the raw XML
207
- * @param {string} rawXML raw XML string used to do keyword replacement
208
- * @param {array} tagValues tag values
209
- * @return {string}
210
- */
211
- replaceTagsByValue(rawXML, tagValues) {
212
- Object.keys(tagValues).forEach(t => {
213
- rawXML = rawXML.replace(new RegExp(`("?)\\{${t}\\}`, 'g'), escapeTag(tagValues[t]));
214
- });
215
- return rawXML;
216
- },
217
- /**
218
- * @desc Helper function to build the AttributeStatement tag
219
- * @param {LoginResponseAttribute} attributes an array of attribute configuration
220
- * @param {AttributeTemplate} attributeTemplate the attribute tag template to be used
221
- * @param {AttributeStatementTemplate} attributeStatementTemplate the attributeStatement tag template to be used
222
- * @return {string}
223
- */
224
- /* attributeStatementBuilder(
225
- attributes: LoginResponseAttribute[],
226
- attributeTemplate: AttributeTemplate = defaultAttributeTemplate,
227
- attributeStatementTemplate: AttributeStatementTemplate = defaultAttributeStatementTemplate
228
- ): string {
229
- const attr = attributes.map(({name, nameFormat, valueTag, valueXsiType, type, valueXmlnsXs, valueXmlnsXsi}) => {
230
- const defaultValueXmlnsXs = 'http://www.w3.org/2001/XMLSchema';
231
- const defaultValueXmlnsXsi = 'http://www.w3.org/2001/XMLSchema-instance';
232
- let attributeLine = attributeTemplate.context;
233
- if (attributeLine && typeof attributeLine === 'function') {
234
- // 安全调用
235
- // @ts-ignore
236
- return attributeLine({
237
- name,
238
- nameFormat,
239
- valueTag,
240
- valueXsiType,
241
- type,
242
- valueXmlnsXs: valueXmlnsXs ?? defaultValueXmlnsXs,
243
- valueXmlnsXsi: valueXmlnsXsi ?? defaultValueXmlnsXsi
244
- })
245
- } else {
246
- attributeLine = attributeLine.replace('{Name}', name);
247
- attributeLine = attributeLine.replace('{NameFormat}', nameFormat);
248
- attributeLine = attributeLine.replace('{ValueXmlnsXs}', valueXmlnsXs ? valueXmlnsXs : defaultValueXmlnsXs);
249
- attributeLine = attributeLine.replace('{ValueXmlnsXsi}', valueXmlnsXsi ? valueXmlnsXsi : defaultValueXmlnsXsi);
250
- attributeLine = attributeLine.replace('{ValueXsiType}', valueXsiType);
251
- attributeLine = attributeLine.replace('{Value}', `{${tagging('attr', valueTag)}}`);
252
- return attributeLine;
253
- }
254
-
255
- }).join('');
256
- return attributeStatementTemplate.context.replace('{Attributes}', attr);
257
- },*/
258
- /** For Test */
259
- attributeStatementBuilder(attributeData) {
260
- // 构建 XML 元素数组
261
- // 构建 XML 结构
262
- const attributeStatement = {
263
- 'saml:AttributeStatement': [
264
- // 命名空间声明(在 AttributeStatement 上定义)
265
- {},
266
- // 遍历生成多个 Attribute
267
- ...attributeData.map(attr => ({
268
- 'saml:Attribute ': [
269
- // Attribute 属性
270
- {
271
- _attr: {
272
- Name: attr.Name,
273
- NameFormat: attr.NameFormat
274
- }
275
- },
276
- // 遍历生成多个 AttributeValue
277
- ...attr.valueArray.map((valueObj) => ({
278
- 'saml:AttributeValue ': [
279
- // 数据类型(根据 ValueType)
280
- {
281
- _attr: attr.ValueType === 1
282
- ? { 'xsi:type': 'xs:string' }
283
- : {}
284
- },
285
- // 值内容
286
- valueObj.value
287
- ]
288
- }))
289
- ]
290
- }))
291
- ]
292
- };
293
- // 生成 XML(关闭自动声明头)
294
- const xmlString = (0, xml_1.default)([attributeStatement], { declaration: false });
295
- return xmlString.trim();
296
- },
297
- /**
298
- * @desc Construct the XML signature for POST binding
299
- * @param {string} rawSamlMessage request/response xml string
300
- * @param {string} referenceTagXPath reference uri
301
- * @param {string} privateKey declares the private key
302
- * @param {string} passphrase passphrase of the private key [optional]
303
- * @param {string|buffer} signingCert signing certificate
304
- * @param {string} signatureAlgorithm signature algorithm
305
- * @param {string[]} transformationAlgorithms canonicalization and transformation Algorithms
306
- * @return {string} base64 encoded string
307
- */
308
- constructSAMLSignature(opts) {
309
- const { rawSamlMessage, referenceTagXPath, privateKey, privateKeyPass, signatureAlgorithm = signatureAlgorithms.RSA_SHA512, transformationAlgorithms = [
310
- 'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
311
- 'http://www.w3.org/2001/10/xml-exc-c14n#',
312
- ], signingCert, signatureConfig, isBase64Output = true, isMessageSigned = false, } = opts;
313
- const sig = new xml_crypto_1.SignedXml();
314
- // Add assertion sections as reference
315
- const digestAlgorithm = getDigestMethod(signatureAlgorithm);
316
- if (referenceTagXPath) {
317
- sig.addReference({
318
- xpath: referenceTagXPath,
319
- transforms: transformationAlgorithms,
320
- digestAlgorithm: digestAlgorithm
321
- });
322
- }
323
- if (isMessageSigned) {
324
- sig.addReference({
325
- // reference to the root node
326
- xpath: '/*',
327
- transforms: transformationAlgorithms,
328
- digestAlgorithm
329
- });
330
- }
331
- sig.signatureAlgorithm = signatureAlgorithm;
332
- sig.publicCert = this.getKeyInfo(signingCert, signatureConfig).getKey();
333
- sig.getKeyInfoContent = this.getKeyInfo(signingCert, signatureConfig).getKeyInfo;
334
- sig.privateKey = utility_js_1.default.readPrivateKey(privateKey, privateKeyPass, true);
335
- sig.canonicalizationAlgorithm = 'http://www.w3.org/2001/10/xml-exc-c14n#';
336
- if (signatureConfig) {
337
- sig.computeSignature(rawSamlMessage, signatureConfig);
338
- }
339
- else {
340
- sig.computeSignature(rawSamlMessage);
341
- }
342
- return isBase64Output !== false ? utility_js_1.default.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
343
- },
344
- /**
345
- * @desc Verify the XML signature
346
- * @param {string} xml xml
347
- * @param {SignatureVerifierOptions} opts cert declares the X509 certificate
348
- * @return {[boolean, string | null]} - A tuple where:
349
- * - The first element is `true` if the signature is valid, `false` otherwise.
350
- * - The second element is the cryptographically authenticated assertion node as a string, or `null` if not found.
351
- */
352
- // tslint:disable-next-line:no-shadowed-variable
353
- verifySignature(xml, opts) {
354
- const { dom } = (0, api_js_1.getContext)();
355
- const doc = dom.parseFromString(xml);
356
- const docParser = new xmldom_1.DOMParser();
357
- // In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element
358
- // message signature (logout response / saml response)
359
- const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
360
- // assertion signature (logout response / saml response)
361
- const assertionSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']/*[local-name(.)='Signature']";
362
- // check if there is a potential malicious wrapping signature
363
- const wrappingElementsXPath = "/*[contains(local-name(), 'Response')]/*[local-name(.)='Assertion']/*[local-name(.)='Subject']/*[local-name(.)='SubjectConfirmation']/*[local-name(.)='SubjectConfirmationData']//*[local-name(.)='Assertion' or local-name(.)='Signature']";
364
- // select the signature node
365
- let selection = [];
366
- const messageSignatureNode = (0, xpath_1.select)(messageSignatureXpath, doc);
367
- const assertionSignatureNode = (0, xpath_1.select)(assertionSignatureXpath, doc);
368
- const wrappingElementNode = (0, xpath_1.select)(wrappingElementsXPath, doc);
369
- selection = selection.concat(messageSignatureNode);
370
- selection = selection.concat(assertionSignatureNode);
371
- // try to catch potential wrapping attack
372
- if (wrappingElementNode.length !== 0) {
373
- throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
374
- }
375
- // guarantee to have a signature in saml response
376
- if (selection.length === 0) {
377
- throw new Error('ERR_ZERO_SIGNATURE');
378
- }
379
- // need to refactor later on
380
- for (const signatureNode of selection) {
381
- const sig = new xml_crypto_1.SignedXml();
382
- let verified = false;
383
- sig.signatureAlgorithm = opts.signatureAlgorithm;
384
- if (!opts.keyFile && !opts.metadata) {
385
- throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
386
- }
387
- if (opts.keyFile) {
388
- sig.publicCert = fs.readFileSync(opts.keyFile);
389
- }
390
- if (opts.metadata) {
391
- const certificateNode = (0, xpath_1.select)(".//*[local-name(.)='X509Certificate']", signatureNode);
392
- // certificate in metadata
393
- let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
394
- // flattens the nested array of Certificates from each KeyDescriptor
395
- if (Array.isArray(metadataCert)) {
396
- metadataCert = (0, utility_js_1.flattenDeep)(metadataCert);
397
- }
398
- else if (typeof metadataCert === 'string') {
399
- metadataCert = [metadataCert];
400
- }
401
- // normalise the certificate string
402
- metadataCert = metadataCert.map(utility_js_1.default.normalizeCerString);
403
- // no certificate in node response nor metadata
404
- if (certificateNode.length === 0 && metadataCert.length === 0) {
405
- throw new Error('NO_SELECTED_CERTIFICATE');
406
- }
407
- // certificate node in response
408
- if (certificateNode.length !== 0) {
409
- const x509CertificateData = certificateNode[0].firstChild.data;
410
- const x509Certificate = utility_js_1.default.normalizeCerString(x509CertificateData);
411
- if (metadataCert.length >= 1 &&
412
- !metadataCert.find(cert => cert.trim() === x509Certificate.trim())) {
413
- // keep this restriction for rolling certificate usage
414
- // to make sure the response certificate is one of those specified in metadata
415
- throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
416
- }
417
- sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
418
- }
419
- else {
420
- // Select first one from metadata
421
- sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
422
- }
423
- }
424
- sig.loadSignature(signatureNode);
425
- doc.removeChild(signatureNode);
426
- verified = sig.checkSignature(doc.toString());
427
- // immediately throw error when any one of the signature is failed to get verified
428
- if (!verified) {
429
- throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
430
- }
431
- // attempt is made to get the signed Reference as a string();
432
- // note, we don't have access to the actual signedReferences API unfortunately
433
- // mainly a sanity check here for SAML. (Although ours would still be secure, if multiple references are used)
434
- if (!(sig.getSignedReferences().length >= 1)) {
435
- throw new Error('NO_SIGNATURE_REFERENCES');
436
- }
437
- const signedVerifiedXML = sig.getSignedReferences()[0];
438
- const rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement;
439
- // process the verified signature:
440
- // case 1, rootSignedDoc is a response:
441
- if (rootNode.localName === 'Response') {
442
- // try getting the Xml from the first assertion
443
- const EncryptedAssertions = (0, xpath_1.select)("./*[local-name()='EncryptedAssertion']", rootNode);
444
- const assertions = (0, xpath_1.select)("./*[local-name()='Assertion']", rootNode);
445
- // now we can process the assertion as an assertion
446
- if (EncryptedAssertions.length === 1) {
447
- return [true, EncryptedAssertions[0].toString()];
448
- }
449
- if (assertions.length === 1) {
450
- return [true, assertions[0].toString()];
451
- }
452
- }
453
- else if (rootNode.localName === 'Assertion') {
454
- return [true, rootNode.toString()];
455
- }
456
- else {
457
- return [true, null]; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null
458
- }
459
- }
460
- // something has gone seriously wrong if we are still here
461
- throw new Error('ERR_ZERO_SIGNATURE');
462
- // response must be signed, either entire document or assertion
463
- // default we will take the assertion section under root
464
- /* if (messageSignatureNode.length === 1) {
465
- const node = select("/!*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/!*[local-name(.)='Assertion']", doc);
466
- if (node.length === 1) {
467
- assertionNode = node[0].toString();
468
- }
469
- }
470
-
471
- if (assertionSignatureNode.length === 1) {
472
- const verifiedAssertionInfo = extract(assertionSignatureNode[0].toString(), [{
473
- key: 'refURI',
474
- localPath: ['Signature', 'SignedInfo', 'Reference'],
475
- attributes: ['URI']
476
- }]);
477
- // get the assertion supposed to be the one should be verified
478
- const desiredAssertionInfo = extract(doc.toString(), [{
479
- key: 'id',
480
- localPath: ['~Response', 'Assertion'],
481
- attributes: ['ID']
482
- }]);
483
- // 5.4.2 References
484
- // SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of
485
- // the assertion or protocol message being signed. The assertion’s or protocol message's root element may
486
- // or may not be the root element of the actual XML document containing the signed assertion or protocol
487
- // message (e.g., it might be contained within a SOAP envelope).
488
- // Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID
489
- // attribute value of the root element of the assertion or protocol message being signed. For example, if the
490
- // ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo".
491
- if (verifiedAssertionInfo.refURI !== `#${desiredAssertionInfo.id}`) {
492
- throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
493
- }
494
- const verifiedDoc = extract(doc.toString(), [{
495
- key: 'assertion',
496
- localPath: ['~Response', 'Assertion'],
497
- attributes: [],
498
- context: true
499
- }]);
500
- assertionNode = verifiedDoc.assertion.toString();
501
- }
502
-
503
- return [verified, assertionNode];*/
504
- },
505
- /**
506
- * @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
507
- * @param {string} use type of certificate (e.g. signing, encrypt)
508
- * @param {string} certString declares the certificate String
509
- * @return {object} object used in xml module
510
- */
511
- createKeySection(use, certString) {
512
- return {
513
- ['KeyDescriptor']: [
514
- {
515
- _attr: { use },
516
- },
517
- {
518
- ['ds:KeyInfo']: [
519
- {
520
- _attr: {
521
- 'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
522
- },
523
- },
524
- {
525
- ['ds:X509Data']: [{
526
- 'ds:X509Certificate': utility_js_1.default.normalizeCerString(certString),
527
- }],
528
- },
529
- ],
530
- }
531
- ],
532
- };
533
- },
534
- /**
535
- * SAML 消息签名 (符合 SAML V2.0 绑定规范)
536
- * @param octetString - 要签名的原始数据 (OCTET STRING)
537
- * @param key - PEM 格式私钥
538
- * @param passphrase - 私钥密码 (如果有加密)
539
- * @param isBase64 - 是否返回 base64 编码 (默认 true)
540
- * @param signingAlgorithm - 签名算法 (默认 'rsa-sha256')
541
- * @returns 消息签名
542
- */
543
- constructMessageSignature(octetString, key, passphrase, isBase64 = true, signingAlgorithm = nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA256]) {
544
- try {
545
- // 1. 标准化输入数据
546
- const inputData = Buffer.isBuffer(octetString)
547
- ? octetString
548
- : Buffer.from(octetString, 'utf8');
549
- // 2. 创建签名器并设置算
550
- const signingAlgorithmValue = getSigningSchemeForNode(signingAlgorithm);
551
- const signer = (0, node_crypto_1.createSign)(signingAlgorithmValue);
552
- // 3. 加载私钥
553
- const privateKey = (0, node_crypto_1.createPrivateKey)({
554
- key: key,
555
- format: 'pem',
556
- passphrase: passphrase,
557
- encoding: 'utf8'
558
- });
559
- signer.write(octetString);
560
- signer.end();
561
- const signature = signer.sign(privateKey, 'base64');
562
- console.log(signature.toString());
563
- console.log('dayingyixia');
564
- // 5. 处理编码输出
565
- return isBase64 ? signature.toString() : signature;
566
- }
567
- catch (error) {
568
- throw new Error(`SAML 签名失败: ${error.message}`);
569
- }
570
- },
571
- verifyMessageSignature(metadata, octetString, signature, verifyAlgorithm) {
572
- const signCert = metadata.getX509Certificate(certUse.signing);
573
- const signingScheme = getSigningSchemeForNode(verifyAlgorithm);
574
- const verifier = (0, node_crypto_1.createVerify)(signingScheme);
575
- verifier.update(octetString);
576
- const isValid = verifier.verify(utility_js_1.default.getPublicKeyPemFromCertificate(signCert), Buffer.isBuffer(signature) ? signature : Buffer.from(signature, 'base64'));
577
- console.log(isValid);
578
- console.log('-------------签名验证结果-------------');
579
- return isValid;
580
- },
581
- /**
582
- * @desc Get the public key in string format
583
- * @param {string} x509Certificate certificate
584
- * @return {string} public key
585
- */
586
- getKeyInfo(x509Certificate, signatureConfig = {}) {
587
- const prefix = signatureConfig.prefix ? `${signatureConfig.prefix}:` : '';
588
- return {
589
- getKeyInfo: () => {
590
- return `<${prefix}X509Data><${prefix}X509Certificate>${x509Certificate}</${prefix}X509Certificate></${prefix}X509Data>`;
591
- },
592
- getKey: () => {
593
- return utility_js_1.default.getPublicKeyPemFromCertificate(x509Certificate).toString();
594
- },
595
- };
596
- },
597
- /**
598
- * @desc Encrypt the assertion section in Response
599
- * @param {Entity} sourceEntity source entity
600
- * @param {Entity} targetEntity target entity
601
- * @param {string} xml response in xml string format
602
- * @return {Promise} a promise to resolve the finalized xml
603
- */
604
- // tslint:disable-next-line:no-shadowed-variable
605
- encryptAssertion(sourceEntity, targetEntity, xml) {
606
- // Implement encryption after signature if it has
607
- return new Promise((resolve, reject) => {
608
- if (!xml) {
609
- return reject(new Error('ERR_UNDEFINED_ASSERTION'));
610
- }
611
- const sourceEntitySetting = sourceEntity.entitySetting;
612
- const targetEntityMetadata = targetEntity.entityMeta;
613
- const { dom } = (0, api_js_1.getContext)();
614
- const doc = dom.parseFromString(xml);
615
- const assertions = (0, xpath_1.select)("//*[local-name(.)='Assertion']", doc);
616
- if (!Array.isArray(assertions) || assertions.length === 0) {
617
- throw new Error('ERR_NO_ASSERTION');
618
- }
619
- if (assertions.length > 1) {
620
- throw new Error('ERR_MULTIPLE_ASSERTION');
621
- }
622
- const rawAssertionNode = assertions[0];
623
- // Perform encryption depends on the setting, default is false
624
- if (sourceEntitySetting.isAssertionEncrypted) {
625
- const publicKeyPem = utility_js_1.default.getPublicKeyPemFromCertificate(targetEntityMetadata.getX509Certificate(certUse.encrypt));
626
- xmlenc.encrypt(rawAssertionNode.toString(), {
627
- // use xml-encryption module
628
- rsa_pub: Buffer.from(publicKeyPem),
629
- pem: Buffer.from(`-----BEGIN CERTIFICATE-----${targetEntityMetadata.getX509Certificate(certUse.encrypt)}-----END CERTIFICATE-----`),
630
- encryptionAlgorithm: sourceEntitySetting.dataEncryptionAlgorithm,
631
- keyEncryptionAlgorithm: sourceEntitySetting.keyEncryptionAlgorithm,
632
- keyEncryptionDigest: 'SHA-512',
633
- disallowEncryptionWithInsecureAlgorithm: true,
634
- warnInsecureAlgorithm: true
635
- }, (err, res) => {
636
- if (err) {
637
- console.error(err);
638
- return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_ENCRYPTION'));
639
- }
640
- if (!res) {
641
- return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
642
- }
643
- const { encryptedAssertion: encAssertionPrefix } = sourceEntitySetting.tagPrefix;
644
- const encryptAssertionDoc = dom.parseFromString(`<${encAssertionPrefix}:EncryptedAssertion xmlns:${encAssertionPrefix}="${urn_js_1.namespace.names.assertion}">${res}</${encAssertionPrefix}:EncryptedAssertion>`);
645
- doc.documentElement.replaceChild(encryptAssertionDoc.documentElement, rawAssertionNode);
646
- return resolve(utility_js_1.default.base64Encode(doc.toString()));
647
- });
648
- }
649
- else {
650
- return resolve(utility_js_1.default.base64Encode(xml)); // No need to do encryption
651
- }
652
- });
653
- },
654
- /**
655
- * @desc Decrypt the assertion section in Response
656
- * @param {string} type only accept SAMLResponse to proceed decryption
657
- * @param {Entity} here this entity
658
- * @param {Entity} from from the entity where the message is sent
659
- * @param {string} entireXML response in xml string format
660
- * @return {function} a promise to get back the entire xml with decrypted assertion
661
- */
662
- decryptAssertion(here, entireXML) {
663
- return new Promise((resolve, reject) => {
664
- // Implement decryption first then check the signature
665
- if (!entireXML) {
666
- return reject(new Error('ERR_UNDEFINED_ASSERTION'));
667
- }
668
- // Perform encryption depends on the setting of where the message is sent, default is false
669
- const hereSetting = here.entitySetting;
670
- const { dom } = (0, api_js_1.getContext)();
671
- const doc = dom.parseFromString(entireXML);
672
- const encryptedAssertions = (0, xpath_1.select)("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc);
673
- if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
674
- throw new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION');
675
- }
676
- if (encryptedAssertions.length > 1) {
677
- throw new Error('ERR_MULTIPLE_ASSERTION');
678
- }
679
- const encAssertionNode = encryptedAssertions[0];
680
- return xmlenc.decrypt(encAssertionNode.toString(), {
681
- key: utility_js_1.default.readPrivateKey(hereSetting.encPrivateKey, hereSetting.encPrivateKeyPass),
682
- }, (err, res) => {
683
- if (err) {
684
- console.error(err);
685
- return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION'));
686
- }
687
- if (!res) {
688
- return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
689
- }
690
- const rawAssertionDoc = dom.parseFromString(res);
691
- doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
692
- return resolve([doc.toString(), res]);
693
- });
694
- });
695
- },
696
- /**
697
- * @desc Check if the xml string is valid and bounded
698
- */
699
- async isValidXml(input) {
700
- // check if global api contains the validate function
701
- const { validate } = (0, api_js_1.getContext)();
702
- /**
703
- * user can write a validate function that always returns
704
- * a resolved promise and skip the validator even in
705
- * production, user will take the responsibility if
706
- * they intend to skip the validation
707
- */
708
- if (!validate) {
709
- // otherwise, an error will be thrown
710
- 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)');
711
- }
712
- try {
713
- return await validate(input);
714
- }
715
- catch (e) {
716
- throw e;
717
- }
718
- },
719
- };
720
- };
721
- exports.default = libSaml();
1
+ /**
2
+ * @file SamlLib.js
3
+ * @author tngan
4
+ * @desc A simple library including some common functions
5
+ */
6
+ import xml from 'xml';
7
+ import { createSign, createPrivateKey, createVerify } from 'node:crypto';
8
+ import utility, { flattenDeep, isString } from './utility.js';
9
+ import { algorithms, wording, namespace } from './urn.js';
10
+ import { select } from 'xpath';
11
+ import { SignedXml } from 'xml-crypto';
12
+ import * as xmlenc from 'xml-encryption';
13
+ import camelCase from 'camelcase';
14
+ import { getContext } from './api.js';
15
+ import xmlEscape from 'xml-escape';
16
+ import * as fs from 'fs';
17
+ import { DOMParser } from '@xmldom/xmldom';
18
+ const signatureAlgorithms = algorithms.signature;
19
+ const digestAlgorithms = algorithms.digest;
20
+ const certUse = wording.certUse;
21
+ const urlParams = wording.urlParams;
22
+ /**
23
+ * 算法名称映射表 (兼容 X.509 和 SAML 规范)
24
+ */
25
+ function mapSignAlgorithm(algorithm) {
26
+ const algorithmMap = {
27
+ 'rsa-sha1': 'RSA-SHA1',
28
+ 'rsa-sha256': 'RSA-SHA256',
29
+ 'rsa-sha384': 'RSA-SHA384',
30
+ 'rsa-sha512': 'RSA-SHA512',
31
+ 'ecdsa-sha256': 'ECDSA-SHA256',
32
+ 'ecdsa-sha384': 'ECDSA-SHA384',
33
+ 'ecdsa-sha512': 'ECDSA-SHA512'
34
+ };
35
+ return algorithmMap[algorithm.toLowerCase()] || algorithm;
36
+ }
37
+ const libSaml = () => {
38
+ /**
39
+ * @desc helper function to get back the query param for redirect binding for SLO/SSO
40
+ * @type {string}
41
+ */
42
+ function getQueryParamByType(type) {
43
+ if ([urlParams.logoutRequest, urlParams.samlRequest].indexOf(type) !== -1) {
44
+ return 'SAMLRequest';
45
+ }
46
+ if ([urlParams.logoutResponse, urlParams.samlResponse].indexOf(type) !== -1) {
47
+ return 'SAMLResponse';
48
+ }
49
+ throw new Error('ERR_UNDEFINED_QUERY_PARAMS');
50
+ }
51
+ /**
52
+ *
53
+ */
54
+ const nrsaAliasMapping = {
55
+ 'http://www.w3.org/2000/09/xmldsig#rsa-sha1': 'pkcs1-sha1',
56
+ 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'pkcs1-sha256',
57
+ 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': 'pkcs1-sha512',
58
+ };
59
+ const nrsaAliasMappingForNode = {
60
+ 'http://www.w3.org/2000/09/xmldsig#rsa-sha1': 'RSA-SHA1',
61
+ 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'RSA-SHA256',
62
+ 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': 'RSA-SHA512',
63
+ };
64
+ /**
65
+ * @desc Default login request template
66
+ * @type {LoginRequestTemplate}
67
+ */
68
+ const defaultLoginRequestTemplate = {
69
+ 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>',
70
+ };
71
+ /**
72
+ * @desc Default logout request template
73
+ * @type {LogoutRequestTemplate}
74
+ */
75
+ const defaultLogoutRequestTemplate = {
76
+ 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>',
77
+ };
78
+ /**
79
+ * @desc Default AttributeStatement template
80
+ * @type {AttributeStatementTemplate}
81
+ */
82
+ const defaultAttributeStatementTemplate = {
83
+ context: '<saml:AttributeStatement>{Attributes}</saml:AttributeStatement>',
84
+ };
85
+ /**
86
+ * @desc Default Attribute template
87
+ * @type {AttributeTemplate}
88
+ */
89
+ const defaultAttributeTemplate = {
90
+ context: '<saml:Attribute Name="{Name}" NameFormat="{NameFormat}">{AttributeValues}</saml:Attribute>',
91
+ };
92
+ /**
93
+ * @desc Default AttributeValue template
94
+ * @type {AttributeTemplate}
95
+ */
96
+ const defaultAttributeValueTemplate = {
97
+ context: '<saml:AttributeValue xmlns:xs="{ValueXmlnsXs}" xmlns:xsi="{ValueXmlnsXsi}" xsi:type="{ValueXsiType}">{Value}</saml:AttributeValue>',
98
+ };
99
+ /**
100
+ * @desc Default login response template
101
+ * @type {LoginResponseTemplate}
102
+ */
103
+ const defaultLoginResponseTemplate = {
104
+ 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>',
105
+ attributes: [],
106
+ additionalTemplates: {
107
+ 'attributeStatementTemplate': defaultAttributeStatementTemplate,
108
+ 'attributeTemplate': defaultAttributeTemplate
109
+ }
110
+ };
111
+ /**
112
+ * @desc Default logout response template
113
+ * @type {LogoutResponseTemplate}
114
+ */
115
+ const defaultLogoutResponseTemplate = {
116
+ 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>',
117
+ };
118
+ function getSigningSchemeForNode(sigAlg) {
119
+ if (sigAlg) {
120
+ const algAlias = nrsaAliasMappingForNode[sigAlg];
121
+ if (!(algAlias === undefined)) {
122
+ return algAlias;
123
+ }
124
+ }
125
+ return nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA256];
126
+ }
127
+ /**
128
+ * @private
129
+ * @desc Get the digest algorithms by signature algorithms
130
+ * @param {string} sigAlg signature algorithm
131
+ * @return {string/undefined} digest algorithm
132
+ */
133
+ function getDigestMethod(sigAlg) {
134
+ return digestAlgorithms[sigAlg];
135
+ }
136
+ /**
137
+ * @public
138
+ * @desc Create XPath
139
+ * @param {string/object} local parameters to create XPath
140
+ * @param {boolean} isExtractAll define whether returns whole content according to the XPath
141
+ * @return {string} xpath
142
+ */
143
+ function createXPath(local, isExtractAll) {
144
+ if (isString(local)) {
145
+ return isExtractAll === true ? "//*[local-name(.)='" + local + "']/text()" : "//*[local-name(.)='" + local + "']";
146
+ }
147
+ return "//*[local-name(.)='" + local.name + "']/@" + local.attr;
148
+ }
149
+ /**
150
+ * @private
151
+ * @desc Tag normalization
152
+ * @param {string} prefix prefix of the tag
153
+ * @param {content} content normalize it to capitalized camel case
154
+ * @return {string}
155
+ */
156
+ function tagging(prefix, content) {
157
+ const camelContent = camelCase(content, { locale: 'en-us' });
158
+ return prefix + camelContent.charAt(0).toUpperCase() + camelContent.slice(1);
159
+ }
160
+ function escapeTag(replacement) {
161
+ return (_match, quote) => {
162
+ const text = (replacement === null || replacement === undefined) ? '' : String(replacement);
163
+ // not having a quote means this interpolation isn't for an attribute, and so does not need escaping
164
+ return quote ? `${quote}${xmlEscape(text)}` : text;
165
+ };
166
+ }
167
+ return {
168
+ createXPath,
169
+ getQueryParamByType,
170
+ defaultLoginRequestTemplate,
171
+ defaultLoginResponseTemplate,
172
+ defaultAttributeStatementTemplate,
173
+ defaultAttributeTemplate,
174
+ defaultLogoutRequestTemplate,
175
+ defaultLogoutResponseTemplate,
176
+ defaultAttributeValueTemplate,
177
+ /**
178
+ * @desc Replace the tag (e.g. {tag}) inside the raw XML
179
+ * @param {string} rawXML raw XML string used to do keyword replacement
180
+ * @param {array} tagValues tag values
181
+ * @return {string}
182
+ */
183
+ replaceTagsByValue(rawXML, tagValues) {
184
+ Object.keys(tagValues).forEach(t => {
185
+ rawXML = rawXML.replace(new RegExp(`("?)\\{${t}\\}`, 'g'), escapeTag(tagValues[t]));
186
+ });
187
+ return rawXML;
188
+ },
189
+ /**
190
+ * @desc Helper function to build the AttributeStatement tag
191
+ * @param {LoginResponseAttribute} attributes an array of attribute configuration
192
+ * @param {AttributeTemplate} attributeTemplate the attribute tag template to be used
193
+ * @param {AttributeStatementTemplate} attributeStatementTemplate the attributeStatement tag template to be used
194
+ * @return {string}
195
+ */
196
+ /* attributeStatementBuilder(
197
+ attributes: LoginResponseAttribute[],
198
+ attributeTemplate: AttributeTemplate = defaultAttributeTemplate,
199
+ attributeStatementTemplate: AttributeStatementTemplate = defaultAttributeStatementTemplate
200
+ ): string {
201
+ const attr = attributes.map(({name, nameFormat, valueTag, valueXsiType, type, valueXmlnsXs, valueXmlnsXsi}) => {
202
+ const defaultValueXmlnsXs = 'http://www.w3.org/2001/XMLSchema';
203
+ const defaultValueXmlnsXsi = 'http://www.w3.org/2001/XMLSchema-instance';
204
+ let attributeLine = attributeTemplate.context;
205
+ if (attributeLine && typeof attributeLine === 'function') {
206
+ // 安全调用
207
+ // @ts-ignore
208
+ return attributeLine({
209
+ name,
210
+ nameFormat,
211
+ valueTag,
212
+ valueXsiType,
213
+ type,
214
+ valueXmlnsXs: valueXmlnsXs ?? defaultValueXmlnsXs,
215
+ valueXmlnsXsi: valueXmlnsXsi ?? defaultValueXmlnsXsi
216
+ })
217
+ } else {
218
+ attributeLine = attributeLine.replace('{Name}', name);
219
+ attributeLine = attributeLine.replace('{NameFormat}', nameFormat);
220
+ attributeLine = attributeLine.replace('{ValueXmlnsXs}', valueXmlnsXs ? valueXmlnsXs : defaultValueXmlnsXs);
221
+ attributeLine = attributeLine.replace('{ValueXmlnsXsi}', valueXmlnsXsi ? valueXmlnsXsi : defaultValueXmlnsXsi);
222
+ attributeLine = attributeLine.replace('{ValueXsiType}', valueXsiType);
223
+ attributeLine = attributeLine.replace('{Value}', `{${tagging('attr', valueTag)}}`);
224
+ return attributeLine;
225
+ }
226
+
227
+ }).join('');
228
+ return attributeStatementTemplate.context.replace('{Attributes}', attr);
229
+ },*/
230
+ /** For Test */
231
+ attributeStatementBuilder(attributeData) {
232
+ // 构建 XML 元素数组
233
+ // 构建 XML 结构
234
+ const attributeStatement = {
235
+ 'saml:AttributeStatement': [
236
+ // 命名空间声明(在 AttributeStatement 上定义)
237
+ {},
238
+ // 遍历生成多个 Attribute
239
+ ...attributeData.map(attr => ({
240
+ 'saml:Attribute ': [
241
+ // Attribute 属性
242
+ {
243
+ _attr: {
244
+ Name: attr.Name,
245
+ NameFormat: attr.NameFormat
246
+ }
247
+ },
248
+ // 遍历生成多个 AttributeValue
249
+ ...attr.valueArray.map((valueObj) => ({
250
+ 'saml:AttributeValue ': [
251
+ // 数据类型(根据 ValueType)
252
+ {
253
+ _attr: attr.ValueType === 1
254
+ ? { 'xsi:type': 'xs:string' }
255
+ : {}
256
+ },
257
+ // 值内容
258
+ valueObj.value
259
+ ]
260
+ }))
261
+ ]
262
+ }))
263
+ ]
264
+ };
265
+ // 生成 XML(关闭自动声明头)
266
+ const xmlString = xml([attributeStatement], { declaration: false });
267
+ return xmlString.trim();
268
+ },
269
+ /**
270
+ * @desc Construct the XML signature for POST binding
271
+ * @param {string} rawSamlMessage request/response xml string
272
+ * @param {string} referenceTagXPath reference uri
273
+ * @param {string} privateKey declares the private key
274
+ * @param {string} passphrase passphrase of the private key [optional]
275
+ * @param {string|buffer} signingCert signing certificate
276
+ * @param {string} signatureAlgorithm signature algorithm
277
+ * @param {string[]} transformationAlgorithms canonicalization and transformation Algorithms
278
+ * @return {string} base64 encoded string
279
+ */
280
+ constructSAMLSignature(opts) {
281
+ const { rawSamlMessage, referenceTagXPath, privateKey, privateKeyPass, signatureAlgorithm = signatureAlgorithms.RSA_SHA512, transformationAlgorithms = [
282
+ 'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
283
+ 'http://www.w3.org/2001/10/xml-exc-c14n#',
284
+ ], signingCert, signatureConfig, isBase64Output = true, isMessageSigned = false, } = opts;
285
+ const sig = new SignedXml();
286
+ // Add assertion sections as reference
287
+ const digestAlgorithm = getDigestMethod(signatureAlgorithm);
288
+ if (referenceTagXPath) {
289
+ sig.addReference({
290
+ xpath: referenceTagXPath,
291
+ transforms: transformationAlgorithms,
292
+ digestAlgorithm: digestAlgorithm
293
+ });
294
+ }
295
+ if (isMessageSigned) {
296
+ sig.addReference({
297
+ // reference to the root node
298
+ xpath: '/*',
299
+ transforms: transformationAlgorithms,
300
+ digestAlgorithm
301
+ });
302
+ }
303
+ sig.signatureAlgorithm = signatureAlgorithm;
304
+ sig.publicCert = this.getKeyInfo(signingCert, signatureConfig).getKey();
305
+ sig.getKeyInfoContent = this.getKeyInfo(signingCert, signatureConfig).getKeyInfo;
306
+ sig.privateKey = utility.readPrivateKey(privateKey, privateKeyPass, true);
307
+ sig.canonicalizationAlgorithm = 'http://www.w3.org/2001/10/xml-exc-c14n#';
308
+ if (signatureConfig) {
309
+ sig.computeSignature(rawSamlMessage, signatureConfig);
310
+ }
311
+ else {
312
+ sig.computeSignature(rawSamlMessage);
313
+ }
314
+ return isBase64Output !== false ? utility.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
315
+ },
316
+ /**
317
+ * @desc Verify the XML signature
318
+ * @param {string} xml xml
319
+ * @param {SignatureVerifierOptions} opts cert declares the X509 certificate
320
+ * @return {[boolean, string | null]} - A tuple where:
321
+ * - The first element is `true` if the signature is valid, `false` otherwise.
322
+ * - The second element is the cryptographically authenticated assertion node as a string, or `null` if not found.
323
+ */
324
+ // tslint:disable-next-line:no-shadowed-variable
325
+ verifySignature(xml, opts) {
326
+ const { dom } = getContext();
327
+ const doc = dom.parseFromString(xml);
328
+ const docParser = new DOMParser();
329
+ // In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element
330
+ // message signature (logout response / saml response)
331
+ const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']";
332
+ // assertion signature (logout response / saml response)
333
+ const assertionSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']/*[local-name(.)='Signature']";
334
+ // check if there is a potential malicious wrapping signature
335
+ const wrappingElementsXPath = "/*[contains(local-name(), 'Response')]/*[local-name(.)='Assertion']/*[local-name(.)='Subject']/*[local-name(.)='SubjectConfirmation']/*[local-name(.)='SubjectConfirmationData']//*[local-name(.)='Assertion' or local-name(.)='Signature']";
336
+ // select the signature node
337
+ let selection = [];
338
+ const messageSignatureNode = select(messageSignatureXpath, doc);
339
+ const assertionSignatureNode = select(assertionSignatureXpath, doc);
340
+ const wrappingElementNode = select(wrappingElementsXPath, doc);
341
+ selection = selection.concat(messageSignatureNode);
342
+ selection = selection.concat(assertionSignatureNode);
343
+ // try to catch potential wrapping attack
344
+ if (wrappingElementNode.length !== 0) {
345
+ throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
346
+ }
347
+ // guarantee to have a signature in saml response
348
+ if (selection.length === 0) {
349
+ throw new Error('ERR_ZERO_SIGNATURE');
350
+ }
351
+ // need to refactor later on
352
+ for (const signatureNode of selection) {
353
+ const sig = new SignedXml();
354
+ let verified = false;
355
+ sig.signatureAlgorithm = opts.signatureAlgorithm;
356
+ if (!opts.keyFile && !opts.metadata) {
357
+ throw new Error('ERR_UNDEFINED_SIGNATURE_VERIFIER_OPTIONS');
358
+ }
359
+ if (opts.keyFile) {
360
+ sig.publicCert = fs.readFileSync(opts.keyFile);
361
+ }
362
+ if (opts.metadata) {
363
+ const certificateNode = select(".//*[local-name(.)='X509Certificate']", signatureNode);
364
+ // certificate in metadata
365
+ let metadataCert = opts.metadata.getX509Certificate(certUse.signing);
366
+ // flattens the nested array of Certificates from each KeyDescriptor
367
+ if (Array.isArray(metadataCert)) {
368
+ metadataCert = flattenDeep(metadataCert);
369
+ }
370
+ else if (typeof metadataCert === 'string') {
371
+ metadataCert = [metadataCert];
372
+ }
373
+ // normalise the certificate string
374
+ metadataCert = metadataCert.map(utility.normalizeCerString);
375
+ // no certificate in node response nor metadata
376
+ if (certificateNode.length === 0 && metadataCert.length === 0) {
377
+ throw new Error('NO_SELECTED_CERTIFICATE');
378
+ }
379
+ // certificate node in response
380
+ if (certificateNode.length !== 0) {
381
+ const x509CertificateData = certificateNode[0].firstChild.data;
382
+ const x509Certificate = utility.normalizeCerString(x509CertificateData);
383
+ if (metadataCert.length >= 1 &&
384
+ !metadataCert.find(cert => cert.trim() === x509Certificate.trim())) {
385
+ // keep this restriction for rolling certificate usage
386
+ // to make sure the response certificate is one of those specified in metadata
387
+ throw new Error('ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA');
388
+ }
389
+ sig.publicCert = this.getKeyInfo(x509Certificate).getKey();
390
+ }
391
+ else {
392
+ // Select first one from metadata
393
+ sig.publicCert = this.getKeyInfo(metadataCert[0]).getKey();
394
+ }
395
+ }
396
+ sig.loadSignature(signatureNode);
397
+ doc.removeChild(signatureNode);
398
+ verified = sig.checkSignature(doc.toString());
399
+ // immediately throw error when any one of the signature is failed to get verified
400
+ if (!verified) {
401
+ throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE');
402
+ }
403
+ // attempt is made to get the signed Reference as a string();
404
+ // note, we don't have access to the actual signedReferences API unfortunately
405
+ // mainly a sanity check here for SAML. (Although ours would still be secure, if multiple references are used)
406
+ if (!(sig.getSignedReferences().length >= 1)) {
407
+ throw new Error('NO_SIGNATURE_REFERENCES');
408
+ }
409
+ const signedVerifiedXML = sig.getSignedReferences()[0];
410
+ const rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement;
411
+ // process the verified signature:
412
+ // case 1, rootSignedDoc is a response:
413
+ if (rootNode.localName === 'Response') {
414
+ // try getting the Xml from the first assertion
415
+ const EncryptedAssertions = select("./*[local-name()='EncryptedAssertion']", rootNode);
416
+ const assertions = select("./*[local-name()='Assertion']", rootNode);
417
+ // now we can process the assertion as an assertion
418
+ if (EncryptedAssertions.length === 1) {
419
+ return [true, EncryptedAssertions[0].toString()];
420
+ }
421
+ if (assertions.length === 1) {
422
+ return [true, assertions[0].toString()];
423
+ }
424
+ }
425
+ else if (rootNode.localName === 'Assertion') {
426
+ return [true, rootNode.toString()];
427
+ }
428
+ else {
429
+ return [true, null]; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null
430
+ }
431
+ }
432
+ // something has gone seriously wrong if we are still here
433
+ throw new Error('ERR_ZERO_SIGNATURE');
434
+ // response must be signed, either entire document or assertion
435
+ // default we will take the assertion section under root
436
+ /* if (messageSignatureNode.length === 1) {
437
+ const node = select("/!*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/!*[local-name(.)='Assertion']", doc);
438
+ if (node.length === 1) {
439
+ assertionNode = node[0].toString();
440
+ }
441
+ }
442
+
443
+ if (assertionSignatureNode.length === 1) {
444
+ const verifiedAssertionInfo = extract(assertionSignatureNode[0].toString(), [{
445
+ key: 'refURI',
446
+ localPath: ['Signature', 'SignedInfo', 'Reference'],
447
+ attributes: ['URI']
448
+ }]);
449
+ // get the assertion supposed to be the one should be verified
450
+ const desiredAssertionInfo = extract(doc.toString(), [{
451
+ key: 'id',
452
+ localPath: ['~Response', 'Assertion'],
453
+ attributes: ['ID']
454
+ }]);
455
+ // 5.4.2 References
456
+ // SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of
457
+ // the assertion or protocol message being signed. The assertion’s or protocol message's root element may
458
+ // or may not be the root element of the actual XML document containing the signed assertion or protocol
459
+ // message (e.g., it might be contained within a SOAP envelope).
460
+ // Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID
461
+ // attribute value of the root element of the assertion or protocol message being signed. For example, if the
462
+ // ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo".
463
+ if (verifiedAssertionInfo.refURI !== `#${desiredAssertionInfo.id}`) {
464
+ throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
465
+ }
466
+ const verifiedDoc = extract(doc.toString(), [{
467
+ key: 'assertion',
468
+ localPath: ['~Response', 'Assertion'],
469
+ attributes: [],
470
+ context: true
471
+ }]);
472
+ assertionNode = verifiedDoc.assertion.toString();
473
+ }
474
+
475
+ return [verified, assertionNode];*/
476
+ },
477
+ /**
478
+ * @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
479
+ * @param {string} use type of certificate (e.g. signing, encrypt)
480
+ * @param {string} certString declares the certificate String
481
+ * @return {object} object used in xml module
482
+ */
483
+ createKeySection(use, certString) {
484
+ return {
485
+ ['KeyDescriptor']: [
486
+ {
487
+ _attr: { use },
488
+ },
489
+ {
490
+ ['ds:KeyInfo']: [
491
+ {
492
+ _attr: {
493
+ 'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
494
+ },
495
+ },
496
+ {
497
+ ['ds:X509Data']: [{
498
+ 'ds:X509Certificate': utility.normalizeCerString(certString),
499
+ }],
500
+ },
501
+ ],
502
+ }
503
+ ],
504
+ };
505
+ },
506
+ /**
507
+ * SAML 消息签名 (符合 SAML V2.0 绑定规范)
508
+ * @param octetString - 要签名的原始数据 (OCTET STRING)
509
+ * @param key - PEM 格式私钥
510
+ * @param passphrase - 私钥密码 (如果有加密)
511
+ * @param isBase64 - 是否返回 base64 编码 (默认 true)
512
+ * @param signingAlgorithm - 签名算法 (默认 'rsa-sha256')
513
+ * @returns 消息签名
514
+ */
515
+ constructMessageSignature(octetString, key, passphrase, isBase64 = true, signingAlgorithm = nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA256]) {
516
+ try {
517
+ // 1. 标准化输入数据
518
+ const inputData = Buffer.isBuffer(octetString)
519
+ ? octetString
520
+ : Buffer.from(octetString, 'utf8');
521
+ // 2. 创建签名器并设置算
522
+ const signingAlgorithmValue = getSigningSchemeForNode(signingAlgorithm);
523
+ const signer = createSign(signingAlgorithmValue);
524
+ // 3. 加载私钥
525
+ const privateKey = createPrivateKey({
526
+ key: key,
527
+ format: 'pem',
528
+ passphrase: passphrase,
529
+ encoding: 'utf8'
530
+ });
531
+ signer.write(octetString);
532
+ signer.end();
533
+ const signature = signer.sign(privateKey, 'base64');
534
+ console.log(signature.toString());
535
+ console.log('dayingyixia');
536
+ // 5. 处理编码输出
537
+ return isBase64 ? signature.toString() : signature;
538
+ }
539
+ catch (error) {
540
+ throw new Error(`SAML 签名失败: ${error.message}`);
541
+ }
542
+ },
543
+ verifyMessageSignature(metadata, octetString, signature, verifyAlgorithm) {
544
+ const signCert = metadata.getX509Certificate(certUse.signing);
545
+ const signingScheme = getSigningSchemeForNode(verifyAlgorithm);
546
+ const verifier = createVerify(signingScheme);
547
+ verifier.update(octetString);
548
+ const isValid = verifier.verify(utility.getPublicKeyPemFromCertificate(signCert), Buffer.isBuffer(signature) ? signature : Buffer.from(signature, 'base64'));
549
+ console.log(isValid);
550
+ console.log('-------------签名验证结果-------------');
551
+ return isValid;
552
+ },
553
+ /**
554
+ * @desc Get the public key in string format
555
+ * @param {string} x509Certificate certificate
556
+ * @return {string} public key
557
+ */
558
+ getKeyInfo(x509Certificate, signatureConfig = {}) {
559
+ const prefix = signatureConfig.prefix ? `${signatureConfig.prefix}:` : '';
560
+ return {
561
+ getKeyInfo: () => {
562
+ return `<${prefix}X509Data><${prefix}X509Certificate>${x509Certificate}</${prefix}X509Certificate></${prefix}X509Data>`;
563
+ },
564
+ getKey: () => {
565
+ return utility.getPublicKeyPemFromCertificate(x509Certificate).toString();
566
+ },
567
+ };
568
+ },
569
+ /**
570
+ * @desc Encrypt the assertion section in Response
571
+ * @param {Entity} sourceEntity source entity
572
+ * @param {Entity} targetEntity target entity
573
+ * @param {string} xml response in xml string format
574
+ * @return {Promise} a promise to resolve the finalized xml
575
+ */
576
+ // tslint:disable-next-line:no-shadowed-variable
577
+ encryptAssertion(sourceEntity, targetEntity, xml) {
578
+ // Implement encryption after signature if it has
579
+ return new Promise((resolve, reject) => {
580
+ if (!xml) {
581
+ return reject(new Error('ERR_UNDEFINED_ASSERTION'));
582
+ }
583
+ const sourceEntitySetting = sourceEntity.entitySetting;
584
+ const targetEntityMetadata = targetEntity.entityMeta;
585
+ const { dom } = getContext();
586
+ const doc = dom.parseFromString(xml);
587
+ const assertions = select("//*[local-name(.)='Assertion']", doc);
588
+ if (!Array.isArray(assertions) || assertions.length === 0) {
589
+ throw new Error('ERR_NO_ASSERTION');
590
+ }
591
+ if (assertions.length > 1) {
592
+ throw new Error('ERR_MULTIPLE_ASSERTION');
593
+ }
594
+ const rawAssertionNode = assertions[0];
595
+ // Perform encryption depends on the setting, default is false
596
+ if (sourceEntitySetting.isAssertionEncrypted) {
597
+ const publicKeyPem = utility.getPublicKeyPemFromCertificate(targetEntityMetadata.getX509Certificate(certUse.encrypt));
598
+ xmlenc.encrypt(rawAssertionNode.toString(), {
599
+ // use xml-encryption module
600
+ rsa_pub: Buffer.from(publicKeyPem), // public key from certificate
601
+ pem: Buffer.from(`-----BEGIN CERTIFICATE-----${targetEntityMetadata.getX509Certificate(certUse.encrypt)}-----END CERTIFICATE-----`),
602
+ encryptionAlgorithm: sourceEntitySetting.dataEncryptionAlgorithm,
603
+ keyEncryptionAlgorithm: sourceEntitySetting.keyEncryptionAlgorithm,
604
+ keyEncryptionDigest: 'SHA-512',
605
+ disallowEncryptionWithInsecureAlgorithm: true,
606
+ warnInsecureAlgorithm: true
607
+ }, (err, res) => {
608
+ if (err) {
609
+ console.error(err);
610
+ return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_ENCRYPTION'));
611
+ }
612
+ if (!res) {
613
+ return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
614
+ }
615
+ const { encryptedAssertion: encAssertionPrefix } = sourceEntitySetting.tagPrefix;
616
+ const encryptAssertionDoc = dom.parseFromString(`<${encAssertionPrefix}:EncryptedAssertion xmlns:${encAssertionPrefix}="${namespace.names.assertion}">${res}</${encAssertionPrefix}:EncryptedAssertion>`);
617
+ doc.documentElement.replaceChild(encryptAssertionDoc.documentElement, rawAssertionNode);
618
+ return resolve(utility.base64Encode(doc.toString()));
619
+ });
620
+ }
621
+ else {
622
+ return resolve(utility.base64Encode(xml)); // No need to do encryption
623
+ }
624
+ });
625
+ },
626
+ /**
627
+ * @desc Decrypt the assertion section in Response
628
+ * @param {string} type only accept SAMLResponse to proceed decryption
629
+ * @param {Entity} here this entity
630
+ * @param {Entity} from from the entity where the message is sent
631
+ * @param {string} entireXML response in xml string format
632
+ * @return {function} a promise to get back the entire xml with decrypted assertion
633
+ */
634
+ decryptAssertion(here, entireXML) {
635
+ return new Promise((resolve, reject) => {
636
+ // Implement decryption first then check the signature
637
+ if (!entireXML) {
638
+ return reject(new Error('ERR_UNDEFINED_ASSERTION'));
639
+ }
640
+ // Perform encryption depends on the setting of where the message is sent, default is false
641
+ const hereSetting = here.entitySetting;
642
+ const { dom } = getContext();
643
+ const doc = dom.parseFromString(entireXML);
644
+ const encryptedAssertions = select("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc);
645
+ if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
646
+ throw new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION');
647
+ }
648
+ if (encryptedAssertions.length > 1) {
649
+ throw new Error('ERR_MULTIPLE_ASSERTION');
650
+ }
651
+ const encAssertionNode = encryptedAssertions[0];
652
+ return xmlenc.decrypt(encAssertionNode.toString(), {
653
+ key: utility.readPrivateKey(hereSetting.encPrivateKey, hereSetting.encPrivateKeyPass),
654
+ }, (err, res) => {
655
+ if (err) {
656
+ console.error(err);
657
+ return reject(new Error('ERR_EXCEPTION_OF_ASSERTION_DECRYPTION'));
658
+ }
659
+ if (!res) {
660
+ return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
661
+ }
662
+ const rawAssertionDoc = dom.parseFromString(res);
663
+ doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
664
+ return resolve([doc.toString(), res]);
665
+ });
666
+ });
667
+ },
668
+ /**
669
+ * @desc Check if the xml string is valid and bounded
670
+ */
671
+ async isValidXml(input) {
672
+ // check if global api contains the validate function
673
+ const { validate } = getContext();
674
+ /**
675
+ * user can write a validate function that always returns
676
+ * a resolved promise and skip the validator even in
677
+ * production, user will take the responsibility if
678
+ * they intend to skip the validation
679
+ */
680
+ if (!validate) {
681
+ // otherwise, an error will be thrown
682
+ 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)');
683
+ }
684
+ try {
685
+ return await validate(input);
686
+ }
687
+ catch (e) {
688
+ throw e;
689
+ }
690
+ },
691
+ };
692
+ };
693
+ export default libSaml();
722
694
  //# sourceMappingURL=libsaml.js.map