samlesa 2.12.10 → 2.12.11

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.

package/src/libsaml.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  /**
2
- * @file SamlLib.js
3
- * @author tngan
4
- * @desc A simple library including some common functions
5
- */
6
- import { createSign, createPrivateKey,createVerify } from 'node:crypto';
7
- import utility, { flattenDeep, isString } from './utility.js';
8
- import { algorithms, wording, namespace } from './urn.js';
9
- import { select } from 'xpath';
10
- import { MetadataInterface } from './metadata.js';
11
-
12
- import { SignedXml } from 'xml-crypto';
2
+ * @file SamlLib.js
3
+ * @author tngan
4
+ * @desc A simple library including some common functions
5
+ */
6
+ import {createSign, createPrivateKey, createVerify} from 'node:crypto';
7
+ import utility, {flattenDeep, isString} from './utility.js';
8
+ import {algorithms, wording, namespace} from './urn.js';
9
+ import {select} from 'xpath';
10
+ import {MetadataInterface} from './metadata.js';
11
+ import {create} from 'xmlbuilder2'
12
+ import {SignedXml} from 'xml-crypto';
13
13
  import * as xmlenc from 'xml-encryption';
14
- import { extract } from './extractor.js';
14
+ import {extract} from './extractor.js';
15
15
  import camelCase from 'camelcase';
16
- import { getContext } from './api.js';
16
+ import {getContext} from './api.js';
17
17
  import xmlEscape from 'xml-escape';
18
18
  import * as fs from 'fs';
19
19
  import {DOMParser} from '@xmldom/xmldom';
@@ -22,11 +22,12 @@ const signatureAlgorithms = algorithms.signature;
22
22
  const digestAlgorithms = algorithms.digest;
23
23
  const certUse = wording.certUse;
24
24
  const urlParams = wording.urlParams;
25
+
25
26
  /**
26
27
  * 算法名称映射表 (兼容 X.509 和 SAML 规范)
27
28
  */
28
29
  function mapSignAlgorithm(algorithm: string): string {
29
- const algorithmMap = {
30
+ const algorithmMap = {
30
31
  'rsa-sha1': 'RSA-SHA1',
31
32
  'rsa-sha256': 'RSA-SHA256',
32
33
  'rsa-sha384': 'RSA-SHA384',
@@ -38,6 +39,15 @@ function mapSignAlgorithm(algorithm: string): string {
38
39
 
39
40
  return algorithmMap[algorithm.toLowerCase()] || algorithm;
40
41
  }
42
+
43
+
44
+ /**
45
+ * 生成 SAML Attribute 元素(不带 XML 声明头)
46
+ * @param {Array} attributeData - 属性配置数据
47
+ * @returns {string} SAML Attribute XML 字符串
48
+ */
49
+
50
+
41
51
  export interface SignatureConstructor {
42
52
  rawSamlMessage: string;
43
53
  referenceTagXPath?: string;
@@ -59,6 +69,7 @@ export interface SignatureVerifierOptions {
59
69
 
60
70
  export interface ExtractorResult {
61
71
  [key: string]: any;
72
+
62
73
  signature?: string | string[];
63
74
  issuer?: string | string[];
64
75
  nameID?: string;
@@ -88,15 +99,21 @@ export interface LoginResponseTemplate extends BaseSamlTemplate {
88
99
  attributes?: LoginResponseAttribute[];
89
100
  additionalTemplates?: LoginResponseAdditionalTemplates;
90
101
  }
91
- export interface AttributeStatementTemplate extends BaseSamlTemplate { }
92
102
 
93
- export interface AttributeTemplate extends BaseSamlTemplate { }
103
+ export interface AttributeStatementTemplate extends BaseSamlTemplate {
104
+ }
105
+
106
+ export interface AttributeTemplate extends BaseSamlTemplate {
107
+ }
94
108
 
95
- export interface LoginRequestTemplate extends BaseSamlTemplate { }
109
+ export interface LoginRequestTemplate extends BaseSamlTemplate {
110
+ }
96
111
 
97
- export interface LogoutRequestTemplate extends BaseSamlTemplate { }
112
+ export interface LogoutRequestTemplate extends BaseSamlTemplate {
113
+ }
98
114
 
99
- export interface LogoutResponseTemplate extends BaseSamlTemplate { }
115
+ export interface LogoutResponseTemplate extends BaseSamlTemplate {
116
+ }
100
117
 
101
118
  export type KeyUse = 'signing' | 'encryption';
102
119
 
@@ -134,9 +151,9 @@ export interface LibSamlInterface {
134
151
  const libSaml = () => {
135
152
 
136
153
  /**
137
- * @desc helper function to get back the query param for redirect binding for SLO/SSO
138
- * @type {string}
139
- */
154
+ * @desc helper function to get back the query param for redirect binding for SLO/SSO
155
+ * @type {string}
156
+ */
140
157
  function getQueryParamByType(type: string) {
141
158
  if ([urlParams.logoutRequest, urlParams.samlRequest].indexOf(type) !== -1) {
142
159
  return 'SAMLRequest';
@@ -146,6 +163,7 @@ const libSaml = () => {
146
163
  }
147
164
  throw new Error('ERR_UNDEFINED_QUERY_PARAMS');
148
165
  }
166
+
149
167
  /**
150
168
  *
151
169
  */
@@ -160,40 +178,47 @@ const libSaml = () => {
160
178
  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': 'RSA-SHA512',
161
179
  };
162
180
  /**
163
- * @desc Default login request template
164
- * @type {LoginRequestTemplate}
165
- */
181
+ * @desc Default login request template
182
+ * @type {LoginRequestTemplate}
183
+ */
166
184
  const defaultLoginRequestTemplate = {
167
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>',
168
186
  };
169
187
  /**
170
- * @desc Default logout request template
171
- * @type {LogoutRequestTemplate}
172
- */
188
+ * @desc Default logout request template
189
+ * @type {LogoutRequestTemplate}
190
+ */
173
191
  const defaultLogoutRequestTemplate = {
174
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>',
175
193
  };
176
194
 
177
195
  /**
178
- * @desc Default AttributeStatement template
179
- * @type {AttributeStatementTemplate}
180
- */
196
+ * @desc Default AttributeStatement template
197
+ * @type {AttributeStatementTemplate}
198
+ */
181
199
  const defaultAttributeStatementTemplate = {
182
200
  context: '<saml:AttributeStatement>{Attributes}</saml:AttributeStatement>',
183
201
  };
184
202
 
185
203
  /**
186
- * @desc Default Attribute template
187
- * @type {AttributeTemplate}
188
- */
204
+ * @desc Default Attribute template
205
+ * @type {AttributeTemplate}
206
+ */
189
207
  const defaultAttributeTemplate = {
190
- context: '<saml:Attribute Name="{Name}" NameFormat="{NameFormat}"><saml:AttributeValue xmlns:xs="{ValueXmlnsXs}" xmlns:xsi="{ValueXmlnsXsi}" xsi:type="{ValueXsiType}">{Value}</saml:AttributeValue></saml:Attribute>',
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>',
191
216
  };
192
217
 
193
218
  /**
194
- * @desc Default login response template
195
- * @type {LoginResponseTemplate}
196
- */
219
+ * @desc Default login response template
220
+ * @type {LoginResponseTemplate}
221
+ */
197
222
  const defaultLoginResponseTemplate = {
198
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>',
199
224
  attributes: [],
@@ -203,14 +228,14 @@ const libSaml = () => {
203
228
  }
204
229
  };
205
230
  /**
206
- * @desc Default logout response template
207
- * @type {LogoutResponseTemplate}
208
- */
231
+ * @desc Default logout response template
232
+ * @type {LogoutResponseTemplate}
233
+ */
209
234
  const defaultLogoutResponseTemplate = {
210
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>',
211
236
  };
212
237
 
213
- function getSigningSchemeForNode(sigAlg?: string){
238
+ function getSigningSchemeForNode(sigAlg?: string) {
214
239
  if (sigAlg) {
215
240
  const algAlias = nrsaAliasMappingForNode[sigAlg];
216
241
  if (!(algAlias === undefined)) {
@@ -219,22 +244,24 @@ const libSaml = () => {
219
244
  }
220
245
  return nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA256];
221
246
  }
247
+
222
248
  /**
223
- * @private
224
- * @desc Get the digest algorithms by signature algorithms
225
- * @param {string} sigAlg signature algorithm
226
- * @return {string/undefined} digest algorithm
227
- */
249
+ * @private
250
+ * @desc Get the digest algorithms by signature algorithms
251
+ * @param {string} sigAlg signature algorithm
252
+ * @return {string/undefined} digest algorithm
253
+ */
228
254
  function getDigestMethod(sigAlg: string): string | undefined {
229
255
  return digestAlgorithms[sigAlg];
230
256
  }
257
+
231
258
  /**
232
- * @public
233
- * @desc Create XPath
234
- * @param {string/object} local parameters to create XPath
235
- * @param {boolean} isExtractAll define whether returns whole content according to the XPath
236
- * @return {string} xpath
237
- */
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
+ */
238
265
  function createXPath(local, isExtractAll?: boolean): string {
239
266
  if (isString(local)) {
240
267
  return isExtractAll === true ? "//*[local-name(.)='" + local + "']/text()" : "//*[local-name(.)='" + local + "']";
@@ -273,13 +300,13 @@ const libSaml = () => {
273
300
  defaultAttributeTemplate,
274
301
  defaultLogoutRequestTemplate,
275
302
  defaultLogoutResponseTemplate,
276
-
303
+ defaultAttributeValueTemplate,
277
304
  /**
278
- * @desc Replace the tag (e.g. {tag}) inside the raw XML
279
- * @param {string} rawXML raw XML string used to do keyword replacement
280
- * @param {array} tagValues tag values
281
- * @return {string}
282
- */
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
+ */
283
310
  replaceTagsByValue(rawXML: string, tagValues: Record<string, unknown>): string {
284
311
  Object.keys(tagValues).forEach(t => {
285
312
  rawXML = rawXML.replace(
@@ -290,50 +317,87 @@ const libSaml = () => {
290
317
  return rawXML;
291
318
  },
292
319
  /**
293
- * @desc Helper function to build the AttributeStatement tag
294
- * @param {LoginResponseAttribute} attributes an array of attribute configuration
295
- * @param {AttributeTemplate} attributeTemplate the attribute tag template to be used
296
- * @param {AttributeStatementTemplate} attributeStatementTemplate the attributeStatement tag template to be used
297
- * @return {string}
298
- */
299
- attributeStatementBuilder(
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(
300
327
  attributes: LoginResponseAttribute[],
301
328
  attributeTemplate: AttributeTemplate = defaultAttributeTemplate,
302
329
  attributeStatementTemplate: AttributeStatementTemplate = defaultAttributeStatementTemplate
303
330
  ): string {
304
- const attr = attributes.map(({name, nameFormat, valueTag, valueXsiType,type, valueXmlnsXs, valueXmlnsXsi }) => {
331
+ const attr = attributes.map(({name, nameFormat, valueTag, valueXsiType, type, valueXmlnsXs, valueXmlnsXsi}) => {
305
332
  const defaultValueXmlnsXs = 'http://www.w3.org/2001/XMLSchema';
306
333
  const defaultValueXmlnsXsi = 'http://www.w3.org/2001/XMLSchema-instance';
307
- let attributeLine = attributeTemplate.context;
308
- if (attributeLine && typeof attributeLine === 'function') {
309
- // 安全调用
310
- // @ts-ignore
311
- return attributeLine({ name, nameFormat, valueTag, valueXsiType,type, valueXmlnsXs: valueXmlnsXs ?? defaultValueXmlnsXs, valueXmlnsXsi :valueXmlnsXsi ?? defaultValueXmlnsXsi })
312
- }else{
313
- attributeLine = attributeLine.replace('{Name}', name);
314
- attributeLine = attributeLine.replace('{NameFormat}', nameFormat);
315
- attributeLine = attributeLine.replace('{ValueXmlnsXs}', valueXmlnsXs ? valueXmlnsXs : defaultValueXmlnsXs);
316
- attributeLine = attributeLine.replace('{ValueXmlnsXsi}', valueXmlnsXsi ? valueXmlnsXsi : defaultValueXmlnsXsi);
317
- attributeLine = attributeLine.replace('{ValueXsiType}', valueXsiType);
318
- attributeLine = attributeLine.replace('{Value}', `{${tagging('attr', valueTag)}}`);
319
- return attributeLine;
320
- }
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
+ }
321
356
 
322
- }).join('');
323
- return attributeStatementTemplate.context.replace('{Attributes}', attr);
324
- },
357
+ }).join('');
358
+ return attributeStatementTemplate.context.replace('{Attributes}', attr);
359
+ },*/
360
+ /** For Test */
361
+ attributeStatementBuilder(attributeData: any[]): string {
362
+ const root = create({
363
+ // 关键配置:关闭 XML 声明头和独立文档标识
364
+ headless: true
365
+ }).ele('saml:AttributeStatement', {
366
+ index: 1
367
+ });
368
+
369
+ attributeData.forEach(attr => {
370
+ const attribute = root.ele('saml:Attribute', {
371
+ Name: attr.Name,
372
+ NameFormat: attr.NameFormat,
373
+ FriendlyName: attr.FriendlyName
374
+ });
375
+
376
+ attr.valueArray.forEach(valueObj => {
377
+ const valueElement = attribute.ele('saml:AttributeValue');
325
378
 
379
+ // 根据 ValueType 添加数据类型
380
+ /* if (attr.ValueType === 1) {
381
+ valueElement.att('xsi:type', 'xs:string');
382
+ } // 可扩展其他类型...*/
383
+
384
+ valueElement.txt(valueObj.value);
385
+ });
386
+ });
387
+
388
+ return root.end({prettyPrint: true});
389
+ },
326
390
  /**
327
- * @desc Construct the XML signature for POST binding
328
- * @param {string} rawSamlMessage request/response xml string
329
- * @param {string} referenceTagXPath reference uri
330
- * @param {string} privateKey declares the private key
331
- * @param {string} passphrase passphrase of the private key [optional]
332
- * @param {string|buffer} signingCert signing certificate
333
- * @param {string} signatureAlgorithm signature algorithm
334
- * @param {string[]} transformationAlgorithms canonicalization and transformation Algorithms
335
- * @return {string} base64 encoded string
336
- */
391
+ * @desc Construct the XML signature for POST binding
392
+ * @param {string} rawSamlMessage request/response xml string
393
+ * @param {string} referenceTagXPath reference uri
394
+ * @param {string} privateKey declares the private key
395
+ * @param {string} passphrase passphrase of the private key [optional]
396
+ * @param {string|buffer} signingCert signing certificate
397
+ * @param {string} signatureAlgorithm signature algorithm
398
+ * @param {string[]} transformationAlgorithms canonicalization and transformation Algorithms
399
+ * @return {string} base64 encoded string
400
+ */
337
401
  constructSAMLSignature(opts: SignatureConstructor) {
338
402
  const {
339
403
  rawSamlMessage,
@@ -382,15 +446,15 @@ const libSaml = () => {
382
446
  return isBase64Output !== false ? utility.base64Encode(sig.getSignedXml()) : sig.getSignedXml();
383
447
  },
384
448
  /**
385
- * @desc Verify the XML signature
386
- * @param {string} xml xml
387
- * @param {SignatureVerifierOptions} opts cert declares the X509 certificate
449
+ * @desc Verify the XML signature
450
+ * @param {string} xml xml
451
+ * @param {SignatureVerifierOptions} opts cert declares the X509 certificate
388
452
  * @return {[boolean, string | null]} - A tuple where:
389
453
  * - The first element is `true` if the signature is valid, `false` otherwise.
390
454
  * - The second element is the cryptographically authenticated assertion node as a string, or `null` if not found.
391
455
  */
392
456
  verifySignature(xml: string, opts: SignatureVerifierOptions) {
393
- const { dom } = getContext();
457
+ const {dom} = getContext();
394
458
  const doc = dom.parseFromString(xml);
395
459
 
396
460
  const docParser = new DOMParser();
@@ -423,7 +487,7 @@ const libSaml = () => {
423
487
 
424
488
 
425
489
  // need to refactor later on
426
- for (const signatureNode of selection){
490
+ for (const signatureNode of selection) {
427
491
  const sig = new SignedXml();
428
492
  let verified = false;
429
493
 
@@ -502,19 +566,19 @@ const libSaml = () => {
502
566
 
503
567
  // try getting the Xml from the first assertion
504
568
  const EncryptedAssertions = select(
505
- "./*[local-name()='EncryptedAssertion']",
506
- rootNode
569
+ "./*[local-name()='EncryptedAssertion']",
570
+ rootNode
507
571
  );
508
572
  const assertions = select(
509
- "./*[local-name()='Assertion']",
510
- rootNode
573
+ "./*[local-name()='Assertion']",
574
+ rootNode
511
575
  );
512
576
 
513
- // now we can process the assertion as an assertion
514
- if (EncryptedAssertions.length === 1) {
577
+ // now we can process the assertion as an assertion
578
+ if (EncryptedAssertions.length === 1) {
515
579
 
516
- return [true, EncryptedAssertions[0].toString()];
517
- }
580
+ return [true, EncryptedAssertions[0].toString()];
581
+ }
518
582
 
519
583
  if (assertions.length === 1) {
520
584
 
@@ -526,65 +590,65 @@ const libSaml = () => {
526
590
  } else {
527
591
  return [true, null]; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null
528
592
  }
529
- };
593
+ }
530
594
 
531
595
  // something has gone seriously wrong if we are still here
532
596
  throw new Error('ERR_ZERO_SIGNATURE');
533
597
 
534
598
  // response must be signed, either entire document or assertion
535
599
  // default we will take the assertion section under root
536
- /* if (messageSignatureNode.length === 1) {
537
- const node = select("/!*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/!*[local-name(.)='Assertion']", doc);
538
- if (node.length === 1) {
539
- assertionNode = node[0].toString();
540
- }
541
- }
600
+ /* if (messageSignatureNode.length === 1) {
601
+ const node = select("/!*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/!*[local-name(.)='Assertion']", doc);
602
+ if (node.length === 1) {
603
+ assertionNode = node[0].toString();
604
+ }
605
+ }
542
606
 
543
- if (assertionSignatureNode.length === 1) {
544
- const verifiedAssertionInfo = extract(assertionSignatureNode[0].toString(), [{
545
- key: 'refURI',
546
- localPath: ['Signature', 'SignedInfo', 'Reference'],
547
- attributes: ['URI']
548
- }]);
549
- // get the assertion supposed to be the one should be verified
550
- const desiredAssertionInfo = extract(doc.toString(), [{
551
- key: 'id',
552
- localPath: ['~Response', 'Assertion'],
553
- attributes: ['ID']
554
- }]);
555
- // 5.4.2 References
556
- // SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of
557
- // the assertion or protocol message being signed. The assertion’s or protocol message's root element may
558
- // or may not be the root element of the actual XML document containing the signed assertion or protocol
559
- // message (e.g., it might be contained within a SOAP envelope).
560
- // Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID
561
- // attribute value of the root element of the assertion or protocol message being signed. For example, if the
562
- // ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo".
563
- if (verifiedAssertionInfo.refURI !== `#${desiredAssertionInfo.id}`) {
564
- throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
565
- }
566
- const verifiedDoc = extract(doc.toString(), [{
567
- key: 'assertion',
568
- localPath: ['~Response', 'Assertion'],
569
- attributes: [],
570
- context: true
571
- }]);
572
- assertionNode = verifiedDoc.assertion.toString();
573
- }
607
+ if (assertionSignatureNode.length === 1) {
608
+ const verifiedAssertionInfo = extract(assertionSignatureNode[0].toString(), [{
609
+ key: 'refURI',
610
+ localPath: ['Signature', 'SignedInfo', 'Reference'],
611
+ attributes: ['URI']
612
+ }]);
613
+ // get the assertion supposed to be the one should be verified
614
+ const desiredAssertionInfo = extract(doc.toString(), [{
615
+ key: 'id',
616
+ localPath: ['~Response', 'Assertion'],
617
+ attributes: ['ID']
618
+ }]);
619
+ // 5.4.2 References
620
+ // SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of
621
+ // the assertion or protocol message being signed. The assertion’s or protocol message's root element may
622
+ // or may not be the root element of the actual XML document containing the signed assertion or protocol
623
+ // message (e.g., it might be contained within a SOAP envelope).
624
+ // Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID
625
+ // attribute value of the root element of the assertion or protocol message being signed. For example, if the
626
+ // ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo".
627
+ if (verifiedAssertionInfo.refURI !== `#${desiredAssertionInfo.id}`) {
628
+ throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
629
+ }
630
+ const verifiedDoc = extract(doc.toString(), [{
631
+ key: 'assertion',
632
+ localPath: ['~Response', 'Assertion'],
633
+ attributes: [],
634
+ context: true
635
+ }]);
636
+ assertionNode = verifiedDoc.assertion.toString();
637
+ }
574
638
 
575
- return [verified, assertionNode];*/
639
+ return [verified, assertionNode];*/
576
640
  },
577
641
  /**
578
- * @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
579
- * @param {string} use type of certificate (e.g. signing, encrypt)
580
- * @param {string} certString declares the certificate String
581
- * @return {object} object used in xml module
582
- */
642
+ * @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
643
+ * @param {string} use type of certificate (e.g. signing, encrypt)
644
+ * @param {string} certString declares the certificate String
645
+ * @return {object} object used in xml module
646
+ */
583
647
  createKeySection(use: KeyUse, certString: string | Buffer): KeyComponent {
584
648
  return {
585
649
  ['KeyDescriptor']: [
586
650
  {
587
- _attr: { use },
651
+ _attr: {use},
588
652
  },
589
653
  {
590
654
  ['ds:KeyInfo']: [
@@ -615,38 +679,38 @@ const libSaml = () => {
615
679
 
616
680
  constructMessageSignature(
617
681
  octetString: string | Buffer,
618
- key: string | Buffer,
619
- passphrase?: string,
620
- isBase64: boolean = true,
621
- signingAlgorithm: string = nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA256]
622
- ): string | Buffer {
623
- try {
624
- // 1. 标准化输入数据
625
- const inputData = Buffer.isBuffer(octetString)
626
- ? octetString
627
- : Buffer.from(octetString, 'utf8');
628
- // 2. 创建签名器并设置算
629
- const signingAlgorithmValue = getSigningSchemeForNode(signingAlgorithm)
630
- const signer = createSign(signingAlgorithmValue)
631
-
632
- // 3. 加载私钥
633
- const privateKey = createPrivateKey({
634
- key: key,
635
- format: 'pem',
636
- passphrase: passphrase,
637
- encoding: 'utf8'
638
- });
639
- signer.write(octetString);
640
- signer.end();
641
- const signature = signer.sign(privateKey, 'base64');
642
- console.log(signature.toString());
643
- console.log('dayingyixia')
644
- // 5. 处理编码输出
645
- return isBase64 ? signature.toString() : signature;
646
- } catch (error) {
647
- throw new Error(`SAML 签名失败: ${error.message}`);
648
- }
649
- },
682
+ key: string | Buffer,
683
+ passphrase?: string,
684
+ isBase64: boolean = true,
685
+ signingAlgorithm: string = nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA256]
686
+ ): string | Buffer {
687
+ try {
688
+ // 1. 标准化输入数据
689
+ const inputData = Buffer.isBuffer(octetString)
690
+ ? octetString
691
+ : Buffer.from(octetString, 'utf8');
692
+ // 2. 创建签名器并设置算
693
+ const signingAlgorithmValue = getSigningSchemeForNode(signingAlgorithm)
694
+ const signer = createSign(signingAlgorithmValue)
695
+
696
+ // 3. 加载私钥
697
+ const privateKey = createPrivateKey({
698
+ key: key,
699
+ format: 'pem',
700
+ passphrase: passphrase,
701
+ encoding: 'utf8'
702
+ });
703
+ signer.write(octetString);
704
+ signer.end();
705
+ const signature = signer.sign(privateKey, 'base64');
706
+ console.log(signature.toString());
707
+ console.log('dayingyixia')
708
+ // 5. 处理编码输出
709
+ return isBase64 ? signature.toString() : signature;
710
+ } catch (error) {
711
+ throw new Error(`SAML 签名失败: ${error.message}`);
712
+ }
713
+ },
650
714
  verifyMessageSignature(
651
715
  metadata,
652
716
  octetString: string,
@@ -657,19 +721,19 @@ const libSaml = () => {
657
721
  const signingScheme = getSigningSchemeForNode(verifyAlgorithm);
658
722
  const verifier = createVerify(signingScheme);
659
723
  verifier.update(octetString);
660
- const isValid = verifier.verify(utility.getPublicKeyPemFromCertificate(signCert), Buffer.isBuffer(signature) ? signature : Buffer.from(signature, 'base64'));
724
+ const isValid = verifier.verify(utility.getPublicKeyPemFromCertificate(signCert), Buffer.isBuffer(signature) ? signature : Buffer.from(signature, 'base64'));
661
725
  console.log(isValid);
662
- console.log('验证结果-------------')
726
+ console.log('-------------签名验证结果-------------')
663
727
  return isValid
664
728
 
665
729
  },
666
730
 
667
731
 
668
732
  /**
669
- * @desc Get the public key in string format
670
- * @param {string} x509Certificate certificate
671
- * @return {string} public key
672
- */
733
+ * @desc Get the public key in string format
734
+ * @param {string} x509Certificate certificate
735
+ * @return {string} public key
736
+ */
673
737
  getKeyInfo(x509Certificate: string, signatureConfig: any = {}) {
674
738
  const prefix = signatureConfig.prefix ? `${signatureConfig.prefix}:` : '';
675
739
  return {
@@ -682,12 +746,12 @@ const libSaml = () => {
682
746
  };
683
747
  },
684
748
  /**
685
- * @desc Encrypt the assertion section in Response
686
- * @param {Entity} sourceEntity source entity
687
- * @param {Entity} targetEntity target entity
688
- * @param {string} xml response in xml string format
689
- * @return {Promise} a promise to resolve the finalized xml
690
- */
749
+ * @desc Encrypt the assertion section in Response
750
+ * @param {Entity} sourceEntity source entity
751
+ * @param {Entity} targetEntity target entity
752
+ * @param {string} xml response in xml string format
753
+ * @return {Promise} a promise to resolve the finalized xml
754
+ */
691
755
  encryptAssertion(sourceEntity, targetEntity, xml?: string) {
692
756
  // Implement encryption after signature if it has
693
757
  return new Promise<string>((resolve, reject) => {
@@ -698,7 +762,7 @@ const libSaml = () => {
698
762
 
699
763
  const sourceEntitySetting = sourceEntity.entitySetting;
700
764
  const targetEntityMetadata = targetEntity.entityMeta;
701
- const { dom } = getContext();
765
+ const {dom} = getContext();
702
766
  const doc = dom.parseFromString(xml);
703
767
  const assertions = select("//*[local-name(.)='Assertion']", doc) as Node[];
704
768
  if (!Array.isArray(assertions) || assertions.length === 0) {
@@ -731,7 +795,7 @@ const libSaml = () => {
731
795
  if (!res) {
732
796
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
733
797
  }
734
- const { encryptedAssertion: encAssertionPrefix } = sourceEntitySetting.tagPrefix;
798
+ const {encryptedAssertion: encAssertionPrefix} = sourceEntitySetting.tagPrefix;
735
799
  const encryptAssertionDoc = dom.parseFromString(`<${encAssertionPrefix}:EncryptedAssertion xmlns:${encAssertionPrefix}="${namespace.names.assertion}">${res}</${encAssertionPrefix}:EncryptedAssertion>`);
736
800
  doc.documentElement.replaceChild(encryptAssertionDoc.documentElement, rawAssertionNode);
737
801
  return resolve(utility.base64Encode(doc.toString()));
@@ -742,13 +806,13 @@ const libSaml = () => {
742
806
  });
743
807
  },
744
808
  /**
745
- * @desc Decrypt the assertion section in Response
746
- * @param {string} type only accept SAMLResponse to proceed decryption
747
- * @param {Entity} here this entity
748
- * @param {Entity} from from the entity where the message is sent
749
- * @param {string} entireXML response in xml string format
750
- * @return {function} a promise to get back the entire xml with decrypted assertion
751
- */
809
+ * @desc Decrypt the assertion section in Response
810
+ * @param {string} type only accept SAMLResponse to proceed decryption
811
+ * @param {Entity} here this entity
812
+ * @param {Entity} from from the entity where the message is sent
813
+ * @param {string} entireXML response in xml string format
814
+ * @return {function} a promise to get back the entire xml with decrypted assertion
815
+ */
752
816
  decryptAssertion(here, entireXML: string) {
753
817
  return new Promise<[string, any]>((resolve, reject) => {
754
818
  // Implement decryption first then check the signature
@@ -757,7 +821,7 @@ const libSaml = () => {
757
821
  }
758
822
  // Perform encryption depends on the setting of where the message is sent, default is false
759
823
  const hereSetting = here.entitySetting;
760
- const { dom } = getContext();
824
+ const {dom} = getContext();
761
825
  const doc = dom.parseFromString(entireXML);
762
826
  const encryptedAssertions = select("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc) as Node[];
763
827
  if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
@@ -789,7 +853,7 @@ const libSaml = () => {
789
853
  async isValidXml(input: string) {
790
854
 
791
855
  // check if global api contains the validate function
792
- const { validate } = getContext();
856
+ const {validate} = getContext();
793
857
 
794
858
  /**
795
859
  * user can write a validate function that always returns