samlesa 2.13.0 → 2.14.1

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